Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
880ba30d20 | ||
|
|
d889f9b25d | ||
|
|
0de1b20951 | ||
|
|
338a947b57 | ||
|
|
98ce928f0c | ||
|
|
61cad5dd96 | ||
|
|
a87184214f |
Generated
+164
-123
@@ -4,9 +4,9 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.24.2"
|
version = "0.25.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gimli",
|
"gimli",
|
||||||
]
|
]
|
||||||
@@ -190,7 +190,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assets"
|
name = "assets"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -224,9 +224,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.4.30"
|
version = "0.4.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "977eb15ea9efd848bb8a4a1a2500347ed7f0bf794edf0dc3ddcf439f43d36b23"
|
checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compression-codecs",
|
"compression-codecs",
|
||||||
"compression-core",
|
"compression-core",
|
||||||
@@ -275,7 +275,7 @@ dependencies = [
|
|||||||
"polling",
|
"polling",
|
||||||
"rustix 1.1.2",
|
"rustix 1.1.2",
|
||||||
"slab",
|
"slab",
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -344,7 +344,7 @@ dependencies = [
|
|||||||
"rustix 1.1.2",
|
"rustix 1.1.2",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"slab",
|
"slab",
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -428,7 +428,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "auto_update"
|
name = "auto_update"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo-packager-updater",
|
"cargo-packager-updater",
|
||||||
@@ -472,9 +472,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-rs"
|
name = "aws-lc-rs"
|
||||||
version = "1.14.0"
|
version = "1.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786"
|
checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-sys",
|
"aws-lc-sys",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
@@ -482,22 +482,23 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-sys"
|
name = "aws-lc-sys"
|
||||||
version = "0.31.0"
|
version = "0.32.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd"
|
checksum = "1ba2e2516bdf37af57fc6ff047855f54abad0066e5c4fdaaeb76dabb2e05bcf5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen 0.72.1",
|
"bindgen 0.72.1",
|
||||||
"cc",
|
"cc",
|
||||||
"cmake",
|
"cmake",
|
||||||
"dunce",
|
"dunce",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.75"
|
version = "0.3.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"addr2line",
|
"addr2line",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -505,7 +506,7 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
"windows-targets 0.52.6",
|
"windows-link 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -909,9 +910,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.38"
|
version = "1.2.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9"
|
checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
@@ -1026,7 +1027,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "client_keys"
|
name = "client_keys"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"global",
|
"global",
|
||||||
@@ -1128,7 +1129,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
@@ -1163,7 +1164,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -1184,9 +1185,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compression-codecs"
|
name = "compression-codecs"
|
||||||
version = "0.4.30"
|
version = "0.4.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "485abf41ac0c8047c07c87c72c8fb3eb5197f6e9d7ded615dfd1a00ae00a0f64"
|
checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compression-core",
|
"compression-core",
|
||||||
"deflate64",
|
"deflate64",
|
||||||
@@ -1236,7 +1237,7 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coop"
|
name = "coop"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assets",
|
"assets",
|
||||||
@@ -1546,9 +1547,9 @@ checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.3"
|
version = "0.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
|
checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
@@ -1569,7 +1570,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1837,7 +1838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1979,9 +1980,9 @@ checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flatbuffers"
|
name = "flatbuffers"
|
||||||
version = "25.2.10"
|
version = "25.9.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1"
|
checksum = "09b6620799e7340ebd9968d2e0708eb82cf1971e9a16821e2091b6d6e475eed5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
@@ -2015,6 +2016,15 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
|
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fluent-uri"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@@ -2391,9 +2401,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.31.1"
|
version = "0.32.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
@@ -2403,7 +2413,7 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "global"
|
name = "global"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
@@ -2496,7 +2506,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
@@ -2590,7 +2600,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -2602,7 +2612,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_tokio"
|
name = "gpui_tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -2822,7 +2832,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2842,7 +2852,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -2945,7 +2955,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "i18n"
|
name = "i18n"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rust-i18n",
|
"rust-i18n",
|
||||||
]
|
]
|
||||||
@@ -2962,7 +2972,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.62.0",
|
"windows-core 0.62.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3139,9 +3149,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imgref"
|
name = "imgref"
|
||||||
version = "1.11.0"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
|
checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
@@ -3340,9 +3350,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.80"
|
version = "0.3.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e"
|
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -3401,9 +3411,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.175"
|
version = "0.2.176"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libfuzzer-sys"
|
name = "libfuzzer-sys"
|
||||||
@@ -3417,12 +3427,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.8.9"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-link 0.2.0",
|
"windows-targets 0.53.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3544,6 +3554,19 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lsp-types"
|
||||||
|
version = "0.97.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"fluent-uri",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_repr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lyon"
|
name = "lyon"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
@@ -3634,7 +3657,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.71.1",
|
"bindgen 0.71.1",
|
||||||
@@ -3649,9 +3672,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.5"
|
version = "2.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap2"
|
name = "memmap2"
|
||||||
@@ -3876,13 +3899,13 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b"
|
checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.43.0"
|
version = "0.43.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#418e34bcab62869c80cd86bf7c6ace1b9960b09d"
|
source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -3906,7 +3929,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.43.0"
|
version = "0.43.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#418e34bcab62869c80cd86bf7c6ace1b9960b09d"
|
source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3918,7 +3941,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.43.0"
|
version = "0.43.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#418e34bcab62869c80cd86bf7c6ace1b9960b09d"
|
source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -3929,7 +3952,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.43.0"
|
version = "0.43.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#418e34bcab62869c80cd86bf7c6ace1b9960b09d"
|
source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"flume",
|
"flume",
|
||||||
@@ -3943,7 +3966,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.43.0"
|
version = "0.43.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#418e34bcab62869c80cd86bf7c6ace1b9960b09d"
|
source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3960,7 +3983,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.43.0"
|
version = "0.43.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#418e34bcab62869c80cd86bf7c6ace1b9960b09d"
|
source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -4236,9 +4259,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.37.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -4477,7 +4500,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4619,7 +4642,7 @@ dependencies = [
|
|||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustix 1.1.2",
|
"rustix 1.1.2",
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5085,7 +5108,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
@@ -5093,9 +5116,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.2"
|
version = "1.11.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -5105,9 +5128,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.10"
|
version = "0.4.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -5122,7 +5145,7 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "registry"
|
name = "registry"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
@@ -5239,7 +5262,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5291,6 +5314,21 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rope"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"log",
|
||||||
|
"rayon",
|
||||||
|
"smallvec",
|
||||||
|
"sum_tree",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"util",
|
||||||
|
"workspace-hack",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
@@ -5446,7 +5484,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.11.0",
|
"linux-raw-sys 0.11.0",
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5627,7 +5665,7 @@ version = "0.1.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5774,7 +5812,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "semantic_version"
|
name = "semantic_version"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5789,9 +5827,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.226"
|
version = "1.0.228"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
@@ -5799,18 +5837,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.226"
|
version = "1.0.228"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.226"
|
version = "1.0.228"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -5920,7 +5958,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "settings"
|
name = "settings"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"global",
|
"global",
|
||||||
@@ -5987,7 +6025,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signer_proxy"
|
name = "signer_proxy"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"atomic-destructor",
|
"atomic-destructor",
|
||||||
@@ -6226,7 +6264,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -6496,15 +6534,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.22.0"
|
version = "3.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53"
|
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand 2.3.0",
|
"fastrand 2.3.0",
|
||||||
"getrandom 0.3.3",
|
"getrandom 0.3.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.1.2",
|
"rustix 1.1.2",
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6529,7 +6567,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theme"
|
name = "theme"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -6692,7 +6730,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "title_bar"
|
name = "title_bar"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
@@ -6750,9 +6788,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.3"
|
version = "0.26.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd"
|
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -7075,7 +7113,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ui"
|
name = "ui"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
@@ -7086,15 +7124,18 @@ dependencies = [
|
|||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"linkify",
|
"linkify",
|
||||||
"log",
|
"log",
|
||||||
|
"lsp-types",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
"registry",
|
"registry",
|
||||||
|
"rope",
|
||||||
"rust-i18n",
|
"rust-i18n",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
|
"sum_tree",
|
||||||
"theme",
|
"theme",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -7269,7 +7310,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -7304,7 +7345,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#681a4adc42df0c93b95fd1a36a3ad47eaca086c0"
|
source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7473,9 +7514,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.103"
|
version = "0.2.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819"
|
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -7486,9 +7527,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.103"
|
version = "0.2.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c"
|
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
@@ -7500,9 +7541,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.53"
|
version = "0.4.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67"
|
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -7513,9 +7554,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.103"
|
version = "0.2.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0"
|
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -7523,9 +7564,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.103"
|
version = "0.2.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32"
|
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7536,9 +7577,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.103"
|
version = "0.2.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf"
|
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -7643,9 +7684,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.80"
|
version = "0.3.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc"
|
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -7752,7 +7793,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.0",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7824,8 +7865,8 @@ version = "0.61.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement 0.60.0",
|
"windows-implement 0.60.1",
|
||||||
"windows-interface 0.59.1",
|
"windows-interface 0.59.2",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-result 0.3.4",
|
"windows-result 0.3.4",
|
||||||
"windows-strings 0.4.2",
|
"windows-strings 0.4.2",
|
||||||
@@ -7833,12 +7874,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.62.0"
|
version = "0.62.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c"
|
checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement 0.60.0",
|
"windows-implement 0.60.1",
|
||||||
"windows-interface 0.59.1",
|
"windows-interface 0.59.2",
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.0",
|
||||||
"windows-result 0.4.0",
|
"windows-result 0.4.0",
|
||||||
"windows-strings 0.5.0",
|
"windows-strings 0.5.0",
|
||||||
@@ -7868,9 +7909,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-implement"
|
name = "windows-implement"
|
||||||
version = "0.60.0"
|
version = "0.60.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7890,9 +7931,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-interface"
|
name = "windows-interface"
|
||||||
version = "0.59.1"
|
version = "0.59.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7929,7 +7970,7 @@ checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-result 0.3.4",
|
"windows-result 0.3.4",
|
||||||
"windows-strings 0.3.1",
|
"windows-strings 0.3.1",
|
||||||
"windows-targets 0.53.3",
|
"windows-targets 0.53.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8039,14 +8080,14 @@ version = "0.60.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets 0.53.3",
|
"windows-targets 0.53.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.0"
|
version = "0.61.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
|
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.0",
|
||||||
]
|
]
|
||||||
@@ -8099,11 +8140,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.53.3"
|
version = "0.53.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.2.0",
|
||||||
"windows_aarch64_gnullvm 0.53.0",
|
"windows_aarch64_gnullvm 0.53.0",
|
||||||
"windows_aarch64_msvc 0.53.0",
|
"windows_aarch64_msvc 0.53.0",
|
||||||
"windows_i686_gnu 0.53.0",
|
"windows_i686_gnu 0.53.0",
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ members = ["crates/*"]
|
|||||||
default-members = ["crates/coop"]
|
default-members = ["crates/coop"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17.75 19.25h2.596c1.163 0 2.106-1.001 1.788-2.12-.733-2.573-2.465-4.38-5.134-4.38-.446 0-.866.05-1.26.147M11.25 7a3.25 3.25 0 1 1-6.5 0 3.25 3.25 0 0 1 6.5 0Zm8.5.5a2.75 2.75 0 1 1-5.5 0 2.75 2.75 0 0 1 5.5 0ZM2.08 18.126c.78-3.14 2.78-5.376 5.92-5.376s5.14 2.237 5.918 5.376c.28 1.128-.658 2.124-1.82 2.124H3.901c-1.162 0-2.1-.996-1.82-2.124Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 550 B |
@@ -14,7 +14,7 @@ product-name = "Coop"
|
|||||||
description = "Chat Freely, Stay Private on Nostr"
|
description = "Chat Freely, Stay Private on Nostr"
|
||||||
identifier = "su.reya.coop"
|
identifier = "su.reya.coop"
|
||||||
category = "SocialNetworking"
|
category = "SocialNetworking"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
out-dir = "../../dist"
|
out-dir = "../../dist"
|
||||||
before-packaging-command = "cargo build --release"
|
before-packaging-command = "cargo build --release"
|
||||||
resources = ["Cargo.toml", "src"]
|
resources = ["Cargo.toml", "src"]
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use global::constants::{
|
|||||||
use global::{app_state, nostr_client, AuthRequest, Notice, SignalKind, UnwrappingStatus};
|
use global::{app_state, nostr_client, AuthRequest, Notice, SignalKind, UnwrappingStatus};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
deferred, div, px, rems, App, AppContext, AsyncWindowContext, Axis, Context, Entity,
|
deferred, div, px, rems, App, AppContext, AsyncWindowContext, Axis, ClipboardItem, Context,
|
||||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
Entity, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||||
StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, Window,
|
StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, Window,
|
||||||
};
|
};
|
||||||
use i18n::{shared_t, t};
|
use i18n::{shared_t, t};
|
||||||
@@ -30,7 +30,7 @@ use signer_proxy::{BrowserSignerProxy, BrowserSignerProxyOptions};
|
|||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use theme::{ActiveTheme, Theme, ThemeMode};
|
use theme::{ActiveTheme, Theme, ThemeMode};
|
||||||
use title_bar::TitleBar;
|
use title_bar::TitleBar;
|
||||||
use ui::actions::OpenProfile;
|
use ui::actions::{CopyPublicKey, OpenPublicKey};
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock_area::dock::DockPlacement;
|
use ui::dock_area::dock::DockPlacement;
|
||||||
@@ -676,7 +676,7 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stores an unwrapped event in local database with reference to original
|
/// Stores an unwrapped event in local database with reference to original
|
||||||
async fn set_unwrapped_event(root: EventId, unwrapped: &Event) -> Result<(), Error> {
|
async fn set_unwrapped_event(gift_wrap: EventId, unwrapped: &Event) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
|
|
||||||
// Save unwrapped event
|
// Save unwrapped event
|
||||||
@@ -684,7 +684,7 @@ impl ChatSpace {
|
|||||||
|
|
||||||
// Create a reference event pointing to the unwrapped event
|
// Create a reference event pointing to the unwrapped event
|
||||||
let event = EventBuilder::new(Kind::ApplicationSpecificData, "")
|
let event = EventBuilder::new(Kind::ApplicationSpecificData, "")
|
||||||
.tags(vec![Tag::identifier(root), Tag::event(unwrapped.id)])
|
.tags(vec![Tag::identifier(gift_wrap), Tag::event(unwrapped.id)])
|
||||||
.sign(&Keys::generate())
|
.sign(&Keys::generate())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -744,8 +744,6 @@ impl ChatSpace {
|
|||||||
match event.created_at >= app_state.init_at {
|
match event.created_at >= app_state.init_at {
|
||||||
// New message: send a signal to notify the UI
|
// New message: send a signal to notify the UI
|
||||||
true => {
|
true => {
|
||||||
// A small delay to prevent UI flickering
|
|
||||||
smol::Timer::after(Duration::from_millis(200)).await;
|
|
||||||
app_state
|
app_state
|
||||||
.signal
|
.signal
|
||||||
.send(SignalKind::NewMessage((target.id, event)))
|
.send(SignalKind::NewMessage((target.id, event)))
|
||||||
@@ -1179,7 +1177,7 @@ impl ChatSpace {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_open_profile(&mut self, ev: &OpenProfile, window: &mut Window, cx: &mut Context<Self>) {
|
fn on_open_pubkey(&mut self, ev: &OpenPublicKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let public_key = ev.0;
|
let public_key = ev.0;
|
||||||
let profile = user_profile::init(public_key, window, cx);
|
let profile = user_profile::init(public_key, window, cx);
|
||||||
|
|
||||||
@@ -1197,6 +1195,12 @@ impl ChatSpace {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_copy_pubkey(&mut self, ev: &CopyPublicKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let Ok(bech32) = ev.0.to_bech32();
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new_string(bech32));
|
||||||
|
window.push_notification(t!("common.copied"), cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn render_proxy_modal(&mut self, window: &mut Window, cx: &mut App) {
|
fn render_proxy_modal(&mut self, window: &mut Window, cx: &mut App) {
|
||||||
window.open_modal(cx, |this, _window, _cx| {
|
window.open_modal(cx, |this, _window, _cx| {
|
||||||
this.overlay_closable(false)
|
this.overlay_closable(false)
|
||||||
@@ -1498,7 +1502,8 @@ impl Render for ChatSpace {
|
|||||||
.on_action(cx.listener(Self::on_settings))
|
.on_action(cx.listener(Self::on_settings))
|
||||||
.on_action(cx.listener(Self::on_dark_mode))
|
.on_action(cx.listener(Self::on_dark_mode))
|
||||||
.on_action(cx.listener(Self::on_sign_out))
|
.on_action(cx.listener(Self::on_sign_out))
|
||||||
.on_action(cx.listener(Self::on_open_profile))
|
.on_action(cx.listener(Self::on_open_pubkey))
|
||||||
|
.on_action(cx.listener(Self::on_copy_pubkey))
|
||||||
.on_action(cx.listener(Self::on_reload_metadata))
|
.on_action(cx.listener(Self::on_reload_metadata))
|
||||||
.relative()
|
.relative()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use common::display::{RenderedProfile, RenderedTimestamp};
|
use common::display::{RenderedProfile, RenderedTimestamp};
|
||||||
use common::nip96::nip96_upload;
|
use common::nip96::nip96_upload;
|
||||||
@@ -24,8 +24,10 @@ use settings::AppSettings;
|
|||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
use ui::actions::{CopyPublicKey, OpenPublicKey};
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
|
use ui::context_menu::ContextMenuExt;
|
||||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||||
use ui::emoji_picker::EmojiPicker;
|
use ui::emoji_picker::EmojiPicker;
|
||||||
use ui::input::{InputEvent, InputState, TextInput};
|
use ui::input::{InputEvent, InputState, TextInput};
|
||||||
@@ -61,7 +63,7 @@ pub struct Chat {
|
|||||||
|
|
||||||
// New Message
|
// New Message
|
||||||
input: Entity<InputState>,
|
input: Entity<InputState>,
|
||||||
replies_to: Entity<Vec<EventId>>,
|
replies_to: Entity<HashSet<EventId>>,
|
||||||
|
|
||||||
// Media Attachment
|
// Media Attachment
|
||||||
attachments: Entity<Vec<Url>>,
|
attachments: Entity<Vec<Url>>,
|
||||||
@@ -79,7 +81,7 @@ pub struct Chat {
|
|||||||
impl Chat {
|
impl Chat {
|
||||||
pub fn new(room: Entity<Room>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
pub fn new(room: Entity<Room>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let attachments = cx.new(|_| vec![]);
|
let attachments = cx.new(|_| vec![]);
|
||||||
let replies_to = cx.new(|_| vec![]);
|
let replies_to = cx.new(|_| HashSet::new());
|
||||||
|
|
||||||
let relays = cx.new(|_| {
|
let relays = cx.new(|_| {
|
||||||
let this: HashMap<PublicKey, Vec<RelayUrl>> = HashMap::new();
|
let this: HashMap<PublicKey, Vec<RelayUrl>> = HashMap::new();
|
||||||
@@ -89,11 +91,8 @@ impl Chat {
|
|||||||
let input = cx.new(|cx| {
|
let input = cx.new(|cx| {
|
||||||
InputState::new(window, cx)
|
InputState::new(window, cx)
|
||||||
.placeholder(t!("chat.placeholder"))
|
.placeholder(t!("chat.placeholder"))
|
||||||
.multi_line()
|
|
||||||
.prevent_new_line_on_enter()
|
|
||||||
.rows(1)
|
|
||||||
.multi_line()
|
|
||||||
.auto_grow(1, 20)
|
.auto_grow(1, 20)
|
||||||
|
.prevent_new_line_on_enter()
|
||||||
.clean_on_escape()
|
.clean_on_escape()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,14 +154,8 @@ impl Chat {
|
|||||||
&input,
|
&input,
|
||||||
window,
|
window,
|
||||||
move |this: &mut Self, _input, event, window, cx| {
|
move |this: &mut Self, _input, event, window, cx| {
|
||||||
match event {
|
if let InputEvent::PressEnter { .. } = event {
|
||||||
InputEvent::PressEnter { .. } => {
|
|
||||||
this.send_message(window, cx);
|
this.send_message(window, cx);
|
||||||
}
|
|
||||||
InputEvent::Change(_) => {
|
|
||||||
// this.mention_popup(text, input, cx);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -173,9 +166,21 @@ impl Chat {
|
|||||||
cx.subscribe_in(&room, window, move |this, _, signal, window, cx| {
|
cx.subscribe_in(&room, window, move |this, _, signal, window, cx| {
|
||||||
match signal {
|
match signal {
|
||||||
RoomSignal::NewMessage((gift_wrap_id, event)) => {
|
RoomSignal::NewMessage((gift_wrap_id, event)) => {
|
||||||
if !this.is_sent_by_coop(gift_wrap_id) {
|
let gift_wrap_id = gift_wrap_id.to_owned();
|
||||||
this.insert_message(Message::user(event), false, cx);
|
let message = Message::user(event);
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let app_state = app_state();
|
||||||
|
let sent_ids = app_state.sent_ids.read().await;
|
||||||
|
|
||||||
|
this.update_in(cx, |this, _window, cx| {
|
||||||
|
if !sent_ids.contains(&gift_wrap_id) {
|
||||||
|
this.insert_message(message, false, cx);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
RoomSignal::Refresh => {
|
RoomSignal::Refresh => {
|
||||||
this.load_messages(window, cx);
|
this.load_messages(window, cx);
|
||||||
@@ -251,20 +256,19 @@ impl Chat {
|
|||||||
self._tasks.push(
|
self._tasks.push(
|
||||||
// Run the task in the background
|
// Run the task in the background
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
match load_messages.await {
|
let result = load_messages.await;
|
||||||
|
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
match result {
|
||||||
Ok(events) => {
|
Ok(events) => {
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.insert_messages(events, cx);
|
this.insert_messages(events, cx);
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
cx.update(|window, cx| {
|
window.push_notification(Notification::error(e.to_string()), cx);
|
||||||
window.push_notification(e.to_string(), cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -298,11 +302,6 @@ impl Chat {
|
|||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the event is sent by Coop
|
|
||||||
fn is_sent_by_coop(&self, gift_wrap_id: &EventId) -> bool {
|
|
||||||
app_state().sent_ids.read_blocking().contains(gift_wrap_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a message to all members of the chat
|
/// Send a message to all members of the chat
|
||||||
fn send_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn send_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// Get the message which includes all attachments
|
// Get the message which includes all attachments
|
||||||
@@ -314,17 +313,11 @@ impl Chat {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary disable input
|
|
||||||
self.input.update(cx, |this, cx| {
|
|
||||||
this.set_loading(true, cx);
|
|
||||||
this.set_disabled(true, cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the backup setting
|
// Get the backup setting
|
||||||
let backup = AppSettings::get_backup_messages(cx);
|
let backup = AppSettings::get_backup_messages(cx);
|
||||||
|
|
||||||
// Get replies_to if it's present
|
// Get replies_to if it's present
|
||||||
let replies = self.replies_to.read(cx).clone();
|
let replies = self.replies_to.read(cx).iter().copied().collect_vec();
|
||||||
|
|
||||||
// Get the current room entity
|
// Get the current room entity
|
||||||
let room = self.room.read(cx);
|
let room = self.room.read(cx);
|
||||||
@@ -337,29 +330,27 @@ impl Chat {
|
|||||||
// Create a task for sending the message in the background
|
// Create a task for sending the message in the background
|
||||||
let send_message = room.send_in_background(&content, replies, backup, cx);
|
let send_message = room.send_in_background(&content, replies, backup, cx);
|
||||||
|
|
||||||
cx.defer_in(window, |this, window, cx| {
|
|
||||||
// Optimistically update message list
|
// Optimistically update message list
|
||||||
this.insert_message(Message::user(temp_message), true, cx);
|
self.insert_message(Message::user(temp_message), true, cx);
|
||||||
|
|
||||||
// Remove all replies
|
// Remove all replies
|
||||||
this.remove_all_replies(cx);
|
self.remove_all_replies(cx);
|
||||||
|
|
||||||
// remove all attachments
|
// remove all attachments
|
||||||
this.remove_all_attachments(cx);
|
self.remove_all_attachments(cx);
|
||||||
|
|
||||||
// Reset the input state
|
// Reset the input state
|
||||||
this.input.update(cx, |this, cx| {
|
self.input.update(cx, |this, cx| {
|
||||||
this.set_loading(false, cx);
|
|
||||||
this.set_disabled(false, cx);
|
|
||||||
this.set_value("", window, cx);
|
this.set_value("", window, cx);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Continue sending the message in the background
|
// Continue sending the message in the background
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
match send_message.await {
|
let result = send_message.await;
|
||||||
|
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
match result {
|
||||||
Ok(reports) => {
|
Ok(reports) => {
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.room.update(cx, |this, cx| {
|
this.room.update(cx, |this, cx| {
|
||||||
if this.kind != RoomKind::Ongoing {
|
if this.kind != RoomKind::Ongoing {
|
||||||
// Update the room kind to ongoing
|
// Update the room kind to ongoing
|
||||||
@@ -375,16 +366,13 @@ impl Chat {
|
|||||||
this.reports_by_id.insert(temp_id, reports);
|
this.reports_by_id.insert(temp_id, reports);
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
cx.update(|window, cx| {
|
|
||||||
window.push_notification(e.to_string(), cx);
|
window.push_notification(e.to_string(), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
@@ -439,8 +427,9 @@ impl Chat {
|
|||||||
item_ix: self.list_state.item_count(),
|
item_ix: self.list_state.item_count(),
|
||||||
offset_in_item: px(0.0),
|
offset_in_item: px(0.0),
|
||||||
});
|
});
|
||||||
cx.notify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,11 +437,12 @@ impl Chat {
|
|||||||
fn insert_messages(&mut self, events: Vec<Event>, cx: &mut Context<Self>) {
|
fn insert_messages(&mut self, events: Vec<Event>, cx: &mut Context<Self>) {
|
||||||
for event in events.into_iter() {
|
for event in events.into_iter() {
|
||||||
let m = Message::user(event);
|
let m = Message::user(event);
|
||||||
|
// Bulk inserting messages, so no need to scroll to the latest message
|
||||||
self.insert_message(m, false, cx);
|
self.insert_message(m, false, cx);
|
||||||
}
|
}
|
||||||
cx.notify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a warning message into the chat panel
|
||||||
fn insert_warning(&mut self, content: impl Into<String>, cx: &mut Context<Self>) {
|
fn insert_warning(&mut self, content: impl Into<String>, cx: &mut Context<Self>) {
|
||||||
let m = Message::warning(content.into());
|
let m = Message::warning(content.into());
|
||||||
self.insert_message(m, true, cx);
|
self.insert_message(m, true, cx);
|
||||||
@@ -515,7 +505,7 @@ impl Chat {
|
|||||||
fn reply_to(&mut self, id: &EventId, cx: &mut Context<Self>) {
|
fn reply_to(&mut self, id: &EventId, cx: &mut Context<Self>) {
|
||||||
if let Some(text) = self.message(id) {
|
if let Some(text) = self.message(id) {
|
||||||
self.replies_to.update(cx, |this, cx| {
|
self.replies_to.update(cx, |this, cx| {
|
||||||
this.push(text.id);
|
this.insert(text.id);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -523,16 +513,14 @@ impl Chat {
|
|||||||
|
|
||||||
fn remove_reply(&mut self, id: &EventId, cx: &mut Context<Self>) {
|
fn remove_reply(&mut self, id: &EventId, cx: &mut Context<Self>) {
|
||||||
self.replies_to.update(cx, |this, cx| {
|
self.replies_to.update(cx, |this, cx| {
|
||||||
if let Some(ix) = this.iter().position(|this| this == id) {
|
this.remove(id);
|
||||||
this.remove(ix);
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_all_replies(&mut self, cx: &mut Context<Self>) {
|
fn remove_all_replies(&mut self, cx: &mut Context<Self>) {
|
||||||
self.replies_to.update(cx, |this, cx| {
|
self.replies_to.update(cx, |this, cx| {
|
||||||
*this = vec![];
|
this.clear();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -707,6 +695,7 @@ impl Chat {
|
|||||||
|
|
||||||
let id = message.id;
|
let id = message.id;
|
||||||
let author = self.profile(&message.author, cx);
|
let author = self.profile(&message.author, cx);
|
||||||
|
let public_key = author.public_key();
|
||||||
|
|
||||||
let replies = message.replies_to.as_slice();
|
let replies = message.replies_to.as_slice();
|
||||||
let has_replies = !replies.is_empty();
|
let has_replies = !replies.is_empty();
|
||||||
@@ -729,7 +718,18 @@ impl Chat {
|
|||||||
.flex()
|
.flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.when(!hide_avatar, |this| {
|
.when(!hide_avatar, |this| {
|
||||||
this.child(Avatar::new(author.avatar(proxy)).size(rems(2.)))
|
this.child(
|
||||||
|
div()
|
||||||
|
.id(SharedString::from(format!("{ix}-avatar")))
|
||||||
|
.child(Avatar::new(author.avatar(proxy)).size(rems(2.)))
|
||||||
|
.context_menu(move |this, _window, _cx| {
|
||||||
|
let view = Box::new(OpenPublicKey(public_key));
|
||||||
|
let copy = Box::new(CopyPublicKey(public_key));
|
||||||
|
|
||||||
|
this.menu(t!("profile.view"), view)
|
||||||
|
.menu(t!("profile.copy"), copy)
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -1332,9 +1332,7 @@ impl Panel for Chat {
|
|||||||
let label = this.display_name(cx);
|
let label = this.display_name(cx);
|
||||||
let url = this.display_image(proxy, cx);
|
let url = this.display_image(proxy, cx);
|
||||||
|
|
||||||
div()
|
h_flex()
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.child(Avatar::new(url).size(rems(1.25)))
|
.child(Avatar::new(url).size(rems(1.25)))
|
||||||
.child(label)
|
.child(label)
|
||||||
|
|||||||
@@ -424,7 +424,7 @@ impl Compose {
|
|||||||
impl Render for Compose {
|
impl Render for Compose {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let error = self.error_message.read(cx).as_ref();
|
let error = self.error_message.read(cx).as_ref();
|
||||||
let loading = self.user_input.read(cx).loading(cx);
|
let loading = self.user_input.read(cx).loading;
|
||||||
let contacts = self.contacts.read(cx);
|
let contacts = self.contacts.read(cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use global::nostr_client;
|
|||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, relative, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
|
div, relative, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
|
||||||
Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Window,
|
Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task,
|
||||||
|
Window,
|
||||||
};
|
};
|
||||||
use i18n::{shared_t, t};
|
use i18n::{shared_t, t};
|
||||||
use nostr_connect::prelude::*;
|
use nostr_connect::prelude::*;
|
||||||
@@ -292,21 +293,15 @@ impl Login {
|
|||||||
|
|
||||||
// Handle connection
|
// Handle connection
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let client = nostr_client();
|
|
||||||
|
|
||||||
match signer.bunker_uri().await {
|
match signer.bunker_uri().await {
|
||||||
Ok(uri) => {
|
Ok(uri) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.write_uri_to_disk(&uri, cx);
|
this.write_uri_to_disk(signer, uri, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Set the client's signer with the current nostr connect instance
|
|
||||||
client.set_signer(signer).await;
|
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
cx.update(|window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_error(error.to_string(), window, cx);
|
this.set_error(error.to_string(), window, cx);
|
||||||
// Force reset the client keys
|
// Force reset the client keys
|
||||||
//
|
//
|
||||||
@@ -316,46 +311,47 @@ impl Login {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_uri_to_disk(&mut self, uri: &NostrConnectURI, cx: &mut Context<Self>) {
|
fn write_uri_to_disk(
|
||||||
let Some(public_key) = uri.remote_signer_public_key().cloned() else {
|
&mut self,
|
||||||
log::error!("Remote Signer's public key not found");
|
signer: NostrConnect,
|
||||||
return;
|
uri: NostrConnectURI,
|
||||||
};
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let mut uri_without_secret = uri.to_string();
|
||||||
|
|
||||||
let mut value = uri.to_string();
|
// Clear the secret parameter in the URI if it exists
|
||||||
|
|
||||||
// Clear the secret param if it exists
|
|
||||||
if let Some(secret) = uri.secret() {
|
if let Some(secret) = uri.secret() {
|
||||||
value = value.replace(secret, "");
|
uri_without_secret = uri_without_secret.replace(secret, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let keys = Keys::generate();
|
|
||||||
let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)];
|
|
||||||
let kind = Kind::ApplicationSpecificData;
|
|
||||||
|
|
||||||
let builder = EventBuilder::new(kind, value)
|
// Update the client's signer
|
||||||
.tags(tags)
|
client.set_signer(signer).await;
|
||||||
|
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
|
let event = EventBuilder::new(Kind::ApplicationSpecificData, uri_without_secret)
|
||||||
|
.tags(vec![Tag::identifier(ACCOUNT_IDENTIFIER)])
|
||||||
.build(public_key)
|
.build(public_key)
|
||||||
.sign(&keys)
|
.sign(&Keys::generate())
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
if let Ok(event) = builder {
|
// Save the event to the database
|
||||||
if let Err(e) = client.database().save_event(&event).await {
|
client.database().save_event(&event).await?;
|
||||||
log::error!("Failed to save event: {e}");
|
|
||||||
};
|
Ok(())
|
||||||
}
|
});
|
||||||
})
|
|
||||||
.detach();
|
task.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_keys_to_disk(&self, keys: &Keys, password: String, cx: &mut Context<Self>) {
|
pub fn write_keys_to_disk(&self, keys: &Keys, password: String, cx: &mut Context<Self>) {
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ impl Onboarding {
|
|||||||
self._tasks.push(
|
self._tasks.push(
|
||||||
// Wait for Nostr Connect approval
|
// Wait for Nostr Connect approval
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let client = nostr_client();
|
|
||||||
let connect = this.read_with(cx, |this, cx| this.nostr_connect.read(cx).clone());
|
let connect = this.read_with(cx, |this, cx| this.nostr_connect.read(cx).clone());
|
||||||
|
|
||||||
if let Ok(Some(signer)) = connect {
|
if let Ok(Some(signer)) = connect {
|
||||||
@@ -143,12 +142,9 @@ impl Onboarding {
|
|||||||
Ok(uri) => {
|
Ok(uri) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_connecting(cx);
|
this.set_connecting(cx);
|
||||||
this.write_uri_to_disk(&uri, cx);
|
this.write_uri_to_disk(signer, uri, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Set the client's signer with the current nostr connect instance
|
|
||||||
client.set_signer(signer).await;
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
this.update_in(cx, |_, window, cx| {
|
this.update_in(cx, |_, window, cx| {
|
||||||
@@ -169,38 +165,41 @@ impl Onboarding {
|
|||||||
ChatSpace::proxy_signer(window, cx);
|
ChatSpace::proxy_signer(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_uri_to_disk(&mut self, uri: &NostrConnectURI, cx: &mut Context<Self>) {
|
fn write_uri_to_disk(
|
||||||
let Some(public_key) = uri.remote_signer_public_key().cloned() else {
|
&mut self,
|
||||||
log::error!("Remote Signer's public key not found");
|
signer: NostrConnect,
|
||||||
return;
|
uri: NostrConnectURI,
|
||||||
};
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let mut uri_without_secret = uri.to_string();
|
||||||
|
|
||||||
let mut value = uri.to_string();
|
// Clear the secret parameter in the URI if it exists
|
||||||
|
|
||||||
// Clear the secret param if it exists
|
|
||||||
if let Some(secret) = uri.secret() {
|
if let Some(secret) = uri.secret() {
|
||||||
value = value.replace(secret, "");
|
uri_without_secret = uri_without_secret.replace(secret, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let keys = Keys::generate();
|
|
||||||
let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)];
|
|
||||||
let kind = Kind::ApplicationSpecificData;
|
|
||||||
|
|
||||||
let builder = EventBuilder::new(kind, value)
|
// Update the client's signer
|
||||||
.tags(tags)
|
client.set_signer(signer).await;
|
||||||
|
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
|
let event = EventBuilder::new(Kind::ApplicationSpecificData, uri_without_secret)
|
||||||
|
.tags(vec![Tag::identifier(ACCOUNT_IDENTIFIER)])
|
||||||
.build(public_key)
|
.build(public_key)
|
||||||
.sign(&keys)
|
.sign(&Keys::generate())
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
if let Ok(event) = builder {
|
// Save the event to the database
|
||||||
if let Err(e) = client.database().save_event(&event).await {
|
client.database().save_event(&event).await?;
|
||||||
log::error!("Failed to save event: {e}");
|
|
||||||
};
|
Ok(())
|
||||||
}
|
});
|
||||||
})
|
|
||||||
.detach();
|
task.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_uri(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn copy_uri(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ impl Render for Preferences {
|
|||||||
.on_click(move |_, _window, cx| {
|
.on_click(move |_, _window, cx| {
|
||||||
if let Some(input) = input_state.upgrade() {
|
if let Some(input) = input_state.upgrade() {
|
||||||
let Ok(url) =
|
let Ok(url) =
|
||||||
Url::parse(input.read(cx).value())
|
Url::parse(&input.read(cx).value())
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ use registry::room::RoomKind;
|
|||||||
use registry::Registry;
|
use registry::Registry;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
use ui::actions::{CopyPublicKey, OpenPublicKey};
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
|
use ui::context_menu::ContextMenuExt;
|
||||||
use ui::modal::ModalButtonProps;
|
use ui::modal::ModalButtonProps;
|
||||||
use ui::skeleton::Skeleton;
|
use ui::skeleton::Skeleton;
|
||||||
use ui::{h_flex, ContextModal, StyledExt};
|
use ui::{h_flex, ContextModal, StyledExt};
|
||||||
@@ -166,6 +168,10 @@ impl RenderOnce for RoomListItem {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.hover(|this| this.bg(cx.theme().elevated_surface_background))
|
.hover(|this| this.bg(cx.theme().elevated_surface_background))
|
||||||
|
.context_menu(move |this, _window, _cx| {
|
||||||
|
this.menu(t!("profile.view"), Box::new(OpenPublicKey(public_key)))
|
||||||
|
.menu(t!("profile.copy"), Box::new(CopyPublicKey(public_key)))
|
||||||
|
})
|
||||||
.on_click(move |event, window, cx| {
|
.on_click(move |event, window, cx| {
|
||||||
handler(event, window, cx);
|
handler(event, window, cx);
|
||||||
|
|
||||||
|
|||||||
@@ -100,12 +100,12 @@ impl Sidebar {
|
|||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Subscribe for find input events
|
// Subscribe for find input events
|
||||||
cx.subscribe_in(&find_input, window, |this, _state, event, window, cx| {
|
cx.subscribe_in(&find_input, window, |this, state, event, window, cx| {
|
||||||
match event {
|
match event {
|
||||||
InputEvent::PressEnter { .. } => this.search(window, cx),
|
InputEvent::PressEnter { .. } => this.search(window, cx),
|
||||||
InputEvent::Change(text) => {
|
InputEvent::Change => {
|
||||||
// Clear the result when input is empty
|
// Clear the result when input is empty
|
||||||
if text.is_empty() {
|
if state.read(cx).value().is_empty() {
|
||||||
this.clear_search_results(window, cx);
|
this.clear_search_results(window, cx);
|
||||||
} else {
|
} else {
|
||||||
// Run debounced search
|
// Run debounced search
|
||||||
@@ -722,6 +722,7 @@ impl Render for Sidebar {
|
|||||||
.small()
|
.small()
|
||||||
.cleanable()
|
.cleanable()
|
||||||
.appearance(true)
|
.appearance(true)
|
||||||
|
.text_xs()
|
||||||
.suffix(
|
.suffix(
|
||||||
Button::new("find")
|
Button::new("find")
|
||||||
.icon(IconName::Search)
|
.icon(IconName::Search)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use smallvec::{smallvec, SmallVec};
|
|||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::{h_flex, v_flex, Disableable, Icon, IconName, Sizable, StyledExt};
|
use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt};
|
||||||
|
|
||||||
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<UserProfile> {
|
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<UserProfile> {
|
||||||
cx.new(|cx| UserProfile::new(public_key, window, cx))
|
cx.new(|cx| UserProfile::new(public_key, window, cx))
|
||||||
@@ -32,27 +32,24 @@ pub struct UserProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UserProfile {
|
impl UserProfile {
|
||||||
pub fn new(public_key: PublicKey, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
pub fn new(target: PublicKey, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let registry = Registry::read_global(cx);
|
let registry = Registry::read_global(cx);
|
||||||
let identity = registry.identity(cx).public_key();
|
let profile = registry.get_person(&target, cx);
|
||||||
let profile = registry.get_person(&public_key, cx);
|
|
||||||
|
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
let check_follow: Task<bool> = cx.background_spawn(async move {
|
let check_follow: Task<Result<bool, Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let filter = Filter::new()
|
let signer = client.signer().await?;
|
||||||
.kind(Kind::ContactList)
|
let public_key = signer.get_public_key().await?;
|
||||||
.author(identity)
|
let contact_list = client.database().contacts_public_keys(public_key).await?;
|
||||||
.pubkey(public_key)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
client.database().count(filter).await.unwrap_or(0) >= 1
|
Ok(contact_list.contains(&target))
|
||||||
});
|
});
|
||||||
|
|
||||||
let verify_nip05 = if let Some(address) = profile.metadata().nip05 {
|
let verify_nip05 = if let Some(address) = profile.metadata().nip05 {
|
||||||
Some(Tokio::spawn(cx, async move {
|
Some(Tokio::spawn(cx, async move {
|
||||||
nip05_verify(public_key, &address).await.unwrap_or(false)
|
nip05_verify(target, &address).await.unwrap_or(false)
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -61,7 +58,7 @@ impl UserProfile {
|
|||||||
tasks.push(
|
tasks.push(
|
||||||
// Load user profile data
|
// Load user profile data
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let followed = check_follow.await;
|
let followed = check_follow.await.unwrap_or(false);
|
||||||
|
|
||||||
// Update the followed status
|
// Update the followed status
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
@@ -133,6 +130,7 @@ impl Render for UserProfile {
|
|||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
|
.text_sm()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
@@ -188,10 +186,8 @@ impl Render for UserProfile {
|
|||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_sm()
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.block()
|
|
||||||
.text_color(cx.theme().text_muted)
|
.text_color(cx.theme().text_muted)
|
||||||
.child(SharedString::from("Public Key:")),
|
.child(SharedString::from("Public Key:")),
|
||||||
)
|
)
|
||||||
@@ -201,12 +197,13 @@ impl Render for UserProfile {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.p_2()
|
.p_2()
|
||||||
.h_9()
|
.h_7()
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(cx.theme().elevated_surface_background)
|
.bg(cx.theme().elevated_surface_background)
|
||||||
.truncate()
|
.truncate()
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.line_clamp(1)
|
.line_clamp(1)
|
||||||
|
.line_height(relative(1.))
|
||||||
.child(shared_bech32),
|
.child(shared_bech32),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -218,8 +215,8 @@ impl Render for UserProfile {
|
|||||||
IconName::Copy
|
IconName::Copy
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.ghost()
|
.cta()
|
||||||
.disabled(self.copied)
|
.ghost_alt()
|
||||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||||
this.copy_pubkey(window, cx);
|
this.copy_pubkey(window, cx);
|
||||||
})),
|
})),
|
||||||
@@ -229,7 +226,6 @@ impl Render for UserProfile {
|
|||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_sm()
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_color(cx.theme().text_muted)
|
.text_color(cx.theme().text_muted)
|
||||||
|
|||||||
@@ -159,10 +159,10 @@ pub struct AppState {
|
|||||||
|
|
||||||
pub auto_close_opts: Option<SubscribeAutoCloseOptions>,
|
pub auto_close_opts: Option<SubscribeAutoCloseOptions>,
|
||||||
|
|
||||||
pub seen_on_relays: RwLock<HashMap<EventId, HashSet<RelayUrl>>>,
|
|
||||||
|
|
||||||
pub sent_ids: RwLock<HashSet<EventId>>,
|
pub sent_ids: RwLock<HashSet<EventId>>,
|
||||||
|
|
||||||
|
pub seen_on_relays: RwLock<HashMap<EventId, HashSet<RelayUrl>>>,
|
||||||
|
|
||||||
pub resent_ids: RwLock<Vec<Output<EventId>>>,
|
pub resent_ids: RwLock<Vec<Output<EventId>>>,
|
||||||
|
|
||||||
pub resend_queue: RwLock<HashMap<EventId, RelayUrl>>,
|
pub resend_queue: RwLock<HashMap<EventId, RelayUrl>>,
|
||||||
@@ -196,8 +196,8 @@ impl AppState {
|
|||||||
gift_wrap_sub_id: SubscriptionId::new("inbox"),
|
gift_wrap_sub_id: SubscriptionId::new("inbox"),
|
||||||
gift_wrap_processing: AtomicBool::new(false),
|
gift_wrap_processing: AtomicBool::new(false),
|
||||||
auto_close_opts: Some(opts),
|
auto_close_opts: Some(opts),
|
||||||
seen_on_relays: RwLock::new(HashMap::new()),
|
|
||||||
sent_ids: RwLock::new(HashSet::new()),
|
sent_ids: RwLock::new(HashSet::new()),
|
||||||
|
seen_on_relays: RwLock::new(HashMap::new()),
|
||||||
resent_ids: RwLock::new(Vec::new()),
|
resent_ids: RwLock::new(Vec::new()),
|
||||||
resend_queue: RwLock::new(HashMap::new()),
|
resend_queue: RwLock::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,20 +21,26 @@ impl Message {
|
|||||||
pub fn system() -> Self {
|
pub fn system() -> Self {
|
||||||
Self::System(Timestamp::default())
|
Self::System(Timestamp::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn timestamp(&self) -> &Timestamp {
|
||||||
|
match self {
|
||||||
|
Message::User(msg) => &msg.created_at,
|
||||||
|
Message::Warning(_, ts) => ts,
|
||||||
|
Message::System(ts) => ts,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for Message {
|
impl Ord for Message {
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Message::User(a), Message::User(b)) => a.cmp(b),
|
// System always comes first
|
||||||
(Message::System(a), Message::System(b)) => a.cmp(b),
|
(Message::System(_), Message::System(_)) => self.timestamp().cmp(other.timestamp()),
|
||||||
(Message::User(a), Message::System(b)) => a.created_at.cmp(b),
|
(Message::System(_), _) => std::cmp::Ordering::Less,
|
||||||
(Message::System(a), Message::User(b)) => a.cmp(&b.created_at),
|
(_, Message::System(_)) => std::cmp::Ordering::Greater,
|
||||||
(Message::Warning(_, a), Message::Warning(_, b)) => a.cmp(b),
|
|
||||||
(Message::Warning(_, a), Message::User(b)) => a.cmp(&b.created_at),
|
// For non-system messages, compare by timestamp
|
||||||
(Message::User(a), Message::Warning(_, b)) => a.created_at.cmp(b),
|
_ => self.timestamp().cmp(other.timestamp()),
|
||||||
(Message::Warning(_, a), Message::System(b)) => a.cmp(b),
|
|
||||||
(Message::System(a), Message::Warning(_, b)) => a.cmp(b),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,20 +157,16 @@ fn extract_reply_ids(inner: &Tags) -> Vec<EventId> {
|
|||||||
let mut replies_to = vec![];
|
let mut replies_to = vec![];
|
||||||
|
|
||||||
for tag in inner.filter(TagKind::e()) {
|
for tag in inner.filter(TagKind::e()) {
|
||||||
if let Some(content) = tag.content() {
|
if let Some(id) = tag.content().and_then(|id| EventId::parse(id).ok()) {
|
||||||
if let Ok(id) = EventId::from_hex(content) {
|
|
||||||
replies_to.push(id);
|
replies_to.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for tag in inner.filter(TagKind::q()) {
|
for tag in inner.filter(TagKind::q()) {
|
||||||
if let Some(content) = tag.content() {
|
if let Some(id) = tag.content().and_then(|id| EventId::parse(id).ok()) {
|
||||||
if let Ok(id) = EventId::from_hex(content) {
|
|
||||||
replies_to.push(id);
|
replies_to.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
replies_to
|
replies_to
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,21 +365,51 @@ impl Room {
|
|||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let public_key = members[members.len() - 1];
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
let sent_ids = app_state()
|
||||||
|
.sent_ids
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
let sent = Filter::new()
|
// Get seen events from database
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::ApplicationSpecificData)
|
||||||
|
.identifiers(sent_ids);
|
||||||
|
|
||||||
|
let seen_events = client.database().query(filter).await?;
|
||||||
|
|
||||||
|
// Extract seen event IDs
|
||||||
|
let seen_ids: Vec<EventId> = seen_events
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|event| event.tags.event_ids().next().copied())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Get events that sent by current user
|
||||||
|
let filter = Filter::new()
|
||||||
.kind(Kind::PrivateDirectMessage)
|
.kind(Kind::PrivateDirectMessage)
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
.pubkeys(members.clone());
|
.pubkeys(members.clone());
|
||||||
|
|
||||||
let recv = Filter::new()
|
let sent_events = client.database().query(filter).await?;
|
||||||
|
|
||||||
|
// Get events that received by current user
|
||||||
|
let filter = Filter::new()
|
||||||
.kind(Kind::PrivateDirectMessage)
|
.kind(Kind::PrivateDirectMessage)
|
||||||
.authors(members)
|
.authors(members)
|
||||||
.pubkey(public_key);
|
.pubkey(public_key);
|
||||||
|
|
||||||
let sent_events = client.database().query(sent).await?;
|
let recv_events = client.database().query(filter).await?;
|
||||||
let recv_events = client.database().query(recv).await?;
|
|
||||||
let events: Vec<Event> = sent_events.merge(recv_events).into_iter().collect();
|
// Merge events
|
||||||
|
let events: Vec<Event> = sent_events
|
||||||
|
.merge(recv_events)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|event| !seen_ids.contains(&event.id))
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(events)
|
Ok(events)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,3 +28,6 @@ uuid = "1.10"
|
|||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
image = "0.25.1"
|
image = "0.25.1"
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
|
lsp-types = "0.97.0"
|
||||||
|
rope = { git = "https://github.com/zed-industries/zed.git" }
|
||||||
|
sum_tree = { git = "https://github.com/zed-industries/zed.git" }
|
||||||
|
|||||||
@@ -2,10 +2,15 @@ use gpui::{actions, Action};
|
|||||||
use nostr_sdk::prelude::PublicKey;
|
use nostr_sdk::prelude::PublicKey;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
/// Define a open profile action
|
/// Define a open public key action
|
||||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize, Debug)]
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize, Debug)]
|
||||||
#[action(namespace = profile, no_json)]
|
#[action(namespace = pubkey, no_json)]
|
||||||
pub struct OpenProfile(pub PublicKey);
|
pub struct OpenPublicKey(pub PublicKey);
|
||||||
|
|
||||||
|
/// Define a copy inline public key action
|
||||||
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize, Debug)]
|
||||||
|
#[action(namespace = pubkey, no_json)]
|
||||||
|
pub struct CopyPublicKey(pub PublicKey);
|
||||||
|
|
||||||
/// Define a custom confirm action
|
/// Define a custom confirm action
|
||||||
#[derive(Clone, Action, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Action, PartialEq, Eq, Deserialize)]
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ pub struct History<I: HistoryItem> {
|
|||||||
redos: Vec<I>,
|
redos: Vec<I>,
|
||||||
last_changed_at: Instant,
|
last_changed_at: Instant,
|
||||||
version: usize,
|
version: usize,
|
||||||
pub(crate) ignore: bool,
|
|
||||||
max_undo: usize,
|
max_undo: usize,
|
||||||
group_interval: Option<Duration>,
|
group_interval: Option<Duration>,
|
||||||
unique: bool,
|
unique: bool,
|
||||||
|
pub ignore: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I> History<I>
|
impl<I> History<I>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ pub enum IconName {
|
|||||||
Plus,
|
Plus,
|
||||||
PlusFill,
|
PlusFill,
|
||||||
PlusCircleFill,
|
PlusCircleFill,
|
||||||
|
Group,
|
||||||
ResizeCorner,
|
ResizeCorner,
|
||||||
Reply,
|
Reply,
|
||||||
Report,
|
Report,
|
||||||
@@ -106,6 +107,7 @@ impl IconName {
|
|||||||
Self::Plus => "icons/plus.svg",
|
Self::Plus => "icons/plus.svg",
|
||||||
Self::PlusFill => "icons/plus-fill.svg",
|
Self::PlusFill => "icons/plus-fill.svg",
|
||||||
Self::PlusCircleFill => "icons/plus-circle-fill.svg",
|
Self::PlusCircleFill => "icons/plus-circle-fill.svg",
|
||||||
|
Self::Group => "icons/group.svg",
|
||||||
Self::ResizeCorner => "icons/resize-corner.svg",
|
Self::ResizeCorner => "icons/resize-corner.svg",
|
||||||
Self::Reply => "icons/reply.svg",
|
Self::Reply => "icons/reply.svg",
|
||||||
Self::Report => "icons/report.svg",
|
Self::Report => "icons/report.svg",
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use gpui::{Context, Timer};
|
use gpui::{px, Context, Pixels, Timer};
|
||||||
|
|
||||||
static INTERVAL: Duration = Duration::from_millis(500);
|
static INTERVAL: Duration = Duration::from_millis(500);
|
||||||
static PAUSE_DELAY: Duration = Duration::from_millis(300);
|
static PAUSE_DELAY: Duration = Duration::from_millis(300);
|
||||||
|
pub(super) const CURSOR_WIDTH: Pixels = px(1.5);
|
||||||
|
|
||||||
/// To manage the Input cursor blinking.
|
/// To manage the Input cursor blinking.
|
||||||
///
|
///
|
||||||
@@ -11,7 +12,7 @@ static PAUSE_DELAY: Duration = Duration::from_millis(300);
|
|||||||
/// Every loop will notify the view to update the `visible`, and Input will observe this update to touch repaint.
|
/// Every loop will notify the view to update the `visible`, and Input will observe this update to touch repaint.
|
||||||
///
|
///
|
||||||
/// The input painter will check if this in visible state, then it will draw the cursor.
|
/// The input painter will check if this in visible state, then it will draw the cursor.
|
||||||
pub(crate) struct BlinkCursor {
|
pub struct BlinkCursor {
|
||||||
visible: bool,
|
visible: bool,
|
||||||
paused: bool,
|
paused: bool,
|
||||||
epoch: usize,
|
epoch: usize,
|
||||||
@@ -52,10 +53,8 @@ impl BlinkCursor {
|
|||||||
|
|
||||||
// Schedule the next blink
|
// Schedule the next blink
|
||||||
let epoch = self.next_epoch();
|
let epoch = self.next_epoch();
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
Timer::after(INTERVAL).await;
|
Timer::after(INTERVAL).await;
|
||||||
|
|
||||||
if let Some(this) = this.upgrade() {
|
if let Some(this) = this.upgrade() {
|
||||||
this.update(cx, |this, cx| this.blink(epoch, cx)).ok();
|
this.update(cx, |this, cx| this.blink(epoch, cx)).ok();
|
||||||
}
|
}
|
||||||
@@ -71,11 +70,11 @@ impl BlinkCursor {
|
|||||||
/// Pause the blinking, and delay 500ms to resume the blinking.
|
/// Pause the blinking, and delay 500ms to resume the blinking.
|
||||||
pub fn pause(&mut self, cx: &mut Context<Self>) {
|
pub fn pause(&mut self, cx: &mut Context<Self>) {
|
||||||
self.paused = true;
|
self.paused = true;
|
||||||
|
self.visible = true;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
// delay 500ms to start the blinking
|
// delay 500ms to start the blinking
|
||||||
let epoch = self.next_epoch();
|
let epoch = self.next_epoch();
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
Timer::after(PAUSE_DELAY).await;
|
Timer::after(PAUSE_DELAY).await;
|
||||||
|
|
||||||
@@ -90,3 +89,9 @@ impl BlinkCursor {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for BlinkCursor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::Range;
|
|
||||||
|
|
||||||
use crate::history::HistoryItem;
|
use crate::history::HistoryItem;
|
||||||
|
use crate::input::cursor::Selection;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Change {
|
pub struct Change {
|
||||||
pub(crate) old_range: Range<usize>,
|
pub(crate) old_range: Selection,
|
||||||
pub(crate) old_text: String,
|
pub(crate) old_text: String,
|
||||||
pub(crate) new_range: Range<usize>,
|
pub(crate) new_range: Selection,
|
||||||
pub(crate) new_text: String,
|
pub(crate) new_text: String,
|
||||||
version: usize,
|
version: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Change {
|
impl Change {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
old_range: Range<usize>,
|
old_range: impl Into<Selection>,
|
||||||
old_text: &str,
|
old_text: &str,
|
||||||
new_range: Range<usize>,
|
new_range: impl Into<Selection>,
|
||||||
new_text: &str,
|
new_text: &str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
old_range,
|
old_range: old_range.into(),
|
||||||
old_text: old_text.to_string(),
|
old_text: old_text.to_string(),
|
||||||
new_range,
|
new_range: new_range.into(),
|
||||||
new_text: new_text.to_string(),
|
new_text: new_text.to_string(),
|
||||||
version: 0,
|
version: 0,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
use gpui::{App, Styled};
|
use gpui::{App, Styled};
|
||||||
use i18n::t;
|
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
use crate::button::{Button, ButtonVariants as _};
|
use crate::button::{Button, ButtonVariants};
|
||||||
use crate::{Icon, IconName, Sizable as _};
|
use crate::{Icon, IconName, Sizable};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn clear_button(cx: &App) -> Button {
|
pub(crate) fn clear_button(cx: &App) -> Button {
|
||||||
Button::new("clean")
|
Button::new("clean")
|
||||||
.icon(Icon::new(IconName::CloseCircle))
|
.icon(Icon::new(IconName::CloseCircle))
|
||||||
.tooltip(t!("common.clear"))
|
.tooltip("Clear")
|
||||||
.small()
|
.small()
|
||||||
.text_color(cx.theme().text_muted)
|
|
||||||
.transparent()
|
.transparent()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
/// A selection in the text, represented by start and end byte indices.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct Selection {
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Selection {
|
||||||
|
pub fn new(start: usize, end: usize) -> Self {
|
||||||
|
Self { start, end }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.end.saturating_sub(self.start)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.start == self.end
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the selection, setting start and end to 0.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.start = 0;
|
||||||
|
self.end = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the given offset is within the selection range.
|
||||||
|
pub fn contains(&self, offset: usize) -> bool {
|
||||||
|
offset >= self.start && offset < self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Range<usize>> for Selection {
|
||||||
|
fn from(value: Range<usize>) -> Self {
|
||||||
|
Self::new(value.start, value.end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Selection> for Range<usize> {
|
||||||
|
fn from(value: Selection) -> Self {
|
||||||
|
value.start..value.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Position = lsp_types::Position;
|
||||||
+354
-207
@@ -1,29 +1,34 @@
|
|||||||
use std::{ops::Range, rc::Rc};
|
use std::ops::Range;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
||||||
Entity, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, Path, Pixels,
|
Entity, GlobalElementId, Half, Hitbox, IntoElement, LayoutId, MouseButton, MouseMoveEvent,
|
||||||
Point, SharedString, Size, Style, TextAlign, TextRun, UnderlineStyle, Window, WrappedLine,
|
Path, Pixels, Point, ShapedLine, SharedString, Size, Style, TextAlign, TextRun, UnderlineStyle,
|
||||||
|
Window,
|
||||||
};
|
};
|
||||||
|
use rope::Rope;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
use super::{InputState, LastLayout};
|
use super::blink_cursor::CURSOR_WIDTH;
|
||||||
|
use super::rope_ext::RopeExt;
|
||||||
|
use super::state::{InputState, LastLayout};
|
||||||
use crate::Root;
|
use crate::Root;
|
||||||
|
|
||||||
const CURSOR_THICKNESS: Pixels = px(2.);
|
const BOTTOM_MARGIN_ROWS: usize = 3;
|
||||||
const RIGHT_MARGIN: Pixels = px(5.);
|
pub(super) const RIGHT_MARGIN: Pixels = px(10.);
|
||||||
const BOTTOM_MARGIN_ROWS: usize = 1;
|
pub(super) const LINE_NUMBER_RIGHT_MARGIN: Pixels = px(10.);
|
||||||
|
|
||||||
pub(super) struct TextElement {
|
pub(super) struct TextElement {
|
||||||
input: Entity<InputState>,
|
pub(crate) state: Entity<InputState>,
|
||||||
placeholder: SharedString,
|
placeholder: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextElement {
|
impl TextElement {
|
||||||
pub(super) fn new(input: Entity<InputState>) -> Self {
|
pub(super) fn new(state: Entity<InputState>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
input,
|
state,
|
||||||
placeholder: SharedString::default(),
|
placeholder: SharedString::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,12 +41,12 @@ impl TextElement {
|
|||||||
|
|
||||||
fn paint_mouse_listeners(&mut self, window: &mut Window, _: &mut App) {
|
fn paint_mouse_listeners(&mut self, window: &mut Window, _: &mut App) {
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
let input = self.input.clone();
|
let state = self.state.clone();
|
||||||
|
|
||||||
move |event: &MouseMoveEvent, _, window, cx| {
|
move |event: &MouseMoveEvent, _, window, cx| {
|
||||||
if event.pressed_button == Some(MouseButton::Left) {
|
if event.pressed_button == Some(MouseButton::Left) {
|
||||||
input.update(cx, |input, cx| {
|
state.update(cx, |state, cx| {
|
||||||
input.on_drag_move(event, window, cx);
|
state.on_drag_move(event, window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,33 +57,44 @@ impl TextElement {
|
|||||||
///
|
///
|
||||||
/// - cursor bounds
|
/// - cursor bounds
|
||||||
/// - scroll offset
|
/// - scroll offset
|
||||||
/// - current line index
|
/// - current row index (No only the visible lines, but all lines)
|
||||||
|
///
|
||||||
|
/// This method also will update for track scroll to cursor.
|
||||||
fn layout_cursor(
|
fn layout_cursor(
|
||||||
&self,
|
&self,
|
||||||
lines: &[WrappedLine],
|
last_layout: &LastLayout,
|
||||||
line_height: Pixels,
|
|
||||||
bounds: &mut Bounds<Pixels>,
|
bounds: &mut Bounds<Pixels>,
|
||||||
line_number_width: Pixels,
|
_: &mut Window,
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> (Option<Bounds<Pixels>>, Point<Pixels>, Option<usize>) {
|
) -> (Option<Bounds<Pixels>>, Point<Pixels>, Option<usize>) {
|
||||||
let input = self.input.read(cx);
|
let state = self.state.read(cx);
|
||||||
let mut selected_range = input.selected_range.clone();
|
|
||||||
if let Some(marked_range) = &input.marked_range {
|
let line_height = last_layout.line_height;
|
||||||
selected_range = marked_range.end..marked_range.end;
|
let visible_range = &last_layout.visible_range;
|
||||||
|
let lines = &last_layout.lines;
|
||||||
|
let text_wrapper = &state.text_wrapper;
|
||||||
|
let line_number_width = last_layout.line_number_width;
|
||||||
|
|
||||||
|
let mut selected_range = state.selected_range;
|
||||||
|
if let Some(ime_marked_range) = &state.ime_marked_range {
|
||||||
|
selected_range = (ime_marked_range.end..ime_marked_range.end).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cursor_offset = input.cursor_offset();
|
let cursor = state.cursor();
|
||||||
let mut current_line_index = None;
|
let mut current_row = None;
|
||||||
let mut scroll_offset = input.scroll_handle.offset();
|
let mut scroll_offset = state.scroll_handle.offset();
|
||||||
let mut cursor_bounds = None;
|
let mut cursor_bounds = None;
|
||||||
|
|
||||||
// If the input has a fixed height (Otherwise is auto-grow), we need to add a bottom margin to the input.
|
// If the input has a fixed height (Otherwise is auto-grow), we need to add a bottom margin to the input.
|
||||||
let bottom_margin = if input.is_auto_grow() {
|
let top_bottom_margin = if state.mode.is_auto_grow() {
|
||||||
px(0.) + line_height
|
#[allow(clippy::if_same_then_else)]
|
||||||
|
line_height
|
||||||
|
} else if visible_range.len() < BOTTOM_MARGIN_ROWS * 8 {
|
||||||
|
line_height
|
||||||
} else {
|
} else {
|
||||||
BOTTOM_MARGIN_ROWS * line_height + line_height
|
BOTTOM_MARGIN_ROWS * line_height
|
||||||
};
|
};
|
||||||
|
|
||||||
// The cursor corresponds to the current cursor position in the text no only the line.
|
// The cursor corresponds to the current cursor position in the text no only the line.
|
||||||
let mut cursor_pos = None;
|
let mut cursor_pos = None;
|
||||||
let mut cursor_start = None;
|
let mut cursor_start = None;
|
||||||
@@ -86,17 +102,26 @@ impl TextElement {
|
|||||||
|
|
||||||
let mut prev_lines_offset = 0;
|
let mut prev_lines_offset = 0;
|
||||||
let mut offset_y = px(0.);
|
let mut offset_y = px(0.);
|
||||||
for (line_ix, line) in lines.iter().enumerate() {
|
|
||||||
|
for (ix, wrap_line) in text_wrapper.lines.iter().enumerate() {
|
||||||
|
let row = ix;
|
||||||
|
let line_origin = point(px(0.), offset_y);
|
||||||
|
|
||||||
// break loop if all cursor positions are found
|
// break loop if all cursor positions are found
|
||||||
if cursor_pos.is_some() && cursor_start.is_some() && cursor_end.is_some() {
|
if cursor_pos.is_some() && cursor_start.is_some() && cursor_end.is_some() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let line_origin = point(px(0.), offset_y);
|
let in_visible_range = ix >= visible_range.start;
|
||||||
|
if let Some(line) = in_visible_range
|
||||||
|
.then(|| lines.get(ix.saturating_sub(visible_range.start)))
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
// If in visible range lines
|
||||||
if cursor_pos.is_none() {
|
if cursor_pos.is_none() {
|
||||||
let offset = cursor_offset.saturating_sub(prev_lines_offset);
|
let offset = cursor.saturating_sub(prev_lines_offset);
|
||||||
if let Some(pos) = line.position_for_index(offset, line_height) {
|
if let Some(pos) = line.position_for_index(offset, line_height) {
|
||||||
current_line_index = Some(line_ix);
|
current_row = Some(row);
|
||||||
cursor_pos = Some(line_origin + pos);
|
cursor_pos = Some(line_origin + pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,40 +139,61 @@ impl TextElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
offset_y += line.size(line_height).height;
|
offset_y += line.size(line_height).height;
|
||||||
// +1 for skip the last `\n`
|
// +1 for the last `\n`
|
||||||
prev_lines_offset += line.len() + 1;
|
prev_lines_offset += line.len() + 1;
|
||||||
|
} else {
|
||||||
|
// If not in the visible range.
|
||||||
|
|
||||||
|
// Just increase the offset_y and prev_lines_offset.
|
||||||
|
// This will let the scroll_offset to track the cursor position correctly.
|
||||||
|
if prev_lines_offset >= cursor && cursor_pos.is_none() {
|
||||||
|
current_row = Some(row);
|
||||||
|
cursor_pos = Some(line_origin);
|
||||||
|
}
|
||||||
|
if prev_lines_offset >= selected_range.start && cursor_start.is_none() {
|
||||||
|
cursor_start = Some(line_origin);
|
||||||
|
}
|
||||||
|
if prev_lines_offset >= selected_range.end && cursor_end.is_none() {
|
||||||
|
cursor_end = Some(line_origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_y += wrap_line.height(line_height);
|
||||||
|
// +1 for the last `\n`
|
||||||
|
prev_lines_offset += wrap_line.len() + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(cursor_pos), Some(cursor_start), Some(cursor_end)) =
|
if let (Some(cursor_pos), Some(cursor_start), Some(cursor_end)) =
|
||||||
(cursor_pos, cursor_start, cursor_end)
|
(cursor_pos, cursor_start, cursor_end)
|
||||||
{
|
{
|
||||||
let cursor_moved = input.last_cursor_offset != Some(cursor_offset);
|
let selection_changed = state.last_selected_range != Some(selected_range);
|
||||||
let selection_changed = input.last_selected_range != Some(selected_range.clone());
|
if selection_changed {
|
||||||
|
scroll_offset.x = if scroll_offset.x + cursor_pos.x
|
||||||
if cursor_moved || selection_changed {
|
> (bounds.size.width - line_number_width - RIGHT_MARGIN)
|
||||||
scroll_offset.x =
|
{
|
||||||
if scroll_offset.x + cursor_pos.x > (bounds.size.width - RIGHT_MARGIN) {
|
|
||||||
// cursor is out of right
|
// cursor is out of right
|
||||||
bounds.size.width - RIGHT_MARGIN - cursor_pos.x
|
bounds.size.width - line_number_width - RIGHT_MARGIN - cursor_pos.x
|
||||||
} else if scroll_offset.x + cursor_pos.x < px(0.) {
|
} else if scroll_offset.x + cursor_pos.x < px(0.) {
|
||||||
// cursor is out of left
|
// cursor is out of left
|
||||||
scroll_offset.x - cursor_pos.x
|
scroll_offset.x - cursor_pos.x
|
||||||
} else {
|
} else {
|
||||||
scroll_offset.x
|
scroll_offset.x
|
||||||
};
|
};
|
||||||
scroll_offset.y = if scroll_offset.y + cursor_pos.y + line_height
|
|
||||||
> bounds.size.height - bottom_margin
|
// If we change the scroll_offset.y, GPUI will render and trigger the next run loop.
|
||||||
{
|
// So, here we just adjust offset by `line_height` for move smooth.
|
||||||
|
scroll_offset.y =
|
||||||
|
if scroll_offset.y + cursor_pos.y > bounds.size.height - top_bottom_margin {
|
||||||
// cursor is out of bottom
|
// cursor is out of bottom
|
||||||
bounds.size.height - bottom_margin - cursor_pos.y
|
scroll_offset.y - line_height
|
||||||
} else if scroll_offset.y + cursor_pos.y < px(0.) {
|
} else if scroll_offset.y + cursor_pos.y < top_bottom_margin {
|
||||||
// cursor is out of top
|
// cursor is out of top
|
||||||
scroll_offset.y - cursor_pos.y
|
(scroll_offset.y + line_height).min(px(0.))
|
||||||
} else {
|
} else {
|
||||||
scroll_offset.y
|
scroll_offset.y
|
||||||
};
|
};
|
||||||
|
|
||||||
if input.selection_reversed {
|
if state.selection_reversed {
|
||||||
if scroll_offset.x + cursor_start.x < px(0.) {
|
if scroll_offset.x + cursor_start.x < px(0.) {
|
||||||
// selection start is out of left
|
// selection start is out of left
|
||||||
scroll_offset.x = -cursor_start.x;
|
scroll_offset.x = -cursor_start.x;
|
||||||
@@ -168,54 +214,55 @@ impl TextElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.show_cursor(window, cx) {
|
// cursor bounds
|
||||||
// cursor blink
|
|
||||||
let cursor_height = line_height;
|
let cursor_height = line_height;
|
||||||
cursor_bounds = Some(Bounds::new(
|
cursor_bounds = Some(Bounds::new(
|
||||||
point(
|
point(
|
||||||
bounds.left() + cursor_pos.x + line_number_width + scroll_offset.x,
|
bounds.left() + cursor_pos.x + line_number_width + scroll_offset.x,
|
||||||
bounds.top() + cursor_pos.y + ((line_height - cursor_height) / 2.),
|
bounds.top() + cursor_pos.y + ((line_height - cursor_height) / 2.),
|
||||||
),
|
),
|
||||||
size(CURSOR_THICKNESS, cursor_height),
|
size(CURSOR_WIDTH, cursor_height),
|
||||||
));
|
));
|
||||||
};
|
}
|
||||||
|
|
||||||
|
if let Some(deferred_scroll_offset) = state.deferred_scroll_offset {
|
||||||
|
scroll_offset = deferred_scroll_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds.origin += scroll_offset;
|
bounds.origin += scroll_offset;
|
||||||
|
|
||||||
(cursor_bounds, scroll_offset, current_line_index)
|
(cursor_bounds, scroll_offset, current_row)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_selections(
|
/// Layout the match range to a Path.
|
||||||
&self,
|
pub(crate) fn layout_match_range(
|
||||||
lines: &[WrappedLine],
|
range: Range<usize>,
|
||||||
line_height: Pixels,
|
last_layout: &LastLayout,
|
||||||
bounds: &mut Bounds<Pixels>,
|
bounds: &mut Bounds<Pixels>,
|
||||||
line_number_width: Pixels,
|
|
||||||
_: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Option<Path<Pixels>> {
|
) -> Option<Path<Pixels>> {
|
||||||
let input = self.input.read(cx);
|
if range.is_empty() {
|
||||||
let mut selected_range = input.selected_range.clone();
|
|
||||||
if let Some(marked_range) = &input.marked_range {
|
|
||||||
if !marked_range.is_empty() {
|
|
||||||
selected_range = marked_range.end..marked_range.end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if selected_range.is_empty() {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (start_ix, end_ix) = if selected_range.start < selected_range.end {
|
if range.start < last_layout.visible_range_offset.start
|
||||||
(selected_range.start, selected_range.end)
|
|| range.end > last_layout.visible_range_offset.end
|
||||||
} else {
|
{
|
||||||
(selected_range.end, selected_range.start)
|
return None;
|
||||||
};
|
}
|
||||||
|
|
||||||
let mut prev_lines_offset = 0;
|
let line_height = last_layout.line_height;
|
||||||
|
let visible_top = last_layout.visible_top;
|
||||||
|
let visible_start_offset = last_layout.visible_range_offset.start;
|
||||||
|
let lines = &last_layout.lines;
|
||||||
|
let line_number_width = last_layout.line_number_width;
|
||||||
|
|
||||||
|
let start_ix = range.start;
|
||||||
|
let end_ix = range.end;
|
||||||
|
|
||||||
|
let mut prev_lines_offset = visible_start_offset;
|
||||||
|
let mut offset_y = visible_top;
|
||||||
let mut line_corners = vec![];
|
let mut line_corners = vec![];
|
||||||
|
|
||||||
let mut offset_y = px(0.);
|
|
||||||
for line in lines.iter() {
|
for line in lines.iter() {
|
||||||
let line_size = line.size(line_height);
|
let line_size = line.size(line_height);
|
||||||
let line_wrap_width = line_size.width;
|
let line_wrap_width = line_size.width;
|
||||||
@@ -239,7 +286,6 @@ impl TextElement {
|
|||||||
(end.y / line_height).ceil() as usize - (start.y / line_height).ceil() as usize;
|
(end.y / line_height).ceil() as usize - (start.y / line_height).ceil() as usize;
|
||||||
|
|
||||||
let mut end_x = end.x;
|
let mut end_x = end.x;
|
||||||
|
|
||||||
if wrapped_lines > 0 {
|
if wrapped_lines > 0 {
|
||||||
end_x = line_wrap_width;
|
end_x = line_wrap_width;
|
||||||
}
|
}
|
||||||
@@ -322,39 +368,79 @@ impl TextElement {
|
|||||||
builder.build().ok()
|
builder.build().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_selections(
|
||||||
|
&self,
|
||||||
|
last_layout: &LastLayout,
|
||||||
|
bounds: &mut Bounds<Pixels>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Option<Path<Pixels>> {
|
||||||
|
let state = self.state.read(cx);
|
||||||
|
let mut selected_range = state.selected_range;
|
||||||
|
if let Some(ime_marked_range) = &state.ime_marked_range {
|
||||||
|
if !ime_marked_range.is_empty() {
|
||||||
|
selected_range = (ime_marked_range.end..ime_marked_range.end).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if selected_range.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (start_ix, end_ix) = if selected_range.start < selected_range.end {
|
||||||
|
(selected_range.start, selected_range.end)
|
||||||
|
} else {
|
||||||
|
(selected_range.end, selected_range.start)
|
||||||
|
};
|
||||||
|
|
||||||
|
let range = start_ix.max(last_layout.visible_range_offset.start)
|
||||||
|
..end_ix.min(last_layout.visible_range_offset.end);
|
||||||
|
|
||||||
|
Self::layout_match_range(range, last_layout, bounds)
|
||||||
|
}
|
||||||
|
|
||||||
/// Calculate the visible range of lines in the viewport.
|
/// Calculate the visible range of lines in the viewport.
|
||||||
///
|
///
|
||||||
/// The visible range is based on unwrapped lines (Zero based).
|
/// Returns
|
||||||
|
///
|
||||||
|
/// - visible_range: The visible range is based on unwrapped lines (Zero based).
|
||||||
|
/// - visible_top: The top position of the first visible line in the scroll viewport.
|
||||||
fn calculate_visible_range(
|
fn calculate_visible_range(
|
||||||
&self,
|
&self,
|
||||||
state: &InputState,
|
state: &InputState,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
input_height: Pixels,
|
input_height: Pixels,
|
||||||
) -> Range<usize> {
|
) -> (Range<usize>, Pixels) {
|
||||||
if state.is_single_line() {
|
// Add extra rows to avoid showing empty space when scroll to bottom.
|
||||||
return 0..1;
|
let extra_rows = 1;
|
||||||
|
let mut visible_top = px(0.);
|
||||||
|
if state.mode.is_single_line() {
|
||||||
|
return (0..1, visible_top);
|
||||||
}
|
}
|
||||||
|
|
||||||
let scroll_top = -state.scroll_handle.offset().y;
|
let total_lines = state.text_wrapper.len();
|
||||||
let total_lines = state.text_wrapper.lines.len();
|
let scroll_top = if let Some(deferred_scroll_offset) = state.deferred_scroll_offset {
|
||||||
|
deferred_scroll_offset.y
|
||||||
|
} else {
|
||||||
|
state.scroll_handle.offset().y
|
||||||
|
};
|
||||||
|
|
||||||
let mut visible_range = 0..total_lines;
|
let mut visible_range = 0..total_lines;
|
||||||
let mut line_top = px(0.);
|
let mut line_bottom = px(0.);
|
||||||
|
|
||||||
for (ix, line) in state.text_wrapper.lines.iter().enumerate() {
|
for (ix, line) in state.text_wrapper.lines.iter().enumerate() {
|
||||||
line_top += line.height(line_height);
|
let wrapped_height = line.height(line_height);
|
||||||
|
line_bottom += wrapped_height;
|
||||||
|
|
||||||
if line_top < scroll_top {
|
if line_bottom < -scroll_top {
|
||||||
|
visible_top = line_bottom - wrapped_height;
|
||||||
visible_range.start = ix;
|
visible_range.start = ix;
|
||||||
}
|
}
|
||||||
|
|
||||||
if line_top > scroll_top + input_height {
|
if line_bottom + scroll_top >= input_height {
|
||||||
visible_range.end = (ix + 1).min(total_lines);
|
visible_range.end = (ix + extra_rows).min(total_lines);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visible_range
|
(visible_range, visible_top)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,13 +448,17 @@ pub(super) struct PrepaintState {
|
|||||||
/// The lines of entire lines.
|
/// The lines of entire lines.
|
||||||
last_layout: LastLayout,
|
last_layout: LastLayout,
|
||||||
/// The lines only contains the visible lines in the viewport, based on `visible_range`.
|
/// The lines only contains the visible lines in the viewport, based on `visible_range`.
|
||||||
line_numbers: Option<Vec<SmallVec<[WrappedLine; 1]>>>,
|
///
|
||||||
line_number_width: Pixels,
|
/// The child is the soft lines.
|
||||||
|
line_numbers: Option<Vec<SmallVec<[ShapedLine; 1]>>>,
|
||||||
/// Size of the scrollable area by entire lines.
|
/// Size of the scrollable area by entire lines.
|
||||||
scroll_size: Size<Pixels>,
|
scroll_size: Size<Pixels>,
|
||||||
cursor_bounds: Option<Bounds<Pixels>>,
|
cursor_bounds: Option<Bounds<Pixels>>,
|
||||||
cursor_scroll_offset: Point<Pixels>,
|
cursor_scroll_offset: Point<Pixels>,
|
||||||
selection_path: Option<Path<Pixels>>,
|
selection_path: Option<Path<Pixels>>,
|
||||||
|
hover_highlight_path: Option<Path<Pixels>>,
|
||||||
|
search_match_paths: Vec<(Path<Pixels>, bool)>,
|
||||||
|
hover_definition_hitbox: Option<Hitbox>,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,34 +470,9 @@ impl IntoElement for TextElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A debug function to print points as SVG path.
|
|
||||||
#[allow(unused)]
|
|
||||||
fn print_points_as_svg_path(line_corners: &Vec<Corners<Point<Pixels>>>, points: &[Point<Pixels>]) {
|
|
||||||
for corners in line_corners {
|
|
||||||
println!(
|
|
||||||
"tl: ({}, {}), tr: ({}, {}), bl: ({}, {}), br: ({}, {})",
|
|
||||||
corners.top_left.x.0 as i32,
|
|
||||||
corners.top_left.y.0 as i32,
|
|
||||||
corners.top_right.x.0 as i32,
|
|
||||||
corners.top_right.y.0 as i32,
|
|
||||||
corners.bottom_left.x.0 as i32,
|
|
||||||
corners.bottom_left.y.0 as i32,
|
|
||||||
corners.bottom_right.x.0 as i32,
|
|
||||||
corners.bottom_right.y.0 as i32,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !points.is_empty() {
|
|
||||||
println!("M{},{}", points[0].x.0 as i32, points[0].y.0 as i32);
|
|
||||||
for p in points.iter().skip(1) {
|
|
||||||
println!("L{},{}", p.x.0 as i32, p.y.0 as i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element for TextElement {
|
impl Element for TextElement {
|
||||||
type RequestLayoutState = ();
|
|
||||||
type PrepaintState = PrepaintState;
|
type PrepaintState = PrepaintState;
|
||||||
|
type RequestLayoutState = ();
|
||||||
|
|
||||||
fn id(&self) -> Option<ElementId> {
|
fn id(&self) -> Option<ElementId> {
|
||||||
None
|
None
|
||||||
@@ -424,19 +489,20 @@ impl Element for TextElement {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> (LayoutId, Self::RequestLayoutState) {
|
) -> (LayoutId, Self::RequestLayoutState) {
|
||||||
let input = self.input.read(cx);
|
let state = self.state.read(cx);
|
||||||
let line_height = window.line_height();
|
let line_height = window.line_height();
|
||||||
|
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.size.width = relative(1.).into();
|
style.size.width = relative(1.).into();
|
||||||
if self.input.read(cx).is_multi_line() {
|
if state.mode.is_multi_line() {
|
||||||
style.flex_grow = 1.0;
|
style.flex_grow = 1.0;
|
||||||
if let Some(h) = input.mode.height() {
|
|
||||||
style.size.height = h.into();
|
|
||||||
style.min_size.height = line_height.into();
|
|
||||||
} else {
|
|
||||||
style.size.height = relative(1.).into();
|
style.size.height = relative(1.).into();
|
||||||
style.min_size.height = (input.mode.rows() * line_height).into();
|
if state.mode.is_auto_grow() {
|
||||||
|
// Auto grow to let height match to rows, but not exceed max rows.
|
||||||
|
let rows = state.mode.max_rows().min(state.mode.rows());
|
||||||
|
style.min_size.height = (rows * line_height).into();
|
||||||
|
} else {
|
||||||
|
style.min_size.height = line_height.into();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For single-line inputs, the minimum height should be the line height
|
// For single-line inputs, the minimum height should be the line height
|
||||||
@@ -455,11 +521,19 @@ impl Element for TextElement {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Self::PrepaintState {
|
) -> Self::PrepaintState {
|
||||||
|
let state = self.state.read(cx);
|
||||||
let line_height = window.line_height();
|
let line_height = window.line_height();
|
||||||
let input = self.input.read(cx);
|
|
||||||
let multi_line = input.is_multi_line();
|
let (visible_range, visible_top) =
|
||||||
let visible_range = self.calculate_visible_range(input, line_height, bounds.size.height);
|
self.calculate_visible_range(state, line_height, bounds.size.height);
|
||||||
let text = input.text.clone();
|
let visible_start_offset = state.text.line_start_offset(visible_range.start);
|
||||||
|
let visible_end_offset = state
|
||||||
|
.text
|
||||||
|
.line_end_offset(visible_range.end.saturating_sub(1));
|
||||||
|
|
||||||
|
let state = self.state.read(cx);
|
||||||
|
let multi_line = state.mode.is_multi_line();
|
||||||
|
let text = state.text.clone();
|
||||||
let is_empty = text.is_empty();
|
let is_empty = text.is_empty();
|
||||||
let placeholder = self.placeholder.clone();
|
let placeholder = self.placeholder.clone();
|
||||||
let style = window.text_style();
|
let style = window.text_style();
|
||||||
@@ -467,9 +541,9 @@ impl Element for TextElement {
|
|||||||
let mut bounds = bounds;
|
let mut bounds = bounds;
|
||||||
|
|
||||||
let (display_text, text_color) = if is_empty {
|
let (display_text, text_color) = if is_empty {
|
||||||
(placeholder, cx.theme().text_muted)
|
(Rope::from(placeholder.as_str()), cx.theme().text_muted)
|
||||||
} else if input.masked {
|
} else if state.masked {
|
||||||
("*".repeat(text.chars().count()).into(), cx.theme().text)
|
(Rope::from("*".repeat(text.chars_count())), cx.theme().text)
|
||||||
} else {
|
} else {
|
||||||
(text.clone(), cx.theme().text)
|
(text.clone(), cx.theme().text)
|
||||||
};
|
};
|
||||||
@@ -500,20 +574,20 @@ impl Element for TextElement {
|
|||||||
|
|
||||||
let runs = if !is_empty {
|
let runs = if !is_empty {
|
||||||
vec![run]
|
vec![run]
|
||||||
} else if let Some(marked_range) = &input.marked_range {
|
} else if let Some(ime_marked_range) = &state.ime_marked_range {
|
||||||
// IME marked text
|
// IME marked text
|
||||||
vec![
|
vec![
|
||||||
TextRun {
|
TextRun {
|
||||||
len: marked_range.start,
|
len: ime_marked_range.start,
|
||||||
..run.clone()
|
..run.clone()
|
||||||
},
|
},
|
||||||
TextRun {
|
TextRun {
|
||||||
len: marked_range.end - marked_range.start,
|
len: ime_marked_range.end - ime_marked_range.start,
|
||||||
underline: marked_run.underline,
|
underline: marked_run.underline,
|
||||||
..run.clone()
|
..run.clone()
|
||||||
},
|
},
|
||||||
TextRun {
|
TextRun {
|
||||||
len: display_text.len() - marked_range.end,
|
len: display_text.len() - ime_marked_range.end,
|
||||||
..run.clone()
|
..run.clone()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -524,35 +598,76 @@ impl Element for TextElement {
|
|||||||
vec![run]
|
vec![run]
|
||||||
};
|
};
|
||||||
|
|
||||||
let wrap_width = if multi_line {
|
let wrap_width = if multi_line && state.soft_wrap {
|
||||||
Some(bounds.size.width - line_number_width - RIGHT_MARGIN)
|
Some(bounds.size.width - line_number_width - RIGHT_MARGIN)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// NOTE: Here 50 lines about 150µs
|
||||||
|
// let measure = crate::Measure::new("shape_text");
|
||||||
|
let visible_text = display_text
|
||||||
|
.slice_rows(visible_range.start as u32..visible_range.end as u32)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let lines = window
|
let lines = window
|
||||||
.text_system()
|
.text_system()
|
||||||
.shape_text(display_text, font_size, &runs, wrap_width, None)
|
.shape_text(visible_text.into(), font_size, &runs, wrap_width, None)
|
||||||
.expect("failed to shape text");
|
.expect("failed to shape text");
|
||||||
|
// measure.end();
|
||||||
|
|
||||||
let total_wrapped_lines = lines
|
let mut longest_line_width = wrap_width.unwrap_or(px(0.));
|
||||||
.iter()
|
if state.mode.is_multi_line() && !state.soft_wrap && lines.len() > 1 {
|
||||||
.map(|line| {
|
let longtest_line: SharedString = state
|
||||||
// +1 is the first line, `wrap_boundaries` is the wrapped lines after the `\n`.
|
.text
|
||||||
1 + line.wrap_boundaries.len()
|
.line(state.text.summary().longest_row as usize)
|
||||||
})
|
.to_string()
|
||||||
.sum::<usize>();
|
.into();
|
||||||
|
longest_line_width = window
|
||||||
|
.text_system()
|
||||||
|
.shape_line(
|
||||||
|
longtest_line.clone(),
|
||||||
|
font_size,
|
||||||
|
&[TextRun {
|
||||||
|
len: longtest_line.len(),
|
||||||
|
font: style.font(),
|
||||||
|
color: gpui::black(),
|
||||||
|
background_color: None,
|
||||||
|
underline: None,
|
||||||
|
strikethrough: None,
|
||||||
|
}],
|
||||||
|
wrap_width,
|
||||||
|
)
|
||||||
|
.width;
|
||||||
|
}
|
||||||
|
|
||||||
let max_line_width = lines
|
let total_wrapped_lines = state.text_wrapper.len();
|
||||||
.iter()
|
let empty_bottom_height = bounds
|
||||||
.map(|line| line.width())
|
.size
|
||||||
.max()
|
.height
|
||||||
.unwrap_or(bounds.size.width);
|
.half()
|
||||||
|
.max(BOTTOM_MARGIN_ROWS * line_height);
|
||||||
let scroll_size = size(
|
let scroll_size = size(
|
||||||
max_line_width + line_number_width + RIGHT_MARGIN,
|
if longest_line_width + line_number_width + RIGHT_MARGIN > bounds.size.width {
|
||||||
(total_wrapped_lines as f32 * line_height).max(bounds.size.height),
|
longest_line_width + line_number_width + RIGHT_MARGIN
|
||||||
|
} else {
|
||||||
|
longest_line_width
|
||||||
|
},
|
||||||
|
(total_wrapped_lines as f32 * line_height + empty_bottom_height)
|
||||||
|
.max(bounds.size.height),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut last_layout = LastLayout {
|
||||||
|
visible_range,
|
||||||
|
visible_top,
|
||||||
|
visible_range_offset: visible_start_offset..visible_end_offset,
|
||||||
|
line_height,
|
||||||
|
wrap_width,
|
||||||
|
line_number_width,
|
||||||
|
lines: Rc::new(lines),
|
||||||
|
cursor_bounds: None,
|
||||||
|
};
|
||||||
|
|
||||||
// `position_for_index` for example
|
// `position_for_index` for example
|
||||||
//
|
//
|
||||||
// #### text
|
// #### text
|
||||||
@@ -584,37 +699,27 @@ impl Element for TextElement {
|
|||||||
|
|
||||||
// Calculate the scroll offset to keep the cursor in view
|
// Calculate the scroll offset to keep the cursor in view
|
||||||
|
|
||||||
let (cursor_bounds, cursor_scroll_offset, _) = self.layout_cursor(
|
let (cursor_bounds, cursor_scroll_offset, _) =
|
||||||
&lines,
|
self.layout_cursor(&last_layout, &mut bounds, window, cx);
|
||||||
line_height,
|
last_layout.cursor_bounds = cursor_bounds;
|
||||||
&mut bounds,
|
|
||||||
line_number_width,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
let selection_path = self.layout_selections(
|
let selection_path = self.layout_selections(&last_layout, &mut bounds, cx);
|
||||||
&lines,
|
let search_match_paths = vec![];
|
||||||
line_height,
|
let hover_highlight_path = None;
|
||||||
&mut bounds,
|
let line_numbers = None;
|
||||||
line_number_width,
|
let hover_definition_hitbox = None;
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
PrepaintState {
|
PrepaintState {
|
||||||
bounds,
|
bounds,
|
||||||
last_layout: LastLayout {
|
last_layout,
|
||||||
lines: Rc::new(lines),
|
|
||||||
line_height,
|
|
||||||
visible_range,
|
|
||||||
},
|
|
||||||
scroll_size,
|
scroll_size,
|
||||||
line_numbers: None,
|
line_numbers,
|
||||||
line_number_width,
|
|
||||||
cursor_bounds,
|
cursor_bounds,
|
||||||
cursor_scroll_offset,
|
cursor_scroll_offset,
|
||||||
selection_path,
|
selection_path,
|
||||||
|
search_match_paths,
|
||||||
|
hover_highlight_path,
|
||||||
|
hover_definition_hitbox,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,21 +733,21 @@ impl Element for TextElement {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
let focus_handle = self.input.read(cx).focus_handle.clone();
|
let focus_handle = self.state.read(cx).focus_handle.clone();
|
||||||
|
let show_cursor = self.state.read(cx).show_cursor(window, cx);
|
||||||
let focused = focus_handle.is_focused(window);
|
let focused = focus_handle.is_focused(window);
|
||||||
let bounds = prepaint.bounds;
|
let bounds = prepaint.bounds;
|
||||||
let selected_range = self.input.read(cx).selected_range.clone();
|
let selected_range = self.state.read(cx).selected_range;
|
||||||
let visible_range = &prepaint.last_layout.visible_range;
|
|
||||||
|
|
||||||
window.handle_input(
|
window.handle_input(
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
ElementInputHandler::new(bounds, self.input.clone()),
|
ElementInputHandler::new(bounds, self.state.clone()),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set Root focused_input when self is focused
|
// Set Root focused_input when self is focused
|
||||||
if focused {
|
if focused {
|
||||||
let state = self.input.clone();
|
let state = self.state.clone();
|
||||||
if Root::read(window, cx).focused_input.as_ref() != Some(&state) {
|
if Root::read(window, cx).focused_input.as_ref() != Some(&state) {
|
||||||
Root::update(window, cx, |root, _, cx| {
|
Root::update(window, cx, |root, _, cx| {
|
||||||
root.focused_input = Some(state);
|
root.focused_input = Some(state);
|
||||||
@@ -653,7 +758,7 @@ impl Element for TextElement {
|
|||||||
|
|
||||||
// And reset focused_input when next_frame start
|
// And reset focused_input when next_frame start
|
||||||
window.on_next_frame({
|
window.on_next_frame({
|
||||||
let state = self.input.clone();
|
let state = self.state.clone();
|
||||||
move |window, cx| {
|
move |window, cx| {
|
||||||
if !focused && Root::read(window, cx).focused_input.as_ref() == Some(&state) {
|
if !focused && Root::read(window, cx).focused_input.as_ref() == Some(&state) {
|
||||||
Root::update(window, cx, |root, _, cx| {
|
Root::update(window, cx, |root, _, cx| {
|
||||||
@@ -668,13 +773,10 @@ impl Element for TextElement {
|
|||||||
let line_height = window.line_height();
|
let line_height = window.line_height();
|
||||||
let origin = bounds.origin;
|
let origin = bounds.origin;
|
||||||
|
|
||||||
let mut invisible_top_padding = px(0.);
|
let invisible_top_padding = prepaint.last_layout.visible_top;
|
||||||
for line in prepaint.last_layout.lines.iter().take(visible_range.start) {
|
|
||||||
invisible_top_padding += line.size(line_height).height;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mask_offset_y = px(0.);
|
let mut mask_offset_y = px(0.);
|
||||||
if self.input.read(cx).masked {
|
if self.state.read(cx).masked {
|
||||||
// Move down offset for vertical centering the *****
|
// Move down offset for vertical centering the *****
|
||||||
if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
mask_offset_y = px(3.);
|
mask_offset_y = px(3.);
|
||||||
@@ -683,60 +785,105 @@ impl Element for TextElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Paint active line
|
||||||
let mut offset_y = px(0.);
|
let mut offset_y = px(0.);
|
||||||
if let Some(line_numbers) = prepaint.line_numbers.as_ref() {
|
if let Some(line_numbers) = prepaint.line_numbers.as_ref() {
|
||||||
offset_y += invisible_top_padding;
|
offset_y += invisible_top_padding;
|
||||||
|
|
||||||
// Each item is the normal lines.
|
// Each item is the normal lines.
|
||||||
for lines in line_numbers.iter() {
|
for lines in line_numbers.iter() {
|
||||||
for line in lines {
|
let height = line_height * lines.len() as f32;
|
||||||
let p = point(origin.x, origin.y + offset_y);
|
offset_y += height;
|
||||||
let line_size = line.size(line_height);
|
|
||||||
_ = line.paint(p, line_height, TextAlign::Left, None, window, cx);
|
|
||||||
offset_y += line_size.height;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint selections
|
// Paint selections
|
||||||
|
if window.is_window_active() {
|
||||||
|
let secondary_selection = cx.theme().selection;
|
||||||
|
for (path, is_active) in prepaint.search_match_paths.iter() {
|
||||||
|
window.paint_path(path.clone(), secondary_selection);
|
||||||
|
|
||||||
|
if *is_active {
|
||||||
|
window.paint_path(path.clone(), cx.theme().selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(path) = prepaint.selection_path.take() {
|
if let Some(path) = prepaint.selection_path.take() {
|
||||||
window.paint_path(path, cx.theme().selection);
|
window.paint_path(path, cx.theme().selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Paint hover highlight
|
||||||
|
if let Some(path) = prepaint.hover_highlight_path.take() {
|
||||||
|
window.paint_path(path, secondary_selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Paint text
|
// Paint text
|
||||||
let mut offset_y = mask_offset_y + invisible_top_padding;
|
let mut offset_y = mask_offset_y + invisible_top_padding;
|
||||||
for line in prepaint
|
for line in prepaint.last_layout.lines.iter() {
|
||||||
.last_layout
|
let p = point(
|
||||||
.iter()
|
origin.x + prepaint.last_layout.line_number_width,
|
||||||
.skip(visible_range.start)
|
origin.y + offset_y,
|
||||||
.take(visible_range.len())
|
);
|
||||||
{
|
|
||||||
let p = point(origin.x + prepaint.line_number_width, origin.y + offset_y);
|
|
||||||
_ = line.paint(p, line_height, TextAlign::Left, None, window, cx);
|
_ = line.paint(p, line_height, TextAlign::Left, None, window, cx);
|
||||||
offset_y += line.size(line_height).height;
|
offset_y += line.size(line_height).height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if focused {
|
// Paint blinking cursor
|
||||||
|
if focused && show_cursor {
|
||||||
if let Some(mut cursor_bounds) = prepaint.cursor_bounds.take() {
|
if let Some(mut cursor_bounds) = prepaint.cursor_bounds.take() {
|
||||||
cursor_bounds.origin.y += prepaint.cursor_scroll_offset.y;
|
cursor_bounds.origin.y += prepaint.cursor_scroll_offset.y;
|
||||||
window.paint_quad(fill(cursor_bounds, cx.theme().cursor));
|
window.paint_quad(fill(cursor_bounds, cx.theme().cursor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.input.update(cx, |input, cx| {
|
// Paint line numbers
|
||||||
input.last_layout = Some(prepaint.last_layout.clone());
|
let mut offset_y = px(0.);
|
||||||
input.last_bounds = Some(bounds);
|
if let Some(line_numbers) = prepaint.line_numbers.as_ref() {
|
||||||
input.last_cursor_offset = Some(input.cursor_offset());
|
offset_y += invisible_top_padding;
|
||||||
input.set_input_bounds(input_bounds, cx);
|
|
||||||
input.last_selected_range = Some(selected_range);
|
// Paint line number background
|
||||||
input.scroll_size = prepaint.scroll_size;
|
window.paint_quad(fill(
|
||||||
input.line_number_width = prepaint.line_number_width;
|
Bounds {
|
||||||
input
|
origin: input_bounds.origin,
|
||||||
|
size: size(
|
||||||
|
prepaint.last_layout.line_number_width - LINE_NUMBER_RIGHT_MARGIN,
|
||||||
|
input_bounds.size.height,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cx.theme().background,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Each item is the normal lines.
|
||||||
|
for lines in line_numbers.iter() {
|
||||||
|
let p = point(input_bounds.origin.x, origin.y + offset_y);
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
_ = line.paint(p, line_height, window, cx);
|
||||||
|
offset_y += line_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state.update(cx, |state, cx| {
|
||||||
|
state.last_layout = Some(prepaint.last_layout.clone());
|
||||||
|
state.last_bounds = Some(bounds);
|
||||||
|
state.last_cursor = Some(state.cursor());
|
||||||
|
state.set_input_bounds(input_bounds, cx);
|
||||||
|
state.last_selected_range = Some(selected_range);
|
||||||
|
state.scroll_size = prepaint.scroll_size;
|
||||||
|
state
|
||||||
.scroll_handle
|
.scroll_handle
|
||||||
.set_offset(prepaint.cursor_scroll_offset);
|
.set_offset(prepaint.cursor_scroll_offset);
|
||||||
|
state.deferred_scroll_offset = None;
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(hitbox) = prepaint.hover_definition_hitbox.as_ref() {
|
||||||
|
window.set_cursor_style(gpui::CursorStyle::PointingHand, hitbox);
|
||||||
|
}
|
||||||
|
|
||||||
self.paint_mouse_listeners(window, cx);
|
self.paint_mouse_listeners(window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ use gpui::SharedString;
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum MaskToken {
|
pub enum MaskToken {
|
||||||
|
/// 0 Digit, equivalent to `[0]`
|
||||||
|
// Digit0,
|
||||||
/// Digit, equivalent to `[0-9]`
|
/// Digit, equivalent to `[0-9]`
|
||||||
Digit,
|
Digit,
|
||||||
/// Letter, equivalent to `[a-zA-Z]`
|
/// Letter, equivalent to `[a-zA-Z]`
|
||||||
@@ -200,11 +202,25 @@ impl MaskPattern {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the integer part is valid
|
let sign_positions: Vec<usize> = int_part
|
||||||
if !int_part
|
|
||||||
.chars()
|
.chars()
|
||||||
.all(|ch| ch.is_ascii_digit() || Some(ch) == *separator)
|
.enumerate()
|
||||||
{
|
.filter_map(|(i, ch)| match is_sign(&ch) {
|
||||||
|
true => Some(i),
|
||||||
|
false => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// only one sign is valid
|
||||||
|
// sign is only valid at the beginning of the string
|
||||||
|
if sign_positions.len() > 1 || sign_positions.first() > Some(&0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the integer part is valid
|
||||||
|
if !int_part.chars().enumerate().all(|(i, ch)| {
|
||||||
|
ch.is_ascii_digit() || is_sign(&ch) && i == 0 || Some(ch) == *separator
|
||||||
|
}) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +302,11 @@ impl MaskPattern {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Reverse the integer part for easier grouping
|
// Reverse the integer part for easier grouping
|
||||||
let chars: Vec<char> = int_part.chars().rev().collect();
|
let mut chars: Vec<char> = int_part.chars().rev().collect();
|
||||||
|
|
||||||
|
// Removing the sign from formatting to avoid cases such as: -,123
|
||||||
|
let maybe_signed = chars.iter().position(is_sign).map(|pos| chars.remove(pos));
|
||||||
|
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
for (i, ch) in chars.iter().enumerate() {
|
for (i, ch) in chars.iter().enumerate() {
|
||||||
if i > 0 && i % 3 == 0 {
|
if i > 0 && i % 3 == 0 {
|
||||||
@@ -305,6 +325,13 @@ impl MaskPattern {
|
|||||||
} else {
|
} else {
|
||||||
int_with_sep
|
int_with_sep
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let final_str = if let Some(sign) = maybe_signed {
|
||||||
|
format!("{sign}{final_str}")
|
||||||
|
} else {
|
||||||
|
final_str
|
||||||
|
};
|
||||||
|
|
||||||
return final_str.into();
|
return final_str.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,3 +403,8 @@ impl MaskPattern {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_sign(ch: &char) -> bool {
|
||||||
|
matches!(ch, '+' | '-')
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
mod blink_cursor;
|
mod blink_cursor;
|
||||||
mod change;
|
mod change;
|
||||||
|
mod cursor;
|
||||||
mod element;
|
mod element;
|
||||||
mod mask_pattern;
|
mod mask_pattern;
|
||||||
|
mod mode;
|
||||||
|
mod rope_ext;
|
||||||
mod state;
|
mod state;
|
||||||
mod text_input;
|
mod text_input;
|
||||||
mod text_wrapper;
|
mod text_wrapper;
|
||||||
|
|
||||||
pub(crate) mod clear_button;
|
pub(crate) mod clear_button;
|
||||||
|
|
||||||
#[allow(ambiguous_glob_reexports)]
|
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
pub use text_input::*;
|
pub use text_input::*;
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
use gpui::SharedString;
|
||||||
|
|
||||||
|
use super::text_wrapper::TextWrapper;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct TabSize {
|
||||||
|
/// Default is 2
|
||||||
|
pub tab_size: usize,
|
||||||
|
/// Set true to use `\t` as tab indent, default is false
|
||||||
|
pub hard_tabs: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TabSize {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
tab_size: 2,
|
||||||
|
hard_tabs: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabSize {
|
||||||
|
pub(super) fn to_string(self) -> SharedString {
|
||||||
|
if self.hard_tabs {
|
||||||
|
"\t".into()
|
||||||
|
} else {
|
||||||
|
" ".repeat(self.tab_size).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub enum InputMode {
|
||||||
|
#[default]
|
||||||
|
SingleLine,
|
||||||
|
MultiLine {
|
||||||
|
tab: TabSize,
|
||||||
|
rows: usize,
|
||||||
|
},
|
||||||
|
AutoGrow {
|
||||||
|
rows: usize,
|
||||||
|
min_rows: usize,
|
||||||
|
max_rows: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
impl InputMode {
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn is_single_line(&self) -> bool {
|
||||||
|
matches!(self, InputMode::SingleLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn is_auto_grow(&self) -> bool {
|
||||||
|
matches!(self, InputMode::AutoGrow { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn is_multi_line(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
InputMode::MultiLine { .. } | InputMode::AutoGrow { .. }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn set_rows(&mut self, new_rows: usize) {
|
||||||
|
match self {
|
||||||
|
InputMode::MultiLine { rows, .. } => {
|
||||||
|
*rows = new_rows;
|
||||||
|
}
|
||||||
|
InputMode::AutoGrow {
|
||||||
|
rows,
|
||||||
|
min_rows,
|
||||||
|
max_rows,
|
||||||
|
} => {
|
||||||
|
*rows = new_rows.clamp(*min_rows, *max_rows);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn update_auto_grow(&mut self, text_wrapper: &TextWrapper) {
|
||||||
|
if self.is_single_line() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrapped_lines = text_wrapper.len();
|
||||||
|
self.set_rows(wrapped_lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// At least 1 row be return.
|
||||||
|
pub(super) fn rows(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
InputMode::MultiLine { rows, .. } => *rows,
|
||||||
|
InputMode::AutoGrow { rows, .. } => *rows,
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
.max(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// At least 1 row be return.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(super) fn min_rows(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
InputMode::MultiLine { .. } => 1,
|
||||||
|
InputMode::AutoGrow { min_rows, .. } => *min_rows,
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
.max(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(super) fn max_rows(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
InputMode::MultiLine { .. } => usize::MAX,
|
||||||
|
InputMode::AutoGrow { max_rows, .. } => *max_rows,
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn tab_size(&self) -> Option<&TabSize> {
|
||||||
|
match self {
|
||||||
|
InputMode::MultiLine { tab, .. } => Some(tab),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use rope::{Point, Rope};
|
||||||
|
|
||||||
|
use super::cursor::Position;
|
||||||
|
|
||||||
|
/// An extension trait for `Rope` to provide additional utility methods.
|
||||||
|
pub trait RopeExt {
|
||||||
|
/// Get the line at the given row (0-based) index, including the `\r` at the end, but not `\n`.
|
||||||
|
///
|
||||||
|
/// Return empty rope if the row (0-based) is out of bounds.
|
||||||
|
fn line(&self, row: usize) -> Rope;
|
||||||
|
|
||||||
|
/// Start offset of the line at the given row (0-based) index.
|
||||||
|
fn line_start_offset(&self, row: usize) -> usize;
|
||||||
|
|
||||||
|
/// Line the end offset (including `\n`) of the line at the given row (0-based) index.
|
||||||
|
///
|
||||||
|
/// Return the end of the rope if the row is out of bounds.
|
||||||
|
fn line_end_offset(&self, row: usize) -> usize;
|
||||||
|
|
||||||
|
/// Return the number of lines in the rope.
|
||||||
|
fn lines_len(&self) -> usize;
|
||||||
|
|
||||||
|
/// Return the lines iterator.
|
||||||
|
///
|
||||||
|
/// Each line is including the `\r` at the end, but not `\n`.
|
||||||
|
fn lines(&self) -> RopeLines;
|
||||||
|
|
||||||
|
/// Check is equal to another rope.
|
||||||
|
fn eq(&self, other: &Rope) -> bool;
|
||||||
|
|
||||||
|
/// Total number of characters in the rope.
|
||||||
|
fn chars_count(&self) -> usize;
|
||||||
|
|
||||||
|
/// Get char at the given offset (byte).
|
||||||
|
///
|
||||||
|
/// If the offset is in the middle of a multi-byte character will panic.
|
||||||
|
///
|
||||||
|
/// If the offset is out of bounds, return None.
|
||||||
|
fn char_at(&self, offset: usize) -> Option<char>;
|
||||||
|
|
||||||
|
/// Get the byte offset from the given line, column [`Position`] (0-based).
|
||||||
|
fn position_to_offset(&self, line_col: &Position) -> usize;
|
||||||
|
|
||||||
|
/// Get the line, column [`Position`] (0-based) from the given byte offset.
|
||||||
|
fn offset_to_position(&self, offset: usize) -> Position;
|
||||||
|
|
||||||
|
/// Get the word byte range at the given offset (byte).
|
||||||
|
fn word_range(&self, offset: usize) -> Option<Range<usize>>;
|
||||||
|
|
||||||
|
/// Get word at the given offset (byte).
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn word_at(&self, offset: usize) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over the lines of a `Rope`.
|
||||||
|
pub struct RopeLines {
|
||||||
|
row: usize,
|
||||||
|
end_row: usize,
|
||||||
|
rope: Rope,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RopeLines {
|
||||||
|
/// Create a new `RopeLines` iterator.
|
||||||
|
pub fn new(rope: Rope) -> Self {
|
||||||
|
let end_row = rope.lines_len();
|
||||||
|
Self {
|
||||||
|
row: 0,
|
||||||
|
end_row,
|
||||||
|
rope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for RopeLines {
|
||||||
|
type Item = Rope;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.row >= self.end_row {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = self.rope.line(self.row);
|
||||||
|
self.row += 1;
|
||||||
|
Some(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
self.row = self.row.saturating_add(n);
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let len = self.end_row - self.row;
|
||||||
|
(len, Some(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::iter::ExactSizeIterator for RopeLines {}
|
||||||
|
impl std::iter::FusedIterator for RopeLines {}
|
||||||
|
|
||||||
|
impl RopeExt for Rope {
|
||||||
|
fn line(&self, row: usize) -> Rope {
|
||||||
|
let start = self.line_start_offset(row);
|
||||||
|
let end = start + self.line_len(row as u32) as usize;
|
||||||
|
self.slice(start..end)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_start_offset(&self, row: usize) -> usize {
|
||||||
|
let row = row as u32;
|
||||||
|
self.point_to_offset(Point::new(row, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position_to_offset(&self, pos: &Position) -> usize {
|
||||||
|
let line = self.line(pos.line as usize);
|
||||||
|
self.line_start_offset(pos.line as usize)
|
||||||
|
+ line
|
||||||
|
.chars()
|
||||||
|
.take(pos.character as usize)
|
||||||
|
.map(|c| c.len_utf8())
|
||||||
|
.sum::<usize>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_to_position(&self, offset: usize) -> Position {
|
||||||
|
let point = self.offset_to_point(offset);
|
||||||
|
let line = self.line(point.row as usize);
|
||||||
|
let column = line.clip_offset(point.column as usize, sum_tree::Bias::Left);
|
||||||
|
let character = line.slice(0..column).chars().count();
|
||||||
|
Position::new(point.row, character as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_end_offset(&self, row: usize) -> usize {
|
||||||
|
if row > self.max_point().row as usize {
|
||||||
|
return self.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.line_start_offset(row) + self.line_len(row as u32) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lines_len(&self) -> usize {
|
||||||
|
self.max_point().row as usize + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lines(&self) -> RopeLines {
|
||||||
|
RopeLines::new(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eq(&self, other: &Rope) -> bool {
|
||||||
|
self.summary() == other.summary()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chars_count(&self) -> usize {
|
||||||
|
self.chars().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn char_at(&self, offset: usize) -> Option<char> {
|
||||||
|
if offset > self.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = self.clip_offset(offset, sum_tree::Bias::Left);
|
||||||
|
self.slice(offset..self.len()).chars().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn word_range(&self, offset: usize) -> Option<Range<usize>> {
|
||||||
|
if offset >= self.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = self.clip_offset(offset, sum_tree::Bias::Left);
|
||||||
|
|
||||||
|
let mut left = String::new();
|
||||||
|
for c in self.reversed_chars_at(offset) {
|
||||||
|
if c.is_alphanumeric() || c == '_' {
|
||||||
|
left.insert(0, c);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let start = offset.saturating_sub(left.len());
|
||||||
|
|
||||||
|
let right = self
|
||||||
|
.chars_at(offset)
|
||||||
|
.take_while(|c| c.is_alphanumeric() || *c == '_')
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let end = offset + right.len();
|
||||||
|
|
||||||
|
if start == end {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(start..end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn word_at(&self, offset: usize) -> String {
|
||||||
|
if let Some(range) = self.word_range(offset) {
|
||||||
|
self.slice(range).to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+929
-717
File diff suppressed because it is too large
Load Diff
@@ -6,11 +6,10 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
use super::InputState;
|
use super::clear_button::clear_button;
|
||||||
use crate::button::{Button, ButtonVariants as _};
|
use super::state::{InputState, CONTEXT};
|
||||||
|
use crate::button::{Button, ButtonVariants};
|
||||||
use crate::indicator::Indicator;
|
use crate::indicator::Indicator;
|
||||||
use crate::input::clear_button::clear_button;
|
|
||||||
use crate::scroll::{Scrollbar, ScrollbarAxis};
|
|
||||||
use crate::{h_flex, IconName, Sizable, Size, StyleSized, StyledExt};
|
use crate::{h_flex, IconName, Sizable, Size, StyleSized, StyledExt};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
@@ -18,7 +17,6 @@ pub struct TextInput {
|
|||||||
state: Entity<InputState>,
|
state: Entity<InputState>,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
size: Size,
|
size: Size,
|
||||||
no_gap: bool,
|
|
||||||
prefix: Option<AnyElement>,
|
prefix: Option<AnyElement>,
|
||||||
suffix: Option<AnyElement>,
|
suffix: Option<AnyElement>,
|
||||||
height: Option<DefiniteLength>,
|
height: Option<DefiniteLength>,
|
||||||
@@ -26,6 +24,8 @@ pub struct TextInput {
|
|||||||
cleanable: bool,
|
cleanable: bool,
|
||||||
mask_toggle: bool,
|
mask_toggle: bool,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
|
bordered: bool,
|
||||||
|
focus_bordered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sizable for TextInput {
|
impl Sizable for TextInput {
|
||||||
@@ -40,9 +40,8 @@ impl TextInput {
|
|||||||
pub fn new(state: &Entity<InputState>) -> Self {
|
pub fn new(state: &Entity<InputState>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
style: StyleRefinement::default(),
|
|
||||||
size: Size::default(),
|
size: Size::default(),
|
||||||
no_gap: false,
|
style: StyleRefinement::default(),
|
||||||
prefix: None,
|
prefix: None,
|
||||||
suffix: None,
|
suffix: None,
|
||||||
height: None,
|
height: None,
|
||||||
@@ -50,6 +49,8 @@ impl TextInput {
|
|||||||
cleanable: false,
|
cleanable: false,
|
||||||
mask_toggle: false,
|
mask_toggle: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
bordered: true,
|
||||||
|
focus_bordered: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,12 +76,24 @@ impl TextInput {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the appearance of the input field.
|
/// Set the appearance of the input field, if false the input field will no border, background.
|
||||||
pub fn appearance(mut self, appearance: bool) -> Self {
|
pub fn appearance(mut self, appearance: bool) -> Self {
|
||||||
self.appearance = appearance;
|
self.appearance = appearance;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the bordered for the input, default: true
|
||||||
|
pub fn bordered(mut self, bordered: bool) -> Self {
|
||||||
|
self.bordered = bordered;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set focus border for the input, default is true.
|
||||||
|
pub fn focus_bordered(mut self, bordered: bool) -> Self {
|
||||||
|
self.focus_bordered = bordered;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set true to show the clear button when the input field is not empty.
|
/// Set true to show the clear button when the input field is not empty.
|
||||||
pub fn cleanable(mut self) -> Self {
|
pub fn cleanable(mut self) -> Self {
|
||||||
self.cleanable = true;
|
self.cleanable = true;
|
||||||
@@ -99,15 +112,6 @@ impl TextInput {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set true to not use gap between input and prefix, suffix, and clear button.
|
|
||||||
///
|
|
||||||
/// Default: false
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(super) fn no_gap(mut self) -> Self {
|
|
||||||
self.no_gap = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_toggle_mask_button(state: Entity<InputState>) -> impl IntoElement {
|
fn render_toggle_mask_button(state: Entity<InputState>) -> impl IntoElement {
|
||||||
Button::new("toggle-mask")
|
Button::new("toggle-mask")
|
||||||
.icon(IconName::Eye)
|
.icon(IconName::Eye)
|
||||||
@@ -132,44 +136,51 @@ impl TextInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Styled for TextInput {
|
||||||
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
|
&mut self.style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for TextInput {
|
impl RenderOnce for TextInput {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
const LINE_HEIGHT: Rems = Rems(1.25);
|
const LINE_HEIGHT: Rems = Rems(1.25);
|
||||||
|
let font = window.text_style().font();
|
||||||
|
let font_size = window.text_style().font_size.to_pixels(window.rem_size());
|
||||||
|
|
||||||
self.state.update(cx, |state, _| {
|
self.state.update(cx, |state, cx| {
|
||||||
state.mode.set_height(self.height);
|
state.text_wrapper.set_font(font, font_size, cx);
|
||||||
state.disabled = self.disabled;
|
state.disabled = self.disabled;
|
||||||
});
|
});
|
||||||
|
|
||||||
let state = self.state.read(cx);
|
let state = self.state.read(cx);
|
||||||
let focused = state.focus_handle.is_focused(window);
|
|
||||||
|
|
||||||
let mut gap_x = match self.size {
|
let gap_x = match self.size {
|
||||||
Size::Small => px(4.),
|
Size::Small => px(4.),
|
||||||
Size::Large => px(8.),
|
Size::Large => px(8.),
|
||||||
_ => px(4.),
|
_ => px(4.),
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.no_gap {
|
|
||||||
gap_x = px(0.);
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix = self.prefix;
|
|
||||||
let suffix = self.suffix;
|
|
||||||
|
|
||||||
let show_clear_button =
|
|
||||||
self.cleanable && !state.loading && !state.text.is_empty() && state.is_single_line();
|
|
||||||
|
|
||||||
let bg = if state.disabled {
|
let bg = if state.disabled {
|
||||||
cx.theme().surface_background
|
cx.theme().surface_background
|
||||||
} else {
|
} else {
|
||||||
cx.theme().elevated_surface_background
|
cx.theme().elevated_surface_background
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let prefix = self.prefix;
|
||||||
|
let suffix = self.suffix;
|
||||||
|
|
||||||
|
let show_clear_button = self.cleanable
|
||||||
|
&& !state.loading
|
||||||
|
&& !state.text.is_empty()
|
||||||
|
&& state.mode.is_single_line();
|
||||||
|
|
||||||
|
let has_suffix = suffix.is_some() || state.loading || self.mask_toggle || show_clear_button;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id(("input", self.state.entity_id()))
|
.id(("input", self.state.entity_id()))
|
||||||
.flex()
|
.flex()
|
||||||
.key_context(crate::input::CONTEXT)
|
.key_context(CONTEXT)
|
||||||
.track_focus(&state.focus_handle)
|
.track_focus(&state.focus_handle)
|
||||||
.when(!state.disabled, |this| {
|
.when(!state.disabled, |this| {
|
||||||
this.on_action(window.listener_for(&self.state, InputState::backspace))
|
this.on_action(window.listener_for(&self.state, InputState::backspace))
|
||||||
@@ -182,17 +193,31 @@ impl RenderOnce for TextInput {
|
|||||||
.on_action(window.listener_for(&self.state, InputState::delete_next_word))
|
.on_action(window.listener_for(&self.state, InputState::delete_next_word))
|
||||||
.on_action(window.listener_for(&self.state, InputState::enter))
|
.on_action(window.listener_for(&self.state, InputState::enter))
|
||||||
.on_action(window.listener_for(&self.state, InputState::escape))
|
.on_action(window.listener_for(&self.state, InputState::escape))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::paste))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::cut))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::undo))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::redo))
|
||||||
|
.when(state.mode.is_multi_line(), |this| {
|
||||||
|
this.on_action(window.listener_for(&self.state, InputState::indent_inline))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::outdent_inline))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::indent_block))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::outdent_block))
|
||||||
|
.on_action(
|
||||||
|
window.listener_for(&self.state, InputState::shift_to_new_line),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.on_action(window.listener_for(&self.state, InputState::left))
|
.on_action(window.listener_for(&self.state, InputState::left))
|
||||||
.on_action(window.listener_for(&self.state, InputState::right))
|
.on_action(window.listener_for(&self.state, InputState::right))
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_left))
|
.on_action(window.listener_for(&self.state, InputState::select_left))
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_right))
|
.on_action(window.listener_for(&self.state, InputState::select_right))
|
||||||
.when(state.is_multi_line(), |this| {
|
.when(state.mode.is_multi_line(), |this| {
|
||||||
this.on_action(window.listener_for(&self.state, InputState::up))
|
this.on_action(window.listener_for(&self.state, InputState::up))
|
||||||
.on_action(window.listener_for(&self.state, InputState::down))
|
.on_action(window.listener_for(&self.state, InputState::down))
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_up))
|
.on_action(window.listener_for(&self.state, InputState::select_up))
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_down))
|
.on_action(window.listener_for(&self.state, InputState::select_down))
|
||||||
.on_action(window.listener_for(&self.state, InputState::shift_to_new_line))
|
.on_action(window.listener_for(&self.state, InputState::page_up))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::page_down))
|
||||||
})
|
})
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_all))
|
.on_action(window.listener_for(&self.state, InputState::select_all))
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_to_start_of_line))
|
.on_action(window.listener_for(&self.state, InputState::select_to_start_of_line))
|
||||||
@@ -209,54 +234,51 @@ impl RenderOnce for TextInput {
|
|||||||
.on_action(window.listener_for(&self.state, InputState::select_to_end))
|
.on_action(window.listener_for(&self.state, InputState::select_to_end))
|
||||||
.on_action(window.listener_for(&self.state, InputState::show_character_palette))
|
.on_action(window.listener_for(&self.state, InputState::show_character_palette))
|
||||||
.on_action(window.listener_for(&self.state, InputState::copy))
|
.on_action(window.listener_for(&self.state, InputState::copy))
|
||||||
.on_action(window.listener_for(&self.state, InputState::paste))
|
|
||||||
.on_action(window.listener_for(&self.state, InputState::cut))
|
|
||||||
.on_action(window.listener_for(&self.state, InputState::undo))
|
|
||||||
.on_action(window.listener_for(&self.state, InputState::redo))
|
|
||||||
.on_key_down(window.listener_for(&self.state, InputState::on_key_down))
|
.on_key_down(window.listener_for(&self.state, InputState::on_key_down))
|
||||||
.on_mouse_down(
|
.on_mouse_down(
|
||||||
MouseButton::Left,
|
MouseButton::Left,
|
||||||
window.listener_for(&self.state, InputState::on_mouse_down),
|
window.listener_for(&self.state, InputState::on_mouse_down),
|
||||||
)
|
)
|
||||||
.on_mouse_down(
|
.on_mouse_down(
|
||||||
MouseButton::Middle,
|
MouseButton::Right,
|
||||||
window.listener_for(&self.state, InputState::on_mouse_down),
|
window.listener_for(&self.state, InputState::on_mouse_down),
|
||||||
)
|
)
|
||||||
.on_mouse_up(
|
.on_mouse_up(
|
||||||
MouseButton::Left,
|
MouseButton::Left,
|
||||||
window.listener_for(&self.state, InputState::on_mouse_up),
|
window.listener_for(&self.state, InputState::on_mouse_up),
|
||||||
)
|
)
|
||||||
|
.on_mouse_up(
|
||||||
|
MouseButton::Right,
|
||||||
|
window.listener_for(&self.state, InputState::on_mouse_up),
|
||||||
|
)
|
||||||
.on_scroll_wheel(window.listener_for(&self.state, InputState::on_scroll_wheel))
|
.on_scroll_wheel(window.listener_for(&self.state, InputState::on_scroll_wheel))
|
||||||
.size_full()
|
.size_full()
|
||||||
.line_height(LINE_HEIGHT)
|
.line_height(LINE_HEIGHT)
|
||||||
.cursor_text()
|
.input_px(self.size)
|
||||||
.input_py(self.size)
|
.input_py(self.size)
|
||||||
.input_h(self.size)
|
.input_h(self.size)
|
||||||
.when(state.is_multi_line(), |this| {
|
.cursor_text()
|
||||||
|
.text_size(font_size)
|
||||||
|
.items_center()
|
||||||
|
.when(state.mode.is_multi_line(), |this| {
|
||||||
this.h_auto()
|
this.h_auto()
|
||||||
.when_some(self.height, |this, height| this.h(height))
|
.when_some(self.height, |this, height| this.h(height))
|
||||||
})
|
})
|
||||||
.when(self.appearance, |this| {
|
.when(self.appearance, |this| {
|
||||||
this.bg(bg)
|
this.bg(bg).rounded(cx.theme().radius)
|
||||||
.rounded(cx.theme().radius)
|
|
||||||
.when(focused, |this| this.border_color(cx.theme().ring))
|
|
||||||
})
|
})
|
||||||
.when(prefix.is_none(), |this| this.input_pl(self.size))
|
|
||||||
.input_pr(self.size)
|
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap(gap_x)
|
.gap(gap_x)
|
||||||
|
.refine_style(&self.style)
|
||||||
.children(prefix)
|
.children(prefix)
|
||||||
// TODO: Define height here, and use it in the input element
|
|
||||||
.child(self.state.clone())
|
.child(self.state.clone())
|
||||||
.child(
|
.when(has_suffix, |this| {
|
||||||
|
this.pr_2().child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("suffix")
|
.id("suffix")
|
||||||
.absolute()
|
|
||||||
.gap(gap_x)
|
.gap(gap_x)
|
||||||
.when(self.appearance, |this| this.bg(bg))
|
.when(self.appearance, |this| this.bg(bg))
|
||||||
.items_center()
|
.items_center()
|
||||||
.when(suffix.is_none(), |this| this.pr_1())
|
|
||||||
.right_0()
|
|
||||||
.when(state.loading, |this| {
|
.when(state.loading, |this| {
|
||||||
this.child(Indicator::new().color(cx.theme().text_muted))
|
this.child(Indicator::new().color(cx.theme().text_muted))
|
||||||
})
|
})
|
||||||
@@ -275,24 +297,6 @@ impl RenderOnce for TextInput {
|
|||||||
})
|
})
|
||||||
.children(suffix),
|
.children(suffix),
|
||||||
)
|
)
|
||||||
.when(state.is_multi_line(), |this| {
|
|
||||||
if state.last_layout.is_some() {
|
|
||||||
this.relative().child(
|
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.top_0()
|
|
||||||
.left_0()
|
|
||||||
.right(px(1.))
|
|
||||||
.bottom_0()
|
|
||||||
.child(
|
|
||||||
Scrollbar::vertical(&state.scrollbar_state, &state.scroll_handle)
|
|
||||||
.axis(ScrollbarAxis::Vertical),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.refine_style(&self.style)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +1,215 @@
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use gpui::{App, Font, LineFragment, Pixels, SharedString};
|
use gpui::{App, Font, LineFragment, Pixels};
|
||||||
|
use rope::Rope;
|
||||||
|
|
||||||
#[allow(unused)]
|
use super::rope_ext::RopeExt;
|
||||||
pub(super) struct LineWrap {
|
|
||||||
/// The number of soft wrapped lines of this line (Not include first line.)
|
|
||||||
pub(super) wrap_lines: usize,
|
|
||||||
/// The range of the line text in the entire text.
|
|
||||||
pub(super) range: Range<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineWrap {
|
/// A line with soft wrapped lines info.
|
||||||
pub(super) fn height(&self, line_height: Pixels) -> Pixels {
|
#[derive(Clone)]
|
||||||
line_height * (self.wrap_lines + 1)
|
pub(super) struct LineItem {
|
||||||
}
|
/// The original line text.
|
||||||
}
|
line: Rope,
|
||||||
|
/// The soft wrapped lines relative byte range (0..line.len) of this line (Include first line).
|
||||||
/// Used to prepare the text with soft_wrap to be get lines to displayed in the TextArea
|
|
||||||
///
|
///
|
||||||
/// After use lines to calculate the scroll size of the TextArea
|
/// FIXME: Here in somecase, the `line_wrapper.wrap_line` has returned different
|
||||||
|
/// like the `window.text_system().shape_text`. So, this value may not equal
|
||||||
|
/// the actual rendered lines.
|
||||||
|
wrapped_lines: Vec<Range<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineItem {
|
||||||
|
/// Get the bytes length of this line.
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn len(&self) -> usize {
|
||||||
|
self.line.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get number of soft wrapped lines of this line (include the first line).
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn lines_len(&self) -> usize {
|
||||||
|
self.wrapped_lines.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the height of this line item with given line height.
|
||||||
|
pub(super) fn height(&self, line_height: Pixels) -> Pixels {
|
||||||
|
self.lines_len() as f32 * line_height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to prepare the text with soft wrap to be get lines to displayed in the Editor.
|
||||||
|
///
|
||||||
|
/// After use lines to calculate the scroll size of the Editor.
|
||||||
pub(super) struct TextWrapper {
|
pub(super) struct TextWrapper {
|
||||||
pub(super) text: SharedString,
|
text: Rope,
|
||||||
/// The wrapped lines, value is start and end index of the line (by split \n).
|
/// Total wrapped lines (Inlucde the first line), value is start and end index of the line.
|
||||||
pub(super) wrapped_lines: Vec<Range<usize>>,
|
soft_lines: usize,
|
||||||
/// The lines by split \n
|
font: Font,
|
||||||
pub(super) lines: Vec<LineWrap>,
|
font_size: Pixels,
|
||||||
pub(super) font: Font,
|
|
||||||
pub(super) font_size: Pixels,
|
|
||||||
/// If is none, it means the text is not wrapped
|
/// If is none, it means the text is not wrapped
|
||||||
pub(super) wrap_width: Option<Pixels>,
|
wrap_width: Option<Pixels>,
|
||||||
|
/// The lines by split \n
|
||||||
|
pub(super) lines: Vec<LineItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
impl TextWrapper {
|
impl TextWrapper {
|
||||||
pub(super) fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {
|
pub(super) fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: SharedString::default(),
|
text: Rope::new(),
|
||||||
font,
|
font,
|
||||||
font_size,
|
font_size,
|
||||||
wrap_width,
|
wrap_width,
|
||||||
wrapped_lines: Vec::new(),
|
soft_lines: 0,
|
||||||
lines: Vec::new(),
|
lines: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn set_default_text(&mut self, text: &Rope) {
|
||||||
|
self.text = text.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the total number of lines including wrapped lines.
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn len(&self) -> usize {
|
||||||
|
self.soft_lines
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the line item by row index.
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn line(&self, row: usize) -> Option<&LineItem> {
|
||||||
|
self.lines.get(row)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {
|
pub(super) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {
|
||||||
self.wrap_width = wrap_width;
|
if wrap_width == self.wrap_width {
|
||||||
self.update(&self.text.clone(), true, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
|
|
||||||
self.font = font;
|
|
||||||
self.font_size = font_size;
|
|
||||||
self.update(&self.text.clone(), true, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update(&mut self, text: &SharedString, force: bool, cx: &mut App) {
|
|
||||||
if &self.text == text && !force {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut wrapped_lines = vec![];
|
self.wrap_width = wrap_width;
|
||||||
let mut lines = vec![];
|
self.update_all(&self.text.clone(), true, cx);
|
||||||
let wrap_width = self.wrap_width.unwrap_or(Pixels::MAX);
|
}
|
||||||
|
|
||||||
|
pub(super) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
|
||||||
|
if self.font.eq(&font) && self.font_size == font_size {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.font = font;
|
||||||
|
self.font_size = font_size;
|
||||||
|
self.update_all(&self.text.clone(), true, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the text wrapper and recalculate the wrapped lines.
|
||||||
|
///
|
||||||
|
/// If the `text` is the same as the current text, do nothing.
|
||||||
|
///
|
||||||
|
/// - `changed_text`: The text [`Rope`] that has changed.
|
||||||
|
/// - `range`: The `selected_range` before change.
|
||||||
|
/// - `new_text`: The inserted text.
|
||||||
|
/// - `force`: Whether to force the update, if false, the update will be skipped if the text is the same.
|
||||||
|
/// - `cx`: The application context.
|
||||||
|
pub(super) fn update(
|
||||||
|
&mut self,
|
||||||
|
changed_text: &Rope,
|
||||||
|
range: &Range<usize>,
|
||||||
|
new_text: &Rope,
|
||||||
|
force: bool,
|
||||||
|
cx: &mut App,
|
||||||
|
) {
|
||||||
let mut line_wrapper = cx
|
let mut line_wrapper = cx
|
||||||
.text_system()
|
.text_system()
|
||||||
.line_wrapper(self.font.clone(), self.font_size);
|
.line_wrapper(self.font.clone(), self.font_size);
|
||||||
|
self._update(
|
||||||
|
changed_text,
|
||||||
|
range,
|
||||||
|
new_text,
|
||||||
|
force,
|
||||||
|
&mut |line_str, wrap_width| {
|
||||||
|
line_wrapper
|
||||||
|
.wrap_line(&[LineFragment::text(line_str)], wrap_width)
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let mut prev_line_ix = 0;
|
fn _update<F>(
|
||||||
for line in text.split('\n') {
|
&mut self,
|
||||||
let mut line_wraps = vec![];
|
changed_text: &Rope,
|
||||||
|
range: &Range<usize>,
|
||||||
|
new_text: &Rope,
|
||||||
|
force: bool,
|
||||||
|
wrap_line: &mut F,
|
||||||
|
) where
|
||||||
|
F: FnMut(&str, Pixels) -> Vec<gpui::Boundary>,
|
||||||
|
{
|
||||||
|
if self.text.eq(changed_text) && !force {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the old changed lines.
|
||||||
|
let start_row = self.text.offset_to_point(range.start).row as usize;
|
||||||
|
let start_row = start_row.min(self.lines.len().saturating_sub(1));
|
||||||
|
let end_row = self.text.offset_to_point(range.end).row as usize;
|
||||||
|
let end_row = end_row.min(self.lines.len().saturating_sub(1));
|
||||||
|
let rows_range = start_row..=end_row;
|
||||||
|
|
||||||
|
// To add the new lines.
|
||||||
|
let new_start_row = changed_text.offset_to_point(range.start).row as usize;
|
||||||
|
let new_start_offset = changed_text.line_start_offset(new_start_row);
|
||||||
|
let new_end_row = changed_text
|
||||||
|
.offset_to_point(range.start + new_text.len())
|
||||||
|
.row as usize;
|
||||||
|
let new_end_offset = changed_text.line_end_offset(new_end_row);
|
||||||
|
let new_range = new_start_offset..new_end_offset;
|
||||||
|
|
||||||
|
let mut new_lines = vec![];
|
||||||
|
|
||||||
|
let wrap_width = self.wrap_width;
|
||||||
|
|
||||||
|
for line in changed_text.slice(new_range).lines() {
|
||||||
|
let line_str = line.to_string();
|
||||||
|
let mut wrapped_lines = vec![];
|
||||||
let mut prev_boundary_ix = 0;
|
let mut prev_boundary_ix = 0;
|
||||||
|
|
||||||
|
// If wrap_width is Pixels::MAX, skip wrapping to disable word wrap
|
||||||
|
if let Some(wrap_width) = wrap_width {
|
||||||
// Here only have wrapped line, if there is no wrap meet, the `line_wraps` result will empty.
|
// Here only have wrapped line, if there is no wrap meet, the `line_wraps` result will empty.
|
||||||
for boundary in line_wrapper.wrap_line(&[LineFragment::text(line)], wrap_width) {
|
for boundary in wrap_line(&line_str, wrap_width) {
|
||||||
line_wraps.push(prev_boundary_ix..boundary.ix);
|
wrapped_lines.push(prev_boundary_ix..boundary.ix);
|
||||||
prev_boundary_ix = boundary.ix;
|
prev_boundary_ix = boundary.ix;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lines.push(LineWrap {
|
|
||||||
wrap_lines: line_wraps.len(),
|
|
||||||
range: prev_line_ix..prev_line_ix + line.len(),
|
|
||||||
});
|
|
||||||
|
|
||||||
wrapped_lines.extend(line_wraps);
|
|
||||||
// Reset of the line
|
// Reset of the line
|
||||||
if !line[prev_boundary_ix..].is_empty() || prev_boundary_ix == 0 {
|
if !line_str[prev_boundary_ix..].is_empty() || prev_boundary_ix == 0 {
|
||||||
wrapped_lines.push(prev_line_ix + prev_boundary_ix..prev_line_ix + line.len());
|
wrapped_lines.push(prev_boundary_ix..line.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_line_ix += line.len() + 1;
|
new_lines.push(LineItem {
|
||||||
|
line: line.clone(),
|
||||||
|
wrapped_lines,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.text = text.clone();
|
// dbg!(&new_lines.len());
|
||||||
self.wrapped_lines = wrapped_lines;
|
// dbg!(self.lines.len());
|
||||||
self.lines = lines;
|
if self.lines.is_empty() {
|
||||||
|
self.lines = new_lines;
|
||||||
|
} else {
|
||||||
|
self.lines.splice(rows_range, new_lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbg!(self.lines.len());
|
||||||
|
self.text = changed_text.clone();
|
||||||
|
self.soft_lines = self.lines.iter().map(|l| l.lines_len()).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the text wrapper and recalculate the wrapped lines.
|
||||||
|
///
|
||||||
|
/// If the `text` is the same as the current text, do nothing.
|
||||||
|
pub(crate) fn update_all(&mut self, text: &Rope, force: bool, cx: &mut App) {
|
||||||
|
self.update(text, &(0..text.len()), text, force, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,14 +299,16 @@ where
|
|||||||
|
|
||||||
fn on_query_input_event(
|
fn on_query_input_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &Entity<InputState>,
|
state: &Entity<InputState>,
|
||||||
event: &InputEvent,
|
event: &InputEvent,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
InputEvent::Change(text) => {
|
InputEvent::Change => {
|
||||||
|
let text = state.read(cx).value();
|
||||||
let text = text.trim().to_string();
|
let text = text.trim().to_string();
|
||||||
|
|
||||||
if Some(&text) == self.last_query.as_ref() {
|
if Some(&text) == self.last_query.as_ref() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -347,7 +349,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_querying(&mut self, querying: bool, _: &mut Window, cx: &mut Context<Self>) {
|
fn set_querying(&mut self, querying: bool, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.querying = querying;
|
self.querying = querying;
|
||||||
if let Some(input) = &self.query_input {
|
if let Some(input) = &self.query_input {
|
||||||
input.update(cx, |input, cx| input.set_loading(querying, cx))
|
input.update(cx, |input, cx| input.set_loading(querying, cx))
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use regex::Regex;
|
|||||||
use registry::Registry;
|
use registry::Registry;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
use crate::actions::OpenProfile;
|
use crate::actions::OpenPublicKey;
|
||||||
|
|
||||||
static URL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
static URL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(r"^(?:[a-zA-Z]+://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?(/.*)?$").unwrap()
|
Regex::new(r"^(?:[a-zA-Z]+://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?(/.*)?$").unwrap()
|
||||||
@@ -140,7 +140,7 @@ impl RenderedText {
|
|||||||
log::error!("Failed to parse public key from: {clean_url}");
|
log::error!("Failed to parse public key from: {clean_url}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
window.dispatch_action(Box::new(OpenProfile(public_key)), cx);
|
window.dispatch_action(Box::new(OpenPublicKey(public_key)), cx);
|
||||||
} else if is_url(token) {
|
} else if is_url(token) {
|
||||||
if !token.starts_with("http") {
|
if !token.starts_with("http") {
|
||||||
cx.open_url(&format!("https://{token}"));
|
cx.open_url(&format!("https://{token}"));
|
||||||
|
|||||||
+3
-1
@@ -272,9 +272,11 @@ profile:
|
|||||||
unknown:
|
unknown:
|
||||||
en: "Unknown contact"
|
en: "Unknown contact"
|
||||||
njump:
|
njump:
|
||||||
en: "Open in njump.me"
|
en: "View on njump.me"
|
||||||
no_bio:
|
no_bio:
|
||||||
en: "No bio."
|
en: "No bio."
|
||||||
|
copy:
|
||||||
|
en: "Copy Public Key"
|
||||||
|
|
||||||
preferences:
|
preferences:
|
||||||
account_header:
|
account_header:
|
||||||
|
|||||||
Executable
+79
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to release a new version of the application
|
||||||
|
# Usage: ./release <new_version>
|
||||||
|
|
||||||
|
set -e # Exit on any error
|
||||||
|
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo "Usage: $0 <new_version>"
|
||||||
|
echo "Example: $0 1.0.0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEW_VERSION="$1"
|
||||||
|
WORKSPACE_CARGO="Cargo.toml"
|
||||||
|
CRATE_CARGO="crates/coop/Cargo.toml"
|
||||||
|
|
||||||
|
# Check if both Cargo.toml files exist
|
||||||
|
if [ ! -f "$WORKSPACE_CARGO" ]; then
|
||||||
|
echo "Error: $WORKSPACE_CARGO not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$CRATE_CARGO" ]; then
|
||||||
|
echo "Error: $CRATE_CARGO not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to update version in a Cargo.toml file
|
||||||
|
update_version() {
|
||||||
|
local file="$1"
|
||||||
|
local backup="${file}.bak"
|
||||||
|
|
||||||
|
# Backup the original file
|
||||||
|
cp "$file" "$backup"
|
||||||
|
|
||||||
|
# Replace the version in Cargo.toml
|
||||||
|
if sed -i.bak -E "s/^version = \"[0-9]+\.[0-9]+\.[0-9]+\"/version = \"$NEW_VERSION\"/" "$file"; then
|
||||||
|
echo "✓ Updated version to $NEW_VERSION in $file"
|
||||||
|
# Remove backup created by sed
|
||||||
|
if [ -f "${file}.bak" ]; then
|
||||||
|
rm "${file}.bak"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Error: Failed to update version in $file"
|
||||||
|
# Restore original backup
|
||||||
|
mv "$backup" "$file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove the initial backup file
|
||||||
|
rm -f "$backup"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update both Cargo.toml files
|
||||||
|
echo "Updating versions..."
|
||||||
|
update_version "$WORKSPACE_CARGO"
|
||||||
|
update_version "$CRATE_CARGO"
|
||||||
|
|
||||||
|
# Create git tag
|
||||||
|
TAG_NAME="v$NEW_VERSION"
|
||||||
|
COMMIT_MSG="Release version $NEW_VERSION"
|
||||||
|
|
||||||
|
if git tag -a "$TAG_NAME" -m "$COMMIT_MSG"; then
|
||||||
|
echo "✓ Created git tag: $TAG_NAME"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to create git tag"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Push to origin (both commits and tags)
|
||||||
|
echo "Pushing to origin..."
|
||||||
|
if git push origin master && git push origin "$TAG_NAME"; then
|
||||||
|
echo "✓ Successfully pushed to origin"
|
||||||
|
echo "✓ Release $NEW_VERSION completed successfully!"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to push to origin"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user