Merge branch 'main' into editor_renovation
This commit is contained in:
45
.github/workflows/release_actions.yml
vendored
45
.github/workflows/release_actions.yml
vendored
@@ -6,26 +6,27 @@ jobs:
|
||||
discord_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get release URL
|
||||
id: get-release-url
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
||||
URL="https://zed.dev/releases/preview/latest"
|
||||
else
|
||||
URL="https://zed.dev/releases/stable/latest"
|
||||
fi
|
||||
echo "::set-output name=URL::$URL"
|
||||
- name: Get content
|
||||
uses: 2428392/gh-truncate-string-action@v1.2.0
|
||||
id: get-content
|
||||
with:
|
||||
stringToTruncate: |
|
||||
📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released!
|
||||
- name: Get release URL
|
||||
id: get-release-url
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
||||
URL="https://zed.dev/releases/preview/latest"
|
||||
else
|
||||
URL="https://zed.dev/releases/stable/latest"
|
||||
fi
|
||||
echo "::set-output name=URL::$URL"
|
||||
- name: Get content
|
||||
uses: 2428392/gh-truncate-string-action@v1.3.0
|
||||
id: get-content
|
||||
with:
|
||||
stringToTruncate: |
|
||||
📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released!
|
||||
|
||||
${{ github.event.release.body }}
|
||||
maxLength: 2000
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ steps.get-content.outputs.string }}
|
||||
${{ github.event.release.body }}
|
||||
maxLength: 2000
|
||||
truncationSymbol: "..."
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ steps.get-content.outputs.string }}
|
||||
|
||||
225
Cargo.lock
generated
225
Cargo.lock
generated
@@ -1169,7 +1169,7 @@ dependencies = [
|
||||
"futures 0.3.28",
|
||||
"gpui2",
|
||||
"language2",
|
||||
"live_kit_client",
|
||||
"live_kit_client2",
|
||||
"log",
|
||||
"media",
|
||||
"postage",
|
||||
@@ -1541,13 +1541,12 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"settings",
|
||||
"settings2",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"sysinfo",
|
||||
"tempfile",
|
||||
"text",
|
||||
"text2",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tiny_http",
|
||||
@@ -1603,7 +1602,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.27.0"
|
||||
version = "0.28.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3092,7 +3091,7 @@ dependencies = [
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"tempfile",
|
||||
"text",
|
||||
"text2",
|
||||
"time",
|
||||
"util",
|
||||
]
|
||||
@@ -3384,6 +3383,26 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git3"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"clock",
|
||||
"collections",
|
||||
"futures 0.3.28",
|
||||
"git2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"text2",
|
||||
"unindent",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
@@ -4225,7 +4244,7 @@ dependencies = [
|
||||
"settings2",
|
||||
"shellexpand",
|
||||
"util",
|
||||
"workspace",
|
||||
"workspace2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4358,7 +4377,7 @@ dependencies = [
|
||||
"env_logger 0.9.3",
|
||||
"futures 0.3.28",
|
||||
"fuzzy2",
|
||||
"git",
|
||||
"git3",
|
||||
"globset",
|
||||
"gpui2",
|
||||
"indoc",
|
||||
@@ -4379,7 +4398,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"text",
|
||||
"text2",
|
||||
"theme2",
|
||||
"tree-sitter",
|
||||
"tree-sitter-elixir",
|
||||
@@ -4601,6 +4620,39 @@ dependencies = [
|
||||
"simplelog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "live_kit_client2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-broadcast",
|
||||
"async-trait",
|
||||
"block",
|
||||
"byteorder",
|
||||
"bytes 1.5.0",
|
||||
"cocoa",
|
||||
"collections",
|
||||
"core-foundation",
|
||||
"core-graphics",
|
||||
"foreign-types",
|
||||
"futures 0.3.28",
|
||||
"gpui2",
|
||||
"hmac 0.12.1",
|
||||
"jwt",
|
||||
"live_kit_server",
|
||||
"log",
|
||||
"media",
|
||||
"nanoid",
|
||||
"objc",
|
||||
"parking_lot 0.11.2",
|
||||
"postage",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sha2 0.10.7",
|
||||
"simplelog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "live_kit_server"
|
||||
version = "0.1.0"
|
||||
@@ -5047,6 +5099,53 @@ dependencies = [
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multi_buffer2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
"client2",
|
||||
"clock",
|
||||
"collections",
|
||||
"convert_case 0.6.0",
|
||||
"copilot2",
|
||||
"ctor",
|
||||
"env_logger 0.9.3",
|
||||
"futures 0.3.28",
|
||||
"git3",
|
||||
"gpui2",
|
||||
"indoc",
|
||||
"itertools 0.10.5",
|
||||
"language2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"lsp2",
|
||||
"ordered-float 2.10.0",
|
||||
"parking_lot 0.11.2",
|
||||
"postage",
|
||||
"project2",
|
||||
"pulldown-cmark",
|
||||
"rand 0.8.5",
|
||||
"rich_text2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"settings2",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"snippet",
|
||||
"sum_tree",
|
||||
"text2",
|
||||
"theme2",
|
||||
"tree-sitter",
|
||||
"tree-sitter-html",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"unindent",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.8.3"
|
||||
@@ -6218,8 +6317,8 @@ dependencies = [
|
||||
"fsevent",
|
||||
"futures 0.3.28",
|
||||
"fuzzy2",
|
||||
"git",
|
||||
"git2",
|
||||
"git3",
|
||||
"globset",
|
||||
"gpui2",
|
||||
"ignore",
|
||||
@@ -6247,7 +6346,7 @@ dependencies = [
|
||||
"sum_tree",
|
||||
"tempdir",
|
||||
"terminal2",
|
||||
"text",
|
||||
"text2",
|
||||
"thiserror",
|
||||
"toml 0.5.11",
|
||||
"unindent",
|
||||
@@ -6878,6 +6977,24 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich_text2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"futures 0.3.28",
|
||||
"gpui2",
|
||||
"language2",
|
||||
"lazy_static",
|
||||
"pulldown-cmark",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"theme2",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
@@ -7493,7 +7610,6 @@ dependencies = [
|
||||
"collections",
|
||||
"editor",
|
||||
"futures 0.3.28",
|
||||
"globset",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
@@ -7746,7 +7862,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"feature_flags2",
|
||||
"fs",
|
||||
"fs2",
|
||||
"futures 0.3.28",
|
||||
"gpui2",
|
||||
@@ -8756,6 +8871,29 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clock",
|
||||
"collections",
|
||||
"ctor",
|
||||
"digest 0.9.0",
|
||||
"env_logger 0.9.3",
|
||||
"gpui2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
"postage",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rope",
|
||||
"smallvec",
|
||||
"sum_tree",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
@@ -8785,10 +8923,11 @@ name = "theme2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fs",
|
||||
"fs2",
|
||||
"gpui2",
|
||||
"indexmap 1.9.3",
|
||||
"parking_lot 0.11.2",
|
||||
"refineable",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -8798,21 +8937,6 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "theme_converter"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.4.4",
|
||||
"convert_case 0.6.0",
|
||||
"gpui2",
|
||||
"log",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"simplelog",
|
||||
"theme2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "theme_selector"
|
||||
version = "0.1.0"
|
||||
@@ -9630,6 +9754,7 @@ dependencies = [
|
||||
"itertools 0.11.0",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"settings2",
|
||||
"smallvec",
|
||||
"strum",
|
||||
"theme2",
|
||||
@@ -9791,6 +9916,7 @@ dependencies = [
|
||||
"dirs 3.0.2",
|
||||
"futures 0.3.28",
|
||||
"git2",
|
||||
"globset",
|
||||
"isahc",
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -10752,6 +10878,44 @@ dependencies = [
|
||||
"text",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "workspace2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion 1.0.5",
|
||||
"bincode",
|
||||
"call2",
|
||||
"client2",
|
||||
"collections",
|
||||
"db2",
|
||||
"env_logger 0.9.3",
|
||||
"fs2",
|
||||
"futures 0.3.28",
|
||||
"gpui2",
|
||||
"indoc",
|
||||
"install_cli2",
|
||||
"itertools 0.10.5",
|
||||
"language2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"node_runtime",
|
||||
"parking_lot 0.11.2",
|
||||
"postage",
|
||||
"project2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"settings2",
|
||||
"smallvec",
|
||||
"terminal2",
|
||||
"theme2",
|
||||
"ui2",
|
||||
"util",
|
||||
"uuid 1.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
@@ -10832,7 +10996,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.111.0"
|
||||
version = "0.112.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
@@ -11029,7 +11193,7 @@ dependencies = [
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"tempdir",
|
||||
"text",
|
||||
"text2",
|
||||
"theme2",
|
||||
"thiserror",
|
||||
"tiny_http",
|
||||
@@ -11067,6 +11231,7 @@ dependencies = [
|
||||
"urlencoding",
|
||||
"util",
|
||||
"uuid 1.4.1",
|
||||
"workspace2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -62,6 +62,7 @@ members = [
|
||||
"crates/menu",
|
||||
"crates/menu2",
|
||||
"crates/multi_buffer",
|
||||
"crates/multi_buffer2",
|
||||
"crates/node_runtime",
|
||||
"crates/notifications",
|
||||
"crates/outline",
|
||||
@@ -94,14 +95,13 @@ members = [
|
||||
"crates/text",
|
||||
"crates/theme",
|
||||
"crates/theme2",
|
||||
"crates/theme_converter",
|
||||
"crates/theme_selector",
|
||||
"crates/ui2",
|
||||
"crates/util",
|
||||
"crates/semantic_index",
|
||||
"crates/vim",
|
||||
"crates/vcs_menu",
|
||||
"crates/workspace",
|
||||
"crates/workspace2",
|
||||
"crates/welcome",
|
||||
"crates/xtask",
|
||||
"crates/zed",
|
||||
|
||||
1
assets/icons/at-sign.svg
Normal file
1
assets/icons/at-sign.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-at-sign"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/></svg>
|
||||
|
After Width: | Height: | Size: 300 B |
1
assets/icons/bell-off.svg
Normal file
1
assets/icons/bell-off.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell-off"><path d="M8.7 3A6 6 0 0 1 18 8a21.3 21.3 0 0 0 .6 5"/><path d="M17 17H3s3-2 3-9a4.67 4.67 0 0 1 .3-1.7"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="m2 2 20 20"/></svg>
|
||||
|
After Width: | Height: | Size: 387 B |
1
assets/icons/bell-ring.svg
Normal file
1
assets/icons/bell-ring.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell-ring"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="M4 2C2.8 3.7 2 5.7 2 8"/><path d="M22 8c0-2.3-.8-4.3-2-6"/></svg>
|
||||
|
After Width: | Height: | Size: 382 B |
@@ -1,8 +1 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.60124 1.25086C8.60124 1.75459 8.26278 2.17927 7.80087 2.30989C10.1459 2.4647 12 4.41582 12 6.79999V10.25C12 11.0563 12.0329 11.7074 12.7236 12.0528C12.931 12.1565 13.0399 12.3892 12.9866 12.6149C12.9333 12.8406 12.7319 13 12.5 13H8.16144C8.36904 13.1832 8.49997 13.4513 8.49997 13.75C8.49997 14.3023 8.05226 14.75 7.49997 14.75C6.94769 14.75 6.49997 14.3023 6.49997 13.75C6.49997 13.4513 6.63091 13.1832 6.83851 13H2.49999C2.2681 13 2.06664 12.8406 2.01336 12.6149C1.96009 12.3892 2.06897 12.1565 2.27638 12.0528C2.96708 11.7074 2.99999 11.0563 2.99999 10.25V6.79999C2.99999 4.41537 4.85481 2.46396 7.20042 2.3098C6.73867 2.17908 6.40036 1.75448 6.40036 1.25086C6.40036 0.643104 6.89304 0.150421 7.5008 0.150421C8.10855 0.150421 8.60124 0.643104 8.60124 1.25086ZM7.49999 3.29999C5.56699 3.29999 3.99999 4.86699 3.99999 6.79999V10.25L4.00002 10.3009C4.0005 10.7463 4.00121 11.4084 3.69929 12H11.3007C10.9988 11.4084 10.9995 10.7463 11 10.3009L11 10.25V6.79999C11 4.86699 9.43299 3.29999 7.49999 3.29999Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 309 B |
1
assets/icons/mail-open.svg
Normal file
1
assets/icons/mail-open.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mail-open"><path d="M21.2 8.4c.5.38.8.97.8 1.6v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 .8-1.6l8-6a2 2 0 0 1 2.4 0l8 6Z"/><path d="m22 10-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 10"/></svg>
|
||||
|
After Width: | Height: | Size: 390 B |
@@ -153,10 +153,17 @@ impl FakeCompletionProvider {
|
||||
|
||||
pub fn send_completion(&self, completion: impl Into<String>) {
|
||||
let mut tx = self.last_completion_tx.lock();
|
||||
tx.as_mut().unwrap().try_send(completion.into()).unwrap();
|
||||
|
||||
println!("COMPLETION TX: {:?}", &tx);
|
||||
|
||||
let a = tx.as_mut().unwrap();
|
||||
a.try_send(completion.into()).unwrap();
|
||||
|
||||
// tx.as_mut().unwrap().try_send(completion.into()).unwrap();
|
||||
}
|
||||
|
||||
pub fn finish_completion(&self) {
|
||||
println!("FINISHING COMPLETION");
|
||||
self.last_completion_tx.lock().take().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -181,8 +188,10 @@ impl CompletionProvider for FakeCompletionProvider {
|
||||
&self,
|
||||
_prompt: Box<dyn CompletionRequest>,
|
||||
) -> BoxFuture<'static, anyhow::Result<BoxStream<'static, anyhow::Result<String>>>> {
|
||||
println!("COMPLETING");
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
*self.last_completion_tx.lock() = Some(tx);
|
||||
println!("TX: {:?}", *self.last_completion_tx.lock());
|
||||
async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed()
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn CompletionProvider> {
|
||||
|
||||
@@ -12,9 +12,9 @@ doctest = false
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
gpui2 = { path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
util = { path = "../util" }
|
||||
language2 = { path = "../language2" }
|
||||
language = { package = "language2", path = "../language2" }
|
||||
async-trait.workspace = true
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -35,4 +35,4 @@ rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
bincode = "1.3.3"
|
||||
|
||||
[dev-dependencies]
|
||||
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use async_trait::async_trait;
|
||||
use gpui2::AppContext;
|
||||
use gpui::AppContext;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ProviderCredential {
|
||||
@@ -8,10 +7,9 @@ pub enum ProviderCredential {
|
||||
NotNeeded,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait CredentialProvider: Send + Sync {
|
||||
pub trait CredentialProvider {
|
||||
fn has_credentials(&self) -> bool;
|
||||
async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
|
||||
async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
|
||||
async fn delete_credentials(&self, cx: &mut AppContext);
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
|
||||
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
|
||||
fn delete_credentials(&self, cx: &mut AppContext);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ mod tests {
|
||||
use super::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_similarity(mut rng: StdRng) {
|
||||
assert_eq!(
|
||||
Embedding::from(vec![1., 0., 0., 0., 0.])
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::cmp::Reverse;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use language2::BufferSnapshot;
|
||||
use language::BufferSnapshot;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::models::LanguageModel;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::anyhow;
|
||||
use language2::BufferSnapshot;
|
||||
use language2::ToOffset;
|
||||
use language::BufferSnapshot;
|
||||
use language::ToOffset;
|
||||
|
||||
use crate::models::LanguageModel;
|
||||
use crate::models::TruncationDirection;
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::prompts::base::{PromptArguments, PromptTemplate};
|
||||
use std::fmt::Write;
|
||||
use std::{ops::Range, path::PathBuf};
|
||||
|
||||
use gpui2::{AsyncAppContext, Model};
|
||||
use language2::{Anchor, Buffer};
|
||||
use gpui::{AsyncAppContext, Model};
|
||||
use language::{Anchor, Buffer};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PromptCodeSnippet {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::{
|
||||
future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt,
|
||||
Stream, StreamExt,
|
||||
};
|
||||
use gpui2::{AppContext, Executor};
|
||||
use gpui::{AppContext, BackgroundExecutor};
|
||||
use isahc::{http::StatusCode, Request, RequestExt};
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -105,7 +104,7 @@ pub struct OpenAIResponseStreamEvent {
|
||||
|
||||
pub async fn stream_completion(
|
||||
credential: ProviderCredential,
|
||||
executor: Arc<Executor>,
|
||||
executor: Arc<BackgroundExecutor>,
|
||||
request: Box<dyn CompletionRequest>,
|
||||
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
|
||||
let api_key = match credential {
|
||||
@@ -198,11 +197,11 @@ pub async fn stream_completion(
|
||||
pub struct OpenAICompletionProvider {
|
||||
model: OpenAILanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
executor: Arc<Executor>,
|
||||
executor: Arc<BackgroundExecutor>,
|
||||
}
|
||||
|
||||
impl OpenAICompletionProvider {
|
||||
pub fn new(model_name: &str, executor: Arc<Executor>) -> Self {
|
||||
pub fn new(model_name: &str, executor: Arc<BackgroundExecutor>) -> Self {
|
||||
let model = OpenAILanguageModel::load(model_name);
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
Self {
|
||||
@@ -213,7 +212,6 @@ impl OpenAICompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CredentialProvider for OpenAICompletionProvider {
|
||||
fn has_credentials(&self) -> bool {
|
||||
match *self.credential.read() {
|
||||
@@ -221,52 +219,45 @@ impl CredentialProvider for OpenAICompletionProvider {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
|
||||
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
|
||||
let existing_credential = self.credential.read().clone();
|
||||
|
||||
let retrieved_credential = cx
|
||||
.run_on_main(move |cx| match existing_credential {
|
||||
ProviderCredential::Credentials { .. } => {
|
||||
return existing_credential.clone();
|
||||
}
|
||||
_ => {
|
||||
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||
return ProviderCredential::Credentials { api_key };
|
||||
}
|
||||
|
||||
if let Some(Some((_, api_key))) = cx.read_credentials(OPENAI_API_URL).log_err()
|
||||
{
|
||||
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||
return ProviderCredential::Credentials { api_key };
|
||||
} else {
|
||||
return ProviderCredential::NoCredentials;
|
||||
}
|
||||
let retrieved_credential = match existing_credential {
|
||||
ProviderCredential::Credentials { .. } => existing_credential.clone(),
|
||||
_ => {
|
||||
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||
ProviderCredential::Credentials { api_key }
|
||||
} else if let Some(Some((_, api_key))) =
|
||||
cx.read_credentials(OPENAI_API_URL).log_err()
|
||||
{
|
||||
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||
ProviderCredential::Credentials { api_key }
|
||||
} else {
|
||||
return ProviderCredential::NoCredentials;
|
||||
ProviderCredential::NoCredentials
|
||||
}
|
||||
} else {
|
||||
ProviderCredential::NoCredentials
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
}
|
||||
};
|
||||
*self.credential.write() = retrieved_credential.clone();
|
||||
retrieved_credential
|
||||
}
|
||||
|
||||
async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
|
||||
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
|
||||
*self.credential.write() = credential.clone();
|
||||
let credential = credential.clone();
|
||||
cx.run_on_main(move |cx| match credential {
|
||||
match credential {
|
||||
ProviderCredential::Credentials { api_key } => {
|
||||
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
|
||||
.log_err();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
async fn delete_credentials(&self, cx: &mut AppContext) {
|
||||
cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err())
|
||||
.await;
|
||||
|
||||
fn delete_credentials(&self, cx: &mut AppContext) {
|
||||
cx.delete_credentials(OPENAI_API_URL).log_err();
|
||||
*self.credential.write() = ProviderCredential::NoCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui2::Executor;
|
||||
use gpui2::{serde_json, AppContext};
|
||||
use gpui::BackgroundExecutor;
|
||||
use gpui::{serde_json, AppContext};
|
||||
use isahc::http::StatusCode;
|
||||
use isahc::prelude::Configurable;
|
||||
use isahc::{AsyncBody, Response};
|
||||
@@ -35,7 +35,7 @@ pub struct OpenAIEmbeddingProvider {
|
||||
model: OpenAILanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
pub client: Arc<dyn HttpClient>,
|
||||
pub executor: Arc<Executor>,
|
||||
pub executor: Arc<BackgroundExecutor>,
|
||||
rate_limit_count_rx: watch::Receiver<Option<Instant>>,
|
||||
rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
|
||||
}
|
||||
@@ -66,7 +66,7 @@ struct OpenAIEmbeddingUsage {
|
||||
}
|
||||
|
||||
impl OpenAIEmbeddingProvider {
|
||||
pub fn new(client: Arc<dyn HttpClient>, executor: Arc<Executor>) -> Self {
|
||||
pub fn new(client: Arc<dyn HttpClient>, executor: Arc<BackgroundExecutor>) -> Self {
|
||||
let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
|
||||
let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
|
||||
|
||||
@@ -146,7 +146,6 @@ impl OpenAIEmbeddingProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||
fn has_credentials(&self) -> bool {
|
||||
match *self.credential.read() {
|
||||
@@ -154,52 +153,45 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
|
||||
let existing_credential = self.credential.read().clone();
|
||||
|
||||
let retrieved_credential = cx
|
||||
.run_on_main(move |cx| match existing_credential {
|
||||
ProviderCredential::Credentials { .. } => {
|
||||
return existing_credential.clone();
|
||||
}
|
||||
_ => {
|
||||
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||
return ProviderCredential::Credentials { api_key };
|
||||
}
|
||||
|
||||
if let Some(Some((_, api_key))) = cx.read_credentials(OPENAI_API_URL).log_err()
|
||||
{
|
||||
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||
return ProviderCredential::Credentials { api_key };
|
||||
} else {
|
||||
return ProviderCredential::NoCredentials;
|
||||
}
|
||||
let retrieved_credential = match existing_credential {
|
||||
ProviderCredential::Credentials { .. } => existing_credential.clone(),
|
||||
_ => {
|
||||
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||
ProviderCredential::Credentials { api_key }
|
||||
} else if let Some(Some((_, api_key))) =
|
||||
cx.read_credentials(OPENAI_API_URL).log_err()
|
||||
{
|
||||
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||
ProviderCredential::Credentials { api_key }
|
||||
} else {
|
||||
return ProviderCredential::NoCredentials;
|
||||
ProviderCredential::NoCredentials
|
||||
}
|
||||
} else {
|
||||
ProviderCredential::NoCredentials
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
};
|
||||
|
||||
*self.credential.write() = retrieved_credential.clone();
|
||||
retrieved_credential
|
||||
}
|
||||
|
||||
async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
|
||||
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
|
||||
*self.credential.write() = credential.clone();
|
||||
let credential = credential.clone();
|
||||
cx.run_on_main(move |cx| match credential {
|
||||
match credential {
|
||||
ProviderCredential::Credentials { api_key } => {
|
||||
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
|
||||
.log_err();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
async fn delete_credentials(&self, cx: &mut AppContext) {
|
||||
cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err())
|
||||
.await;
|
||||
|
||||
fn delete_credentials(&self, cx: &mut AppContext) {
|
||||
cx.delete_credentials(OPENAI_API_URL).log_err();
|
||||
*self.credential.write() = ProviderCredential::NoCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::{channel::mpsc, future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
|
||||
use gpui2::AppContext;
|
||||
use gpui::AppContext;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
@@ -100,16 +100,15 @@ impl FakeEmbeddingProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CredentialProvider for FakeEmbeddingProvider {
|
||||
fn has_credentials(&self) -> bool {
|
||||
true
|
||||
}
|
||||
async fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
|
||||
fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
|
||||
ProviderCredential::NotNeeded
|
||||
}
|
||||
async fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
|
||||
async fn delete_credentials(&self, _cx: &mut AppContext) {}
|
||||
fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
|
||||
fn delete_credentials(&self, _cx: &mut AppContext) {}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -162,16 +161,15 @@ impl FakeCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CredentialProvider for FakeCompletionProvider {
|
||||
fn has_credentials(&self) -> bool {
|
||||
true
|
||||
}
|
||||
async fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
|
||||
fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
|
||||
ProviderCredential::NotNeeded
|
||||
}
|
||||
async fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
|
||||
async fn delete_credentials(&self, _cx: &mut AppContext) {}
|
||||
fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
|
||||
fn delete_credentials(&self, _cx: &mut AppContext) {}
|
||||
}
|
||||
|
||||
impl CompletionProvider for FakeCompletionProvider {
|
||||
|
||||
@@ -142,7 +142,7 @@ pub struct AssistantPanel {
|
||||
zoomed: bool,
|
||||
has_focus: bool,
|
||||
toolbar: ViewHandle<Toolbar>,
|
||||
completion_provider: Box<dyn CompletionProvider>,
|
||||
completion_provider: Arc<dyn CompletionProvider>,
|
||||
api_key_editor: Option<ViewHandle<Editor>>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -204,7 +204,7 @@ impl AssistantPanel {
|
||||
|
||||
let semantic_index = SemanticIndex::global(cx);
|
||||
// Defaulting currently to GPT4, allow for this to be set via config.
|
||||
let completion_provider = Box::new(OpenAICompletionProvider::new(
|
||||
let completion_provider = Arc::new(OpenAICompletionProvider::new(
|
||||
"gpt-4",
|
||||
cx.background().clone(),
|
||||
));
|
||||
@@ -259,7 +259,13 @@ impl AssistantPanel {
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let this = if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
|
||||
if this.update(cx, |assistant, _| assistant.has_credentials()) {
|
||||
if this.update(cx, |assistant, cx| {
|
||||
if !assistant.has_credentials() {
|
||||
assistant.load_credentials(cx);
|
||||
};
|
||||
|
||||
assistant.has_credentials()
|
||||
}) {
|
||||
this
|
||||
} else {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
@@ -320,13 +326,10 @@ impl AssistantPanel {
|
||||
};
|
||||
|
||||
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
|
||||
let provider = Arc::new(OpenAICompletionProvider::new(
|
||||
"gpt-4",
|
||||
cx.background().clone(),
|
||||
));
|
||||
let provider = self.completion_provider.clone();
|
||||
|
||||
// Retrieve Credentials Authenticates the Provider
|
||||
// provider.retrieve_credentials(cx);
|
||||
provider.retrieve_credentials(cx);
|
||||
|
||||
let codegen = cx.add_model(|cx| {
|
||||
Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
|
||||
@@ -1439,7 +1442,7 @@ struct Conversation {
|
||||
pending_save: Task<Result<()>>,
|
||||
path: Option<PathBuf>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
completion_provider: Box<dyn CompletionProvider>,
|
||||
completion_provider: Arc<dyn CompletionProvider>,
|
||||
}
|
||||
|
||||
impl Entity for Conversation {
|
||||
@@ -1450,7 +1453,7 @@ impl Conversation {
|
||||
fn new(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
completion_provider: Box<dyn CompletionProvider>,
|
||||
completion_provider: Arc<dyn CompletionProvider>,
|
||||
) -> Self {
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
let buffer = cx.add_model(|cx| {
|
||||
@@ -1544,7 +1547,7 @@ impl Conversation {
|
||||
None => Some(Uuid::new_v4().to_string()),
|
||||
};
|
||||
let model = saved_conversation.model;
|
||||
let completion_provider: Box<dyn CompletionProvider> = Box::new(
|
||||
let completion_provider: Arc<dyn CompletionProvider> = Arc::new(
|
||||
OpenAICompletionProvider::new(model.full_name(), cx.background().clone()),
|
||||
);
|
||||
completion_provider.retrieve_credentials(cx);
|
||||
@@ -2201,7 +2204,7 @@ struct ConversationEditor {
|
||||
|
||||
impl ConversationEditor {
|
||||
fn new(
|
||||
completion_provider: Box<dyn CompletionProvider>,
|
||||
completion_provider: Arc<dyn CompletionProvider>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
@@ -3406,7 +3409,7 @@ mod tests {
|
||||
init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
|
||||
let completion_provider = Box::new(FakeCompletionProvider::new());
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
|
||||
@@ -3535,7 +3538,7 @@ mod tests {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let completion_provider = Box::new(FakeCompletionProvider::new());
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
|
||||
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
@@ -3633,7 +3636,7 @@ mod tests {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let completion_provider = Box::new(FakeCompletionProvider::new());
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
|
||||
@@ -3716,7 +3719,7 @@ mod tests {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let completion_provider = Box::new(FakeCompletionProvider::new());
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
let conversation =
|
||||
cx.add_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Codegen {
|
||||
|
||||
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
|
||||
let diff = cx.background().spawn(async move {
|
||||
let chunks = strip_markdown_codeblock(response.await?);
|
||||
let chunks = strip_invalid_spans_from_codeblock(response.await?);
|
||||
futures::pin_mut!(chunks);
|
||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||
|
||||
@@ -279,12 +279,13 @@ impl Codegen {
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_markdown_codeblock(
|
||||
fn strip_invalid_spans_from_codeblock(
|
||||
stream: impl Stream<Item = Result<String>>,
|
||||
) -> impl Stream<Item = Result<String>> {
|
||||
let mut first_line = true;
|
||||
let mut buffer = String::new();
|
||||
let mut starts_with_fenced_code_block = false;
|
||||
let mut starts_with_markdown_codeblock = false;
|
||||
let mut includes_start_or_end_span = false;
|
||||
stream.filter_map(move |chunk| {
|
||||
let chunk = match chunk {
|
||||
Ok(chunk) => chunk,
|
||||
@@ -292,11 +293,31 @@ fn strip_markdown_codeblock(
|
||||
};
|
||||
buffer.push_str(&chunk);
|
||||
|
||||
if buffer.len() > "<|S|".len() && buffer.starts_with("<|S|") {
|
||||
includes_start_or_end_span = true;
|
||||
|
||||
buffer = buffer
|
||||
.strip_prefix("<|S|>")
|
||||
.or_else(|| buffer.strip_prefix("<|S|"))
|
||||
.unwrap_or(&buffer)
|
||||
.to_string();
|
||||
} else if buffer.ends_with("|E|>") {
|
||||
includes_start_or_end_span = true;
|
||||
} else if buffer.starts_with("<|")
|
||||
|| buffer.starts_with("<|S")
|
||||
|| buffer.starts_with("<|S|")
|
||||
|| buffer.ends_with("|")
|
||||
|| buffer.ends_with("|E")
|
||||
|| buffer.ends_with("|E|")
|
||||
{
|
||||
return future::ready(None);
|
||||
}
|
||||
|
||||
if first_line {
|
||||
if buffer == "" || buffer == "`" || buffer == "``" {
|
||||
return future::ready(None);
|
||||
} else if buffer.starts_with("```") {
|
||||
starts_with_fenced_code_block = true;
|
||||
starts_with_markdown_codeblock = true;
|
||||
if let Some(newline_ix) = buffer.find('\n') {
|
||||
buffer.replace_range(..newline_ix + 1, "");
|
||||
first_line = false;
|
||||
@@ -306,16 +327,26 @@ fn strip_markdown_codeblock(
|
||||
}
|
||||
}
|
||||
|
||||
let text = if starts_with_fenced_code_block {
|
||||
buffer
|
||||
let mut text = buffer.to_string();
|
||||
if starts_with_markdown_codeblock {
|
||||
text = text
|
||||
.strip_suffix("\n```\n")
|
||||
.or_else(|| buffer.strip_suffix("\n```"))
|
||||
.or_else(|| buffer.strip_suffix("\n``"))
|
||||
.or_else(|| buffer.strip_suffix("\n`"))
|
||||
.or_else(|| buffer.strip_suffix('\n'))
|
||||
.unwrap_or(&buffer)
|
||||
} else {
|
||||
&buffer
|
||||
.or_else(|| text.strip_suffix("\n```"))
|
||||
.or_else(|| text.strip_suffix("\n``"))
|
||||
.or_else(|| text.strip_suffix("\n`"))
|
||||
.or_else(|| text.strip_suffix('\n'))
|
||||
.unwrap_or(&text)
|
||||
.to_string();
|
||||
}
|
||||
|
||||
if includes_start_or_end_span {
|
||||
text = text
|
||||
.strip_suffix("|E|>")
|
||||
.or_else(|| text.strip_suffix("E|>"))
|
||||
.or_else(|| text.strip_prefix("|>"))
|
||||
.or_else(|| text.strip_prefix(">"))
|
||||
.unwrap_or(&text)
|
||||
.to_string();
|
||||
};
|
||||
|
||||
if text.contains('\n') {
|
||||
@@ -328,6 +359,7 @@ fn strip_markdown_codeblock(
|
||||
} else {
|
||||
Some(Ok(buffer.clone()))
|
||||
};
|
||||
|
||||
buffer = remainder;
|
||||
future::ready(result)
|
||||
})
|
||||
@@ -335,6 +367,8 @@ fn strip_markdown_codeblock(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use ai::test::FakeCompletionProvider;
|
||||
use futures::stream::{self};
|
||||
@@ -405,6 +439,7 @@ mod tests {
|
||||
let max_len = cmp::min(new_text.len(), 10);
|
||||
let len = rng.gen_range(1..=max_len);
|
||||
let (chunk, suffix) = new_text.split_at(len);
|
||||
println!("CHUNK: {:?}", &chunk);
|
||||
provider.send_completion(chunk);
|
||||
new_text = suffix;
|
||||
deterministic.run_until_parked();
|
||||
@@ -537,6 +572,7 @@ mod tests {
|
||||
let max_len = cmp::min(new_text.len(), 10);
|
||||
let len = rng.gen_range(1..=max_len);
|
||||
let (chunk, suffix) = new_text.split_at(len);
|
||||
println!("{:?}", &chunk);
|
||||
provider.send_completion(chunk);
|
||||
new_text = suffix;
|
||||
deterministic.run_until_parked();
|
||||
@@ -558,50 +594,82 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_strip_markdown_codeblock() {
|
||||
async fn test_strip_invalid_spans_from_codeblock() {
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("Lorem ipsum dolor", 2))
|
||||
strip_invalid_spans_from_codeblock(chunks("Lorem ipsum dolor", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum dolor"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("```\nLorem ipsum dolor", 2))
|
||||
strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum dolor"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("```\nLorem ipsum dolor\n```", 2))
|
||||
strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum dolor"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("```\nLorem ipsum dolor\n```\n", 2))
|
||||
strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```\n", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum dolor"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("```html\n```js\nLorem ipsum dolor\n```\n```", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
strip_invalid_spans_from_codeblock(chunks(
|
||||
"```html\n```js\nLorem ipsum dolor\n```\n```",
|
||||
2
|
||||
))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"```js\nLorem ipsum dolor\n```"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("``\nLorem ipsum dolor\n```", 2))
|
||||
strip_invalid_spans_from_codeblock(chunks("``\nLorem ipsum dolor\n```", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"``\nLorem ipsum dolor\n```"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_invalid_spans_from_codeblock(chunks("<|S|Lorem ipsum|E|>", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
strip_invalid_spans_from_codeblock(chunks("<|S|>Lorem ipsum", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
strip_invalid_spans_from_codeblock(chunks("```\n<|S|>Lorem ipsum\n```", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_invalid_spans_from_codeblock(chunks("```\n<|S|Lorem ipsum|E|>\n```", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum"
|
||||
);
|
||||
fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
|
||||
stream::iter(
|
||||
text.chars()
|
||||
|
||||
@@ -80,12 +80,12 @@ fn summarize(buffer: &BufferSnapshot, selected_range: Range<impl ToOffset>) -> S
|
||||
if !flushed_selection {
|
||||
// The collapsed node ends after the selection starts, so we'll flush the selection first.
|
||||
summary.extend(buffer.text_for_range(offset..selected_range.start));
|
||||
summary.push_str("<|START|");
|
||||
summary.push_str("<|S|");
|
||||
if selected_range.end == selected_range.start {
|
||||
summary.push_str(">");
|
||||
} else {
|
||||
summary.extend(buffer.text_for_range(selected_range.clone()));
|
||||
summary.push_str("|END|>");
|
||||
summary.push_str("|E|>");
|
||||
}
|
||||
offset = selected_range.end;
|
||||
flushed_selection = true;
|
||||
@@ -107,12 +107,12 @@ fn summarize(buffer: &BufferSnapshot, selected_range: Range<impl ToOffset>) -> S
|
||||
// Flush selection if we haven't already done so.
|
||||
if !flushed_selection && offset <= selected_range.start {
|
||||
summary.extend(buffer.text_for_range(offset..selected_range.start));
|
||||
summary.push_str("<|START|");
|
||||
summary.push_str("<|S|");
|
||||
if selected_range.end == selected_range.start {
|
||||
summary.push_str(">");
|
||||
} else {
|
||||
summary.extend(buffer.text_for_range(selected_range.clone()));
|
||||
summary.push_str("|END|>");
|
||||
summary.push_str("|E|>");
|
||||
}
|
||||
offset = selected_range.end;
|
||||
}
|
||||
@@ -260,7 +260,7 @@ pub(crate) mod tests {
|
||||
summarize(&snapshot, Point::new(1, 4)..Point::new(1, 4)),
|
||||
indoc! {"
|
||||
struct X {
|
||||
<|START|>a: usize,
|
||||
<|S|>a: usize,
|
||||
b: usize,
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ pub(crate) mod tests {
|
||||
impl X {
|
||||
|
||||
fn new() -> Self {
|
||||
let <|START|a |END|>= 1;
|
||||
let <|S|a |E|>= 1;
|
||||
let b = 2;
|
||||
Self { a, b }
|
||||
}
|
||||
@@ -307,7 +307,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
impl X {
|
||||
<|START|>
|
||||
<|S|>
|
||||
fn new() -> Self {}
|
||||
|
||||
pub fn a(&self, param: bool) -> usize {}
|
||||
@@ -333,7 +333,7 @@ pub(crate) mod tests {
|
||||
|
||||
pub fn b(&self) -> usize {}
|
||||
}
|
||||
<|START|>"}
|
||||
<|S|>"}
|
||||
);
|
||||
|
||||
// Ensure nested functions get collapsed properly.
|
||||
@@ -369,7 +369,7 @@ pub(crate) mod tests {
|
||||
assert_eq!(
|
||||
summarize(&snapshot, Point::new(0, 0)..Point::new(0, 0)),
|
||||
indoc! {"
|
||||
<|START|>struct X {
|
||||
<|S|>struct X {
|
||||
a: usize,
|
||||
b: usize,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ path = "src/audio2.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
gpui2 = { path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
collections = { path = "../collections" }
|
||||
util = { path = "../util" }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use gpui2::{AppContext, AssetSource};
|
||||
use gpui::{AppContext, AssetSource};
|
||||
use rodio::{
|
||||
source::{Buffered, SamplesConverter},
|
||||
Decoder, Source,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use assets::SoundRegistry;
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use gpui2::{AppContext, AssetSource, Executor};
|
||||
use gpui::{AppContext, AssetSource};
|
||||
use rodio::{OutputStream, OutputStreamHandle};
|
||||
use util::ResultExt;
|
||||
|
||||
mod assets;
|
||||
|
||||
pub fn init(source: impl AssetSource, cx: &mut AppContext) {
|
||||
cx.set_global(Audio::new(cx.executor()));
|
||||
cx.set_global(SoundRegistry::new(source));
|
||||
cx.set_global(Audio::new());
|
||||
}
|
||||
|
||||
pub enum Sound {
|
||||
@@ -34,15 +33,18 @@ impl Sound {
|
||||
}
|
||||
|
||||
pub struct Audio {
|
||||
tx: mpsc::UnboundedSender<Box<dyn FnOnce(&mut AudioState) + Send>>,
|
||||
}
|
||||
|
||||
struct AudioState {
|
||||
_output_stream: Option<OutputStream>,
|
||||
output_handle: Option<OutputStreamHandle>,
|
||||
}
|
||||
|
||||
impl AudioState {
|
||||
impl Audio {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_output_stream: None,
|
||||
output_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
|
||||
if self.output_handle.is_none() {
|
||||
let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
|
||||
@@ -53,59 +55,27 @@ impl AudioState {
|
||||
self.output_handle.as_ref()
|
||||
}
|
||||
|
||||
fn take(&mut self) {
|
||||
self._output_stream.take();
|
||||
self.output_handle.take();
|
||||
}
|
||||
}
|
||||
|
||||
impl Audio {
|
||||
pub fn new(executor: &Executor) -> Self {
|
||||
let (tx, mut rx) = mpsc::unbounded::<Box<dyn FnOnce(&mut AudioState) + Send>>();
|
||||
executor
|
||||
.spawn_on_main(|| async move {
|
||||
let mut audio = AudioState {
|
||||
_output_stream: None,
|
||||
output_handle: None,
|
||||
};
|
||||
|
||||
while let Some(f) = rx.next().await {
|
||||
(f)(&mut audio);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self { tx }
|
||||
}
|
||||
|
||||
pub fn play_sound(sound: Sound, cx: &mut AppContext) {
|
||||
if !cx.has_global::<Self>() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let this = cx.global::<Self>();
|
||||
this.tx
|
||||
.unbounded_send(Box::new(move |state| {
|
||||
if let Some(output_handle) = state.ensure_output_exists() {
|
||||
output_handle.play_raw(source).log_err();
|
||||
}
|
||||
}))
|
||||
.ok();
|
||||
cx.update_global::<Self, _>(|this, cx| {
|
||||
let output_handle = this.ensure_output_exists()?;
|
||||
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
|
||||
output_handle.play_raw(source).log_err()?;
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
||||
pub fn end_call(cx: &AppContext) {
|
||||
pub fn end_call(cx: &mut AppContext) {
|
||||
if !cx.has_global::<Self>() {
|
||||
return;
|
||||
}
|
||||
|
||||
let this = cx.global::<Self>();
|
||||
|
||||
this.tx
|
||||
.unbounded_send(Box::new(move |state| state.take()))
|
||||
.ok();
|
||||
cx.update_global::<Self, _>(|this, _| {
|
||||
this._output_stream.take();
|
||||
this.output_handle.take();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,26 +10,26 @@ doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"client2/test-support",
|
||||
"client/test-support",
|
||||
"collections/test-support",
|
||||
"gpui2/test-support",
|
||||
"gpui/test-support",
|
||||
"live_kit_client/test-support",
|
||||
"project2/test-support",
|
||||
"project/test-support",
|
||||
"util/test-support"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
audio2 = { path = "../audio2" }
|
||||
client2 = { path = "../client2" }
|
||||
audio = { package = "audio2", path = "../audio2" }
|
||||
client = { package = "client2", path = "../client2" }
|
||||
collections = { path = "../collections" }
|
||||
gpui2 = { path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
log.workspace = true
|
||||
live_kit_client = { path = "../live_kit_client" }
|
||||
fs2 = { path = "../fs2" }
|
||||
language2 = { path = "../language2" }
|
||||
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2" }
|
||||
fs = { package = "fs2", path = "../fs2" }
|
||||
language = { package = "language2", path = "../language2" }
|
||||
media = { path = "../media" }
|
||||
project2 = { path = "../project2" }
|
||||
settings2 = { path = "../settings2" }
|
||||
project = { package = "project2", path = "../project2" }
|
||||
settings = { package = "settings2", path = "../settings2" }
|
||||
util = { path = "../util" }
|
||||
|
||||
anyhow.workspace = true
|
||||
@@ -42,11 +42,11 @@ serde_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client2 = { path = "../client2", features = ["test-support"] }
|
||||
fs2 = { path = "../fs2", features = ["test-support"] }
|
||||
language2 = { path = "../language2", features = ["test-support"] }
|
||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
||||
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
|
||||
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||
project2 = { path = "../project2", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] }
|
||||
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
|
||||
@@ -3,21 +3,21 @@ pub mod participant;
|
||||
pub mod room;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use audio2::Audio;
|
||||
use audio::Audio;
|
||||
use call_settings::CallSettings;
|
||||
use client2::{
|
||||
use client::{
|
||||
proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore,
|
||||
ZED_ALWAYS_ACTIVE,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use futures::{future::Shared, FutureExt};
|
||||
use gpui2::{
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
|
||||
WeakModel,
|
||||
};
|
||||
use postage::watch;
|
||||
use project2::Project;
|
||||
use settings2::Settings;
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use participant::ParticipantLocation;
|
||||
@@ -50,7 +50,7 @@ pub struct ActiveCall {
|
||||
),
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
_subscriptions: Vec<client2::Subscription>,
|
||||
_subscriptions: Vec<client::Subscription>,
|
||||
}
|
||||
|
||||
impl EventEmitter for ActiveCall {
|
||||
@@ -196,7 +196,7 @@ impl ActiveCall {
|
||||
})
|
||||
.shared();
|
||||
self.pending_room_creation = Some(room.clone());
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
room.await.map_err(|err| anyhow!("{:?}", err))?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@@ -230,7 +230,7 @@ impl ActiveCall {
|
||||
};
|
||||
|
||||
let client = self.client.clone();
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::CancelCall {
|
||||
room_id,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use gpui2::AppContext;
|
||||
use gpui::AppContext;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings2::Settings;
|
||||
use settings::Settings;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CallSettings {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use client2::ParticipantIndex;
|
||||
use client2::{proto, User};
|
||||
use gpui2::WeakModel;
|
||||
use client::ParticipantIndex;
|
||||
use client::{proto, User};
|
||||
use collections::HashMap;
|
||||
use gpui::WeakModel;
|
||||
pub use live_kit_client::Frame;
|
||||
use project2::Project;
|
||||
use std::{fmt, sync::Arc};
|
||||
use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
|
||||
use project::Project;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ParticipantLocation {
|
||||
@@ -45,27 +47,6 @@ pub struct RemoteParticipant {
|
||||
pub participant_index: ParticipantIndex,
|
||||
pub muted: bool,
|
||||
pub speaking: bool,
|
||||
// pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
||||
// pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RemoteVideoTrack {
|
||||
pub(crate) live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
|
||||
}
|
||||
|
||||
unsafe impl Send for RemoteVideoTrack {}
|
||||
// todo!("remove this sync because it's not legit")
|
||||
unsafe impl Sync for RemoteVideoTrack {}
|
||||
|
||||
impl fmt::Debug for RemoteVideoTrack {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("RemoteVideoTrack").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteVideoTrack {
|
||||
pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
|
||||
self.live_kit_track.frames()
|
||||
}
|
||||
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
||||
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,17 +9,17 @@ path = "src/client2.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["collections/test-support", "gpui2/test-support", "rpc2/test-support"]
|
||||
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
db2 = { path = "../db2" }
|
||||
gpui2 = { path = "../gpui2" }
|
||||
db = { package = "db2", path = "../db2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
util = { path = "../util" }
|
||||
rpc2 = { path = "../rpc2" }
|
||||
text = { path = "../text" }
|
||||
settings2 = { path = "../settings2" }
|
||||
feature_flags2 = { path = "../feature_flags2" }
|
||||
rpc = { package = "rpc2", path = "../rpc2" }
|
||||
text = { package = "text2", path = "../text2" }
|
||||
settings = { package = "settings2", path = "../settings2" }
|
||||
feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
|
||||
anyhow.workspace = true
|
||||
@@ -46,7 +46,7 @@ url = "2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
||||
rpc2 = { path = "../rpc2", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
|
||||
@@ -11,9 +11,10 @@ use async_tungstenite::tungstenite::{
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use futures::{
|
||||
future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt,
|
||||
future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _,
|
||||
TryStreamExt,
|
||||
};
|
||||
use gpui2::{
|
||||
use gpui::{
|
||||
serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, SemanticVersion, Task,
|
||||
WeakModel,
|
||||
};
|
||||
@@ -21,10 +22,10 @@ use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use rand::prelude::*;
|
||||
use rpc2::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings2::Settings;
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::HashMap,
|
||||
@@ -43,7 +44,7 @@ use util::channel::ReleaseChannel;
|
||||
use util::http::HttpClient;
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub use rpc2::*;
|
||||
pub use rpc::*;
|
||||
pub use telemetry::ClickhouseEvent;
|
||||
pub use user::*;
|
||||
|
||||
@@ -240,7 +241,7 @@ struct ClientState {
|
||||
Box<dyn AnyTypedEnvelope>,
|
||||
&Arc<Client>,
|
||||
AsyncAppContext,
|
||||
) -> BoxFuture<'static, Result<()>>,
|
||||
) -> LocalBoxFuture<'static, Result<()>>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
@@ -310,10 +311,7 @@ pub struct PendingEntitySubscription<T: 'static> {
|
||||
consumed: bool,
|
||||
}
|
||||
|
||||
impl<T> PendingEntitySubscription<T>
|
||||
where
|
||||
T: 'static + Send,
|
||||
{
|
||||
impl<T: 'static> PendingEntitySubscription<T> {
|
||||
pub fn set_model(mut self, model: &Model<T>, cx: &mut AsyncAppContext) -> Subscription {
|
||||
self.consumed = true;
|
||||
let mut state = self.client.state.write();
|
||||
@@ -341,10 +339,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for PendingEntitySubscription<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
impl<T: 'static> Drop for PendingEntitySubscription<T> {
|
||||
fn drop(&mut self) {
|
||||
if !self.consumed {
|
||||
let mut state = self.client.state.write();
|
||||
@@ -372,7 +367,7 @@ pub struct TelemetrySettingsContent {
|
||||
pub metrics: Option<bool>,
|
||||
}
|
||||
|
||||
impl settings2::Settings for TelemetrySettings {
|
||||
impl settings::Settings for TelemetrySettings {
|
||||
const KEY: Option<&'static str> = Some("telemetry");
|
||||
|
||||
type FileContent = TelemetrySettingsContent;
|
||||
@@ -505,7 +500,7 @@ impl Client {
|
||||
},
|
||||
&cx,
|
||||
);
|
||||
cx.executor().timer(delay).await;
|
||||
cx.background_executor().timer(delay).await;
|
||||
delay = delay
|
||||
.mul_f32(rng.gen_range(1.0..=2.0))
|
||||
.min(reconnect_interval);
|
||||
@@ -529,7 +524,7 @@ impl Client {
|
||||
remote_id: u64,
|
||||
) -> Result<PendingEntitySubscription<T>>
|
||||
where
|
||||
T: 'static + Send,
|
||||
T: 'static,
|
||||
{
|
||||
let id = (TypeId::of::<T>(), remote_id);
|
||||
|
||||
@@ -557,9 +552,13 @@ impl Client {
|
||||
) -> Subscription
|
||||
where
|
||||
M: EnvelopedMessage,
|
||||
E: 'static + Send,
|
||||
H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
|
||||
F: 'static + Future<Output = Result<()>> + Send,
|
||||
E: 'static,
|
||||
H: 'static
|
||||
+ Sync
|
||||
+ Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
|
||||
+ Send
|
||||
+ Sync,
|
||||
F: 'static + Future<Output = Result<()>>,
|
||||
{
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
|
||||
@@ -573,7 +572,7 @@ impl Client {
|
||||
Arc::new(move |subscriber, envelope, client, cx| {
|
||||
let subscriber = subscriber.downcast::<E>().unwrap();
|
||||
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
|
||||
handler(subscriber, *envelope, client.clone(), cx).boxed()
|
||||
handler(subscriber, *envelope, client.clone(), cx).boxed_local()
|
||||
}),
|
||||
);
|
||||
if prev_handler.is_some() {
|
||||
@@ -599,9 +598,13 @@ impl Client {
|
||||
) -> Subscription
|
||||
where
|
||||
M: RequestMessage,
|
||||
E: 'static + Send,
|
||||
H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
|
||||
F: 'static + Future<Output = Result<M::Response>> + Send,
|
||||
E: 'static,
|
||||
H: 'static
|
||||
+ Sync
|
||||
+ Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
|
||||
+ Send
|
||||
+ Sync,
|
||||
F: 'static + Future<Output = Result<M::Response>>,
|
||||
{
|
||||
self.add_message_handler(model, move |handle, envelope, this, cx| {
|
||||
Self::respond_to_request(
|
||||
@@ -615,9 +618,9 @@ impl Client {
|
||||
pub fn add_model_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
|
||||
where
|
||||
M: EntityMessage,
|
||||
E: 'static + Send,
|
||||
H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
|
||||
F: 'static + Future<Output = Result<()>> + Send,
|
||||
E: 'static,
|
||||
H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
|
||||
F: 'static + Future<Output = Result<()>>,
|
||||
{
|
||||
self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
|
||||
handler(subscriber.downcast::<E>().unwrap(), message, client, cx)
|
||||
@@ -627,9 +630,9 @@ impl Client {
|
||||
fn add_entity_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
|
||||
where
|
||||
M: EntityMessage,
|
||||
E: 'static + Send,
|
||||
H: 'static + Send + Sync + Fn(AnyModel, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
|
||||
F: 'static + Future<Output = Result<()>> + Send,
|
||||
E: 'static,
|
||||
H: 'static + Fn(AnyModel, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
|
||||
F: 'static + Future<Output = Result<()>>,
|
||||
{
|
||||
let model_type_id = TypeId::of::<E>();
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
@@ -655,7 +658,7 @@ impl Client {
|
||||
message_type_id,
|
||||
Arc::new(move |handle, envelope, client, cx| {
|
||||
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
|
||||
handler(handle, *envelope, client.clone(), cx).boxed()
|
||||
handler(handle, *envelope, client.clone(), cx).boxed_local()
|
||||
}),
|
||||
);
|
||||
if prev_handler.is_some() {
|
||||
@@ -666,9 +669,9 @@ impl Client {
|
||||
pub fn add_model_request_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
|
||||
where
|
||||
M: EntityMessage + RequestMessage,
|
||||
E: 'static + Send,
|
||||
H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
|
||||
F: 'static + Future<Output = Result<M::Response>> + Send,
|
||||
E: 'static,
|
||||
H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
|
||||
F: 'static + Future<Output = Result<M::Response>>,
|
||||
{
|
||||
self.add_model_message_handler(move |entity, envelope, client, cx| {
|
||||
Self::respond_to_request::<M, _>(
|
||||
@@ -705,7 +708,7 @@ impl Client {
|
||||
read_credentials_from_keychain(cx).await.is_some()
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
#[async_recursion(?Send)]
|
||||
pub async fn authenticate_and_connect(
|
||||
self: &Arc<Self>,
|
||||
try_keychain: bool,
|
||||
@@ -763,7 +766,8 @@ impl Client {
|
||||
self.set_status(Status::Reconnecting, cx);
|
||||
}
|
||||
|
||||
let mut timeout = futures::FutureExt::fuse(cx.executor().timer(CONNECTION_TIMEOUT));
|
||||
let mut timeout =
|
||||
futures::FutureExt::fuse(cx.background_executor().timer(CONNECTION_TIMEOUT));
|
||||
futures::select_biased! {
|
||||
connection = self.establish_connection(&credentials, cx).fuse() => {
|
||||
match connection {
|
||||
@@ -814,7 +818,7 @@ impl Client {
|
||||
conn: Connection,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let executor = cx.executor();
|
||||
let executor = cx.background_executor();
|
||||
log::info!("add connection to peer");
|
||||
let (connection_id, handle_io, mut incoming) = self.peer.add_connection(conn, {
|
||||
let executor = executor.clone();
|
||||
@@ -975,10 +979,10 @@ impl Client {
|
||||
"Authorization",
|
||||
format!("{} {}", credentials.user_id, credentials.access_token),
|
||||
)
|
||||
.header("x-zed-protocol-version", rpc2::PROTOCOL_VERSION);
|
||||
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
|
||||
|
||||
let http = self.http.clone();
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut rpc_url = Self::get_rpc_url(http, use_preview_server).await?;
|
||||
let rpc_host = rpc_url
|
||||
.host_str()
|
||||
@@ -1025,7 +1029,7 @@ impl Client {
|
||||
// zed server to encrypt the user's access token, so that it can'be intercepted by
|
||||
// any other app running on the user's device.
|
||||
let (public_key, private_key) =
|
||||
rpc2::auth::keypair().expect("failed to generate keypair for auth");
|
||||
rpc::auth::keypair().expect("failed to generate keypair for auth");
|
||||
let public_key_string =
|
||||
String::try_from(public_key).expect("failed to serialize public key for auth");
|
||||
|
||||
@@ -1049,7 +1053,7 @@ impl Client {
|
||||
write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
|
||||
}
|
||||
|
||||
cx.run_on_main(move |cx| cx.open_url(&url))?.await;
|
||||
cx.update(|cx| cx.open_url(&url))?;
|
||||
|
||||
// Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
|
||||
// access token from the query params.
|
||||
@@ -1100,7 +1104,7 @@ impl Client {
|
||||
let access_token = private_key
|
||||
.decrypt_string(&access_token)
|
||||
.context("failed to decrypt access token")?;
|
||||
cx.run_on_main(|cx| cx.activate(true))?.await;
|
||||
cx.update(|cx| cx.activate(true))?;
|
||||
|
||||
Ok(Credentials {
|
||||
user_id: user_id.parse()?,
|
||||
@@ -1292,7 +1296,7 @@ impl Client {
|
||||
sender_id,
|
||||
type_name
|
||||
);
|
||||
cx.spawn_on_main(move |_| async move {
|
||||
cx.spawn(move |_| async move {
|
||||
match future.await {
|
||||
Ok(()) => {
|
||||
log::debug!(
|
||||
@@ -1331,9 +1335,8 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credenti
|
||||
}
|
||||
|
||||
let (user_id, access_token) = cx
|
||||
.run_on_main(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
|
||||
.ok()?
|
||||
.await?;
|
||||
.update(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
|
||||
.ok()??;
|
||||
|
||||
Some(Credentials {
|
||||
user_id: user_id.parse().ok()?,
|
||||
@@ -1345,19 +1348,17 @@ async fn write_credentials_to_keychain(
|
||||
credentials: Credentials,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
cx.run_on_main(move |cx| {
|
||||
cx.update(move |cx| {
|
||||
cx.write_credentials(
|
||||
&ZED_SERVER_URL,
|
||||
&credentials.user_id.to_string(),
|
||||
credentials.access_token.as_bytes(),
|
||||
)
|
||||
})?
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
|
||||
cx.run_on_main(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
|
||||
.await
|
||||
cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
|
||||
}
|
||||
|
||||
const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";
|
||||
@@ -1382,12 +1383,12 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test::FakeServer;
|
||||
|
||||
use gpui2::{Context, Executor, TestAppContext};
|
||||
use gpui::{BackgroundExecutor, Context, TestAppContext};
|
||||
use parking_lot::Mutex;
|
||||
use std::future;
|
||||
use util::http::FakeHttpClient;
|
||||
|
||||
#[gpui2::test(iterations = 10)]
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_reconnection(cx: &mut TestAppContext) {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
@@ -1421,15 +1422,15 @@ mod tests {
|
||||
assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 10)]
|
||||
async fn test_connection_timeout(executor: Executor, cx: &mut TestAppContext) {
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let mut status = client.status();
|
||||
|
||||
// Time out when client tries to connect.
|
||||
client.override_authenticate(move |cx| {
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
Ok(Credentials {
|
||||
user_id,
|
||||
access_token: "token".into(),
|
||||
@@ -1437,7 +1438,7 @@ mod tests {
|
||||
})
|
||||
});
|
||||
client.override_establish_connection(|_, cx| {
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
future::pending::<()>().await;
|
||||
unreachable!()
|
||||
})
|
||||
@@ -1471,7 +1472,7 @@ mod tests {
|
||||
// Time out when re-establishing the connection.
|
||||
server.allow_connections();
|
||||
client.override_establish_connection(|_, cx| {
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
future::pending::<()>().await;
|
||||
unreachable!()
|
||||
})
|
||||
@@ -1489,8 +1490,11 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 10)]
|
||||
async fn test_authenticating_more_than_once(cx: &mut TestAppContext, executor: Executor) {
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_authenticating_more_than_once(
|
||||
cx: &mut TestAppContext,
|
||||
executor: BackgroundExecutor,
|
||||
) {
|
||||
let auth_count = Arc::new(Mutex::new(0));
|
||||
let dropped_auth_count = Arc::new(Mutex::new(0));
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
@@ -1500,7 +1504,7 @@ mod tests {
|
||||
move |cx| {
|
||||
let auth_count = auth_count.clone();
|
||||
let dropped_auth_count = dropped_auth_count.clone();
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
*auth_count.lock() += 1;
|
||||
let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
|
||||
future::pending::<()>().await;
|
||||
@@ -1537,7 +1541,7 @@ mod tests {
|
||||
assert_eq!(decode_worktree_url("not://the-right-format"), None);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
@@ -1590,7 +1594,7 @@ mod tests {
|
||||
done_rx2.next().await.unwrap();
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
@@ -1618,7 +1622,7 @@ mod tests {
|
||||
done_rx2.next().await.unwrap();
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
||||
use gpui2::{serde_json, AppContext, AppMetadata, Executor, Task};
|
||||
use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use settings2::Settings;
|
||||
use settings::Settings;
|
||||
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sysinfo::{
|
||||
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
|
||||
@@ -14,7 +14,7 @@ use util::{channel::ReleaseChannel, TryFutureExt};
|
||||
|
||||
pub struct Telemetry {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
executor: Executor,
|
||||
executor: BackgroundExecutor,
|
||||
state: Mutex<TelemetryState>,
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ impl Telemetry {
|
||||
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
||||
let this = Arc::new(Self {
|
||||
http_client: client,
|
||||
executor: cx.executor().clone(),
|
||||
executor: cx.background_executor().clone(),
|
||||
state: Mutex::new(TelemetryState {
|
||||
app_metadata: cx.app_metadata(),
|
||||
architecture: env::consts::ARCH,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::{stream::BoxStream, StreamExt};
|
||||
use gpui2::{Context, Executor, Model, TestAppContext};
|
||||
use gpui::{BackgroundExecutor, Context, Model, TestAppContext};
|
||||
use parking_lot::Mutex;
|
||||
use rpc2::{
|
||||
use rpc::{
|
||||
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
|
||||
ConnectionId, Peer, Receipt, TypedEnvelope,
|
||||
};
|
||||
@@ -14,7 +14,7 @@ pub struct FakeServer {
|
||||
peer: Arc<Peer>,
|
||||
state: Arc<Mutex<FakeServerState>>,
|
||||
user_id: u64,
|
||||
executor: Executor,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -79,10 +79,10 @@ impl FakeServer {
|
||||
}
|
||||
|
||||
let (client_conn, server_conn, _) =
|
||||
Connection::in_memory(cx.executor().clone());
|
||||
Connection::in_memory(cx.background_executor().clone());
|
||||
let (connection_id, io, incoming) =
|
||||
peer.add_test_connection(server_conn, cx.executor().clone());
|
||||
cx.executor().spawn(io).detach();
|
||||
peer.add_test_connection(server_conn, cx.background_executor().clone());
|
||||
cx.background_executor().spawn(io).detach();
|
||||
{
|
||||
let mut state = state.lock();
|
||||
state.connection_id = Some(connection_id);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use super::{proto, Client, Status, TypedEnvelope};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{hash_map::Entry, HashMap, HashSet};
|
||||
use feature_flags2::FeatureFlagAppExt;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
|
||||
use gpui2::{AsyncAppContext, EventEmitter, ImageData, Model, ModelContext, Task};
|
||||
use gpui::{AsyncAppContext, EventEmitter, ImageData, Model, ModelContext, Task};
|
||||
use postage::{sink::Sink, watch};
|
||||
use rpc2::proto::{RequestMessage, UsersResponse};
|
||||
use rpc::proto::{RequestMessage, UsersResponse};
|
||||
use std::sync::{Arc, Weak};
|
||||
use text::ReplicaId;
|
||||
use util::http::HttpClient;
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.27.0"
|
||||
version = "0.28.0"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -11,21 +11,21 @@ doctest = false
|
||||
[features]
|
||||
test-support = [
|
||||
"collections/test-support",
|
||||
"gpui2/test-support",
|
||||
"language2/test-support",
|
||||
"lsp2/test-support",
|
||||
"settings2/test-support",
|
||||
"gpui/test-support",
|
||||
"language/test-support",
|
||||
"lsp/test-support",
|
||||
"settings/test-support",
|
||||
"util/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
context_menu = { path = "../context_menu" }
|
||||
gpui2 = { path = "../gpui2" }
|
||||
language2 = { path = "../language2" }
|
||||
settings2 = { path = "../settings2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
language = { package = "language2", path = "../language2" }
|
||||
settings = { package = "settings2", path = "../settings2" }
|
||||
theme = { path = "../theme" }
|
||||
lsp2 = { path = "../lsp2" }
|
||||
lsp = { package = "lsp2", path = "../lsp2" }
|
||||
node_runtime = { path = "../node_runtime"}
|
||||
util = { path = "../util" }
|
||||
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
||||
@@ -42,9 +42,9 @@ parking_lot.workspace = true
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
fs = { path = "../fs", features = ["test-support"] }
|
||||
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
||||
language2 = { path = "../language2", features = ["test-support"] }
|
||||
lsp2 = { path = "../lsp2", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
||||
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings2 = { path = "../settings2", features = ["test-support"] }
|
||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
|
||||
@@ -6,20 +6,20 @@ use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||
use gpui2::{
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, ModelContext,
|
||||
Task, WeakModel,
|
||||
};
|
||||
use language2::{
|
||||
use language::{
|
||||
language_settings::{all_language_settings, language_settings},
|
||||
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language,
|
||||
LanguageServerName, PointUtf16, ToPointUtf16,
|
||||
};
|
||||
use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId};
|
||||
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
use request::StatusNotification;
|
||||
use settings2::SettingsStore;
|
||||
use settings::SettingsStore;
|
||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
@@ -172,11 +172,11 @@ impl Status {
|
||||
}
|
||||
|
||||
struct RegisteredBuffer {
|
||||
uri: lsp2::Url,
|
||||
uri: lsp::Url,
|
||||
language_id: String,
|
||||
snapshot: BufferSnapshot,
|
||||
snapshot_version: i32,
|
||||
_subscriptions: [gpui2::Subscription; 2],
|
||||
_subscriptions: [gpui::Subscription; 2],
|
||||
pending_buffer_change: Task<Option<()>>,
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ impl RegisteredBuffer {
|
||||
let new_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot()).ok()?;
|
||||
|
||||
let content_changes = cx
|
||||
.executor()
|
||||
.background_executor()
|
||||
.spawn({
|
||||
let new_snapshot = new_snapshot.clone();
|
||||
async move {
|
||||
@@ -220,8 +220,8 @@ impl RegisteredBuffer {
|
||||
let new_text = new_snapshot
|
||||
.text_for_range(edit.new.start.1..edit.new.end.1)
|
||||
.collect();
|
||||
lsp2::TextDocumentContentChangeEvent {
|
||||
range: Some(lsp2::Range::new(
|
||||
lsp::TextDocumentContentChangeEvent {
|
||||
range: Some(lsp::Range::new(
|
||||
point_to_lsp(edit_start),
|
||||
point_to_lsp(edit_end),
|
||||
)),
|
||||
@@ -243,9 +243,9 @@ impl RegisteredBuffer {
|
||||
buffer.snapshot = new_snapshot;
|
||||
server
|
||||
.lsp
|
||||
.notify::<lsp2::notification::DidChangeTextDocument>(
|
||||
lsp2::DidChangeTextDocumentParams {
|
||||
text_document: lsp2::VersionedTextDocumentIdentifier::new(
|
||||
.notify::<lsp::notification::DidChangeTextDocument>(
|
||||
lsp::DidChangeTextDocumentParams {
|
||||
text_document: lsp::VersionedTextDocumentIdentifier::new(
|
||||
buffer.uri.clone(),
|
||||
buffer.snapshot_version,
|
||||
),
|
||||
@@ -280,7 +280,7 @@ pub struct Copilot {
|
||||
server: CopilotServer,
|
||||
buffers: HashSet<WeakModel<Buffer>>,
|
||||
server_id: LanguageServerId,
|
||||
_subscription: gpui2::Subscription,
|
||||
_subscription: gpui::Subscription,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
@@ -535,7 +535,7 @@ impl Copilot {
|
||||
}
|
||||
};
|
||||
|
||||
cx.executor()
|
||||
cx.background_executor()
|
||||
.spawn(task.map_err(|err| anyhow!("{:?}", err)))
|
||||
} else {
|
||||
// If we're downloading, wait until download is finished
|
||||
@@ -549,7 +549,7 @@ impl Copilot {
|
||||
self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
|
||||
if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
|
||||
let server = server.clone();
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
server
|
||||
.request::<request::SignOut>(request::SignOutParams {})
|
||||
.await?;
|
||||
@@ -579,7 +579,7 @@ impl Copilot {
|
||||
|
||||
cx.notify();
|
||||
|
||||
cx.executor().spawn(start_task)
|
||||
cx.background_executor().spawn(start_task)
|
||||
}
|
||||
|
||||
pub fn language_server(&self) -> Option<(&LanguageServerName, &Arc<LanguageServer>)> {
|
||||
@@ -608,13 +608,13 @@ impl Copilot {
|
||||
registered_buffers
|
||||
.entry(buffer.entity_id())
|
||||
.or_insert_with(|| {
|
||||
let uri: lsp2::Url = uri_for_buffer(buffer, cx);
|
||||
let uri: lsp::Url = uri_for_buffer(buffer, cx);
|
||||
let language_id = id_for_language(buffer.read(cx).language());
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
server
|
||||
.notify::<lsp2::notification::DidOpenTextDocument>(
|
||||
lsp2::DidOpenTextDocumentParams {
|
||||
text_document: lsp2::TextDocumentItem {
|
||||
.notify::<lsp::notification::DidOpenTextDocument>(
|
||||
lsp::DidOpenTextDocumentParams {
|
||||
text_document: lsp::TextDocumentItem {
|
||||
uri: uri.clone(),
|
||||
language_id: language_id.clone(),
|
||||
version: 0,
|
||||
@@ -647,29 +647,29 @@ impl Copilot {
|
||||
fn handle_buffer_event(
|
||||
&mut self,
|
||||
buffer: Model<Buffer>,
|
||||
event: &language2::Event,
|
||||
event: &language::Event,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
if let Ok(server) = self.server.as_running() {
|
||||
if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id())
|
||||
{
|
||||
match event {
|
||||
language2::Event::Edited => {
|
||||
language::Event::Edited => {
|
||||
let _ = registered_buffer.report_changes(&buffer, cx);
|
||||
}
|
||||
language2::Event::Saved => {
|
||||
language::Event::Saved => {
|
||||
server
|
||||
.lsp
|
||||
.notify::<lsp2::notification::DidSaveTextDocument>(
|
||||
lsp2::DidSaveTextDocumentParams {
|
||||
text_document: lsp2::TextDocumentIdentifier::new(
|
||||
.notify::<lsp::notification::DidSaveTextDocument>(
|
||||
lsp::DidSaveTextDocumentParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(
|
||||
registered_buffer.uri.clone(),
|
||||
),
|
||||
text: None,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
language2::Event::FileHandleChanged | language2::Event::LanguageChanged => {
|
||||
language::Event::FileHandleChanged | language::Event::LanguageChanged => {
|
||||
let new_language_id = id_for_language(buffer.read(cx).language());
|
||||
let new_uri = uri_for_buffer(&buffer, cx);
|
||||
if new_uri != registered_buffer.uri
|
||||
@@ -679,16 +679,16 @@ impl Copilot {
|
||||
registered_buffer.language_id = new_language_id;
|
||||
server
|
||||
.lsp
|
||||
.notify::<lsp2::notification::DidCloseTextDocument>(
|
||||
lsp2::DidCloseTextDocumentParams {
|
||||
text_document: lsp2::TextDocumentIdentifier::new(old_uri),
|
||||
.notify::<lsp::notification::DidCloseTextDocument>(
|
||||
lsp::DidCloseTextDocumentParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(old_uri),
|
||||
},
|
||||
)?;
|
||||
server
|
||||
.lsp
|
||||
.notify::<lsp2::notification::DidOpenTextDocument>(
|
||||
lsp2::DidOpenTextDocumentParams {
|
||||
text_document: lsp2::TextDocumentItem::new(
|
||||
.notify::<lsp::notification::DidOpenTextDocument>(
|
||||
lsp::DidOpenTextDocumentParams {
|
||||
text_document: lsp::TextDocumentItem::new(
|
||||
registered_buffer.uri.clone(),
|
||||
registered_buffer.language_id.clone(),
|
||||
registered_buffer.snapshot_version,
|
||||
@@ -711,9 +711,9 @@ impl Copilot {
|
||||
if let Some(buffer) = server.registered_buffers.remove(&buffer.entity_id()) {
|
||||
server
|
||||
.lsp
|
||||
.notify::<lsp2::notification::DidCloseTextDocument>(
|
||||
lsp2::DidCloseTextDocumentParams {
|
||||
text_document: lsp2::TextDocumentIdentifier::new(buffer.uri),
|
||||
.notify::<lsp::notification::DidCloseTextDocument>(
|
||||
lsp::DidCloseTextDocumentParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(buffer.uri),
|
||||
},
|
||||
)
|
||||
.log_err();
|
||||
@@ -760,7 +760,7 @@ impl Copilot {
|
||||
.request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
|
||||
uuid: completion.uuid.clone(),
|
||||
});
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
request.await?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -784,7 +784,7 @@ impl Copilot {
|
||||
.map(|completion| completion.uuid.clone())
|
||||
.collect(),
|
||||
});
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
request.await?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -798,7 +798,7 @@ impl Copilot {
|
||||
) -> Task<Result<Vec<Completion>>>
|
||||
where
|
||||
R: 'static
|
||||
+ lsp2::request::Request<
|
||||
+ lsp::request::Request<
|
||||
Params = request::GetCompletionsParams,
|
||||
Result = request::GetCompletionsResult,
|
||||
>,
|
||||
@@ -827,7 +827,7 @@ impl Copilot {
|
||||
.map(|file| file.path().to_path_buf())
|
||||
.unwrap_or_default();
|
||||
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
let (version, snapshot) = snapshot.await?;
|
||||
let result = lsp
|
||||
.request::<R>(request::GetCompletionsParams {
|
||||
@@ -926,9 +926,9 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp2::Url {
|
||||
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::Url {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
||||
lsp2::Url::from_file_path(file.abs_path(cx)).unwrap()
|
||||
lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
|
||||
} else {
|
||||
format!("buffer://{}", buffer.entity_id()).parse().unwrap()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ pub struct CheckStatusParams {
|
||||
pub local_checks_only: bool,
|
||||
}
|
||||
|
||||
impl lsp2::request::Request for CheckStatus {
|
||||
impl lsp::request::Request for CheckStatus {
|
||||
type Params = CheckStatusParams;
|
||||
type Result = SignInStatus;
|
||||
const METHOD: &'static str = "checkStatus";
|
||||
@@ -33,7 +33,7 @@ pub struct PromptUserDeviceFlow {
|
||||
pub verification_uri: String,
|
||||
}
|
||||
|
||||
impl lsp2::request::Request for SignInInitiate {
|
||||
impl lsp::request::Request for SignInInitiate {
|
||||
type Params = SignInInitiateParams;
|
||||
type Result = SignInInitiateResult;
|
||||
const METHOD: &'static str = "signInInitiate";
|
||||
@@ -66,7 +66,7 @@ pub enum SignInStatus {
|
||||
NotSignedIn,
|
||||
}
|
||||
|
||||
impl lsp2::request::Request for SignInConfirm {
|
||||
impl lsp::request::Request for SignInConfirm {
|
||||
type Params = SignInConfirmParams;
|
||||
type Result = SignInStatus;
|
||||
const METHOD: &'static str = "signInConfirm";
|
||||
@@ -82,7 +82,7 @@ pub struct SignOutParams {}
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignOutResult {}
|
||||
|
||||
impl lsp2::request::Request for SignOut {
|
||||
impl lsp::request::Request for SignOut {
|
||||
type Params = SignOutParams;
|
||||
type Result = SignOutResult;
|
||||
const METHOD: &'static str = "signOut";
|
||||
@@ -102,9 +102,9 @@ pub struct GetCompletionsDocument {
|
||||
pub tab_size: u32,
|
||||
pub indent_size: u32,
|
||||
pub insert_spaces: bool,
|
||||
pub uri: lsp2::Url,
|
||||
pub uri: lsp::Url,
|
||||
pub relative_path: String,
|
||||
pub position: lsp2::Position,
|
||||
pub position: lsp::Position,
|
||||
pub version: usize,
|
||||
}
|
||||
|
||||
@@ -118,13 +118,13 @@ pub struct GetCompletionsResult {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Completion {
|
||||
pub text: String,
|
||||
pub position: lsp2::Position,
|
||||
pub position: lsp::Position,
|
||||
pub uuid: String,
|
||||
pub range: lsp2::Range,
|
||||
pub range: lsp::Range,
|
||||
pub display_text: String,
|
||||
}
|
||||
|
||||
impl lsp2::request::Request for GetCompletions {
|
||||
impl lsp::request::Request for GetCompletions {
|
||||
type Params = GetCompletionsParams;
|
||||
type Result = GetCompletionsResult;
|
||||
const METHOD: &'static str = "getCompletions";
|
||||
@@ -132,7 +132,7 @@ impl lsp2::request::Request for GetCompletions {
|
||||
|
||||
pub enum GetCompletionsCycling {}
|
||||
|
||||
impl lsp2::request::Request for GetCompletionsCycling {
|
||||
impl lsp::request::Request for GetCompletionsCycling {
|
||||
type Params = GetCompletionsParams;
|
||||
type Result = GetCompletionsResult;
|
||||
const METHOD: &'static str = "getCompletionsCycling";
|
||||
@@ -149,7 +149,7 @@ pub struct LogMessageParams {
|
||||
pub extra: Vec<String>,
|
||||
}
|
||||
|
||||
impl lsp2::notification::Notification for LogMessage {
|
||||
impl lsp::notification::Notification for LogMessage {
|
||||
type Params = LogMessageParams;
|
||||
const METHOD: &'static str = "LogMessage";
|
||||
}
|
||||
@@ -162,7 +162,7 @@ pub struct StatusNotificationParams {
|
||||
pub status: String, // One of Normal/InProgress
|
||||
}
|
||||
|
||||
impl lsp2::notification::Notification for StatusNotification {
|
||||
impl lsp::notification::Notification for StatusNotification {
|
||||
type Params = StatusNotificationParams;
|
||||
const METHOD: &'static str = "statusNotification";
|
||||
}
|
||||
@@ -176,7 +176,7 @@ pub struct SetEditorInfoParams {
|
||||
pub editor_plugin_info: EditorPluginInfo,
|
||||
}
|
||||
|
||||
impl lsp2::request::Request for SetEditorInfo {
|
||||
impl lsp::request::Request for SetEditorInfo {
|
||||
type Params = SetEditorInfoParams;
|
||||
type Result = String;
|
||||
const METHOD: &'static str = "setEditorInfo";
|
||||
@@ -204,7 +204,7 @@ pub struct NotifyAcceptedParams {
|
||||
pub uuid: String,
|
||||
}
|
||||
|
||||
impl lsp2::request::Request for NotifyAccepted {
|
||||
impl lsp::request::Request for NotifyAccepted {
|
||||
type Params = NotifyAcceptedParams;
|
||||
type Result = String;
|
||||
const METHOD: &'static str = "notifyAccepted";
|
||||
@@ -218,7 +218,7 @@ pub struct NotifyRejectedParams {
|
||||
pub uuids: Vec<String>,
|
||||
}
|
||||
|
||||
impl lsp2::request::Request for NotifyRejected {
|
||||
impl lsp::request::Request for NotifyRejected {
|
||||
type Params = NotifyRejectedParams;
|
||||
type Result = String;
|
||||
const METHOD: &'static str = "notifyRejected";
|
||||
|
||||
@@ -13,7 +13,7 @@ test-support = []
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
gpui2 = { path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
sqlez = { path = "../sqlez" }
|
||||
sqlez_macros = { path = "../sqlez_macros" }
|
||||
util = { path = "../util" }
|
||||
@@ -28,6 +28,6 @@ serde_derive.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
tempdir.workspace = true
|
||||
|
||||
@@ -4,7 +4,7 @@ pub mod query;
|
||||
// Re-export
|
||||
pub use anyhow;
|
||||
use anyhow::Context;
|
||||
use gpui2::AppContext;
|
||||
use gpui::AppContext;
|
||||
pub use indoc::indoc;
|
||||
pub use lazy_static;
|
||||
pub use smol;
|
||||
@@ -185,143 +185,147 @@ pub fn write_and_log<F>(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send
|
||||
where
|
||||
F: Future<Output = anyhow::Result<()>> + Send,
|
||||
{
|
||||
cx.executor()
|
||||
cx.background_executor()
|
||||
.spawn(async move { db_write().await.log_err() })
|
||||
.detach()
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use std::thread;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::thread;
|
||||
|
||||
// use sqlez::domain::Domain;
|
||||
// use sqlez_macros::sql;
|
||||
// use tempdir::TempDir;
|
||||
use sqlez::domain::Domain;
|
||||
use sqlez_macros::sql;
|
||||
use tempdir::TempDir;
|
||||
|
||||
// use crate::open_db;
|
||||
use crate::open_db;
|
||||
|
||||
// // Test bad migration panics
|
||||
// #[gpui::test]
|
||||
// #[should_panic]
|
||||
// async fn test_bad_migration_panics() {
|
||||
// enum BadDB {}
|
||||
// Test bad migration panics
|
||||
#[gpui::test]
|
||||
#[should_panic]
|
||||
async fn test_bad_migration_panics() {
|
||||
enum BadDB {}
|
||||
|
||||
// impl Domain for BadDB {
|
||||
// fn name() -> &'static str {
|
||||
// "db_tests"
|
||||
// }
|
||||
impl Domain for BadDB {
|
||||
fn name() -> &'static str {
|
||||
"db_tests"
|
||||
}
|
||||
|
||||
// fn migrations() -> &'static [&'static str] {
|
||||
// &[
|
||||
// sql!(CREATE TABLE test(value);),
|
||||
// // failure because test already exists
|
||||
// sql!(CREATE TABLE test(value);),
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[
|
||||
sql!(CREATE TABLE test(value);),
|
||||
// failure because test already exists
|
||||
sql!(CREATE TABLE test(value);),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// let tempdir = TempDir::new("DbTests").unwrap();
|
||||
// let _bad_db = open_db::<BadDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
// }
|
||||
let tempdir = TempDir::new("DbTests").unwrap();
|
||||
let _bad_db = open_db::<BadDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
}
|
||||
|
||||
// /// Test that DB exists but corrupted (causing recreate)
|
||||
// #[gpui::test]
|
||||
// async fn test_db_corruption() {
|
||||
// enum CorruptedDB {}
|
||||
/// Test that DB exists but corrupted (causing recreate)
|
||||
#[gpui::test]
|
||||
async fn test_db_corruption(cx: &mut gpui::TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
// impl Domain for CorruptedDB {
|
||||
// fn name() -> &'static str {
|
||||
// "db_tests"
|
||||
// }
|
||||
enum CorruptedDB {}
|
||||
|
||||
// fn migrations() -> &'static [&'static str] {
|
||||
// &[sql!(CREATE TABLE test(value);)]
|
||||
// }
|
||||
// }
|
||||
impl Domain for CorruptedDB {
|
||||
fn name() -> &'static str {
|
||||
"db_tests"
|
||||
}
|
||||
|
||||
// enum GoodDB {}
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[sql!(CREATE TABLE test(value);)]
|
||||
}
|
||||
}
|
||||
|
||||
// impl Domain for GoodDB {
|
||||
// fn name() -> &'static str {
|
||||
// "db_tests" //Notice same name
|
||||
// }
|
||||
enum GoodDB {}
|
||||
|
||||
// fn migrations() -> &'static [&'static str] {
|
||||
// &[sql!(CREATE TABLE test2(value);)] //But different migration
|
||||
// }
|
||||
// }
|
||||
impl Domain for GoodDB {
|
||||
fn name() -> &'static str {
|
||||
"db_tests" //Notice same name
|
||||
}
|
||||
|
||||
// let tempdir = TempDir::new("DbTests").unwrap();
|
||||
// {
|
||||
// let corrupt_db =
|
||||
// open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
// assert!(corrupt_db.persistent());
|
||||
// }
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[sql!(CREATE TABLE test2(value);)] //But different migration
|
||||
}
|
||||
}
|
||||
|
||||
// let good_db = open_db::<GoodDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
// assert!(
|
||||
// good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||
// .unwrap()
|
||||
// .is_none()
|
||||
// );
|
||||
// }
|
||||
let tempdir = TempDir::new("DbTests").unwrap();
|
||||
{
|
||||
let corrupt_db =
|
||||
open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
assert!(corrupt_db.persistent());
|
||||
}
|
||||
|
||||
// /// Test that DB exists but corrupted (causing recreate)
|
||||
// #[gpui::test(iterations = 30)]
|
||||
// async fn test_simultaneous_db_corruption() {
|
||||
// enum CorruptedDB {}
|
||||
let good_db = open_db::<GoodDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
assert!(
|
||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
// impl Domain for CorruptedDB {
|
||||
// fn name() -> &'static str {
|
||||
// "db_tests"
|
||||
// }
|
||||
/// Test that DB exists but corrupted (causing recreate)
|
||||
#[gpui::test(iterations = 30)]
|
||||
async fn test_simultaneous_db_corruption(cx: &mut gpui::TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
// fn migrations() -> &'static [&'static str] {
|
||||
// &[sql!(CREATE TABLE test(value);)]
|
||||
// }
|
||||
// }
|
||||
enum CorruptedDB {}
|
||||
|
||||
// enum GoodDB {}
|
||||
impl Domain for CorruptedDB {
|
||||
fn name() -> &'static str {
|
||||
"db_tests"
|
||||
}
|
||||
|
||||
// impl Domain for GoodDB {
|
||||
// fn name() -> &'static str {
|
||||
// "db_tests" //Notice same name
|
||||
// }
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[sql!(CREATE TABLE test(value);)]
|
||||
}
|
||||
}
|
||||
|
||||
// fn migrations() -> &'static [&'static str] {
|
||||
// &[sql!(CREATE TABLE test2(value);)] //But different migration
|
||||
// }
|
||||
// }
|
||||
enum GoodDB {}
|
||||
|
||||
// let tempdir = TempDir::new("DbTests").unwrap();
|
||||
// {
|
||||
// // Setup the bad database
|
||||
// let corrupt_db =
|
||||
// open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
// assert!(corrupt_db.persistent());
|
||||
// }
|
||||
impl Domain for GoodDB {
|
||||
fn name() -> &'static str {
|
||||
"db_tests" //Notice same name
|
||||
}
|
||||
|
||||
// // Try to connect to it a bunch of times at once
|
||||
// let mut guards = vec![];
|
||||
// for _ in 0..10 {
|
||||
// let tmp_path = tempdir.path().to_path_buf();
|
||||
// let guard = thread::spawn(move || {
|
||||
// let good_db = smol::block_on(open_db::<GoodDB>(
|
||||
// tmp_path.as_path(),
|
||||
// &util::channel::ReleaseChannel::Dev,
|
||||
// ));
|
||||
// assert!(
|
||||
// good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||
// .unwrap()
|
||||
// .is_none()
|
||||
// );
|
||||
// });
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[sql!(CREATE TABLE test2(value);)] //But different migration
|
||||
}
|
||||
}
|
||||
|
||||
// guards.push(guard);
|
||||
// }
|
||||
let tempdir = TempDir::new("DbTests").unwrap();
|
||||
{
|
||||
// Setup the bad database
|
||||
let corrupt_db =
|
||||
open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
assert!(corrupt_db.persistent());
|
||||
}
|
||||
|
||||
// for guard in guards.into_iter() {
|
||||
// assert!(guard.join().is_ok());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Try to connect to it a bunch of times at once
|
||||
let mut guards = vec![];
|
||||
for _ in 0..10 {
|
||||
let tmp_path = tempdir.path().to_path_buf();
|
||||
let guard = thread::spawn(move || {
|
||||
let good_db = smol::block_on(open_db::<GoodDB>(
|
||||
tmp_path.as_path(),
|
||||
&util::channel::ReleaseChannel::Dev,
|
||||
));
|
||||
assert!(
|
||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
});
|
||||
|
||||
guards.push(guard);
|
||||
}
|
||||
|
||||
for guard in guards.into_iter() {
|
||||
assert!(guard.join().is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,32 +31,32 @@ impl KeyValueStore {
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::kvp::KeyValueStore;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::kvp::KeyValueStore;
|
||||
|
||||
// #[gpui::test]
|
||||
// async fn test_kvp() {
|
||||
// let db = KeyValueStore(crate::open_test_db("test_kvp").await);
|
||||
#[gpui::test]
|
||||
async fn test_kvp() {
|
||||
let db = KeyValueStore(crate::open_test_db("test_kvp").await);
|
||||
|
||||
// assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
||||
assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
||||
|
||||
// db.write_kvp("key-1".to_string(), "one".to_string())
|
||||
// .await
|
||||
// .unwrap();
|
||||
// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
|
||||
db.write_kvp("key-1".to_string(), "one".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
|
||||
|
||||
// db.write_kvp("key-1".to_string(), "one-2".to_string())
|
||||
// .await
|
||||
// .unwrap();
|
||||
// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string()));
|
||||
db.write_kvp("key-1".to_string(), "one-2".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string()));
|
||||
|
||||
// db.write_kvp("key-2".to_string(), "two".to_string())
|
||||
// .await
|
||||
// .unwrap();
|
||||
// assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
|
||||
db.write_kvp("key-2".to_string(), "two".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
|
||||
|
||||
// db.delete_kvp("key-1".to_string()).await.unwrap();
|
||||
// assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
||||
// }
|
||||
// }
|
||||
db.delete_kvp("key-1".to_string()).await.unwrap();
|
||||
assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ publish = false
|
||||
path = "src/feature_flags2.rs"
|
||||
|
||||
[dependencies]
|
||||
gpui2 = { path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
anyhow.workspace = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use gpui2::{AppContext, Subscription, ViewContext};
|
||||
use gpui::{AppContext, Subscription, ViewContext};
|
||||
|
||||
#[derive(Default)]
|
||||
struct FeatureFlags {
|
||||
@@ -28,7 +28,7 @@ pub trait FeatureFlagViewExt<V: 'static> {
|
||||
F: Fn(bool, &mut V, &mut ViewContext<V>) + Send + Sync + 'static;
|
||||
}
|
||||
|
||||
impl<V> FeatureFlagViewExt<V> for ViewContext<'_, '_, V>
|
||||
impl<V> FeatureFlagViewExt<V> for ViewContext<'_, V>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ path = "src/fs2.rs"
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
rope = { path = "../rope" }
|
||||
text = { path = "../text" }
|
||||
text = { package = "text2", path = "../text2" }
|
||||
util = { path = "../util" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
|
||||
@@ -31,10 +31,10 @@ log.workspace = true
|
||||
libc = "0.2"
|
||||
time.workspace = true
|
||||
|
||||
gpui2 = { path = "../gpui2", optional = true}
|
||||
gpui = { package = "gpui2", path = "../gpui2", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
|
||||
[features]
|
||||
test-support = ["gpui2/test-support"]
|
||||
test-support = ["gpui/test-support"]
|
||||
|
||||
@@ -288,7 +288,7 @@ impl Fs for RealFs {
|
||||
pub struct FakeFs {
|
||||
// Use an unfair lock to ensure tests are deterministic.
|
||||
state: Mutex<FakeFsState>,
|
||||
executor: gpui2::Executor,
|
||||
executor: gpui::BackgroundExecutor,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -434,7 +434,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl FakeFs {
|
||||
pub fn new(executor: gpui2::Executor) -> Arc<Self> {
|
||||
pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
executor,
|
||||
state: Mutex::new(FakeFsState {
|
||||
@@ -1222,11 +1222,11 @@ pub fn copy_recursive<'a>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui2::Executor;
|
||||
use gpui::BackgroundExecutor;
|
||||
use serde_json::json;
|
||||
|
||||
#[gpui2::test]
|
||||
async fn test_fake_fs(executor: Executor) {
|
||||
#[gpui::test]
|
||||
async fn test_fake_fs(executor: BackgroundExecutor) {
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
|
||||
@@ -9,5 +9,5 @@ path = "src/fuzzy2.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
gpui2 = { path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
util = { path = "../util" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use gpui2::Executor;
|
||||
use gpui::BackgroundExecutor;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{self, Ordering},
|
||||
@@ -134,7 +134,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
|
||||
smart_case: bool,
|
||||
max_results: usize,
|
||||
cancel_flag: &AtomicBool,
|
||||
executor: Executor,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Vec<PathMatch> {
|
||||
let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
|
||||
if path_count == 0 {
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
matcher::{Match, MatchCandidate, Matcher},
|
||||
CharBag,
|
||||
};
|
||||
use gpui2::Executor;
|
||||
use gpui::BackgroundExecutor;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{self, Ordering},
|
||||
@@ -83,7 +83,7 @@ pub async fn match_strings(
|
||||
smart_case: bool,
|
||||
max_results: usize,
|
||||
cancel_flag: &AtomicBool,
|
||||
executor: Executor,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Vec<StringMatch> {
|
||||
if candidates.is_empty() || max_results == 0 {
|
||||
return Default::default();
|
||||
|
||||
30
crates/git3/Cargo.toml
Normal file
30
crates/git3/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
# git2 was already taken.
|
||||
name = "git3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/git.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clock = { path = "../clock" }
|
||||
lazy_static.workspace = true
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
text = { package = "text2", path = "../text2" }
|
||||
collections = { path = "../collections" }
|
||||
util = { path = "../util" }
|
||||
log.workspace = true
|
||||
smol.workspace = true
|
||||
parking_lot.workspace = true
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
git2.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
unindent.workspace = true
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
412
crates/git3/src/diff.rs
Normal file
412
crates/git3/src/diff.rs
Normal file
@@ -0,0 +1,412 @@
|
||||
use std::{iter, ops::Range};
|
||||
use sum_tree::SumTree;
|
||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point};
|
||||
|
||||
pub use git2 as libgit;
|
||||
use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DiffHunkStatus {
|
||||
Added,
|
||||
Modified,
|
||||
Removed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DiffHunk<T> {
|
||||
pub buffer_range: Range<T>,
|
||||
pub diff_base_byte_range: Range<usize>,
|
||||
}
|
||||
|
||||
impl DiffHunk<u32> {
|
||||
pub fn status(&self) -> DiffHunkStatus {
|
||||
if self.diff_base_byte_range.is_empty() {
|
||||
DiffHunkStatus::Added
|
||||
} else if self.buffer_range.is_empty() {
|
||||
DiffHunkStatus::Removed
|
||||
} else {
|
||||
DiffHunkStatus::Modified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for DiffHunk<Anchor> {
|
||||
type Summary = DiffHunkSummary;
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
DiffHunkSummary {
|
||||
buffer_range: self.buffer_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DiffHunkSummary {
|
||||
buffer_range: Range<Anchor>,
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for DiffHunkSummary {
|
||||
type Context = text::BufferSnapshot;
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||
self.buffer_range.start = self
|
||||
.buffer_range
|
||||
.start
|
||||
.min(&other.buffer_range.start, buffer);
|
||||
self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BufferDiff {
|
||||
last_buffer_version: Option<clock::Global>,
|
||||
tree: SumTree<DiffHunk<Anchor>>,
|
||||
}
|
||||
|
||||
impl BufferDiff {
|
||||
pub fn new() -> BufferDiff {
|
||||
BufferDiff {
|
||||
last_buffer_version: None,
|
||||
tree: SumTree::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tree.is_empty()
|
||||
}
|
||||
|
||||
pub fn hunks_in_row_range<'a>(
|
||||
&'a self,
|
||||
range: Range<u32>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let start = buffer.anchor_before(Point::new(range.start, 0));
|
||||
let end = buffer.anchor_after(Point::new(range.end, 0));
|
||||
|
||||
self.hunks_intersecting_range(start..end, buffer)
|
||||
}
|
||||
|
||||
pub fn hunks_intersecting_range<'a>(
|
||||
&'a self,
|
||||
range: Range<Anchor>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
|
||||
!before_start && !after_end
|
||||
});
|
||||
|
||||
let anchor_iter = std::iter::from_fn(move || {
|
||||
cursor.next(buffer);
|
||||
cursor.item()
|
||||
})
|
||||
.flat_map(move |hunk| {
|
||||
[
|
||||
(&hunk.buffer_range.start, hunk.diff_base_byte_range.start),
|
||||
(&hunk.buffer_range.end, hunk.diff_base_byte_range.end),
|
||||
]
|
||||
.into_iter()
|
||||
});
|
||||
|
||||
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
|
||||
iter::from_fn(move || {
|
||||
let (start_point, start_base) = summaries.next()?;
|
||||
let (end_point, end_base) = summaries.next()?;
|
||||
|
||||
let end_row = if end_point.column > 0 {
|
||||
end_point.row + 1
|
||||
} else {
|
||||
end_point.row
|
||||
};
|
||||
|
||||
Some(DiffHunk {
|
||||
buffer_range: start_point.row..end_row,
|
||||
diff_base_byte_range: start_base..end_base,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hunks_intersecting_range_rev<'a>(
|
||||
&'a self,
|
||||
range: Range<Anchor>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
|
||||
!before_start && !after_end
|
||||
});
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
cursor.prev(buffer);
|
||||
|
||||
let hunk = cursor.item()?;
|
||||
let range = hunk.buffer_range.to_point(buffer);
|
||||
let end_row = if range.end.column > 0 {
|
||||
range.end.row + 1
|
||||
} else {
|
||||
range.end.row
|
||||
};
|
||||
|
||||
Some(DiffHunk {
|
||||
buffer_range: range.start.row..end_row,
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, buffer: &text::BufferSnapshot) {
|
||||
self.last_buffer_version = Some(buffer.version().clone());
|
||||
self.tree = SumTree::new();
|
||||
}
|
||||
|
||||
pub async fn update(&mut self, diff_base: &str, buffer: &text::BufferSnapshot) {
|
||||
let mut tree = SumTree::new();
|
||||
|
||||
let buffer_text = buffer.as_rope().to_string();
|
||||
let patch = Self::diff(&diff_base, &buffer_text);
|
||||
|
||||
if let Some(patch) = patch {
|
||||
let mut divergence = 0;
|
||||
for hunk_index in 0..patch.num_hunks() {
|
||||
let hunk = Self::process_patch_hunk(&patch, hunk_index, buffer, &mut divergence);
|
||||
tree.push(hunk, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
self.tree = tree;
|
||||
self.last_buffer_version = Some(buffer.version().clone());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let start = text.anchor_before(Point::new(0, 0));
|
||||
let end = text.anchor_after(Point::new(u32::MAX, u32::MAX));
|
||||
self.hunks_intersecting_range(start..end, text)
|
||||
}
|
||||
|
||||
fn diff<'a>(head: &'a str, current: &'a str) -> Option<GitPatch<'a>> {
|
||||
let mut options = GitOptions::default();
|
||||
options.context_lines(0);
|
||||
|
||||
let patch = GitPatch::from_buffers(
|
||||
head.as_bytes(),
|
||||
None,
|
||||
current.as_bytes(),
|
||||
None,
|
||||
Some(&mut options),
|
||||
);
|
||||
|
||||
match patch {
|
||||
Ok(patch) => Some(patch),
|
||||
|
||||
Err(err) => {
|
||||
log::error!("`GitPatch::from_buffers` failed: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_patch_hunk<'a>(
|
||||
patch: &GitPatch<'a>,
|
||||
hunk_index: usize,
|
||||
buffer: &text::BufferSnapshot,
|
||||
buffer_row_divergence: &mut i64,
|
||||
) -> DiffHunk<Anchor> {
|
||||
let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
|
||||
assert!(line_item_count > 0);
|
||||
|
||||
let mut first_deletion_buffer_row: Option<u32> = None;
|
||||
let mut buffer_row_range: Option<Range<u32>> = None;
|
||||
let mut diff_base_byte_range: Option<Range<usize>> = None;
|
||||
|
||||
for line_index in 0..line_item_count {
|
||||
let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
|
||||
let kind = line.origin_value();
|
||||
let content_offset = line.content_offset() as isize;
|
||||
let content_len = line.content().len() as isize;
|
||||
|
||||
if kind == GitDiffLineType::Addition {
|
||||
*buffer_row_divergence += 1;
|
||||
let row = line.new_lineno().unwrap().saturating_sub(1);
|
||||
|
||||
match &mut buffer_row_range {
|
||||
Some(buffer_row_range) => buffer_row_range.end = row + 1,
|
||||
None => buffer_row_range = Some(row..row + 1),
|
||||
}
|
||||
}
|
||||
|
||||
if kind == GitDiffLineType::Deletion {
|
||||
let end = content_offset + content_len;
|
||||
|
||||
match &mut diff_base_byte_range {
|
||||
Some(head_byte_range) => head_byte_range.end = end as usize,
|
||||
None => diff_base_byte_range = Some(content_offset as usize..end as usize),
|
||||
}
|
||||
|
||||
if first_deletion_buffer_row.is_none() {
|
||||
let old_row = line.old_lineno().unwrap().saturating_sub(1);
|
||||
let row = old_row as i64 + *buffer_row_divergence;
|
||||
first_deletion_buffer_row = Some(row as u32);
|
||||
}
|
||||
|
||||
*buffer_row_divergence -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
//unwrap_or deletion without addition
|
||||
let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
|
||||
//we cannot have an addition-less hunk without deletion(s) or else there would be no hunk
|
||||
let row = first_deletion_buffer_row.unwrap();
|
||||
row..row
|
||||
});
|
||||
|
||||
//unwrap_or addition without deletion
|
||||
let diff_base_byte_range = diff_base_byte_range.unwrap_or(0..0);
|
||||
|
||||
let start = Point::new(buffer_row_range.start, 0);
|
||||
let end = Point::new(buffer_row_range.end, 0);
|
||||
let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
|
||||
DiffHunk {
|
||||
buffer_range,
|
||||
diff_base_byte_range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Range (crossing new lines), old, new
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[track_caller]
|
||||
pub fn assert_hunks<Iter>(
|
||||
diff_hunks: Iter,
|
||||
buffer: &BufferSnapshot,
|
||||
diff_base: &str,
|
||||
expected_hunks: &[(Range<u32>, &str, &str)],
|
||||
) where
|
||||
Iter: Iterator<Item = DiffHunk<u32>>,
|
||||
{
|
||||
let actual_hunks = diff_hunks
|
||||
.map(|hunk| {
|
||||
(
|
||||
hunk.buffer_range.clone(),
|
||||
&diff_base[hunk.diff_base_byte_range],
|
||||
buffer
|
||||
.text_for_range(
|
||||
Point::new(hunk.buffer_range.start, 0)
|
||||
..Point::new(hunk.buffer_range.end, 0),
|
||||
)
|
||||
.collect::<String>(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let expected_hunks: Vec<_> = expected_hunks
|
||||
.iter()
|
||||
.map(|(r, s, h)| (r.clone(), *s, h.to_string()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(actual_hunks, expected_hunks);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use text::Buffer;
|
||||
use unindent::Unindent as _;
|
||||
|
||||
#[test]
|
||||
fn test_buffer_diff_simple() {
|
||||
let diff_base = "
|
||||
one
|
||||
two
|
||||
three
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer_text = "
|
||||
one
|
||||
HELLO
|
||||
three
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let mut buffer = Buffer::new(0, 0, buffer_text);
|
||||
let mut diff = BufferDiff::new();
|
||||
smol::block_on(diff.update(&diff_base, &buffer));
|
||||
assert_hunks(
|
||||
diff.hunks(&buffer),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "two\n", "HELLO\n")],
|
||||
);
|
||||
|
||||
buffer.edit([(0..0, "point five\n")]);
|
||||
smol::block_on(diff.update(&diff_base, &buffer));
|
||||
assert_hunks(
|
||||
diff.hunks(&buffer),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(0..1, "", "point five\n"), (2..3, "two\n", "HELLO\n")],
|
||||
);
|
||||
|
||||
diff.clear(&buffer);
|
||||
assert_hunks(diff.hunks(&buffer), &buffer, &diff_base, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_diff_range() {
|
||||
let diff_base = "
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
nine
|
||||
ten
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer_text = "
|
||||
A
|
||||
one
|
||||
B
|
||||
two
|
||||
C
|
||||
three
|
||||
HELLO
|
||||
four
|
||||
five
|
||||
SIXTEEN
|
||||
seven
|
||||
eight
|
||||
WORLD
|
||||
nine
|
||||
|
||||
ten
|
||||
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(0, 0, buffer_text);
|
||||
let mut diff = BufferDiff::new();
|
||||
smol::block_on(diff.update(&diff_base, &buffer));
|
||||
assert_eq!(diff.hunks(&buffer).count(), 8);
|
||||
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(7..12, &buffer),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[
|
||||
(6..7, "", "HELLO\n"),
|
||||
(9..10, "six\n", "SIXTEEN\n"),
|
||||
(12..13, "", "WORLD\n"),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
11
crates/git3/src/git.rs
Normal file
11
crates/git3/src/git.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use std::ffi::OsStr;
|
||||
|
||||
pub use git2 as libgit;
|
||||
pub use lazy_static::lazy_static;
|
||||
|
||||
pub mod diff;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DOT_GIT: &'static OsStr = OsStr::new(".git");
|
||||
pub static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
|
||||
}
|
||||
@@ -84,7 +84,7 @@ struct DeterministicState {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ExecutorEvent {
|
||||
PollRunnable { id: usize },
|
||||
EnqueuRunnable { id: usize },
|
||||
EnqueueRunnable { id: usize },
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -199,7 +199,7 @@ impl Deterministic {
|
||||
let unparker = self.parker.lock().unparker();
|
||||
let (runnable, task) = async_task::spawn_local(future, move |runnable| {
|
||||
let mut state = state.lock();
|
||||
state.push_to_history(ExecutorEvent::EnqueuRunnable { id });
|
||||
state.push_to_history(ExecutorEvent::EnqueueRunnable { id });
|
||||
state
|
||||
.scheduled_from_foreground
|
||||
.entry(cx_id)
|
||||
@@ -229,7 +229,7 @@ impl Deterministic {
|
||||
let mut state = state.lock();
|
||||
state
|
||||
.poll_history
|
||||
.push(ExecutorEvent::EnqueuRunnable { id });
|
||||
.push(ExecutorEvent::EnqueueRunnable { id });
|
||||
state
|
||||
.scheduled_from_background
|
||||
.push(BackgroundRunnable { id, runnable });
|
||||
@@ -616,7 +616,7 @@ impl ExecutorEvent {
|
||||
pub fn id(&self) -> usize {
|
||||
match self {
|
||||
ExecutorEvent::PollRunnable { id } => *id,
|
||||
ExecutorEvent::EnqueuRunnable { id } => *id,
|
||||
ExecutorEvent::EnqueueRunnable { id } => *id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use collections::{HashMap, HashSet};
|
||||
use serde::Deserialize;
|
||||
use std::any::{type_name, Any};
|
||||
|
||||
pub trait Action: Any + Send {
|
||||
pub trait Action: 'static {
|
||||
fn qualified_name() -> SharedString
|
||||
where
|
||||
Self: Sized;
|
||||
@@ -19,7 +19,7 @@ pub trait Action: Any + Send {
|
||||
|
||||
impl<A> Action for A
|
||||
where
|
||||
A: for<'a> Deserialize<'a> + PartialEq + Any + Send + Clone + Default,
|
||||
A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + 'static,
|
||||
{
|
||||
fn qualified_name() -> SharedString {
|
||||
type_name::<A>().into()
|
||||
|
||||
@@ -5,6 +5,7 @@ mod model_context;
|
||||
mod test_context;
|
||||
|
||||
pub use async_context::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub use entity_map::*;
|
||||
pub use model_context::*;
|
||||
use refineable::Refineable;
|
||||
@@ -13,30 +14,56 @@ use smallvec::SmallVec;
|
||||
pub use test_context::*;
|
||||
|
||||
use crate::{
|
||||
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource,
|
||||
ClipboardItem, Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId,
|
||||
KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, Render,
|
||||
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
|
||||
TextSystem, View, Window, WindowContext, WindowHandle, WindowId,
|
||||
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
|
||||
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
||||
Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId,
|
||||
PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString,
|
||||
SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem,
|
||||
View, Window, WindowContext, WindowHandle, WindowId,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
use futures::{future::BoxFuture, Future};
|
||||
use futures::{channel::oneshot, future::LocalBoxFuture, Future};
|
||||
use parking_lot::Mutex;
|
||||
use slotmap::SlotMap;
|
||||
use std::{
|
||||
any::{type_name, Any, TypeId},
|
||||
borrow::Borrow,
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
sync::{atomic::Ordering::SeqCst, Arc, Weak},
|
||||
path::{Path, PathBuf},
|
||||
rc::{Rc, Weak},
|
||||
sync::{atomic::Ordering::SeqCst, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
use util::http::{self, HttpClient};
|
||||
|
||||
pub struct App(Arc<Mutex<AppContext>>);
|
||||
/// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
|
||||
/// Strongly consider removing after stabilization.
|
||||
pub struct AppCell {
|
||||
app: RefCell<AppContext>,
|
||||
}
|
||||
|
||||
impl AppCell {
|
||||
pub fn borrow(&self) -> AppRef {
|
||||
AppRef(self.app.borrow())
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> AppRefMut {
|
||||
// let thread_id = std::thread::current().id();
|
||||
// dbg!("borrowed {thread_id:?}");
|
||||
AppRefMut(self.app.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct AppRef<'a>(Ref<'a, AppContext>);
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
|
||||
|
||||
pub struct App(Rc<AppCell>);
|
||||
|
||||
/// Represents an application before it is fully launched. Once your app is
|
||||
/// configured, you'll start the app with `App::run`.
|
||||
@@ -54,13 +81,12 @@ impl App {
|
||||
/// app is fully launched.
|
||||
pub fn run<F>(self, on_finish_launching: F)
|
||||
where
|
||||
F: 'static + FnOnce(&mut MainThread<AppContext>),
|
||||
F: 'static + FnOnce(&mut AppContext),
|
||||
{
|
||||
let this = self.0.clone();
|
||||
let platform = self.0.lock().platform.clone();
|
||||
platform.borrow_on_main_thread().run(Box::new(move || {
|
||||
let cx = &mut *this.lock();
|
||||
let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) };
|
||||
let platform = self.0.borrow().platform.clone();
|
||||
platform.run(Box::new(move || {
|
||||
let cx = &mut *this.borrow_mut();
|
||||
on_finish_launching(cx);
|
||||
}));
|
||||
}
|
||||
@@ -71,16 +97,12 @@ impl App {
|
||||
where
|
||||
F: 'static + FnMut(Vec<String>, &mut AppContext),
|
||||
{
|
||||
let this = Arc::downgrade(&self.0);
|
||||
self.0
|
||||
.lock()
|
||||
.platform
|
||||
.borrow_on_main_thread()
|
||||
.on_open_urls(Box::new(move |urls| {
|
||||
if let Some(app) = this.upgrade() {
|
||||
callback(urls, &mut app.lock());
|
||||
}
|
||||
}));
|
||||
let this = Rc::downgrade(&self.0);
|
||||
self.0.borrow().platform.on_open_urls(Box::new(move |urls| {
|
||||
if let Some(app) = this.upgrade() {
|
||||
callback(urls, &mut *app.borrow_mut());
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -88,49 +110,57 @@ impl App {
|
||||
where
|
||||
F: 'static + FnMut(&mut AppContext),
|
||||
{
|
||||
let this = Arc::downgrade(&self.0);
|
||||
self.0
|
||||
.lock()
|
||||
.platform
|
||||
.borrow_on_main_thread()
|
||||
.on_reopen(Box::new(move || {
|
||||
if let Some(app) = this.upgrade() {
|
||||
callback(&mut app.lock());
|
||||
}
|
||||
}));
|
||||
let this = Rc::downgrade(&self.0);
|
||||
self.0.borrow_mut().platform.on_reopen(Box::new(move || {
|
||||
if let Some(app) = this.upgrade() {
|
||||
callback(&mut app.borrow_mut());
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> AppMetadata {
|
||||
self.0.lock().app_metadata.clone()
|
||||
self.0.borrow().app_metadata.clone()
|
||||
}
|
||||
|
||||
pub fn executor(&self) -> Executor {
|
||||
self.0.lock().executor.clone()
|
||||
pub fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.0.borrow().background_executor.clone()
|
||||
}
|
||||
|
||||
pub fn foreground_executor(&self) -> ForegroundExecutor {
|
||||
self.0.borrow().foreground_executor.clone()
|
||||
}
|
||||
|
||||
pub fn text_system(&self) -> Arc<TextSystem> {
|
||||
self.0.lock().text_system.clone()
|
||||
self.0.borrow().text_system.clone()
|
||||
}
|
||||
}
|
||||
|
||||
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
|
||||
type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
|
||||
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + Send + 'static>;
|
||||
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + Send + 'static>;
|
||||
type QuitHandler = Box<dyn FnMut(&mut AppContext) -> BoxFuture<'static, ()> + Send + 'static>;
|
||||
type ReleaseListener = Box<dyn FnMut(&mut dyn Any, &mut AppContext) + Send + 'static>;
|
||||
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
|
||||
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
|
||||
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
||||
type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
|
||||
type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
|
||||
|
||||
// struct FrameConsumer {
|
||||
// next_frame_callbacks: Vec<FrameCallback>,
|
||||
// task: Task<()>,
|
||||
// display_linker
|
||||
// }
|
||||
|
||||
pub struct AppContext {
|
||||
this: Weak<Mutex<AppContext>>,
|
||||
pub(crate) platform: MainThreadOnly<dyn Platform>,
|
||||
this: Weak<AppCell>,
|
||||
pub(crate) platform: Rc<dyn Platform>,
|
||||
app_metadata: AppMetadata,
|
||||
text_system: Arc<TextSystem>,
|
||||
flushing_effects: bool,
|
||||
pending_updates: usize,
|
||||
pub(crate) active_drag: Option<AnyDrag>,
|
||||
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
||||
pub(crate) executor: Executor,
|
||||
pub(crate) frame_consumers: HashMap<DisplayId, Task<()>>,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
pub(crate) svg_renderer: SvgRenderer,
|
||||
asset_source: Arc<dyn AssetSource>,
|
||||
pub(crate) image_cache: ImageCache,
|
||||
@@ -140,7 +170,7 @@ pub struct AppContext {
|
||||
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
|
||||
pub(crate) keymap: Arc<Mutex<Keymap>>,
|
||||
pub(crate) global_action_listeners:
|
||||
HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self) + Send>>>,
|
||||
HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
|
||||
action_builders: HashMap<SharedString, ActionBuilder>,
|
||||
pending_effects: VecDeque<Effect>,
|
||||
pub(crate) pending_notifications: HashSet<EntityId>,
|
||||
@@ -156,11 +186,12 @@ pub struct AppContext {
|
||||
|
||||
impl AppContext {
|
||||
pub(crate) fn new(
|
||||
platform: Arc<dyn Platform>,
|
||||
platform: Rc<dyn Platform>,
|
||||
asset_source: Arc<dyn AssetSource>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Arc<Mutex<Self>> {
|
||||
let executor = platform.executor();
|
||||
) -> Rc<AppCell> {
|
||||
let executor = platform.background_executor();
|
||||
let foreground_executor = platform.foreground_executor();
|
||||
assert!(
|
||||
executor.is_main_thread(),
|
||||
"must construct App on main thread"
|
||||
@@ -175,16 +206,19 @@ impl AppContext {
|
||||
app_version: platform.app_version().ok(),
|
||||
};
|
||||
|
||||
Arc::new_cyclic(|this| {
|
||||
Mutex::new(AppContext {
|
||||
Rc::new_cyclic(|this| AppCell {
|
||||
app: RefCell::new(AppContext {
|
||||
this: this.clone(),
|
||||
text_system,
|
||||
platform: MainThreadOnly::new(platform, executor.clone()),
|
||||
platform,
|
||||
app_metadata,
|
||||
text_system,
|
||||
flushing_effects: false,
|
||||
pending_updates: 0,
|
||||
next_frame_callbacks: Default::default(),
|
||||
executor,
|
||||
active_drag: None,
|
||||
next_frame_callbacks: HashMap::default(),
|
||||
frame_consumers: HashMap::default(),
|
||||
background_executor: executor,
|
||||
foreground_executor,
|
||||
svg_renderer: SvgRenderer::new(asset_source.clone()),
|
||||
asset_source,
|
||||
image_cache: ImageCache::new(http_client),
|
||||
@@ -205,8 +239,7 @@ impl AppContext {
|
||||
quit_observers: SubscriberSet::new(),
|
||||
layout_id_buffer: Default::default(),
|
||||
propagate_event: true,
|
||||
active_drag: None,
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -215,17 +248,16 @@ impl AppContext {
|
||||
pub fn quit(&mut self) {
|
||||
let mut futures = Vec::new();
|
||||
|
||||
self.quit_observers.clone().retain(&(), |observer| {
|
||||
for observer in self.quit_observers.remove(&()) {
|
||||
futures.push(observer(self));
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
self.windows.clear();
|
||||
self.flush_effects();
|
||||
|
||||
let futures = futures::future::join_all(futures);
|
||||
if self
|
||||
.executor
|
||||
.background_executor
|
||||
.block_with_timeout(Duration::from_millis(100), futures)
|
||||
.is_err()
|
||||
{
|
||||
@@ -244,7 +276,6 @@ impl AppContext {
|
||||
pub fn refresh(&mut self) {
|
||||
self.pending_effects.push_back(Effect::Refresh);
|
||||
}
|
||||
|
||||
pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
|
||||
self.pending_updates += 1;
|
||||
let result = update(self);
|
||||
@@ -257,44 +288,91 @@ impl AppContext {
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn read_window<R>(
|
||||
&mut self,
|
||||
id: WindowId,
|
||||
read: impl FnOnce(&WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
let window = self
|
||||
.windows
|
||||
.get(id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
Ok(read(&WindowContext::immutable(self, &window)))
|
||||
pub fn windows(&self) -> Vec<AnyWindowHandle> {
|
||||
self.windows
|
||||
.values()
|
||||
.filter_map(|window| Some(window.as_ref()?.handle.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn update_window<R>(
|
||||
/// Opens a new window with the given option and the root view returned by the given function.
|
||||
/// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
|
||||
/// functionality.
|
||||
pub fn open_window<V: Render>(
|
||||
&mut self,
|
||||
id: WindowId,
|
||||
update: impl FnOnce(&mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
options: crate::WindowOptions,
|
||||
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
|
||||
) -> WindowHandle<V> {
|
||||
self.update(|cx| {
|
||||
let mut window = cx
|
||||
.windows
|
||||
.get_mut(id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.take()
|
||||
.unwrap();
|
||||
|
||||
let result = update(&mut WindowContext::mutable(cx, &mut window));
|
||||
|
||||
cx.windows
|
||||
.get_mut(id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.replace(window);
|
||||
|
||||
Ok(result)
|
||||
let id = cx.windows.insert(None);
|
||||
let handle = WindowHandle::new(id);
|
||||
let mut window = Window::new(handle.into(), options, cx);
|
||||
let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
|
||||
window.root_view.replace(root_view.into());
|
||||
cx.windows.get_mut(id).unwrap().replace(window);
|
||||
handle
|
||||
})
|
||||
}
|
||||
|
||||
/// Instructs the platform to activate the application by bringing it to the foreground.
|
||||
pub fn activate(&self, ignoring_other_apps: bool) {
|
||||
self.platform.activate(ignoring_other_apps);
|
||||
}
|
||||
|
||||
/// Returns the list of currently active displays.
|
||||
pub fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
self.platform.displays()
|
||||
}
|
||||
|
||||
/// Writes data to the platform clipboard.
|
||||
pub fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
self.platform.write_to_clipboard(item)
|
||||
}
|
||||
|
||||
/// Reads data from the platform clipboard.
|
||||
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
self.platform.read_from_clipboard()
|
||||
}
|
||||
|
||||
/// Writes credentials to the platform keychain.
|
||||
pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
|
||||
self.platform.write_credentials(url, username, password)
|
||||
}
|
||||
|
||||
/// Reads credentials from the platform keychain.
|
||||
pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
|
||||
self.platform.read_credentials(url)
|
||||
}
|
||||
|
||||
/// Deletes credentials from the platform keychain.
|
||||
pub fn delete_credentials(&self, url: &str) -> Result<()> {
|
||||
self.platform.delete_credentials(url)
|
||||
}
|
||||
|
||||
/// Directs the platform's default browser to open the given URL.
|
||||
pub fn open_url(&self, url: &str) {
|
||||
self.platform.open_url(url);
|
||||
}
|
||||
|
||||
pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
|
||||
self.platform.path_for_auxiliary_executable(name)
|
||||
}
|
||||
|
||||
pub fn prompt_for_paths(
|
||||
&self,
|
||||
options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
|
||||
self.platform.prompt_for_paths(options)
|
||||
}
|
||||
|
||||
pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
|
||||
self.platform.prompt_for_new_path(directory)
|
||||
}
|
||||
|
||||
pub fn reveal_path(&self, path: &Path) {
|
||||
self.platform.reveal_path(path)
|
||||
}
|
||||
|
||||
pub(crate) fn push_effect(&mut self, effect: Effect) {
|
||||
match &effect {
|
||||
Effect::Notify { emitter } => {
|
||||
@@ -326,8 +404,11 @@ impl AppContext {
|
||||
self.apply_notify_effect(emitter);
|
||||
}
|
||||
Effect::Emit { emitter, event } => self.apply_emit_effect(emitter, event),
|
||||
Effect::FocusChanged { window_id, focused } => {
|
||||
self.apply_focus_changed_effect(window_id, focused);
|
||||
Effect::FocusChanged {
|
||||
window_handle,
|
||||
focused,
|
||||
} => {
|
||||
self.apply_focus_changed_effect(window_handle, focused);
|
||||
}
|
||||
Effect::Refresh => {
|
||||
self.apply_refresh_effect();
|
||||
@@ -347,18 +428,18 @@ impl AppContext {
|
||||
let dirty_window_ids = self
|
||||
.windows
|
||||
.iter()
|
||||
.filter_map(|(window_id, window)| {
|
||||
.filter_map(|(_, window)| {
|
||||
let window = window.as_ref().unwrap();
|
||||
if window.dirty {
|
||||
Some(window_id)
|
||||
Some(window.handle.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<SmallVec<[_; 8]>>();
|
||||
|
||||
for dirty_window_id in dirty_window_ids {
|
||||
self.update_window(dirty_window_id, |cx| cx.draw()).unwrap();
|
||||
for dirty_window_handle in dirty_window_ids {
|
||||
dirty_window_handle.update(self, |_, cx| cx.draw()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,8 +456,8 @@ impl AppContext {
|
||||
for (entity_id, mut entity) in dropped {
|
||||
self.observers.remove(&entity_id);
|
||||
self.event_listeners.remove(&entity_id);
|
||||
for mut release_callback in self.release_listeners.remove(&entity_id) {
|
||||
release_callback(&mut entity, self);
|
||||
for release_callback in self.release_listeners.remove(&entity_id) {
|
||||
release_callback(entity.as_mut(), self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,27 +467,27 @@ impl AppContext {
|
||||
/// For now, we simply blur the window if this happens, but we may want to support invoking
|
||||
/// a window blur handler to restore focus to some logical element.
|
||||
fn release_dropped_focus_handles(&mut self) {
|
||||
let window_ids = self.windows.keys().collect::<SmallVec<[_; 8]>>();
|
||||
for window_id in window_ids {
|
||||
self.update_window(window_id, |cx| {
|
||||
let mut blur_window = false;
|
||||
let focus = cx.window.focus;
|
||||
cx.window.focus_handles.write().retain(|handle_id, count| {
|
||||
if count.load(SeqCst) == 0 {
|
||||
if focus == Some(handle_id) {
|
||||
blur_window = true;
|
||||
for window_handle in self.windows() {
|
||||
window_handle
|
||||
.update(self, |_, cx| {
|
||||
let mut blur_window = false;
|
||||
let focus = cx.window.focus;
|
||||
cx.window.focus_handles.write().retain(|handle_id, count| {
|
||||
if count.load(SeqCst) == 0 {
|
||||
if focus == Some(handle_id) {
|
||||
blur_window = true;
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if blur_window {
|
||||
cx.blur();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
if blur_window {
|
||||
cx.blur();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,30 +504,35 @@ impl AppContext {
|
||||
.retain(&emitter, |handler| handler(event.as_ref(), self));
|
||||
}
|
||||
|
||||
fn apply_focus_changed_effect(&mut self, window_id: WindowId, focused: Option<FocusId>) {
|
||||
self.update_window(window_id, |cx| {
|
||||
if cx.window.focus == focused {
|
||||
let mut listeners = mem::take(&mut cx.window.focus_listeners);
|
||||
let focused =
|
||||
focused.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
|
||||
let blurred = cx
|
||||
.window
|
||||
.last_blur
|
||||
.take()
|
||||
.unwrap()
|
||||
.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
|
||||
if focused.is_some() || blurred.is_some() {
|
||||
let event = FocusEvent { focused, blurred };
|
||||
for listener in &listeners {
|
||||
listener(&event, cx);
|
||||
fn apply_focus_changed_effect(
|
||||
&mut self,
|
||||
window_handle: AnyWindowHandle,
|
||||
focused: Option<FocusId>,
|
||||
) {
|
||||
window_handle
|
||||
.update(self, |_, cx| {
|
||||
if cx.window.focus == focused {
|
||||
let mut listeners = mem::take(&mut cx.window.focus_listeners);
|
||||
let focused = focused
|
||||
.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
|
||||
let blurred = cx
|
||||
.window
|
||||
.last_blur
|
||||
.take()
|
||||
.unwrap()
|
||||
.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
|
||||
if focused.is_some() || blurred.is_some() {
|
||||
let event = FocusEvent { focused, blurred };
|
||||
for listener in &listeners {
|
||||
listener(&event, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listeners.extend(cx.window.focus_listeners.drain(..));
|
||||
cx.window.focus_listeners = listeners;
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
listeners.extend(cx.window.focus_listeners.drain(..));
|
||||
cx.window.focus_listeners = listeners;
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn apply_refresh_effect(&mut self) {
|
||||
@@ -464,7 +550,7 @@ impl AppContext {
|
||||
.retain(&type_id, |observer| observer(self));
|
||||
}
|
||||
|
||||
fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + Send + 'static>) {
|
||||
fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + 'static>) {
|
||||
callback(self);
|
||||
}
|
||||
|
||||
@@ -473,72 +559,34 @@ impl AppContext {
|
||||
pub fn to_async(&self) -> AsyncAppContext {
|
||||
AsyncAppContext {
|
||||
app: unsafe { mem::transmute(self.this.clone()) },
|
||||
executor: self.executor.clone(),
|
||||
background_executor: self.background_executor.clone(),
|
||||
foreground_executor: self.foreground_executor.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtains a reference to the executor, which can be used to spawn futures.
|
||||
pub fn executor(&self) -> &Executor {
|
||||
&self.executor
|
||||
pub fn background_executor(&self) -> &BackgroundExecutor {
|
||||
&self.background_executor
|
||||
}
|
||||
|
||||
/// Runs the given closure on the main thread, where interaction with the platform
|
||||
/// is possible. The given closure will be invoked with a `MainThread<AppContext>`, which
|
||||
/// has platform-specific methods that aren't present on `AppContext`.
|
||||
pub fn run_on_main<R>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
|
||||
) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
if self.executor.is_main_thread() {
|
||||
Task::ready(f(unsafe {
|
||||
mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(self)
|
||||
}))
|
||||
} else {
|
||||
let this = self.this.upgrade().unwrap();
|
||||
self.executor.run_on_main(move || {
|
||||
let cx = &mut *this.lock();
|
||||
cx.update(|cx| f(unsafe { mem::transmute::<&mut Self, &mut MainThread<Self>>(cx) }))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns the future returned by the given function on the main thread, where interaction with
|
||||
/// the platform is possible. The given closure will be invoked with a `MainThread<AsyncAppContext>`,
|
||||
/// which has platform-specific methods that aren't present on `AsyncAppContext`. The future will be
|
||||
/// polled exclusively on the main thread.
|
||||
// todo!("I think we need somehow to prevent the MainThread<AsyncAppContext> from implementing Send")
|
||||
pub fn spawn_on_main<F, R>(
|
||||
&self,
|
||||
f: impl FnOnce(MainThread<AsyncAppContext>) -> F + Send + 'static,
|
||||
) -> Task<R>
|
||||
where
|
||||
F: Future<Output = R> + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let cx = self.to_async();
|
||||
self.executor.spawn_on_main(move || f(MainThread(cx)))
|
||||
/// Obtains a reference to the executor, which can be used to spawn futures.
|
||||
pub fn foreground_executor(&self) -> &ForegroundExecutor {
|
||||
&self.foreground_executor
|
||||
}
|
||||
|
||||
/// Spawns the future returned by the given function on the thread pool. The closure will be invoked
|
||||
/// with AsyncAppContext, which allows the application state to be accessed across await points.
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
let cx = self.to_async();
|
||||
self.executor.spawn(async move {
|
||||
let future = f(cx);
|
||||
future.await
|
||||
})
|
||||
self.foreground_executor.spawn(f(self.to_async()))
|
||||
}
|
||||
|
||||
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
|
||||
/// that are currently on the stack to be returned to the app.
|
||||
pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static + Send) {
|
||||
pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static) {
|
||||
self.push_effect(Effect::Defer {
|
||||
callback: Box::new(f),
|
||||
});
|
||||
@@ -597,7 +645,7 @@ impl AppContext {
|
||||
|
||||
/// Access the global of the given type mutably. A default value is assigned if a global of this type has not
|
||||
/// yet been assigned.
|
||||
pub fn default_global<G: 'static + Default + Send>(&mut self) -> &mut G {
|
||||
pub fn default_global<G: 'static + Default>(&mut self) -> &mut G {
|
||||
let global_type = TypeId::of::<G>();
|
||||
self.push_effect(Effect::NotifyGlobalObservers { global_type });
|
||||
self.globals_by_type
|
||||
@@ -608,7 +656,7 @@ impl AppContext {
|
||||
}
|
||||
|
||||
/// Set the value of the global of the given type.
|
||||
pub fn set_global<G: Any + Send>(&mut self, global: G) {
|
||||
pub fn set_global<G: Any>(&mut self, global: G) {
|
||||
let global_type = TypeId::of::<G>();
|
||||
self.push_effect(Effect::NotifyGlobalObservers { global_type });
|
||||
self.globals_by_type.insert(global_type, Box::new(global));
|
||||
@@ -626,7 +674,7 @@ impl AppContext {
|
||||
/// Register a callback to be invoked when a global of the given type is updated.
|
||||
pub fn observe_global<G: 'static>(
|
||||
&mut self,
|
||||
mut f: impl FnMut(&mut Self) + Send + 'static,
|
||||
mut f: impl FnMut(&mut Self) + 'static,
|
||||
) -> Subscription {
|
||||
self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
@@ -658,6 +706,24 @@ impl AppContext {
|
||||
self.globals_by_type.insert(global_type, lease.global);
|
||||
}
|
||||
|
||||
pub fn observe_release<E, T>(
|
||||
&mut self,
|
||||
handle: &E,
|
||||
on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
E: Entity<T>,
|
||||
T: 'static,
|
||||
{
|
||||
self.release_listeners.insert(
|
||||
handle.entity_id(),
|
||||
Box::new(move |entity, cx| {
|
||||
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||
on_release(entity, cx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
|
||||
self.text_style_stack.push(text_style);
|
||||
}
|
||||
@@ -673,7 +739,7 @@ impl AppContext {
|
||||
}
|
||||
|
||||
/// Register a global listener for actions invoked via the keyboard.
|
||||
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + Send + 'static) {
|
||||
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
|
||||
self.global_action_listeners
|
||||
.entry(TypeId::of::<A>())
|
||||
.or_default()
|
||||
@@ -711,19 +777,18 @@ impl AppContext {
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
type ModelContext<'a, T> = ModelContext<'a, T>;
|
||||
type Result<T> = T;
|
||||
|
||||
/// Build an entity that is owned by the application. The given function will be invoked with
|
||||
/// a `ModelContext` and must return an object representing the entity. A `Model` will be returned
|
||||
/// which can be used to access the entity in a context.
|
||||
fn build_model<T: 'static + Send>(
|
||||
fn build_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Model<T> {
|
||||
self.update(|cx| {
|
||||
let slot = cx.entities.reserve();
|
||||
let entity = build_model(&mut ModelContext::mutable(cx, slot.downgrade()));
|
||||
let entity = build_model(&mut ModelContext::new(cx, slot.downgrade()));
|
||||
cx.entities.insert(slot, entity)
|
||||
})
|
||||
}
|
||||
@@ -733,117 +798,39 @@ impl Context for AppContext {
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
model: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> R {
|
||||
self.update(|cx| {
|
||||
let mut entity = cx.entities.lease(model);
|
||||
let result = update(
|
||||
&mut entity,
|
||||
&mut ModelContext::mutable(cx, model.downgrade()),
|
||||
);
|
||||
let result = update(&mut entity, &mut ModelContext::new(cx, model.downgrade()));
|
||||
cx.entities.end_lease(entity);
|
||||
result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> MainThread<C>
|
||||
where
|
||||
C: Borrow<AppContext>,
|
||||
{
|
||||
pub(crate) fn platform(&self) -> &dyn Platform {
|
||||
self.0.borrow().platform.borrow_on_main_thread()
|
||||
}
|
||||
|
||||
/// Instructs the platform to activate the application by bringing it to the foreground.
|
||||
pub fn activate(&self, ignoring_other_apps: bool) {
|
||||
self.platform().activate(ignoring_other_apps);
|
||||
}
|
||||
|
||||
/// Writes data to the platform clipboard.
|
||||
pub fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
self.platform().write_to_clipboard(item)
|
||||
}
|
||||
|
||||
/// Reads data from the platform clipboard.
|
||||
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
self.platform().read_from_clipboard()
|
||||
}
|
||||
|
||||
/// Writes credentials to the platform keychain.
|
||||
pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
|
||||
self.platform().write_credentials(url, username, password)
|
||||
}
|
||||
|
||||
/// Reads credentials from the platform keychain.
|
||||
pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
|
||||
self.platform().read_credentials(url)
|
||||
}
|
||||
|
||||
/// Deletes credentials from the platform keychain.
|
||||
pub fn delete_credentials(&self, url: &str) -> Result<()> {
|
||||
self.platform().delete_credentials(url)
|
||||
}
|
||||
|
||||
/// Directs the platform's default browser to open the given URL.
|
||||
pub fn open_url(&self, url: &str) {
|
||||
self.platform().open_url(url);
|
||||
}
|
||||
|
||||
pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
|
||||
self.platform().path_for_auxiliary_executable(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl MainThread<AppContext> {
|
||||
fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
|
||||
self.0.update(|cx| {
|
||||
update(unsafe {
|
||||
std::mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update_window<R>(
|
||||
&mut self,
|
||||
id: WindowId,
|
||||
update: impl FnOnce(&mut MainThread<WindowContext>) -> R,
|
||||
) -> Result<R> {
|
||||
self.0.update_window(id, |cx| {
|
||||
update(unsafe {
|
||||
std::mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Opens a new window with the given option and the root view returned by the given function.
|
||||
/// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
|
||||
/// functionality.
|
||||
pub fn open_window<V: Render>(
|
||||
&mut self,
|
||||
options: crate::WindowOptions,
|
||||
build_root_view: impl FnOnce(&mut WindowContext) -> View<V> + Send + 'static,
|
||||
) -> WindowHandle<V> {
|
||||
fn update_window<T, F>(&mut self, handle: AnyWindowHandle, update: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
self.update(|cx| {
|
||||
let id = cx.windows.insert(None);
|
||||
let handle = WindowHandle::new(id);
|
||||
let mut window = Window::new(handle.into(), options, cx);
|
||||
let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
|
||||
window.root_view.replace(root_view.into());
|
||||
cx.windows.get_mut(id).unwrap().replace(window);
|
||||
handle
|
||||
})
|
||||
}
|
||||
let mut window = cx
|
||||
.windows
|
||||
.get_mut(handle.id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.take()
|
||||
.unwrap();
|
||||
|
||||
/// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
|
||||
/// your closure with mutable access to the `MainThread<AppContext>` and the global simultaneously.
|
||||
pub fn update_global<G: 'static + Send, R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(&mut G, &mut MainThread<AppContext>) -> R,
|
||||
) -> R {
|
||||
self.0.update_global(|global, cx| {
|
||||
let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) };
|
||||
update(global, cx)
|
||||
let root_view = window.root_view.clone().unwrap();
|
||||
let result = update(root_view, &mut WindowContext::new(cx, &mut window));
|
||||
|
||||
if !window.removed {
|
||||
cx.windows
|
||||
.get_mut(handle.id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.replace(window);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -855,10 +842,10 @@ pub(crate) enum Effect {
|
||||
},
|
||||
Emit {
|
||||
emitter: EntityId,
|
||||
event: Box<dyn Any + Send + 'static>,
|
||||
event: Box<dyn Any>,
|
||||
},
|
||||
FocusChanged {
|
||||
window_id: WindowId,
|
||||
window_handle: AnyWindowHandle,
|
||||
focused: Option<FocusId>,
|
||||
},
|
||||
Refresh,
|
||||
@@ -866,7 +853,7 @@ pub(crate) enum Effect {
|
||||
global_type: TypeId,
|
||||
},
|
||||
Defer {
|
||||
callback: Box<dyn FnOnce(&mut AppContext) + Send + 'static>,
|
||||
callback: Box<dyn FnOnce(&mut AppContext) + 'static>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -905,15 +892,3 @@ pub(crate) struct AnyDrag {
|
||||
pub view: AnyView,
|
||||
pub cursor_offset: Point<Pixels>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AppContext;
|
||||
|
||||
#[test]
|
||||
fn test_app_context_send_sync() {
|
||||
// This will not compile if `AppContext` does not implement `Send`
|
||||
fn assert_send<T: Send>() {}
|
||||
assert_send::<AppContext>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,57 @@
|
||||
use crate::{
|
||||
AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task,
|
||||
WindowContext,
|
||||
AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, ForegroundExecutor,
|
||||
Model, ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext,
|
||||
WindowHandle,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use parking_lot::Mutex;
|
||||
use std::{future::Future, sync::Weak};
|
||||
use std::{future::Future, rc::Weak};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AsyncAppContext {
|
||||
pub(crate) app: Weak<Mutex<AppContext>>,
|
||||
pub(crate) executor: Executor,
|
||||
pub(crate) app: Weak<AppCell>,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
}
|
||||
|
||||
impl Context for AsyncAppContext {
|
||||
type ModelContext<'a, T> = ModelContext<'a, T>;
|
||||
type Result<T> = Result<T>;
|
||||
|
||||
fn build_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>
|
||||
where
|
||||
T: 'static + Send,
|
||||
T: 'static,
|
||||
{
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.lock(); // Need this to compile
|
||||
Ok(lock.build_model(build_model))
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.build_model(build_model))
|
||||
}
|
||||
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.lock(); // Need this to compile
|
||||
Ok(lock.update_model(handle, update))
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.update_model(handle, update))
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.update_window(window, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,13 +61,17 @@ impl AsyncAppContext {
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.lock(); // Need this to compile
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.refresh();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn executor(&self) -> &Executor {
|
||||
&self.executor
|
||||
pub fn background_executor(&self) -> &BackgroundExecutor {
|
||||
&self.background_executor
|
||||
}
|
||||
|
||||
pub fn foreground_executor(&self) -> &ForegroundExecutor {
|
||||
&self.foreground_executor
|
||||
}
|
||||
|
||||
pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
|
||||
@@ -66,70 +79,32 @@ impl AsyncAppContext {
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.lock();
|
||||
let mut lock = app.borrow_mut();
|
||||
Ok(f(&mut *lock))
|
||||
}
|
||||
|
||||
pub fn read_window<R>(
|
||||
pub fn open_window<V>(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
update: impl FnOnce(&WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut app_context = app.lock();
|
||||
app_context.read_window(handle.id, update)
|
||||
}
|
||||
|
||||
pub fn update_window<R>(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
update: impl FnOnce(&mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut app_context = app.lock();
|
||||
app_context.update_window(handle.id, update)
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
|
||||
options: crate::WindowOptions,
|
||||
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
|
||||
) -> Result<WindowHandle<V>>
|
||||
where
|
||||
Fut: Future<Output = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
V: Render,
|
||||
{
|
||||
let this = self.clone();
|
||||
self.executor.spawn(async move { f(this).await })
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.borrow_mut();
|
||||
Ok(lock.open_window(options, build_root_view))
|
||||
}
|
||||
|
||||
pub fn spawn_on_main<Fut, R>(
|
||||
&self,
|
||||
f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static,
|
||||
) -> Task<R>
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: Send + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
let this = self.clone();
|
||||
self.executor.spawn_on_main(|| f(this))
|
||||
}
|
||||
|
||||
pub fn run_on_main<R>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
|
||||
) -> Result<Task<R>>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
let app = self
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut app_context = app.lock();
|
||||
Ok(app_context.run_on_main(f))
|
||||
self.foreground_executor.spawn(f(self.clone()))
|
||||
}
|
||||
|
||||
pub fn has_global<G: 'static>(&self) -> Result<bool> {
|
||||
@@ -137,8 +112,8 @@ impl AsyncAppContext {
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let lock = app.lock(); // Need this to compile
|
||||
Ok(lock.has_global::<G>())
|
||||
let app = app.borrow_mut();
|
||||
Ok(app.has_global::<G>())
|
||||
}
|
||||
|
||||
pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result<R> {
|
||||
@@ -146,8 +121,8 @@ impl AsyncAppContext {
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let lock = app.lock(); // Need this to compile
|
||||
Ok(read(lock.global(), &lock))
|
||||
let app = app.borrow_mut();
|
||||
Ok(read(app.global(), &app))
|
||||
}
|
||||
|
||||
pub fn try_read_global<G: 'static, R>(
|
||||
@@ -155,8 +130,8 @@ impl AsyncAppContext {
|
||||
read: impl FnOnce(&G, &AppContext) -> R,
|
||||
) -> Option<R> {
|
||||
let app = self.app.upgrade()?;
|
||||
let lock = app.lock(); // Need this to compile
|
||||
Some(read(lock.try_global()?, &lock))
|
||||
let app = app.borrow_mut();
|
||||
Some(read(app.try_global()?, &app))
|
||||
}
|
||||
|
||||
pub fn update_global<G: 'static, R>(
|
||||
@@ -167,8 +142,8 @@ impl AsyncAppContext {
|
||||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.lock(); // Need this to compile
|
||||
Ok(lock.update_global(update))
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.update_global(update))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,22 +160,22 @@ impl AsyncWindowContext {
|
||||
Self { app, window }
|
||||
}
|
||||
|
||||
pub fn update<R>(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result<R> {
|
||||
pub fn update<R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(AnyView, &mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
self.app.update_window(self.window, update)
|
||||
}
|
||||
|
||||
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) {
|
||||
self.app
|
||||
.update_window(self.window, |cx| cx.on_next_frame(f))
|
||||
.ok();
|
||||
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
|
||||
self.window.update(self, |_, cx| cx.on_next_frame(f)).ok();
|
||||
}
|
||||
|
||||
pub fn read_global<G: 'static, R>(
|
||||
&self,
|
||||
&mut self,
|
||||
read: impl FnOnce(&G, &WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
self.app
|
||||
.read_window(self.window, |cx| read(cx.global(), cx))
|
||||
self.window.update(self, |_, cx| read(cx.global(), cx))
|
||||
}
|
||||
|
||||
pub fn update_global<G, R>(
|
||||
@@ -210,43 +185,78 @@ impl AsyncWindowContext {
|
||||
where
|
||||
G: 'static,
|
||||
{
|
||||
self.app
|
||||
.update_window(self.window, |cx| cx.update_global(update))
|
||||
self.window.update(self, |_, cx| cx.update_global(update))
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
self.foreground_executor.spawn(f(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AsyncWindowContext {
|
||||
type ModelContext<'a, T> = ModelContext<'a, T>;
|
||||
type Result<T> = Result<T>;
|
||||
|
||||
fn build_model<T>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Result<Model<T>>
|
||||
where
|
||||
T: 'static + Send,
|
||||
T: 'static,
|
||||
{
|
||||
self.app
|
||||
.update_window(self.window, |cx| cx.build_model(build_model))
|
||||
self.window
|
||||
.update(self, |_, cx| cx.build_model(build_model))
|
||||
}
|
||||
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Result<R> {
|
||||
self.app
|
||||
.update_window(self.window, |cx| cx.update_model(handle, update))
|
||||
self.window
|
||||
.update(self, |_, cx| cx.update_model(handle, update))
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
self.app.update_window(window, update)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
impl VisualContext for AsyncWindowContext {
|
||||
fn build_view<V>(
|
||||
&mut self,
|
||||
build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: 'static,
|
||||
{
|
||||
self.window
|
||||
.update(self, |_, cx| cx.build_view(build_view_state))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_app_context_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
assert_send_sync::<AsyncAppContext>();
|
||||
fn update_view<V: 'static, R>(
|
||||
&mut self,
|
||||
view: &View<V>,
|
||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
self.window
|
||||
.update(self, |_, cx| cx.update_view(view, update))
|
||||
}
|
||||
|
||||
fn replace_root_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: Render,
|
||||
{
|
||||
self.window
|
||||
.update(self, |_, cx| cx.replace_root_view(build_view))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{private::Sealed, AnyBox, AppContext, Context, Entity};
|
||||
use crate::{private::Sealed, AnyBox, AppContext, Context, Entity, ModelContext};
|
||||
use anyhow::{anyhow, Result};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
@@ -59,7 +59,7 @@ impl EntityMap {
|
||||
/// Insert an entity into a slot obtained by calling `reserve`.
|
||||
pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Model<T>
|
||||
where
|
||||
T: 'static + Send,
|
||||
T: 'static,
|
||||
{
|
||||
let model = slot.0;
|
||||
self.entities.insert(model.entity_id, Box::new(entity));
|
||||
@@ -106,7 +106,12 @@ impl EntityMap {
|
||||
dropped_entity_ids
|
||||
.into_iter()
|
||||
.map(|entity_id| {
|
||||
ref_counts.counts.remove(entity_id);
|
||||
let count = ref_counts.counts.remove(entity_id).unwrap();
|
||||
debug_assert_eq!(
|
||||
count.load(SeqCst),
|
||||
0,
|
||||
"dropped an entity that was referenced"
|
||||
);
|
||||
(entity_id, self.entities.remove(entity_id).unwrap())
|
||||
})
|
||||
.collect()
|
||||
@@ -164,6 +169,10 @@ impl AnyModel {
|
||||
self.entity_id
|
||||
}
|
||||
|
||||
pub fn entity_type(&self) -> TypeId {
|
||||
self.entity_type
|
||||
}
|
||||
|
||||
pub fn downgrade(&self) -> AnyWeakModel {
|
||||
AnyWeakModel {
|
||||
entity_id: self.entity_id,
|
||||
@@ -211,7 +220,7 @@ impl Drop for AnyModel {
|
||||
let count = entity_map
|
||||
.counts
|
||||
.get(self.entity_id)
|
||||
.expect("Detected over-release of a model.");
|
||||
.expect("detected over-release of a handle.");
|
||||
let prev_count = count.fetch_sub(1, SeqCst);
|
||||
assert_ne!(prev_count, 0, "Detected over-release of a model.");
|
||||
if prev_count == 1 {
|
||||
@@ -324,7 +333,7 @@ impl<T: 'static> Model<T> {
|
||||
pub fn update<C, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> C::Result<R>
|
||||
where
|
||||
C: Context,
|
||||
@@ -395,12 +404,16 @@ impl AnyWeakModel {
|
||||
}
|
||||
|
||||
pub fn upgrade(&self) -> Option<AnyModel> {
|
||||
let entity_map = self.entity_ref_counts.upgrade()?;
|
||||
entity_map
|
||||
.read()
|
||||
.counts
|
||||
.get(self.entity_id)?
|
||||
.fetch_add(1, SeqCst);
|
||||
let ref_counts = &self.entity_ref_counts.upgrade()?;
|
||||
let ref_counts = ref_counts.read();
|
||||
let ref_count = ref_counts.counts.get(self.entity_id)?;
|
||||
|
||||
// entity_id is in dropped_entity_ids
|
||||
if ref_count.load(SeqCst) == 0 {
|
||||
return None;
|
||||
}
|
||||
ref_count.fetch_add(1, SeqCst);
|
||||
|
||||
Some(AnyModel {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
@@ -466,7 +479,7 @@ impl<T: 'static> WeakModel<T> {
|
||||
pub fn update<C, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
C: Context,
|
||||
@@ -499,3 +512,60 @@ impl<T> PartialEq<Model<T>> for WeakModel<T> {
|
||||
self.entity_id() == other.any_model.entity_id()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::EntityMap;
|
||||
|
||||
struct TestEntity {
|
||||
pub i: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entity_map_slot_assignment_before_cleanup() {
|
||||
// Tests that slots are not re-used before take_dropped.
|
||||
let mut entity_map = EntityMap::new();
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
entity_map.insert(slot, TestEntity { i: 1 });
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
entity_map.insert(slot, TestEntity { i: 2 });
|
||||
|
||||
let dropped = entity_map.take_dropped();
|
||||
assert_eq!(dropped.len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
dropped
|
||||
.into_iter()
|
||||
.map(|(_, entity)| entity.downcast::<TestEntity>().unwrap().i)
|
||||
.collect::<Vec<i32>>(),
|
||||
vec![1, 2],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entity_map_weak_upgrade_before_cleanup() {
|
||||
// Tests that weak handles are not upgraded before take_dropped
|
||||
let mut entity_map = EntityMap::new();
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
let handle = entity_map.insert(slot, TestEntity { i: 1 });
|
||||
let weak = handle.downgrade();
|
||||
drop(handle);
|
||||
|
||||
let strong = weak.upgrade();
|
||||
assert_eq!(strong, None);
|
||||
|
||||
let dropped = entity_map.take_dropped();
|
||||
assert_eq!(dropped.len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
dropped
|
||||
.into_iter()
|
||||
.map(|(_, entity)| entity.downcast::<TestEntity>().unwrap().i)
|
||||
.collect::<Vec<i32>>(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, MainThread,
|
||||
Model, Reference, Subscription, Task, WeakModel,
|
||||
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId,
|
||||
EventEmitter, Model, Subscription, Task, WeakModel, WindowContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures::FutureExt;
|
||||
use std::{
|
||||
@@ -14,16 +15,13 @@ use std::{
|
||||
pub struct ModelContext<'a, T> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
app: Reference<'a, AppContext>,
|
||||
app: &'a mut AppContext,
|
||||
model_state: WeakModel<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
pub(crate) fn mutable(app: &'a mut AppContext, model_state: WeakModel<T>) -> Self {
|
||||
Self {
|
||||
app: Reference::Mutable(app),
|
||||
model_state,
|
||||
}
|
||||
pub(crate) fn new(app: &'a mut AppContext, model_state: WeakModel<T>) -> Self {
|
||||
Self { app, model_state }
|
||||
}
|
||||
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
@@ -40,15 +38,15 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
self.model_state.clone()
|
||||
}
|
||||
|
||||
pub fn observe<T2, E>(
|
||||
pub fn observe<W, E>(
|
||||
&mut self,
|
||||
entity: &E,
|
||||
mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static + Send,
|
||||
T2: 'static,
|
||||
E: Entity<T2>,
|
||||
T: 'static,
|
||||
W: 'static,
|
||||
E: Entity<W>,
|
||||
{
|
||||
let this = self.weak_model();
|
||||
let entity_id = entity.entity_id();
|
||||
@@ -69,10 +67,10 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
pub fn subscribe<T2, E>(
|
||||
&mut self,
|
||||
entity: &E,
|
||||
mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static + Send,
|
||||
T: 'static,
|
||||
T2: 'static + EventEmitter,
|
||||
E: Entity<T2>,
|
||||
{
|
||||
@@ -95,7 +93,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
|
||||
pub fn on_release(
|
||||
&mut self,
|
||||
mut on_release: impl FnMut(&mut T, &mut AppContext) + Send + 'static,
|
||||
on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
@@ -112,10 +110,10 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
pub fn observe_release<T2, E>(
|
||||
&mut self,
|
||||
entity: &E,
|
||||
mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
on_release: impl FnOnce(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: Any + Send,
|
||||
T: Any,
|
||||
T2: 'static,
|
||||
E: Entity<T2>,
|
||||
{
|
||||
@@ -134,10 +132,10 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
|
||||
pub fn observe_global<G: 'static>(
|
||||
&mut self,
|
||||
mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static + Send,
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_model();
|
||||
self.global_observers.insert(
|
||||
@@ -148,11 +146,11 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
|
||||
pub fn on_app_quit<Fut>(
|
||||
&mut self,
|
||||
mut on_quit: impl FnMut(&mut T, &mut ModelContext<T>) -> Fut + Send + 'static,
|
||||
mut on_quit: impl FnMut(&mut T, &mut ModelContext<T>) -> Fut + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
Fut: 'static + Future<Output = ()> + Send,
|
||||
T: 'static + Send,
|
||||
Fut: 'static + Future<Output = ()>,
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_model();
|
||||
self.app.quit_observers.insert(
|
||||
@@ -164,7 +162,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
future.await;
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
.boxed_local()
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -183,7 +181,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
|
||||
pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
|
||||
where
|
||||
G: 'static + Send,
|
||||
G: 'static,
|
||||
{
|
||||
let mut global = self.app.lease_global::<G>();
|
||||
let result = f(&mut global, self);
|
||||
@@ -191,36 +189,20 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(
|
||||
&self,
|
||||
f: impl FnOnce(WeakModel<T>, AsyncAppContext) -> Fut + Send + 'static,
|
||||
) -> Task<R>
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(WeakModel<T>, AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
T: 'static,
|
||||
Fut: Future<Output = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
let this = self.weak_model();
|
||||
self.app.spawn(|cx| f(this, cx))
|
||||
}
|
||||
|
||||
pub fn spawn_on_main<Fut, R>(
|
||||
&self,
|
||||
f: impl FnOnce(WeakModel<T>, MainThread<AsyncAppContext>) -> Fut + Send + 'static,
|
||||
) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let this = self.weak_model();
|
||||
self.app.spawn_on_main(|cx| f(this, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ModelContext<'a, T>
|
||||
where
|
||||
T: EventEmitter,
|
||||
T::Event: Send,
|
||||
{
|
||||
pub fn emit(&mut self, event: T::Event) {
|
||||
self.app.pending_effects.push_back(Effect::Emit {
|
||||
@@ -231,26 +213,29 @@ where
|
||||
}
|
||||
|
||||
impl<'a, T> Context for ModelContext<'a, T> {
|
||||
type ModelContext<'b, U> = ModelContext<'b, U>;
|
||||
type Result<U> = U;
|
||||
|
||||
fn build_model<U>(
|
||||
fn build_model<U: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U,
|
||||
) -> Model<U>
|
||||
where
|
||||
U: 'static + Send,
|
||||
{
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, U>) -> U,
|
||||
) -> Model<U> {
|
||||
self.app.build_model(build_model)
|
||||
}
|
||||
|
||||
fn update_model<U: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<U>,
|
||||
update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> R,
|
||||
update: impl FnOnce(&mut U, &mut ModelContext<'_, U>) -> R,
|
||||
) -> R {
|
||||
self.app.update_model(handle, update)
|
||||
}
|
||||
|
||||
fn update_window<R, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> R,
|
||||
{
|
||||
self.app.update_window(window, update)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Borrow<AppContext> for ModelContext<'_, T> {
|
||||
|
||||
@@ -1,137 +1,115 @@
|
||||
use crate::{
|
||||
AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, MainThread, Model,
|
||||
ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext,
|
||||
AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
|
||||
EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher,
|
||||
TestPlatform, WindowContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{future::Future, sync::Arc};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestAppContext {
|
||||
pub app: Arc<Mutex<AppContext>>,
|
||||
pub executor: Executor,
|
||||
pub app: Rc<AppCell>,
|
||||
pub background_executor: BackgroundExecutor,
|
||||
pub foreground_executor: ForegroundExecutor,
|
||||
}
|
||||
|
||||
impl Context for TestAppContext {
|
||||
type ModelContext<'a, T> = ModelContext<'a, T>;
|
||||
type Result<T> = T;
|
||||
|
||||
fn build_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>
|
||||
where
|
||||
T: 'static + Send,
|
||||
T: 'static,
|
||||
{
|
||||
let mut lock = self.app.lock();
|
||||
lock.build_model(build_model)
|
||||
let mut app = self.app.borrow_mut();
|
||||
app.build_model(build_model)
|
||||
}
|
||||
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
let mut lock = self.app.lock();
|
||||
lock.update_model(handle, update)
|
||||
let mut app = self.app.borrow_mut();
|
||||
app.update_model(handle, update)
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
let mut lock = self.app.borrow_mut();
|
||||
lock.update_window(window, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestAppContext {
|
||||
pub fn new(dispatcher: TestDispatcher) -> Self {
|
||||
let executor = Executor::new(Arc::new(dispatcher));
|
||||
let platform = Arc::new(TestPlatform::new(executor.clone()));
|
||||
let dispatcher = Arc::new(dispatcher);
|
||||
let background_executor = BackgroundExecutor::new(dispatcher.clone());
|
||||
let foreground_executor = ForegroundExecutor::new(dispatcher);
|
||||
let platform = Rc::new(TestPlatform::new(
|
||||
background_executor.clone(),
|
||||
foreground_executor.clone(),
|
||||
));
|
||||
let asset_source = Arc::new(());
|
||||
let http_client = util::http::FakeHttpClient::with_404_response();
|
||||
Self {
|
||||
app: AppContext::new(platform, asset_source, http_client),
|
||||
executor,
|
||||
background_executor,
|
||||
foreground_executor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quit(&self) {
|
||||
self.app.lock().quit();
|
||||
self.app.borrow_mut().quit();
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) -> Result<()> {
|
||||
let mut lock = self.app.lock();
|
||||
lock.refresh();
|
||||
let mut app = self.app.borrow_mut();
|
||||
app.refresh();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn executor(&self) -> &Executor {
|
||||
&self.executor
|
||||
pub fn executor(&self) -> &BackgroundExecutor {
|
||||
&self.background_executor
|
||||
}
|
||||
|
||||
pub fn foreground_executor(&self) -> &ForegroundExecutor {
|
||||
&self.foreground_executor
|
||||
}
|
||||
|
||||
pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
|
||||
let mut lock = self.app.lock();
|
||||
f(&mut *lock)
|
||||
let mut cx = self.app.borrow_mut();
|
||||
cx.update(f)
|
||||
}
|
||||
|
||||
pub fn read_window<R>(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
read: impl FnOnce(&WindowContext) -> R,
|
||||
) -> R {
|
||||
let mut app_context = self.app.lock();
|
||||
app_context.read_window(handle.id, read).unwrap()
|
||||
}
|
||||
|
||||
pub fn update_window<R>(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
update: impl FnOnce(&mut WindowContext) -> R,
|
||||
) -> R {
|
||||
let mut app = self.app.lock();
|
||||
app.update_window(handle.id, update).unwrap()
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let cx = self.to_async();
|
||||
self.executor.spawn(async move { f(cx).await })
|
||||
}
|
||||
|
||||
pub fn spawn_on_main<Fut, R>(
|
||||
&self,
|
||||
f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static,
|
||||
) -> Task<R>
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: Send + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
let cx = self.to_async();
|
||||
self.executor.spawn_on_main(|| f(cx))
|
||||
}
|
||||
|
||||
pub fn run_on_main<R>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
|
||||
) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
let mut app_context = self.app.lock();
|
||||
app_context.run_on_main(f)
|
||||
self.foreground_executor.spawn(f(self.to_async()))
|
||||
}
|
||||
|
||||
pub fn has_global<G: 'static>(&self) -> bool {
|
||||
let lock = self.app.lock();
|
||||
lock.has_global::<G>()
|
||||
let app = self.app.borrow();
|
||||
app.has_global::<G>()
|
||||
}
|
||||
|
||||
pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
|
||||
let lock = self.app.lock();
|
||||
read(lock.global(), &lock)
|
||||
let app = self.app.borrow();
|
||||
read(app.global(), &app)
|
||||
}
|
||||
|
||||
pub fn try_read_global<G: 'static, R>(
|
||||
&self,
|
||||
read: impl FnOnce(&G, &AppContext) -> R,
|
||||
) -> Option<R> {
|
||||
let lock = self.app.lock();
|
||||
let lock = self.app.borrow();
|
||||
Some(read(lock.try_global()?, &lock))
|
||||
}
|
||||
|
||||
@@ -139,14 +117,75 @@ impl TestAppContext {
|
||||
&mut self,
|
||||
update: impl FnOnce(&mut G, &mut AppContext) -> R,
|
||||
) -> R {
|
||||
let mut lock = self.app.lock();
|
||||
let mut lock = self.app.borrow_mut();
|
||||
lock.update_global(update)
|
||||
}
|
||||
|
||||
pub fn to_async(&self) -> AsyncAppContext {
|
||||
AsyncAppContext {
|
||||
app: Arc::downgrade(&self.app),
|
||||
executor: self.executor.clone(),
|
||||
app: Rc::downgrade(&self.app),
|
||||
background_executor: self.background_executor.clone(),
|
||||
foreground_executor: self.foreground_executor.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
entity.update(self, move |_, cx: &mut ModelContext<T>| {
|
||||
cx.observe(entity, {
|
||||
let tx = tx.clone();
|
||||
move |_, _, _| {
|
||||
let _ = tx.unbounded_send(());
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.on_release(move |_, _| tx.close_channel()).detach();
|
||||
});
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
pub fn events<T: 'static + EventEmitter>(
|
||||
&mut self,
|
||||
entity: &Model<T>,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<T::Event>
|
||||
where
|
||||
T::Event: 'static + Clone,
|
||||
{
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
entity
|
||||
.update(self, |_, cx: &mut ModelContext<T>| {
|
||||
cx.subscribe(entity, move |_model, _handle, event, _cx| {
|
||||
let _ = tx.unbounded_send(event.clone());
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
rx
|
||||
}
|
||||
|
||||
pub async fn condition<T: 'static>(
|
||||
&mut self,
|
||||
model: &Model<T>,
|
||||
mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
|
||||
) {
|
||||
let timer = self.executor().timer(Duration::from_secs(3));
|
||||
let mut notifications = self.notifications(model);
|
||||
|
||||
use futures::FutureExt as _;
|
||||
use smol::future::FutureExt as _;
|
||||
|
||||
async {
|
||||
while notifications.next().await.is_some() {
|
||||
if model.update(self, &mut predicate) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
bail!("model dropped")
|
||||
}
|
||||
.race(timer.map(|_| Err(anyhow!("condition timed out"))))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ pub(crate) use smallvec::SmallVec;
|
||||
use std::{any::Any, mem};
|
||||
|
||||
pub trait Element<V: 'static> {
|
||||
type ElementState: 'static + Send;
|
||||
type ElementState: 'static;
|
||||
|
||||
fn id(&self) -> Option<ElementId>;
|
||||
|
||||
@@ -97,7 +97,7 @@ impl<V, E: Element<V>> RenderedElement<V, E> {
|
||||
impl<V, E> ElementObject<V> for RenderedElement<V, E>
|
||||
where
|
||||
E: Element<V>,
|
||||
E::ElementState: 'static + Send,
|
||||
E::ElementState: 'static,
|
||||
{
|
||||
fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
|
||||
let frame_state = if let Some(id) = self.element.id() {
|
||||
@@ -170,16 +170,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnyElement<V>(Box<dyn ElementObject<V> + Send>);
|
||||
|
||||
unsafe impl<V> Send for AnyElement<V> {}
|
||||
pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
|
||||
|
||||
impl<V> AnyElement<V> {
|
||||
pub fn new<E>(element: E) -> Self
|
||||
where
|
||||
V: 'static,
|
||||
E: 'static + Element<V> + Send,
|
||||
E::ElementState: Any + Send,
|
||||
E: 'static + Element<V>,
|
||||
E::ElementState: Any,
|
||||
{
|
||||
AnyElement(Box::new(RenderedElement::new(element)))
|
||||
}
|
||||
@@ -200,14 +198,19 @@ impl<V> AnyElement<V> {
|
||||
pub trait Component<V> {
|
||||
fn render(self) -> AnyElement<V>;
|
||||
|
||||
fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
|
||||
fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
|
||||
where
|
||||
Self: Sized,
|
||||
U: Component<V>,
|
||||
{
|
||||
f(self)
|
||||
}
|
||||
|
||||
fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if condition {
|
||||
self = then(self);
|
||||
}
|
||||
self
|
||||
self.map(|this| if condition { then(this) } else { this })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,8 +223,8 @@ impl<V> Component<V> for AnyElement<V> {
|
||||
impl<V, E, F> Element<V> for Option<F>
|
||||
where
|
||||
V: 'static,
|
||||
E: 'static + Component<V> + Send,
|
||||
F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
|
||||
E: 'static + Component<V>,
|
||||
F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
|
||||
{
|
||||
type ElementState = AnyElement<V>;
|
||||
|
||||
@@ -264,8 +267,8 @@ where
|
||||
impl<V, E, F> Component<V> for Option<F>
|
||||
where
|
||||
V: 'static,
|
||||
E: 'static + Component<V> + Send,
|
||||
F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
|
||||
E: 'static + Component<V>,
|
||||
F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
|
||||
{
|
||||
fn render(self) -> AnyElement<V> {
|
||||
AnyElement::new(self)
|
||||
@@ -275,8 +278,8 @@ where
|
||||
impl<V, E, F> Component<V> for F
|
||||
where
|
||||
V: 'static,
|
||||
E: 'static + Component<V> + Send,
|
||||
F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
|
||||
E: 'static + Component<V>,
|
||||
F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
|
||||
{
|
||||
fn render(self) -> AnyElement<V> {
|
||||
AnyElement::new(Some(self))
|
||||
|
||||
@@ -305,7 +305,6 @@ where
|
||||
|
||||
impl<V, I, F> Component<V> for Div<V, I, F>
|
||||
where
|
||||
// V: Any + Send + Sync,
|
||||
I: ElementInteraction<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
|
||||
@@ -109,7 +109,9 @@ where
|
||||
let corner_radii = style.corner_radii;
|
||||
|
||||
if let Some(uri) = self.uri.clone() {
|
||||
let image_future = cx.image_cache.get(uri);
|
||||
// eprintln!(">>> image_cache.get({uri}");
|
||||
let image_future = cx.image_cache.get(uri.clone());
|
||||
// eprintln!("<<< image_cache.get({uri}");
|
||||
if let Some(data) = image_future
|
||||
.clone()
|
||||
.now_or_never()
|
||||
|
||||
@@ -44,9 +44,6 @@ pub struct Text<V> {
|
||||
state_type: PhantomData<V>,
|
||||
}
|
||||
|
||||
unsafe impl<V> Send for Text<V> {}
|
||||
unsafe impl<V> Sync for Text<V> {}
|
||||
|
||||
impl<V: 'static> Component<V> for Text<V> {
|
||||
fn render(self) -> AnyElement<V> {
|
||||
AnyElement::new(self)
|
||||
|
||||
@@ -6,7 +6,11 @@ use std::{
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -14,10 +18,16 @@ use util::TryFutureExt;
|
||||
use waker_fn::waker_fn;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Executor {
|
||||
pub struct BackgroundExecutor {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ForegroundExecutor {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
not_send: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub enum Task<T> {
|
||||
Ready(Option<T>),
|
||||
@@ -43,7 +53,7 @@ where
|
||||
E: 'static + Send + Debug,
|
||||
{
|
||||
pub fn detach_and_log_err(self, cx: &mut AppContext) {
|
||||
cx.executor().spawn(self.log_err()).detach();
|
||||
cx.background_executor().spawn(self.log_err()).detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +68,7 @@ impl<T> Future for Task<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
impl BackgroundExecutor {
|
||||
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
|
||||
Self { dispatcher }
|
||||
}
|
||||
@@ -76,68 +86,30 @@ impl Executor {
|
||||
Task::Spawned(task)
|
||||
}
|
||||
|
||||
/// Enqueues the given closure to run on the application's event loop.
|
||||
/// Returns the result asynchronously.
|
||||
pub fn run_on_main<F, R>(&self, func: F) -> Task<R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
if self.dispatcher.is_main_thread() {
|
||||
Task::ready(func())
|
||||
} else {
|
||||
self.spawn_on_main(move || async move { func() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueues the given closure to be run on the application's event loop. The
|
||||
/// closure returns a future which will be run to completion on the main thread.
|
||||
pub fn spawn_on_main<F, R>(&self, func: impl FnOnce() -> F + Send + 'static) -> Task<R>
|
||||
where
|
||||
F: Future<Output = R> + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let (runnable, task) = async_task::spawn(
|
||||
{
|
||||
let this = self.clone();
|
||||
async move {
|
||||
let task = this.spawn_on_main_local(func());
|
||||
task.await
|
||||
}
|
||||
},
|
||||
{
|
||||
let dispatcher = self.dispatcher.clone();
|
||||
move |runnable| dispatcher.dispatch_on_main_thread(runnable)
|
||||
},
|
||||
);
|
||||
runnable.schedule();
|
||||
Task::Spawned(task)
|
||||
}
|
||||
|
||||
/// Enqueues the given closure to be run on the application's event loop. Must
|
||||
/// be called on the main thread.
|
||||
pub fn spawn_on_main_local<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
|
||||
where
|
||||
R: 'static,
|
||||
{
|
||||
assert!(
|
||||
self.dispatcher.is_main_thread(),
|
||||
"must be called on main thread"
|
||||
);
|
||||
|
||||
let dispatcher = self.dispatcher.clone();
|
||||
let (runnable, task) = async_task::spawn_local(future, move |runnable| {
|
||||
dispatcher.dispatch_on_main_thread(runnable)
|
||||
});
|
||||
runnable.schedule();
|
||||
Task::Spawned(task)
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
|
||||
self.block_internal(false, future)
|
||||
}
|
||||
|
||||
pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
|
||||
self.block_internal(true, future)
|
||||
}
|
||||
|
||||
pub(crate) fn block_internal<R>(
|
||||
&self,
|
||||
background_only: bool,
|
||||
future: impl Future<Output = R>,
|
||||
) -> R {
|
||||
pin_mut!(future);
|
||||
let (parker, unparker) = parking::pair();
|
||||
let waker = waker_fn(move || {
|
||||
unparker.unpark();
|
||||
let unparker = self.dispatcher.unparker();
|
||||
let awoken = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let waker = waker_fn({
|
||||
let awoken = awoken.clone();
|
||||
move || {
|
||||
awoken.store(true, SeqCst);
|
||||
unparker.unpark();
|
||||
}
|
||||
});
|
||||
let mut cx = std::task::Context::from_waker(&waker);
|
||||
|
||||
@@ -145,12 +117,24 @@ impl Executor {
|
||||
match future.as_mut().poll(&mut cx) {
|
||||
Poll::Ready(result) => return result,
|
||||
Poll::Pending => {
|
||||
if !self.dispatcher.poll() {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if let Some(_) = self.dispatcher.as_test() {
|
||||
panic!("blocked with nothing left to run")
|
||||
if !self.dispatcher.poll(background_only) {
|
||||
if awoken.swap(false, SeqCst) {
|
||||
continue;
|
||||
}
|
||||
parker.park();
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if let Some(test) = self.dispatcher.as_test() {
|
||||
if !test.parking_allowed() {
|
||||
let mut backtrace_message = String::new();
|
||||
if let Some(backtrace) = test.waiting_backtrace() {
|
||||
backtrace_message =
|
||||
format!("\nbacktrace of waiting future:\n{:?}", backtrace);
|
||||
}
|
||||
panic!("parked with nothing left to run\n{:?}", backtrace_message)
|
||||
}
|
||||
}
|
||||
|
||||
self.dispatcher.park();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,17 +190,17 @@ impl Executor {
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn start_waiting(&self) {
|
||||
todo!("start_waiting")
|
||||
self.dispatcher.as_test().unwrap().start_waiting();
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn finish_waiting(&self) {
|
||||
todo!("finish_waiting")
|
||||
self.dispatcher.as_test().unwrap().finish_waiting();
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
|
||||
self.spawn(self.dispatcher.as_test().unwrap().simulate_random_delay())
|
||||
self.dispatcher.as_test().unwrap().simulate_random_delay()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -229,6 +213,11 @@ impl Executor {
|
||||
self.dispatcher.as_test().unwrap().run_until_parked()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn allow_parking(&self) {
|
||||
self.dispatcher.as_test().unwrap().allow_parking();
|
||||
}
|
||||
|
||||
pub fn num_cpus(&self) -> usize {
|
||||
num_cpus::get()
|
||||
}
|
||||
@@ -238,8 +227,31 @@ impl Executor {
|
||||
}
|
||||
}
|
||||
|
||||
impl ForegroundExecutor {
|
||||
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
|
||||
Self {
|
||||
dispatcher,
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueues the given closure to be run on any thread. The closure returns
|
||||
/// a future which will be run to completion on any available thread.
|
||||
pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
|
||||
where
|
||||
R: 'static,
|
||||
{
|
||||
let dispatcher = self.dispatcher.clone();
|
||||
let (runnable, task) = async_task::spawn_local(future, move |runnable| {
|
||||
dispatcher.dispatch_on_main_thread(runnable)
|
||||
});
|
||||
runnable.schedule();
|
||||
Task::Spawned(task)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Scope<'a> {
|
||||
executor: Executor,
|
||||
executor: BackgroundExecutor,
|
||||
futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
|
||||
tx: Option<mpsc::Sender<()>>,
|
||||
rx: mpsc::Receiver<()>,
|
||||
@@ -247,7 +259,7 @@ pub struct Scope<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
fn new(executor: Executor) -> Self {
|
||||
fn new(executor: BackgroundExecutor) -> Self {
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
Self {
|
||||
executor,
|
||||
|
||||
@@ -8,7 +8,7 @@ use smallvec::SmallVec;
|
||||
pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
|
||||
|
||||
pub type FocusListener<V> =
|
||||
Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + Send + 'static>;
|
||||
Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
|
||||
|
||||
pub trait Focusable<V: 'static>: Element<V> {
|
||||
fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
|
||||
@@ -42,7 +42,7 @@ pub trait Focusable<V: 'static>: Element<V> {
|
||||
|
||||
fn on_focus(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -58,7 +58,7 @@ pub trait Focusable<V: 'static>: Element<V> {
|
||||
|
||||
fn on_blur(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -74,7 +74,7 @@ pub trait Focusable<V: 'static>: Element<V> {
|
||||
|
||||
fn on_focus_in(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -99,7 +99,7 @@ pub trait Focusable<V: 'static>: Element<V> {
|
||||
|
||||
fn on_focus_out(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -122,7 +122,7 @@ pub trait Focusable<V: 'static>: Element<V> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ElementFocus<V: 'static>: 'static + Send {
|
||||
pub trait ElementFocus<V: 'static>: 'static {
|
||||
fn as_focusable(&self) -> Option<&FocusEnabled<V>>;
|
||||
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>>;
|
||||
|
||||
|
||||
@@ -931,6 +931,18 @@ impl From<f64> for GlobalPixels {
|
||||
}
|
||||
}
|
||||
|
||||
impl sqlez::bindable::StaticColumnCount for GlobalPixels {}
|
||||
|
||||
impl sqlez::bindable::Bind for GlobalPixels {
|
||||
fn bind(
|
||||
&self,
|
||||
statement: &sqlez::statement::Statement,
|
||||
start_index: i32,
|
||||
) -> anyhow::Result<i32> {
|
||||
self.0.bind(statement, start_index)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
|
||||
pub struct Rems(f32);
|
||||
|
||||
|
||||
@@ -68,51 +68,56 @@ use derive_more::{Deref, DerefMut};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::{Borrow, BorrowMut},
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
use taffy::TaffyLayoutEngine;
|
||||
|
||||
type AnyBox = Box<dyn Any + Send>;
|
||||
type AnyBox = Box<dyn Any>;
|
||||
|
||||
pub trait Context {
|
||||
type ModelContext<'a, T>;
|
||||
type Result<T>;
|
||||
|
||||
fn build_model<T>(
|
||||
fn build_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>
|
||||
where
|
||||
T: 'static + Send;
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>;
|
||||
|
||||
fn update_model<T: 'static, R>(
|
||||
fn update_model<T, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R>;
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static;
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
|
||||
}
|
||||
|
||||
pub trait VisualContext: Context {
|
||||
type ViewContext<'a, 'w, V>;
|
||||
|
||||
fn build_view<V>(
|
||||
&mut self,
|
||||
build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: 'static + Send;
|
||||
V: 'static;
|
||||
|
||||
fn update_view<V: 'static, R>(
|
||||
&mut self,
|
||||
view: &View<V>,
|
||||
update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R,
|
||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Self::Result<R>;
|
||||
|
||||
fn replace_root_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: Render;
|
||||
}
|
||||
|
||||
pub trait Entity<T>: Sealed {
|
||||
type Weak: 'static + Send;
|
||||
type Weak: 'static;
|
||||
|
||||
fn entity_id(&self) -> EntityId;
|
||||
fn downgrade(&self) -> Self::Weak;
|
||||
@@ -127,106 +132,12 @@ pub enum GlobalKey {
|
||||
Type(TypeId),
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct MainThread<T>(T);
|
||||
|
||||
impl<T> Deref for MainThread<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for MainThread<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Context> Context for MainThread<C> {
|
||||
type ModelContext<'a, T> = MainThread<C::ModelContext<'a, T>>;
|
||||
type Result<T> = C::Result<T>;
|
||||
|
||||
fn build_model<T>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>
|
||||
where
|
||||
T: 'static + Send,
|
||||
{
|
||||
self.0.build_model(|cx| {
|
||||
let cx = unsafe {
|
||||
mem::transmute::<
|
||||
&mut C::ModelContext<'_, T>,
|
||||
&mut MainThread<C::ModelContext<'_, T>>,
|
||||
>(cx)
|
||||
};
|
||||
build_model(cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
self.0.update_model(handle, |entity, cx| {
|
||||
let cx = unsafe {
|
||||
mem::transmute::<
|
||||
&mut C::ModelContext<'_, T>,
|
||||
&mut MainThread<C::ModelContext<'_, T>>,
|
||||
>(cx)
|
||||
};
|
||||
update(entity, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: VisualContext> VisualContext for MainThread<C> {
|
||||
type ViewContext<'a, 'w, V> = MainThread<C::ViewContext<'a, 'w, V>>;
|
||||
|
||||
fn build_view<V>(
|
||||
&mut self,
|
||||
build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: 'static + Send,
|
||||
{
|
||||
self.0.build_view(|cx| {
|
||||
let cx = unsafe {
|
||||
mem::transmute::<
|
||||
&mut C::ViewContext<'_, '_, V>,
|
||||
&mut MainThread<C::ViewContext<'_, '_, V>>,
|
||||
>(cx)
|
||||
};
|
||||
build_view_state(cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_view<V: 'static, R>(
|
||||
&mut self,
|
||||
view: &View<V>,
|
||||
update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
self.0.update_view(view, |view_state, cx| {
|
||||
let cx = unsafe {
|
||||
mem::transmute::<
|
||||
&mut C::ViewContext<'_, '_, V>,
|
||||
&mut MainThread<C::ViewContext<'_, '_, V>>,
|
||||
>(cx)
|
||||
};
|
||||
update(view_state, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BorrowAppContext {
|
||||
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R;
|
||||
|
||||
fn set_global<T: Send + 'static>(&mut self, global: T);
|
||||
fn set_global<T: 'static>(&mut self, global: T);
|
||||
}
|
||||
|
||||
impl<C> BorrowAppContext for C
|
||||
@@ -243,7 +154,7 @@ where
|
||||
result
|
||||
}
|
||||
|
||||
fn set_global<G: 'static + Send>(&mut self, global: G) {
|
||||
fn set_global<G: 'static>(&mut self, global: G) {
|
||||
self.borrow_mut().set_global(global)
|
||||
}
|
||||
}
|
||||
@@ -306,59 +217,3 @@ impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Reference<'a, T> {
|
||||
Immutable(&'a T),
|
||||
Mutable(&'a mut T),
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for Reference<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Reference::Immutable(target) => target,
|
||||
Reference::Mutable(target) => target,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for Reference<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
match self {
|
||||
Reference::Immutable(_) => {
|
||||
panic!("cannot mutably deref an immutable reference. this is a bug in GPUI.");
|
||||
}
|
||||
Reference::Mutable(target) => target,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MainThreadOnly<T: ?Sized> {
|
||||
executor: Executor,
|
||||
value: Arc<T>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for MainThreadOnly<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
executor: self.executor.clone(),
|
||||
value: self.value.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows a value to be accessed only on the main thread, allowing a non-`Send` type
|
||||
/// to become `Send`.
|
||||
impl<T: 'static + ?Sized> MainThreadOnly<T> {
|
||||
pub(crate) fn new(value: Arc<T>, executor: Executor) -> Self {
|
||||
Self { executor, value }
|
||||
}
|
||||
|
||||
pub(crate) fn borrow_on_main_thread(&self) -> &T {
|
||||
assert!(self.executor.is_main_thread());
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized> Send for MainThreadOnly<T> {}
|
||||
|
||||
@@ -50,7 +50,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
fn on_mouse_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -71,7 +71,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
fn on_mouse_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -92,7 +92,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
fn on_mouse_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -113,7 +113,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
fn on_mouse_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -133,7 +133,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
|
||||
fn on_mouse_move(
|
||||
mut self,
|
||||
handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -150,7 +150,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
|
||||
fn on_scroll_wheel(
|
||||
mut self,
|
||||
handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -178,7 +178,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
|
||||
fn on_action<A: 'static>(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -196,7 +196,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
|
||||
fn on_key_down(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -214,7 +214,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
|
||||
fn on_key_up(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -258,9 +258,9 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
self
|
||||
}
|
||||
|
||||
fn on_drop<W: 'static + Send>(
|
||||
fn on_drop<W: 'static>(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -303,7 +303,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
|
||||
fn on_click(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + Send + 'static,
|
||||
listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -316,11 +316,11 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
|
||||
fn on_drag<W>(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + Send + 'static,
|
||||
listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
W: 'static + Send + Render,
|
||||
W: 'static + Render,
|
||||
{
|
||||
debug_assert!(
|
||||
self.stateful_interaction().drag_listener.is_none(),
|
||||
@@ -335,7 +335,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ElementInteraction<V: 'static>: 'static + Send {
|
||||
pub trait ElementInteraction<V: 'static>: 'static {
|
||||
fn as_stateless(&self) -> &StatelessInteraction<V>;
|
||||
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>;
|
||||
fn as_stateful(&self) -> Option<&StatefulInteraction<V>>;
|
||||
@@ -672,7 +672,7 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
|
||||
}
|
||||
}
|
||||
|
||||
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static + Send;
|
||||
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
|
||||
|
||||
pub struct StatelessInteraction<V> {
|
||||
pub dispatch_context: DispatchContext,
|
||||
@@ -1077,32 +1077,25 @@ pub struct FocusEvent {
|
||||
}
|
||||
|
||||
pub type MouseDownListener<V> = Box<
|
||||
dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
>;
|
||||
pub type MouseUpListener<V> = Box<
|
||||
dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
>;
|
||||
|
||||
pub type MouseMoveListener<V> = Box<
|
||||
dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
>;
|
||||
|
||||
pub type ScrollWheelListener<V> = Box<
|
||||
dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + Send + 'static>;
|
||||
pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>;
|
||||
|
||||
pub(crate) type DragListener<V> =
|
||||
Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + Send + 'static>;
|
||||
Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
|
||||
|
||||
pub type KeyListener<V> = Box<
|
||||
dyn Fn(
|
||||
@@ -1112,6 +1105,5 @@ pub type KeyListener<V> = Box<
|
||||
DispatchPhase,
|
||||
&mut ViewContext<V>,
|
||||
) -> Option<Box<dyn Action>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
@@ -5,15 +5,19 @@ mod mac;
|
||||
mod test;
|
||||
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, FontRun,
|
||||
GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams,
|
||||
RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size,
|
||||
point, size, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
|
||||
FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout,
|
||||
Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
|
||||
SharedString, Size,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, bail};
|
||||
use async_task::Runnable;
|
||||
use futures::channel::oneshot;
|
||||
use parking::Unparker;
|
||||
use seahash::SeaHasher;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlez::bindable::{Bind, Column, StaticColumnCount};
|
||||
use sqlez::statement::Statement;
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::time::Duration;
|
||||
@@ -26,6 +30,7 @@ use std::{
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use keystroke::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -35,12 +40,13 @@ pub use test::*;
|
||||
pub use time::UtcOffset;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) fn current_platform() -> Arc<dyn Platform> {
|
||||
Arc::new(MacPlatform::new())
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
Rc::new(MacPlatform::new())
|
||||
}
|
||||
|
||||
pub(crate) trait Platform: 'static {
|
||||
fn executor(&self) -> Executor;
|
||||
fn background_executor(&self) -> BackgroundExecutor;
|
||||
fn foreground_executor(&self) -> ForegroundExecutor;
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
||||
|
||||
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
|
||||
@@ -63,7 +69,7 @@ pub(crate) trait Platform: 'static {
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
display_id: DisplayId,
|
||||
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
|
||||
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
||||
);
|
||||
fn start_display_link(&self, display_id: DisplayId);
|
||||
fn stop_display_link(&self, display_id: DisplayId);
|
||||
@@ -104,6 +110,9 @@ pub(crate) trait Platform: 'static {
|
||||
|
||||
pub trait PlatformDisplay: Send + Sync + Debug {
|
||||
fn id(&self) -> DisplayId;
|
||||
/// Returns a stable identifier for this display that can be persisted and used
|
||||
/// across system restarts.
|
||||
fn uuid(&self) -> Result<Uuid>;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn bounds(&self) -> Bounds<GlobalPixels>;
|
||||
}
|
||||
@@ -129,12 +138,7 @@ pub(crate) trait PlatformWindow {
|
||||
fn mouse_position(&self) -> Point<Pixels>;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
|
||||
fn prompt(
|
||||
&self,
|
||||
level: WindowPromptLevel,
|
||||
msg: &str,
|
||||
answers: &[&str],
|
||||
) -> oneshot::Receiver<usize>;
|
||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
||||
fn activate(&self);
|
||||
fn set_title(&mut self, title: &str);
|
||||
fn set_edited(&mut self, edited: bool);
|
||||
@@ -161,7 +165,9 @@ pub trait PlatformDispatcher: Send + Sync {
|
||||
fn dispatch(&self, runnable: Runnable);
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable);
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
|
||||
fn poll(&self) -> bool;
|
||||
fn poll(&self, background_only: bool) -> bool;
|
||||
fn park(&self);
|
||||
fn unparker(&self) -> Unparker;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_test(&self) -> Option<&TestDispatcher> {
|
||||
@@ -368,6 +374,67 @@ pub enum WindowBounds {
|
||||
Fixed(Bounds<GlobalPixels>),
|
||||
}
|
||||
|
||||
impl StaticColumnCount for WindowBounds {
|
||||
fn column_count() -> usize {
|
||||
5
|
||||
}
|
||||
}
|
||||
|
||||
impl Bind for WindowBounds {
|
||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
||||
let (region, next_index) = match self {
|
||||
WindowBounds::Fullscreen => {
|
||||
let next_index = statement.bind(&"Fullscreen", start_index)?;
|
||||
(None, next_index)
|
||||
}
|
||||
WindowBounds::Maximized => {
|
||||
let next_index = statement.bind(&"Maximized", start_index)?;
|
||||
(None, next_index)
|
||||
}
|
||||
WindowBounds::Fixed(region) => {
|
||||
let next_index = statement.bind(&"Fixed", start_index)?;
|
||||
(Some(*region), next_index)
|
||||
}
|
||||
};
|
||||
|
||||
statement.bind(
|
||||
®ion.map(|region| {
|
||||
(
|
||||
region.origin.x,
|
||||
region.origin.y,
|
||||
region.size.width,
|
||||
region.size.height,
|
||||
)
|
||||
}),
|
||||
next_index,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Column for WindowBounds {
|
||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||
let (window_state, next_index) = String::column(statement, start_index)?;
|
||||
let bounds = match window_state.as_str() {
|
||||
"Fullscreen" => WindowBounds::Fullscreen,
|
||||
"Maximized" => WindowBounds::Maximized,
|
||||
"Fixed" => {
|
||||
let ((x, y, width, height), _) = Column::column(statement, next_index)?;
|
||||
let x: f64 = x;
|
||||
let y: f64 = y;
|
||||
let width: f64 = width;
|
||||
let height: f64 = height;
|
||||
WindowBounds::Fixed(Bounds {
|
||||
origin: point(x.into(), y.into()),
|
||||
size: size(width.into(), height.into()),
|
||||
})
|
||||
}
|
||||
_ => bail!("Window State did not have a valid string"),
|
||||
};
|
||||
|
||||
Ok((bounds, next_index + 4))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum WindowAppearance {
|
||||
Light,
|
||||
@@ -382,14 +449,6 @@ impl Default for WindowAppearance {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||
pub enum WindowPromptLevel {
|
||||
#[default]
|
||||
Info,
|
||||
Warning,
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PathPromptOptions {
|
||||
pub files: bool,
|
||||
|
||||
@@ -9,8 +9,11 @@ use objc::{
|
||||
runtime::{BOOL, YES},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use parking::{Parker, Unparker};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
@@ -20,7 +23,17 @@ pub fn dispatch_get_main_queue() -> dispatch_queue_t {
|
||||
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
|
||||
}
|
||||
|
||||
pub struct MacDispatcher;
|
||||
pub struct MacDispatcher {
|
||||
parker: Arc<Mutex<Parker>>,
|
||||
}
|
||||
|
||||
impl MacDispatcher {
|
||||
pub fn new() -> Self {
|
||||
MacDispatcher {
|
||||
parker: Arc::new(Mutex::new(Parker::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDispatcher for MacDispatcher {
|
||||
fn is_main_thread(&self) -> bool {
|
||||
@@ -68,33 +81,20 @@ impl PlatformDispatcher for MacDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&self) -> bool {
|
||||
fn poll(&self, _background_only: bool) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
self.parker.lock().park()
|
||||
}
|
||||
|
||||
fn unparker(&self) -> Unparker {
|
||||
self.parker.lock().unparker()
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn trampoline(runnable: *mut c_void) {
|
||||
let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
|
||||
task.run();
|
||||
}
|
||||
|
||||
// #include <dispatch/dispatch.h>
|
||||
|
||||
// int main(void) {
|
||||
|
||||
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
// // Do some lengthy background work here...
|
||||
// printf("Background Work\n");
|
||||
|
||||
// dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// // Once done, update your UI on the main queue here.
|
||||
// printf("UI Updated\n");
|
||||
|
||||
// });
|
||||
// });
|
||||
|
||||
// sleep(3); // prevent the program from terminating immediately
|
||||
|
||||
// return 0;
|
||||
// }
|
||||
// ```
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
|
||||
use anyhow::Result;
|
||||
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
||||
use core_graphics::{
|
||||
display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
|
||||
geometry::{CGPoint, CGRect, CGSize},
|
||||
};
|
||||
use std::any::Any;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MacDisplay(pub(crate) CGDirectDisplayID);
|
||||
@@ -11,17 +14,23 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID);
|
||||
unsafe impl Send for MacDisplay {}
|
||||
|
||||
impl MacDisplay {
|
||||
/// Get the screen with the given UUID.
|
||||
/// Get the screen with the given [DisplayId].
|
||||
pub fn find_by_id(id: DisplayId) -> Option<Self> {
|
||||
Self::all().find(|screen| screen.id() == id)
|
||||
}
|
||||
|
||||
/// Get the screen with the given persistent [Uuid].
|
||||
pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
|
||||
Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
|
||||
}
|
||||
|
||||
/// Get the primary screen - the one with the menu bar, and whose bottom left
|
||||
/// corner is at the origin of the AppKit coordinate system.
|
||||
pub fn primary() -> Self {
|
||||
Self::all().next().unwrap()
|
||||
}
|
||||
|
||||
/// Obtains an iterator over all currently active system displays.
|
||||
pub fn all() -> impl Iterator<Item = Self> {
|
||||
unsafe {
|
||||
let mut display_count: u32 = 0;
|
||||
@@ -40,6 +49,11 @@ impl MacDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
|
||||
///
|
||||
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
|
||||
@@ -88,6 +102,34 @@ impl PlatformDisplay for MacDisplay {
|
||||
DisplayId(self.0)
|
||||
}
|
||||
|
||||
fn uuid(&self) -> Result<Uuid> {
|
||||
let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
|
||||
anyhow::ensure!(
|
||||
!cfuuid.is_null(),
|
||||
"AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
|
||||
);
|
||||
|
||||
let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
|
||||
Ok(Uuid::from_bytes([
|
||||
bytes.byte0,
|
||||
bytes.byte1,
|
||||
bytes.byte2,
|
||||
bytes.byte3,
|
||||
bytes.byte4,
|
||||
bytes.byte5,
|
||||
bytes.byte6,
|
||||
bytes.byte7,
|
||||
bytes.byte8,
|
||||
bytes.byte9,
|
||||
bytes.byte10,
|
||||
bytes.byte11,
|
||||
bytes.byte12,
|
||||
bytes.byte13,
|
||||
bytes.byte14,
|
||||
bytes.byte15,
|
||||
]))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ impl MacDisplayLinker {
|
||||
}
|
||||
}
|
||||
|
||||
type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>>;
|
||||
type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>>;
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn set_output_callback(
|
||||
&mut self,
|
||||
display_id: DisplayId,
|
||||
output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
|
||||
output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
||||
) {
|
||||
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
|
||||
let callback = Arc::new(Mutex::new(output_callback));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::BoolExt;
|
||||
use crate::{
|
||||
AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Executor, InputEvent, MacDispatcher,
|
||||
MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform,
|
||||
PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp,
|
||||
WindowOptions,
|
||||
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
|
||||
InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow,
|
||||
PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result,
|
||||
SemanticVersion, VideoTimestamp, WindowOptions,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use block::ConcreteBlock;
|
||||
@@ -143,7 +143,8 @@ unsafe fn build_classes() {
|
||||
pub struct MacPlatform(Mutex<MacPlatformState>);
|
||||
|
||||
pub struct MacPlatformState {
|
||||
executor: Executor,
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<MacTextSystem>,
|
||||
display_linker: MacDisplayLinker,
|
||||
pasteboard: id,
|
||||
@@ -164,8 +165,10 @@ pub struct MacPlatformState {
|
||||
|
||||
impl MacPlatform {
|
||||
pub fn new() -> Self {
|
||||
let dispatcher = Arc::new(MacDispatcher::new());
|
||||
Self(Mutex::new(MacPlatformState {
|
||||
executor: Executor::new(Arc::new(MacDispatcher)),
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher),
|
||||
text_system: Arc::new(MacTextSystem::new()),
|
||||
display_linker: MacDisplayLinker::new(),
|
||||
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
|
||||
@@ -345,8 +348,12 @@ impl MacPlatform {
|
||||
}
|
||||
|
||||
impl Platform for MacPlatform {
|
||||
fn executor(&self) -> Executor {
|
||||
self.0.lock().executor.clone()
|
||||
fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.0.lock().background_executor.clone()
|
||||
}
|
||||
|
||||
fn foreground_executor(&self) -> crate::ForegroundExecutor {
|
||||
self.0.lock().foreground_executor.clone()
|
||||
}
|
||||
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
|
||||
@@ -457,6 +464,10 @@ impl Platform for MacPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
|
||||
// Box::new(StatusItem::add(self.fonts()))
|
||||
// }
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
MacDisplay::all()
|
||||
.into_iter()
|
||||
@@ -464,10 +475,6 @@ impl Platform for MacPlatform {
|
||||
.collect()
|
||||
}
|
||||
|
||||
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
|
||||
// Box::new(StatusItem::add(self.fonts()))
|
||||
// }
|
||||
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
|
||||
}
|
||||
@@ -481,13 +488,13 @@ impl Platform for MacPlatform {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
Box::new(MacWindow::open(handle, options, self.executor()))
|
||||
Box::new(MacWindow::open(handle, options, self.foreground_executor()))
|
||||
}
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
display_id: DisplayId,
|
||||
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
|
||||
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
||||
) {
|
||||
self.0
|
||||
.lock()
|
||||
@@ -589,8 +596,8 @@ impl Platform for MacPlatform {
|
||||
let path = path.to_path_buf();
|
||||
self.0
|
||||
.lock()
|
||||
.executor
|
||||
.spawn_on_main_local(async move {
|
||||
.background_executor
|
||||
.spawn(async move {
|
||||
let full_path = ns_string(path.to_str().unwrap_or(""));
|
||||
let root_full_path = ns_string("");
|
||||
let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
|
||||
@@ -674,23 +681,6 @@ impl Platform for MacPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
|
||||
unsafe {
|
||||
let bundle: id = NSBundle::mainBundle();
|
||||
if bundle.is_null() {
|
||||
Err(anyhow!("app is not running inside a bundle"))
|
||||
} else {
|
||||
let name = ns_string(name);
|
||||
let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
|
||||
if url.is_null() {
|
||||
Err(anyhow!("resource not found"))
|
||||
} else {
|
||||
ns_url_to_path(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
|
||||
// self.0.lock().menu_command = Some(callback);
|
||||
// }
|
||||
@@ -717,6 +707,23 @@ impl Platform for MacPlatform {
|
||||
// }
|
||||
// }
|
||||
|
||||
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
|
||||
unsafe {
|
||||
let bundle: id = NSBundle::mainBundle();
|
||||
if bundle.is_null() {
|
||||
Err(anyhow!("app is not running inside a bundle"))
|
||||
} else {
|
||||
let name = ns_string(name);
|
||||
let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
|
||||
if url.is_null() {
|
||||
Err(anyhow!("resource not found"))
|
||||
} else {
|
||||
ns_url_to_path(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cursor_style(&self, style: CursorStyle) {
|
||||
unsafe {
|
||||
let new_cursor: id = match style {
|
||||
|
||||
@@ -98,10 +98,10 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
|
||||
input.border_color.a *= 1. - saturate(0.5 - inset_distance);
|
||||
|
||||
// Alpha-blend the border and the background.
|
||||
float output_alpha =
|
||||
quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
|
||||
float output_alpha = input.border_color.a +
|
||||
input.background_color.a * (1. - input.border_color.a);
|
||||
float3 premultiplied_border_rgb =
|
||||
input.border_color.rgb * quad.border_color.a;
|
||||
input.border_color.rgb * input.border_color.a;
|
||||
float3 premultiplied_background_rgb =
|
||||
input.background_color.rgb * input.background_color.a;
|
||||
float3 premultiplied_output_rgb =
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
|
||||
use crate::{
|
||||
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, ExternalPaths,
|
||||
FileDropEvent, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size,
|
||||
Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
|
||||
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths,
|
||||
FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke,
|
||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
|
||||
PromptLevel, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
@@ -315,7 +315,7 @@ struct InsertText {
|
||||
|
||||
struct MacWindowState {
|
||||
handle: AnyWindowHandle,
|
||||
executor: Executor,
|
||||
executor: ForegroundExecutor,
|
||||
native_window: id,
|
||||
renderer: MetalRenderer,
|
||||
scene_to_render: Option<Scene>,
|
||||
@@ -451,7 +451,11 @@ unsafe impl Send for MacWindowState {}
|
||||
pub struct MacWindow(Arc<Mutex<MacWindowState>>);
|
||||
|
||||
impl MacWindow {
|
||||
pub fn open(handle: AnyWindowHandle, options: WindowOptions, executor: Executor) -> Self {
|
||||
pub fn open(
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
executor: ForegroundExecutor,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
|
||||
@@ -674,11 +678,13 @@ impl MacWindow {
|
||||
|
||||
impl Drop for MacWindow {
|
||||
fn drop(&mut self) {
|
||||
let this = self.0.clone();
|
||||
let executor = self.0.lock().executor.clone();
|
||||
executor
|
||||
.run_on_main(move || unsafe {
|
||||
this.lock().native_window.close();
|
||||
let this = self.0.lock();
|
||||
let window = this.native_window;
|
||||
this.executor
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
window.close();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -741,12 +747,7 @@ impl PlatformWindow for MacWindow {
|
||||
self.0.as_ref().lock().input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn prompt(
|
||||
&self,
|
||||
level: WindowPromptLevel,
|
||||
msg: &str,
|
||||
answers: &[&str],
|
||||
) -> oneshot::Receiver<usize> {
|
||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize> {
|
||||
// macOs applies overrides to modal window buttons after they are added.
|
||||
// Two most important for this logic are:
|
||||
// * Buttons with "Cancel" title will be displayed as the last buttons in the modal
|
||||
@@ -776,9 +777,9 @@ impl PlatformWindow for MacWindow {
|
||||
let alert: id = msg_send![class!(NSAlert), alloc];
|
||||
let alert: id = msg_send![alert, init];
|
||||
let alert_style = match level {
|
||||
WindowPromptLevel::Info => 1,
|
||||
WindowPromptLevel::Warning => 0,
|
||||
WindowPromptLevel::Critical => 2,
|
||||
PromptLevel::Info => 1,
|
||||
PromptLevel::Warning => 0,
|
||||
PromptLevel::Critical => 2,
|
||||
};
|
||||
let _: () = msg_send![alert, setAlertStyle: alert_style];
|
||||
let _: () = msg_send![alert, setMessageText: ns_string(msg)];
|
||||
@@ -807,7 +808,7 @@ impl PlatformWindow for MacWindow {
|
||||
let native_window = self.0.lock().native_window;
|
||||
let executor = self.0.lock().executor.clone();
|
||||
executor
|
||||
.spawn_on_main_local(async move {
|
||||
.spawn(async move {
|
||||
let _: () = msg_send![
|
||||
alert,
|
||||
beginSheetModalForWindow: native_window
|
||||
@@ -824,7 +825,7 @@ impl PlatformWindow for MacWindow {
|
||||
let window = self.0.lock().native_window;
|
||||
let executor = self.0.lock().executor.clone();
|
||||
executor
|
||||
.spawn_on_main_local(async move {
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
let _: () = msg_send![window, makeKeyAndOrderFront: nil];
|
||||
}
|
||||
@@ -873,7 +874,7 @@ impl PlatformWindow for MacWindow {
|
||||
let this = self.0.lock();
|
||||
let window = this.native_window;
|
||||
this.executor
|
||||
.spawn_on_main_local(async move {
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
window.zoom_(nil);
|
||||
}
|
||||
@@ -885,7 +886,7 @@ impl PlatformWindow for MacWindow {
|
||||
let this = self.0.lock();
|
||||
let window = this.native_window;
|
||||
this.executor
|
||||
.spawn_on_main_local(async move {
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
window.toggleFullScreen_(nil);
|
||||
}
|
||||
@@ -1189,7 +1190,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
||||
lock.synthetic_drag_counter += 1;
|
||||
let executor = lock.executor.clone();
|
||||
executor
|
||||
.spawn_on_main_local(synthetic_drag(
|
||||
.spawn(synthetic_drag(
|
||||
weak_window_state,
|
||||
lock.synthetic_drag_counter,
|
||||
event.clone(),
|
||||
@@ -1317,7 +1318,7 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id)
|
||||
let executor = lock.executor.clone();
|
||||
drop(lock);
|
||||
executor
|
||||
.spawn_on_main_local(async move {
|
||||
.spawn(async move {
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
if let Some(mut callback) = lock.activate_callback.take() {
|
||||
drop(lock);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::PlatformDispatcher;
|
||||
use async_task::Runnable;
|
||||
use backtrace::Backtrace;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use parking::{Parker, Unparker};
|
||||
use parking_lot::Mutex;
|
||||
use rand::prelude::*;
|
||||
use std::{
|
||||
@@ -18,6 +20,8 @@ struct TestDispatcherId(usize);
|
||||
pub struct TestDispatcher {
|
||||
id: TestDispatcherId,
|
||||
state: Arc<Mutex<TestDispatcherState>>,
|
||||
parker: Arc<Mutex<Parker>>,
|
||||
unparker: Unparker,
|
||||
}
|
||||
|
||||
struct TestDispatcherState {
|
||||
@@ -28,10 +32,13 @@ struct TestDispatcherState {
|
||||
time: Duration,
|
||||
is_main_thread: bool,
|
||||
next_id: TestDispatcherId,
|
||||
allow_parking: bool,
|
||||
waiting_backtrace: Option<Backtrace>,
|
||||
}
|
||||
|
||||
impl TestDispatcher {
|
||||
pub fn new(random: StdRng) -> Self {
|
||||
let (parker, unparker) = parking::pair();
|
||||
let state = TestDispatcherState {
|
||||
random,
|
||||
foreground: HashMap::default(),
|
||||
@@ -40,11 +47,15 @@ impl TestDispatcher {
|
||||
time: Duration::ZERO,
|
||||
is_main_thread: true,
|
||||
next_id: TestDispatcherId(1),
|
||||
allow_parking: false,
|
||||
waiting_backtrace: None,
|
||||
};
|
||||
|
||||
TestDispatcher {
|
||||
id: TestDispatcherId(0),
|
||||
state: Arc::new(Mutex::new(state)),
|
||||
parker: Arc::new(Mutex::new(parker)),
|
||||
unparker,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +77,7 @@ impl TestDispatcher {
|
||||
self.state.lock().time = new_now;
|
||||
}
|
||||
|
||||
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
|
||||
pub fn simulate_random_delay(&self) -> impl 'static + Send + Future<Output = ()> {
|
||||
pub struct YieldNow {
|
||||
count: usize,
|
||||
}
|
||||
@@ -91,7 +102,30 @@ impl TestDispatcher {
|
||||
}
|
||||
|
||||
pub fn run_until_parked(&self) {
|
||||
while self.poll() {}
|
||||
while self.poll(false) {}
|
||||
}
|
||||
|
||||
pub fn parking_allowed(&self) -> bool {
|
||||
self.state.lock().allow_parking
|
||||
}
|
||||
|
||||
pub fn allow_parking(&self) {
|
||||
self.state.lock().allow_parking = true
|
||||
}
|
||||
|
||||
pub fn start_waiting(&self) {
|
||||
self.state.lock().waiting_backtrace = Some(Backtrace::new_unresolved());
|
||||
}
|
||||
|
||||
pub fn finish_waiting(&self) {
|
||||
self.state.lock().waiting_backtrace.take();
|
||||
}
|
||||
|
||||
pub fn waiting_backtrace(&self) -> Option<Backtrace> {
|
||||
self.state.lock().waiting_backtrace.take().map(|mut b| {
|
||||
b.resolve();
|
||||
b
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +135,8 @@ impl Clone for TestDispatcher {
|
||||
Self {
|
||||
id: TestDispatcherId(id),
|
||||
state: self.state.clone(),
|
||||
parker: self.parker.clone(),
|
||||
unparker: self.unparker.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,6 +148,7 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
|
||||
fn dispatch(&self, runnable: Runnable) {
|
||||
self.state.lock().background.push(runnable);
|
||||
self.unparker.unpark();
|
||||
}
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||
@@ -121,6 +158,7 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
.entry(self.id)
|
||||
.or_default()
|
||||
.push_back(runnable);
|
||||
self.unparker.unpark();
|
||||
}
|
||||
|
||||
fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
|
||||
@@ -132,7 +170,7 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
state.delayed.insert(ix, (next_time, runnable));
|
||||
}
|
||||
|
||||
fn poll(&self) -> bool {
|
||||
fn poll(&self, background_only: bool) -> bool {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
while let Some((deadline, _)) = state.delayed.first() {
|
||||
@@ -143,11 +181,15 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
state.background.push(runnable);
|
||||
}
|
||||
|
||||
let foreground_len: usize = state
|
||||
.foreground
|
||||
.values()
|
||||
.map(|runnables| runnables.len())
|
||||
.sum();
|
||||
let foreground_len: usize = if background_only {
|
||||
0
|
||||
} else {
|
||||
state
|
||||
.foreground
|
||||
.values()
|
||||
.map(|runnables| runnables.len())
|
||||
.sum()
|
||||
};
|
||||
let background_len = state.background.len();
|
||||
|
||||
if foreground_len == 0 && background_len == 0 {
|
||||
@@ -183,62 +225,15 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
true
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
self.parker.lock().park();
|
||||
}
|
||||
|
||||
fn unparker(&self) -> Unparker {
|
||||
self.unparker.clone()
|
||||
}
|
||||
|
||||
fn as_test(&self) -> Option<&TestDispatcher> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Executor;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn test_dispatch() {
|
||||
let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
|
||||
let executor = Executor::new(Arc::new(dispatcher));
|
||||
|
||||
let result = executor.block(async { executor.run_on_main(|| 1).await });
|
||||
assert_eq!(result, 1);
|
||||
|
||||
let result = executor.block({
|
||||
let executor = executor.clone();
|
||||
async move {
|
||||
executor
|
||||
.spawn_on_main({
|
||||
let executor = executor.clone();
|
||||
assert!(executor.is_main_thread());
|
||||
|| async move {
|
||||
assert!(executor.is_main_thread());
|
||||
let result = executor
|
||||
.spawn({
|
||||
let executor = executor.clone();
|
||||
async move {
|
||||
assert!(!executor.is_main_thread());
|
||||
|
||||
let result = executor
|
||||
.spawn_on_main({
|
||||
let executor = executor.clone();
|
||||
|| async move {
|
||||
assert!(executor.is_main_thread());
|
||||
2
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
assert!(!executor.is_main_thread());
|
||||
result
|
||||
}
|
||||
})
|
||||
.await;
|
||||
assert!(executor.is_main_thread());
|
||||
result
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
});
|
||||
assert_eq!(result, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
use crate::{DisplayId, Executor, Platform, PlatformTextSystem};
|
||||
use crate::{BackgroundExecutor, DisplayId, ForegroundExecutor, Platform, PlatformTextSystem};
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct TestPlatform {
|
||||
executor: Executor,
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
}
|
||||
|
||||
impl TestPlatform {
|
||||
pub fn new(executor: Executor) -> Self {
|
||||
TestPlatform { executor }
|
||||
pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Self {
|
||||
TestPlatform {
|
||||
background_executor: executor,
|
||||
foreground_executor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("implement out what our tests needed in GPUI 1")
|
||||
impl Platform for TestPlatform {
|
||||
fn executor(&self) -> Executor {
|
||||
self.executor.clone()
|
||||
fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.background_executor.clone()
|
||||
}
|
||||
|
||||
fn foreground_executor(&self) -> ForegroundExecutor {
|
||||
self.foreground_executor.clone()
|
||||
}
|
||||
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
|
||||
@@ -73,7 +81,7 @@ impl Platform for TestPlatform {
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
_display_id: DisplayId,
|
||||
_callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp)>,
|
||||
_callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp) + Send>,
|
||||
) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ struct SubscriberSetState<EmitterKey, Callback> {
|
||||
|
||||
impl<EmitterKey, Callback> SubscriberSet<EmitterKey, Callback>
|
||||
where
|
||||
EmitterKey: 'static + Send + Ord + Clone + Debug,
|
||||
Callback: 'static + Send,
|
||||
EmitterKey: 'static + Ord + Clone + Debug,
|
||||
Callback: 'static,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(Mutex::new(SubscriberSetState {
|
||||
@@ -47,8 +47,8 @@ where
|
||||
subscribers.remove(&subscriber_id);
|
||||
if subscribers.is_empty() {
|
||||
lock.subscribers.remove(&emitter_key);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// We didn't manage to remove the subscription, which means it was dropped
|
||||
@@ -96,7 +96,7 @@ where
|
||||
|
||||
#[must_use]
|
||||
pub struct Subscription {
|
||||
unsubscribe: Option<Box<dyn FnOnce() + Send + 'static>>,
|
||||
unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use crate::{
|
||||
private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace,
|
||||
BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, LayoutId, Model, Pixels,
|
||||
Size, ViewContext, VisualContext, WeakModel, WindowContext,
|
||||
BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId,
|
||||
Model, Pixels, Size, ViewContext, VisualContext, WeakModel, WindowContext,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use std::{any::TypeId, marker::PhantomData};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
pub trait Render: 'static + Sized {
|
||||
type Element: Element<Self> + 'static + Send;
|
||||
type Element: Element<Self> + 'static;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
|
||||
}
|
||||
@@ -49,7 +52,7 @@ impl<V: 'static> View<V> {
|
||||
pub fn update<C, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
f: impl FnOnce(&mut V, &mut C::ViewContext<'_, '_, V>) -> R,
|
||||
f: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> C::Result<R>
|
||||
where
|
||||
C: VisualContext,
|
||||
@@ -70,55 +73,23 @@ impl<V> Clone for View<V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Render, ParentViewState: 'static> Component<ParentViewState> for View<V> {
|
||||
fn render(self) -> AnyElement<ParentViewState> {
|
||||
AnyElement::new(EraseViewState {
|
||||
view: self,
|
||||
parent_view_state_type: PhantomData,
|
||||
})
|
||||
impl<V> Hash for View<V> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.model.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Element<()> for View<V>
|
||||
where
|
||||
V: Render,
|
||||
{
|
||||
type ElementState = AnyElement<V>;
|
||||
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
Some(ElementId::View(self.model.entity_id))
|
||||
impl<V> PartialEq for View<V> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.model == other.model
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(
|
||||
&mut self,
|
||||
_: &mut (),
|
||||
_: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<()>,
|
||||
) -> Self::ElementState {
|
||||
self.update(cx, |state, cx| {
|
||||
let mut any_element = AnyElement::new(state.render(cx));
|
||||
any_element.initialize(state, cx);
|
||||
any_element
|
||||
})
|
||||
}
|
||||
impl<V> Eq for View<V> {}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut (),
|
||||
element: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<()>,
|
||||
) -> LayoutId {
|
||||
self.update(cx, |state, cx| element.layout(state, cx))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut (),
|
||||
element: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<()>,
|
||||
) {
|
||||
self.update(cx, |state, cx| element.paint(state, cx))
|
||||
impl<V: Render, ParentViewState: 'static> Component<ParentViewState> for View<V> {
|
||||
fn render(self) -> AnyElement<ParentViewState> {
|
||||
AnyElement::new(AnyView::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,17 +98,25 @@ pub struct WeakView<V> {
|
||||
}
|
||||
|
||||
impl<V: 'static> WeakView<V> {
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.model.entity_id
|
||||
}
|
||||
|
||||
pub fn upgrade(&self) -> Option<View<V>> {
|
||||
Entity::upgrade_from(self)
|
||||
}
|
||||
|
||||
pub fn update<R>(
|
||||
pub fn update<C, R>(
|
||||
&self,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&mut V, &mut ViewContext<V>) -> R,
|
||||
) -> Result<R> {
|
||||
cx: &mut C,
|
||||
f: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
C: VisualContext,
|
||||
Result<C::Result<R>>: Flatten<R>,
|
||||
{
|
||||
let view = self.upgrade().context("error upgrading view")?;
|
||||
Ok(view.update(cx, f))
|
||||
Ok(view.update(cx, f)).flatten()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,115 +128,19 @@ impl<V> Clone for WeakView<V> {
|
||||
}
|
||||
}
|
||||
|
||||
struct EraseViewState<V, ParentV> {
|
||||
view: View<V>,
|
||||
parent_view_state_type: PhantomData<ParentV>,
|
||||
}
|
||||
|
||||
unsafe impl<V, ParentV> Send for EraseViewState<V, ParentV> {}
|
||||
|
||||
impl<V: Render, ParentV: 'static> Component<ParentV> for EraseViewState<V, ParentV> {
|
||||
fn render(self) -> AnyElement<ParentV> {
|
||||
AnyElement::new(self)
|
||||
impl<V> Hash for WeakView<V> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.model.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Render, ParentV: 'static> Element<ParentV> for EraseViewState<V, ParentV> {
|
||||
type ElementState = AnyBox;
|
||||
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
Element::id(&self.view)
|
||||
}
|
||||
|
||||
fn initialize(
|
||||
&mut self,
|
||||
_: &mut ParentV,
|
||||
_: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<ParentV>,
|
||||
) -> Self::ElementState {
|
||||
ViewObject::initialize(&mut self.view, cx)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut ParentV,
|
||||
element: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<ParentV>,
|
||||
) -> LayoutId {
|
||||
ViewObject::layout(&mut self.view, element, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut ParentV,
|
||||
element: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<ParentV>,
|
||||
) {
|
||||
ViewObject::paint(&mut self.view, bounds, element, cx)
|
||||
impl<V> PartialEq for WeakView<V> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.model == other.model
|
||||
}
|
||||
}
|
||||
|
||||
trait ViewObject: Send + Sync {
|
||||
fn entity_type(&self) -> TypeId;
|
||||
fn entity_id(&self) -> EntityId;
|
||||
fn model(&self) -> AnyModel;
|
||||
fn initialize(&self, cx: &mut WindowContext) -> AnyBox;
|
||||
fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId;
|
||||
fn paint(&self, bounds: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext);
|
||||
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
|
||||
}
|
||||
|
||||
impl<V> ViewObject for View<V>
|
||||
where
|
||||
V: Render,
|
||||
{
|
||||
fn entity_type(&self) -> TypeId {
|
||||
TypeId::of::<V>()
|
||||
}
|
||||
|
||||
fn entity_id(&self) -> EntityId {
|
||||
Entity::entity_id(self)
|
||||
}
|
||||
|
||||
fn model(&self) -> AnyModel {
|
||||
self.model.clone().into_any()
|
||||
}
|
||||
|
||||
fn initialize(&self, cx: &mut WindowContext) -> AnyBox {
|
||||
cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| {
|
||||
self.update(cx, |state, cx| {
|
||||
let mut any_element = Box::new(AnyElement::new(state.render(cx)));
|
||||
any_element.initialize(state, cx);
|
||||
any_element
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId {
|
||||
cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| {
|
||||
self.update(cx, |state, cx| {
|
||||
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
|
||||
element.layout(state, cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(&self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
|
||||
cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| {
|
||||
self.update(cx, |state, cx| {
|
||||
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
|
||||
element.paint(state, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct(&format!("AnyView<{}>", std::any::type_name::<V>()))
|
||||
.field("entity_id", &ViewObject::entity_id(self).as_u64())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<V> Eq for WeakView<V> {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyView {
|
||||
@@ -289,7 +172,7 @@ impl AnyView {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn entity_type(&self) -> TypeId {
|
||||
pub fn entity_type(&self) -> TypeId {
|
||||
self.model.entity_type
|
||||
}
|
||||
|
||||
@@ -343,7 +226,7 @@ impl<V: Render> From<View<V>> for AnyView {
|
||||
}
|
||||
|
||||
impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
|
||||
type ElementState = AnyBox;
|
||||
type ElementState = Box<dyn Any>;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
Some(self.model.entity_id.into())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
||||
let expanded = quote! {
|
||||
impl #impl_generics gpui2::Component<#view_type> for #name #ty_generics #where_clause {
|
||||
fn render(self) -> gpui2::AnyElement<#view_type> {
|
||||
(move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, '_, #view_type>| self.render(view_state, cx))
|
||||
(move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, #view_type>| self.render(view_state, cx))
|
||||
.render()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(_seed),));
|
||||
continue;
|
||||
}
|
||||
Some("Executor") => {
|
||||
inner_fn_args.extend(quote!(gpui2::Executor::new(
|
||||
std::sync::Arc::new(dispatcher.clone())
|
||||
Some("BackgroundExecutor") => {
|
||||
inner_fn_args.extend(quote!(gpui::BackgroundExecutor::new(
|
||||
std::sync::Arc::new(dispatcher.clone()),
|
||||
),));
|
||||
continue;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
{
|
||||
let cx_varname = format_ident!("cx_{}", ix);
|
||||
cx_vars.extend(quote!(
|
||||
let mut #cx_varname = gpui2::TestAppContext::new(
|
||||
let mut #cx_varname = gpui::TestAppContext::new(
|
||||
dispatcher.clone()
|
||||
);
|
||||
));
|
||||
@@ -130,13 +130,13 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
fn #outer_fn_name() {
|
||||
#inner_fn
|
||||
|
||||
gpui2::run_test(
|
||||
gpui::run_test(
|
||||
#num_iterations as u64,
|
||||
#max_retries,
|
||||
&mut |dispatcher, _seed| {
|
||||
let executor = gpui2::Executor::new(std::sync::Arc::new(dispatcher.clone()));
|
||||
let executor = gpui::BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone()));
|
||||
#cx_vars
|
||||
executor.block(#inner_fn_name(#inner_fn_args));
|
||||
executor.block_test(#inner_fn_name(#inner_fn_args));
|
||||
#cx_teardowns
|
||||
},
|
||||
#on_failure_fn_name,
|
||||
@@ -167,10 +167,10 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
let cx_varname = format_ident!("cx_{}", ix);
|
||||
let cx_varname_lock = format_ident!("cx_{}_lock", ix);
|
||||
cx_vars.extend(quote!(
|
||||
let mut #cx_varname = gpui2::TestAppContext::new(
|
||||
let mut #cx_varname = gpui::TestAppContext::new(
|
||||
dispatcher.clone()
|
||||
);
|
||||
let mut #cx_varname_lock = #cx_varname.app.lock();
|
||||
let mut #cx_varname_lock = #cx_varname.app.borrow_mut();
|
||||
));
|
||||
inner_fn_args.extend(quote!(&mut #cx_varname_lock,));
|
||||
cx_teardowns.extend(quote!(
|
||||
@@ -182,7 +182,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
Some("TestAppContext") => {
|
||||
let cx_varname = format_ident!("cx_{}", ix);
|
||||
cx_vars.extend(quote!(
|
||||
let mut #cx_varname = gpui2::TestAppContext::new(
|
||||
let mut #cx_varname = gpui::TestAppContext::new(
|
||||
dispatcher.clone()
|
||||
);
|
||||
));
|
||||
@@ -209,7 +209,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
fn #outer_fn_name() {
|
||||
#inner_fn
|
||||
|
||||
gpui2::run_test(
|
||||
gpui::run_test(
|
||||
#num_iterations as u64,
|
||||
#max_retries,
|
||||
&mut |dispatcher, _seed| {
|
||||
|
||||
@@ -14,5 +14,5 @@ test-support = []
|
||||
smol.workspace = true
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
gpui2 = { path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
util = { path = "../util" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use gpui2::AsyncAppContext;
|
||||
use gpui::AsyncAppContext;
|
||||
use std::path::Path;
|
||||
use util::ResultExt;
|
||||
|
||||
@@ -7,9 +7,7 @@ use util::ResultExt;
|
||||
// actions!(cli, [Install]);
|
||||
|
||||
pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
|
||||
let cli_path = cx
|
||||
.run_on_main(|cx| cx.path_for_auxiliary_executable("cli"))?
|
||||
.await?;
|
||||
let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??;
|
||||
let link_path = Path::new("/usr/local/bin/zed");
|
||||
let bin_dir_path = link_path.parent().unwrap();
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
editor = { path = "../editor" }
|
||||
gpui2 = { path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
workspace2 = { path = "../workspace2" }
|
||||
settings2 = { path = "../settings2" }
|
||||
|
||||
anyhow.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use chrono::{Datelike, Local, NaiveTime, Timelike};
|
||||
use gpui2::AppContext;
|
||||
use gpui::AppContext;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings2::Settings;
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use workspace::AppState;
|
||||
use workspace2::AppState;
|
||||
// use zed::AppState;
|
||||
|
||||
// todo!();
|
||||
@@ -59,7 +59,7 @@ pub fn init(_: Arc<AppState>, cx: &mut AppContext) {
|
||||
// cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx));
|
||||
}
|
||||
|
||||
pub fn new_journal_entry(_: Arc<AppState>, cx: &mut AppContext) {
|
||||
pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
let settings = JournalSettings::get_global(cx);
|
||||
let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) {
|
||||
Some(journal_dir) => journal_dir,
|
||||
@@ -77,7 +77,7 @@ pub fn new_journal_entry(_: Arc<AppState>, cx: &mut AppContext) {
|
||||
let now = now.time();
|
||||
let _entry_heading = heading_entry(now, &settings.hour_format);
|
||||
|
||||
let _create_entry = cx.executor().spawn(async move {
|
||||
let create_entry = cx.background_executor().spawn(async move {
|
||||
std::fs::create_dir_all(month_dir)?;
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
@@ -86,37 +86,38 @@ pub fn new_journal_entry(_: Arc<AppState>, cx: &mut AppContext) {
|
||||
Ok::<_, std::io::Error>((journal_dir, entry_path))
|
||||
});
|
||||
|
||||
// todo!("workspace")
|
||||
// cx.spawn(|cx| async move {
|
||||
// let (journal_dir, entry_path) = create_entry.await?;
|
||||
// let (workspace, _) =
|
||||
// cx.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?;
|
||||
cx.spawn(|mut cx| async move {
|
||||
let (journal_dir, entry_path) = create_entry.await?;
|
||||
let (workspace, _) = cx
|
||||
.update(|cx| workspace2::open_paths(&[journal_dir], &app_state, None, cx))?
|
||||
.await?;
|
||||
|
||||
// let opened = workspace
|
||||
// .update(&mut cx, |workspace, cx| {
|
||||
// workspace.open_paths(vec![entry_path], true, cx)
|
||||
// })?
|
||||
// .await;
|
||||
let _opened = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_paths(vec![entry_path], true, cx)
|
||||
})?
|
||||
.await;
|
||||
|
||||
// if let Some(Some(Ok(item))) = opened.first() {
|
||||
// if let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) {
|
||||
// editor.update(&mut cx, |editor, cx| {
|
||||
// let len = editor.buffer().read(cx).len(cx);
|
||||
// editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
||||
// s.select_ranges([len..len])
|
||||
// });
|
||||
// if len > 0 {
|
||||
// editor.insert("\n\n", cx);
|
||||
// }
|
||||
// editor.insert(&entry_heading, cx);
|
||||
// editor.insert("\n\n", cx);
|
||||
// })?;
|
||||
// }
|
||||
// }
|
||||
// todo!("editor")
|
||||
// if let Some(Some(Ok(item))) = opened.first() {
|
||||
// if let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) {
|
||||
// editor.update(&mut cx, |editor, cx| {
|
||||
// let len = editor.buffer().read(cx).len(cx);
|
||||
// editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
||||
// s.select_ranges([len..len])
|
||||
// });
|
||||
// if len > 0 {
|
||||
// editor.insert("\n\n", cx);
|
||||
// }
|
||||
// editor.insert(&entry_heading, cx);
|
||||
// editor.insert("\n\n", cx);
|
||||
// })?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// anyhow::Ok(())
|
||||
// })
|
||||
// .detach_and_log_err(cx);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn journal_dir(path: &str) -> Option<PathBuf> {
|
||||
|
||||
@@ -11,28 +11,28 @@ doctest = false
|
||||
[features]
|
||||
test-support = [
|
||||
"rand",
|
||||
"client2/test-support",
|
||||
"client/test-support",
|
||||
"collections/test-support",
|
||||
"lsp2/test-support",
|
||||
"lsp/test-support",
|
||||
"text/test-support",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"settings2/test-support",
|
||||
"settings/test-support",
|
||||
"util/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
fuzzy2 = { path = "../fuzzy2" }
|
||||
git = { path = "../git" }
|
||||
gpui2 = { path = "../gpui2" }
|
||||
lsp2 = { path = "../lsp2" }
|
||||
rpc2 = { path = "../rpc2" }
|
||||
settings2 = { path = "../settings2" }
|
||||
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||
git = { package = "git3", path = "../git3" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
lsp = { package = "lsp2", path = "../lsp2" }
|
||||
rpc = { package = "rpc2", path = "../rpc2" }
|
||||
settings = { package = "settings2", path = "../settings2" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
text = { path = "../text" }
|
||||
theme2 = { path = "../theme2" }
|
||||
text = { package = "text2", path = "../text2" }
|
||||
theme = { package = "theme2", path = "../theme2" }
|
||||
util = { path = "../util" }
|
||||
|
||||
anyhow.workspace = true
|
||||
@@ -60,12 +60,12 @@ tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
client2 = { path = "../client2", features = ["test-support"] }
|
||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
||||
lsp2 = { path = "../lsp2", features = ["test-support"] }
|
||||
text = { path = "../text", features = ["test-support"] }
|
||||
settings2 = { path = "../settings2", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
||||
text = { package = "text2", path = "../text2", features = ["test-support"] }
|
||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
@@ -16,8 +16,8 @@ use crate::{
|
||||
use anyhow::{anyhow, Result};
|
||||
pub use clock::ReplicaId;
|
||||
use futures::FutureExt as _;
|
||||
use gpui2::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task};
|
||||
use lsp2::LanguageServerId;
|
||||
use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task};
|
||||
use lsp::LanguageServerId;
|
||||
use parking_lot::Mutex;
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use smallvec::SmallVec;
|
||||
@@ -40,7 +40,7 @@ use std::{
|
||||
use sum_tree::TreeMap;
|
||||
use text::operation_queue::OperationQueue;
|
||||
pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, *};
|
||||
use theme2::SyntaxTheme;
|
||||
use theme::SyntaxTheme;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use util::RandomCharIter;
|
||||
use util::{RangeExt, TryFutureExt as _};
|
||||
@@ -48,7 +48,7 @@ use util::{RangeExt, TryFutureExt as _};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use {tree_sitter_rust, tree_sitter_typescript};
|
||||
|
||||
pub use lsp2::DiagnosticSeverity;
|
||||
pub use lsp::DiagnosticSeverity;
|
||||
|
||||
pub struct Buffer {
|
||||
text: TextBuffer,
|
||||
@@ -149,14 +149,14 @@ pub struct Completion {
|
||||
pub new_text: String,
|
||||
pub label: CodeLabel,
|
||||
pub server_id: LanguageServerId,
|
||||
pub lsp_completion: lsp2::CompletionItem,
|
||||
pub lsp_completion: lsp::CompletionItem,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CodeAction {
|
||||
pub server_id: LanguageServerId,
|
||||
pub range: Range<Anchor>,
|
||||
pub lsp_action: lsp2::CodeAction,
|
||||
pub lsp_action: lsp::CodeAction,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -226,7 +226,7 @@ pub trait File: Send + Sync {
|
||||
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
fn to_proto(&self) -> rpc2::proto::File;
|
||||
fn to_proto(&self) -> rpc::proto::File;
|
||||
}
|
||||
|
||||
pub trait LocalFile: File {
|
||||
@@ -375,7 +375,7 @@ impl Buffer {
|
||||
file,
|
||||
);
|
||||
this.text.set_line_ending(proto::deserialize_line_ending(
|
||||
rpc2::proto::LineEnding::from_i32(message.line_ending)
|
||||
rpc::proto::LineEnding::from_i32(message.line_ending)
|
||||
.ok_or_else(|| anyhow!("missing line_ending"))?,
|
||||
));
|
||||
this.saved_version = proto::deserialize_version(&message.saved_version);
|
||||
@@ -434,7 +434,7 @@ impl Buffer {
|
||||
));
|
||||
|
||||
let text_operations = self.text.operations().clone();
|
||||
cx.spawn(|_| async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
let since = since.unwrap_or_default();
|
||||
operations.extend(
|
||||
text_operations
|
||||
@@ -652,7 +652,7 @@ impl Buffer {
|
||||
|
||||
if !self.is_dirty() {
|
||||
let reload = self.reload(cx).log_err().map(drop);
|
||||
task = cx.executor().spawn(reload);
|
||||
task = cx.background_executor().spawn(reload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -684,7 +684,7 @@ impl Buffer {
|
||||
let snapshot = self.snapshot();
|
||||
|
||||
let mut diff = self.git_diff.clone();
|
||||
let diff = cx.executor().spawn(async move {
|
||||
let diff = cx.background_executor().spawn(async move {
|
||||
diff.update(&diff_base, &snapshot).await;
|
||||
diff
|
||||
});
|
||||
@@ -793,7 +793,7 @@ impl Buffer {
|
||||
let mut syntax_snapshot = syntax_map.snapshot();
|
||||
drop(syntax_map);
|
||||
|
||||
let parse_task = cx.executor().spawn({
|
||||
let parse_task = cx.background_executor().spawn({
|
||||
let language = language.clone();
|
||||
let language_registry = language_registry.clone();
|
||||
async move {
|
||||
@@ -803,7 +803,7 @@ impl Buffer {
|
||||
});
|
||||
|
||||
match cx
|
||||
.executor()
|
||||
.background_executor()
|
||||
.block_with_timeout(self.sync_parse_timeout, parse_task)
|
||||
{
|
||||
Ok(new_syntax_snapshot) => {
|
||||
@@ -866,9 +866,9 @@ impl Buffer {
|
||||
|
||||
fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(indent_sizes) = self.compute_autoindents() {
|
||||
let indent_sizes = cx.executor().spawn(indent_sizes);
|
||||
let indent_sizes = cx.background_executor().spawn(indent_sizes);
|
||||
match cx
|
||||
.executor()
|
||||
.background_executor()
|
||||
.block_with_timeout(Duration::from_micros(500), indent_sizes)
|
||||
{
|
||||
Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx),
|
||||
@@ -1117,7 +1117,7 @@ impl Buffer {
|
||||
pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task<Diff> {
|
||||
let old_text = self.as_rope().clone();
|
||||
let base_version = self.version();
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
let old_text = old_text.to_string();
|
||||
let line_ending = LineEnding::detect(&new_text);
|
||||
LineEnding::normalize(&mut new_text);
|
||||
@@ -1155,7 +1155,7 @@ impl Buffer {
|
||||
let old_text = self.as_rope().clone();
|
||||
let line_ending = self.line_ending();
|
||||
let base_version = self.version();
|
||||
cx.executor().spawn(async move {
|
||||
cx.background_executor().spawn(async move {
|
||||
let ranges = trailing_whitespace_ranges(&old_text);
|
||||
let empty = Arc::<str>::from("");
|
||||
Diff {
|
||||
@@ -3003,14 +3003,14 @@ impl IndentSize {
|
||||
impl Completion {
|
||||
pub fn sort_key(&self) -> (usize, &str) {
|
||||
let kind_key = match self.lsp_completion.kind {
|
||||
Some(lsp2::CompletionItemKind::VARIABLE) => 0,
|
||||
Some(lsp::CompletionItemKind::VARIABLE) => 0,
|
||||
_ => 1,
|
||||
};
|
||||
(kind_key, &self.label.text[self.label.filter_range.clone()])
|
||||
}
|
||||
|
||||
pub fn is_snippet(&self) -> bool {
|
||||
self.lsp_completion.insert_text_format == Some(lsp2::InsertTextFormat::SNIPPET)
|
||||
self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ use crate::language_settings::{
|
||||
use crate::Buffer;
|
||||
use clock::ReplicaId;
|
||||
use collections::BTreeMap;
|
||||
use gpui2::{AppContext, Model};
|
||||
use gpui2::{Context, TestAppContext};
|
||||
use gpui::{AppContext, Model};
|
||||
use gpui::{Context, TestAppContext};
|
||||
use indoc::indoc;
|
||||
use proto::deserialize_operation;
|
||||
use rand::prelude::*;
|
||||
use regex::RegexBuilder;
|
||||
use settings2::SettingsStore;
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
env,
|
||||
ops::Range,
|
||||
@@ -38,8 +38,8 @@ fn init_logger() {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
fn test_line_endings(cx: &mut gpui2::AppContext) {
|
||||
#[gpui::test]
|
||||
fn test_line_endings(cx: &mut gpui::AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
cx.build_model(|cx| {
|
||||
@@ -63,7 +63,7 @@ fn test_line_endings(cx: &mut gpui2::AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_select_language() {
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
registry.add(Arc::new(Language::new(
|
||||
@@ -132,8 +132,8 @@ fn test_select_language() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
fn test_edit_events(cx: &mut gpui2::AppContext) {
|
||||
#[gpui::test]
|
||||
fn test_edit_events(cx: &mut gpui::AppContext) {
|
||||
let mut now = Instant::now();
|
||||
let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
|
||||
let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
|
||||
@@ -215,7 +215,7 @@ fn test_edit_events(cx: &mut gpui2::AppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
async fn test_apply_diff(cx: &mut TestAppContext) {
|
||||
let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
|
||||
let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
|
||||
@@ -238,8 +238,8 @@ async fn test_apply_diff(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 10)]
|
||||
async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) {
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
|
||||
let text = [
|
||||
"zero", //
|
||||
"one ", // 2 trailing spaces
|
||||
@@ -311,8 +311,8 @@ async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
async fn test_reparse(cx: &mut gpui2::TestAppContext) {
|
||||
#[gpui::test]
|
||||
async fn test_reparse(cx: &mut gpui::TestAppContext) {
|
||||
let text = "fn a() {}";
|
||||
let buffer = cx.build_model(|cx| {
|
||||
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
|
||||
@@ -440,8 +440,8 @@ async fn test_reparse(cx: &mut gpui2::TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
async fn test_resetting_language(cx: &mut gpui2::TestAppContext) {
|
||||
#[gpui::test]
|
||||
async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
|
||||
let buffer = cx.build_model(|cx| {
|
||||
let mut buffer =
|
||||
Buffer::new(0, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx);
|
||||
@@ -463,8 +463,8 @@ async fn test_resetting_language(cx: &mut gpui2::TestAppContext) {
|
||||
assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
async fn test_outline(cx: &mut gpui2::TestAppContext) {
|
||||
#[gpui::test]
|
||||
async fn test_outline(cx: &mut gpui::TestAppContext) {
|
||||
let text = r#"
|
||||
struct Person {
|
||||
name: String,
|
||||
@@ -556,10 +556,10 @@ async fn test_outline(cx: &mut gpui2::TestAppContext) {
|
||||
async fn search<'a>(
|
||||
outline: &'a Outline<Anchor>,
|
||||
query: &'a str,
|
||||
cx: &'a gpui2::TestAppContext,
|
||||
cx: &'a gpui::TestAppContext,
|
||||
) -> Vec<(&'a str, Vec<usize>)> {
|
||||
let matches = cx
|
||||
.update(|cx| outline.search(query, cx.executor().clone()))
|
||||
.update(|cx| outline.search(query, cx.background_executor().clone()))
|
||||
.await;
|
||||
matches
|
||||
.into_iter()
|
||||
@@ -568,8 +568,8 @@ async fn test_outline(cx: &mut gpui2::TestAppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
async fn test_outline_nodes_with_newlines(cx: &mut gpui2::TestAppContext) {
|
||||
#[gpui::test]
|
||||
async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
|
||||
let text = r#"
|
||||
impl A for B<
|
||||
C
|
||||
@@ -595,8 +595,8 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui2::TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
async fn test_outline_with_extra_context(cx: &mut gpui2::TestAppContext) {
|
||||
#[gpui::test]
|
||||
async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
|
||||
let language = javascript_lang()
|
||||
.with_outline_query(
|
||||
r#"
|
||||
@@ -643,8 +643,8 @@ async fn test_outline_with_extra_context(cx: &mut gpui2::TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
async fn test_symbols_containing(cx: &mut gpui2::TestAppContext) {
|
||||
#[gpui::test]
|
||||
async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
|
||||
let text = r#"
|
||||
impl Person {
|
||||
fn one() {
|
||||
@@ -731,7 +731,7 @@ async fn test_symbols_containing(cx: &mut gpui2::TestAppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
|
||||
let mut assert = |selection_text, range_markers| {
|
||||
assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
|
||||
@@ -847,7 +847,7 @@ fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) {
|
||||
let mut assert = |selection_text, bracket_pair_texts| {
|
||||
assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
|
||||
@@ -879,7 +879,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
|
||||
cx.build_model(|cx| {
|
||||
let text = "fn a() { b(|c| {}) }";
|
||||
@@ -918,7 +918,7 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -959,7 +959,7 @@ fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
|
||||
init_settings(cx, |settings| {
|
||||
settings.defaults.hard_tabs = Some(true);
|
||||
@@ -1002,7 +1002,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1143,7 +1143,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
|
||||
eprintln!("DONE");
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1205,7 +1205,7 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1262,7 +1262,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1280,7 +1280,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1322,7 +1322,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_block_mode(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1406,7 +1406,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1486,7 +1486,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1530,7 +1530,7 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
|
||||
init_settings(cx, |settings| {
|
||||
settings.languages.extend([
|
||||
@@ -1604,7 +1604,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
|
||||
init_settings(cx, |settings| {
|
||||
settings.defaults.tab_size = Some(2.try_into().unwrap());
|
||||
@@ -1649,7 +1649,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1738,7 +1738,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_language_scope_at_with_rust(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1806,7 +1806,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -1854,8 +1854,8 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
fn test_serialization(cx: &mut gpui2::AppContext) {
|
||||
#[gpui::test]
|
||||
fn test_serialization(cx: &mut gpui::AppContext) {
|
||||
let mut now = Instant::now();
|
||||
|
||||
let buffer1 = cx.build_model(|cx| {
|
||||
@@ -1879,7 +1879,7 @@ fn test_serialization(cx: &mut gpui2::AppContext) {
|
||||
|
||||
let state = buffer1.read(cx).to_proto();
|
||||
let ops = cx
|
||||
.executor()
|
||||
.background_executor()
|
||||
.block(buffer1.read(cx).serialize_ops(None, cx));
|
||||
let buffer2 = cx.build_model(|cx| {
|
||||
let mut buffer = Buffer::from_proto(1, state, None).unwrap();
|
||||
@@ -1895,7 +1895,7 @@ fn test_serialization(cx: &mut gpui2::AppContext) {
|
||||
assert_eq!(buffer2.read(cx).text(), "abcDF");
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 100)]
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
|
||||
let min_peers = env::var("MIN_PEERS")
|
||||
.map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
|
||||
@@ -1921,7 +1921,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
|
||||
let buffer = cx.build_model(|cx| {
|
||||
let state = base_buffer.read(cx).to_proto();
|
||||
let ops = cx
|
||||
.executor()
|
||||
.background_executor()
|
||||
.block(base_buffer.read(cx).serialize_ops(None, cx));
|
||||
let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap();
|
||||
buffer
|
||||
@@ -1943,6 +1943,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
|
||||
.detach();
|
||||
buffer
|
||||
});
|
||||
|
||||
buffers.push(buffer);
|
||||
replica_ids.push(i as ReplicaId);
|
||||
network.lock().add_peer(i as ReplicaId);
|
||||
@@ -2025,7 +2026,9 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
|
||||
}
|
||||
50..=59 if replica_ids.len() < max_peers => {
|
||||
let old_buffer_state = buffer.read(cx).to_proto();
|
||||
let old_buffer_ops = cx.executor().block(buffer.read(cx).serialize_ops(None, cx));
|
||||
let old_buffer_ops = cx
|
||||
.background_executor()
|
||||
.block(buffer.read(cx).serialize_ops(None, cx));
|
||||
let new_replica_id = (0..=replica_ids.len() as ReplicaId)
|
||||
.filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
|
||||
.choose(&mut rng)
|
||||
@@ -2196,7 +2199,7 @@ fn test_contiguous_ranges() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 500)]
|
||||
#[gpui::test(iterations = 500)]
|
||||
fn test_trailing_whitespace_ranges(mut rng: StdRng) {
|
||||
// Generate a random multi-line string containing
|
||||
// some lines with trailing whitespace.
|
||||
@@ -2397,7 +2400,7 @@ fn javascript_lang() -> Language {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui2::TestAppContext) -> String {
|
||||
fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
|
||||
buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::Diagnostic;
|
||||
use collections::HashMap;
|
||||
use lsp2::LanguageServerId;
|
||||
use lsp::LanguageServerId;
|
||||
use std::{
|
||||
cmp::{Ordering, Reverse},
|
||||
iter,
|
||||
@@ -37,14 +37,14 @@ pub struct Summary {
|
||||
|
||||
impl<T> DiagnosticEntry<T> {
|
||||
// Used to provide diagnostic context to lsp codeAction request
|
||||
pub fn to_lsp_diagnostic_stub(&self) -> lsp2::Diagnostic {
|
||||
pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
|
||||
let code = self
|
||||
.diagnostic
|
||||
.code
|
||||
.clone()
|
||||
.map(lsp2::NumberOrString::String);
|
||||
.map(lsp::NumberOrString::String);
|
||||
|
||||
lsp2::Diagnostic {
|
||||
lsp::Diagnostic {
|
||||
code,
|
||||
severity: Some(self.diagnostic.severity),
|
||||
..Default::default()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui2::HighlightStyle;
|
||||
use gpui::HighlightStyle;
|
||||
use std::sync::Arc;
|
||||
use theme2::SyntaxTheme;
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HighlightMap(Arc<[HighlightId]>);
|
||||
@@ -79,7 +79,7 @@ impl Default for HighlightId {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui2::rgba;
|
||||
use gpui::rgba;
|
||||
|
||||
#[test]
|
||||
fn test_highlight_map() {
|
||||
|
||||
@@ -17,10 +17,10 @@ use futures::{
|
||||
future::{BoxFuture, Shared},
|
||||
FutureExt, TryFutureExt as _,
|
||||
};
|
||||
use gpui2::{AppContext, AsyncAppContext, Executor, Task};
|
||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||
pub use highlight_map::HighlightMap;
|
||||
use lazy_static::lazy_static;
|
||||
use lsp2::{CodeActionKind, LanguageServerBinary};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
use regex::Regex;
|
||||
@@ -42,7 +42,7 @@ use std::{
|
||||
},
|
||||
};
|
||||
use syntax_map::SyntaxSnapshot;
|
||||
use theme2::{SyntaxTheme, Theme};
|
||||
use theme::{SyntaxTheme, ThemeVariant};
|
||||
use tree_sitter::{self, Query};
|
||||
use unicase::UniCase;
|
||||
use util::{http::HttpClient, paths::PathExt};
|
||||
@@ -51,7 +51,7 @@ use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
|
||||
pub use buffer::Operation;
|
||||
pub use buffer::*;
|
||||
pub use diagnostic_set::DiagnosticEntry;
|
||||
pub use lsp2::LanguageServerId;
|
||||
pub use lsp::LanguageServerId;
|
||||
pub use outline::{Outline, OutlineItem};
|
||||
pub use syntax_map::{OwnedSyntaxLayerInfo, SyntaxLayerInfo};
|
||||
pub use text::LineEnding;
|
||||
@@ -98,7 +98,7 @@ lazy_static! {
|
||||
}
|
||||
|
||||
pub trait ToLspPosition {
|
||||
fn to_lsp_position(self) -> lsp2::Position;
|
||||
fn to_lsp_position(self) -> lsp::Position;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
@@ -203,17 +203,17 @@ impl CachedLspAdapter {
|
||||
self.adapter.workspace_configuration(cx)
|
||||
}
|
||||
|
||||
pub fn process_diagnostics(&self, params: &mut lsp2::PublishDiagnosticsParams) {
|
||||
pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
|
||||
self.adapter.process_diagnostics(params)
|
||||
}
|
||||
|
||||
pub async fn process_completion(&self, completion_item: &mut lsp2::CompletionItem) {
|
||||
pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) {
|
||||
self.adapter.process_completion(completion_item).await
|
||||
}
|
||||
|
||||
pub async fn label_for_completion(
|
||||
&self,
|
||||
completion_item: &lsp2::CompletionItem,
|
||||
completion_item: &lsp::CompletionItem,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
self.adapter
|
||||
@@ -224,7 +224,7 @@ impl CachedLspAdapter {
|
||||
pub async fn label_for_symbol(
|
||||
&self,
|
||||
name: &str,
|
||||
kind: lsp2::SymbolKind,
|
||||
kind: lsp::SymbolKind,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
self.adapter.label_for_symbol(name, kind, language).await
|
||||
@@ -289,13 +289,13 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary>;
|
||||
|
||||
fn process_diagnostics(&self, _: &mut lsp2::PublishDiagnosticsParams) {}
|
||||
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||
|
||||
async fn process_completion(&self, _: &mut lsp2::CompletionItem) {}
|
||||
async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
_: &lsp2::CompletionItem,
|
||||
_: &lsp::CompletionItem,
|
||||
_: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
None
|
||||
@@ -304,7 +304,7 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
async fn label_for_symbol(
|
||||
&self,
|
||||
_: &str,
|
||||
_: lsp2::SymbolKind,
|
||||
_: lsp::SymbolKind,
|
||||
_: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
None
|
||||
@@ -476,8 +476,8 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D
|
||||
pub struct FakeLspAdapter {
|
||||
pub name: &'static str,
|
||||
pub initialization_options: Option<Value>,
|
||||
pub capabilities: lsp2::ServerCapabilities,
|
||||
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp2::FakeLanguageServer)>>,
|
||||
pub capabilities: lsp::ServerCapabilities,
|
||||
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
|
||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||
pub disk_based_diagnostics_sources: Vec<String>,
|
||||
pub prettier_plugins: Vec<&'static str>,
|
||||
@@ -532,7 +532,7 @@ pub struct Language {
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fake_adapter: Option<(
|
||||
mpsc::UnboundedSender<lsp2::FakeLanguageServer>,
|
||||
mpsc::UnboundedSender<lsp::FakeLanguageServer>,
|
||||
Arc<FakeLspAdapter>,
|
||||
)>,
|
||||
}
|
||||
@@ -631,7 +631,7 @@ pub struct LanguageRegistry {
|
||||
lsp_binary_paths: Mutex<
|
||||
HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
|
||||
>,
|
||||
executor: Option<Executor>,
|
||||
executor: Option<BackgroundExecutor>,
|
||||
lsp_binary_status_tx: LspBinaryStatusSender,
|
||||
}
|
||||
|
||||
@@ -642,14 +642,14 @@ struct LanguageRegistryState {
|
||||
next_available_language_id: AvailableLanguageId,
|
||||
loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
|
||||
subscription: (watch::Sender<()>, watch::Receiver<()>),
|
||||
theme: Option<Arc<Theme>>,
|
||||
theme: Option<Arc<ThemeVariant>>,
|
||||
version: usize,
|
||||
reload_count: usize,
|
||||
}
|
||||
|
||||
pub struct PendingLanguageServer {
|
||||
pub server_id: LanguageServerId,
|
||||
pub task: Task<Result<lsp2::LanguageServer>>,
|
||||
pub task: Task<Result<lsp::LanguageServer>>,
|
||||
pub container_dir: Option<Arc<Path>>,
|
||||
}
|
||||
|
||||
@@ -680,7 +680,7 @@ impl LanguageRegistry {
|
||||
Self::new(Task::ready(()))
|
||||
}
|
||||
|
||||
pub fn set_executor(&mut self, executor: Executor) {
|
||||
pub fn set_executor(&mut self, executor: BackgroundExecutor) {
|
||||
self.executor = Some(executor);
|
||||
}
|
||||
|
||||
@@ -743,11 +743,11 @@ impl LanguageRegistry {
|
||||
self.state.read().reload_count
|
||||
}
|
||||
|
||||
pub fn set_theme(&self, theme: Arc<Theme>) {
|
||||
pub fn set_theme(&self, theme: Arc<ThemeVariant>) {
|
||||
let mut state = self.state.write();
|
||||
state.theme = Some(theme.clone());
|
||||
for language in &state.languages {
|
||||
language.set_theme(&theme.syntax);
|
||||
language.set_theme(&theme.syntax());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,7 +905,7 @@ impl LanguageRegistry {
|
||||
if language.fake_adapter.is_some() {
|
||||
let task = cx.spawn(|cx| async move {
|
||||
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
||||
let (server, mut fake_server) = lsp2::LanguageServer::fake(
|
||||
let (server, mut fake_server) = lsp::LanguageServer::fake(
|
||||
fake_adapter.name.to_string(),
|
||||
fake_adapter.capabilities.clone(),
|
||||
cx.clone(),
|
||||
@@ -916,10 +916,10 @@ impl LanguageRegistry {
|
||||
}
|
||||
|
||||
let servers_tx = servers_tx.clone();
|
||||
cx.executor()
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
if fake_server
|
||||
.try_receive_notification::<lsp2::notification::Initialized>()
|
||||
.try_receive_notification::<lsp::notification::Initialized>()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
@@ -988,7 +988,7 @@ impl LanguageRegistry {
|
||||
task.await?;
|
||||
}
|
||||
|
||||
lsp2::LanguageServer::new(
|
||||
lsp::LanguageServer::new(
|
||||
stderr_capture,
|
||||
server_id,
|
||||
binary,
|
||||
@@ -1048,7 +1048,7 @@ impl LanguageRegistryState {
|
||||
|
||||
fn add(&mut self, language: Arc<Language>) {
|
||||
if let Some(theme) = self.theme.as_ref() {
|
||||
language.set_theme(&theme.syntax);
|
||||
language.set_theme(&theme.syntax());
|
||||
}
|
||||
self.languages.push(language);
|
||||
self.version += 1;
|
||||
@@ -1471,7 +1471,7 @@ impl Language {
|
||||
pub async fn set_fake_lsp_adapter(
|
||||
&mut self,
|
||||
fake_lsp_adapter: Arc<FakeLspAdapter>,
|
||||
) -> mpsc::UnboundedReceiver<lsp2::FakeLanguageServer> {
|
||||
) -> mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
let (servers_tx, servers_rx) = mpsc::unbounded();
|
||||
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
|
||||
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
|
||||
@@ -1501,7 +1501,7 @@ impl Language {
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp2::CompletionItem) {
|
||||
pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
|
||||
for adapter in &self.adapters {
|
||||
adapter.process_completion(completion).await;
|
||||
}
|
||||
@@ -1509,7 +1509,7 @@ impl Language {
|
||||
|
||||
pub async fn label_for_completion(
|
||||
self: &Arc<Self>,
|
||||
completion: &lsp2::CompletionItem,
|
||||
completion: &lsp::CompletionItem,
|
||||
) -> Option<CodeLabel> {
|
||||
self.adapters
|
||||
.first()
|
||||
@@ -1521,7 +1521,7 @@ impl Language {
|
||||
pub async fn label_for_symbol(
|
||||
self: &Arc<Self>,
|
||||
name: &str,
|
||||
kind: lsp2::SymbolKind,
|
||||
kind: lsp::SymbolKind,
|
||||
) -> Option<CodeLabel> {
|
||||
self.adapters
|
||||
.first()
|
||||
@@ -1745,7 +1745,7 @@ impl Default for FakeLspAdapter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "the-fake-language-server",
|
||||
capabilities: lsp2::LanguageServer::full_capabilities(),
|
||||
capabilities: lsp::LanguageServer::full_capabilities(),
|
||||
initializer: None,
|
||||
disk_based_diagnostics_progress_token: None,
|
||||
initialization_options: None,
|
||||
@@ -1794,7 +1794,7 @@ impl LspAdapter for Arc<FakeLspAdapter> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn process_diagnostics(&self, _: &mut lsp2::PublishDiagnosticsParams) {}
|
||||
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||
|
||||
async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
|
||||
self.disk_based_diagnostics_sources.clone()
|
||||
@@ -1824,22 +1824,22 @@ fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option<u32>)])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn point_to_lsp(point: PointUtf16) -> lsp2::Position {
|
||||
lsp2::Position::new(point.row, point.column)
|
||||
pub fn point_to_lsp(point: PointUtf16) -> lsp::Position {
|
||||
lsp::Position::new(point.row, point.column)
|
||||
}
|
||||
|
||||
pub fn point_from_lsp(point: lsp2::Position) -> Unclipped<PointUtf16> {
|
||||
pub fn point_from_lsp(point: lsp::Position) -> Unclipped<PointUtf16> {
|
||||
Unclipped(PointUtf16::new(point.line, point.character))
|
||||
}
|
||||
|
||||
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp2::Range {
|
||||
lsp2::Range {
|
||||
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
|
||||
lsp::Range {
|
||||
start: point_to_lsp(range.start),
|
||||
end: point_to_lsp(range.end),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range_from_lsp(range: lsp2::Range) -> Range<Unclipped<PointUtf16>> {
|
||||
pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> {
|
||||
let mut start = point_from_lsp(range.start);
|
||||
let mut end = point_from_lsp(range.end);
|
||||
if start > end {
|
||||
@@ -1851,9 +1851,9 @@ pub fn range_from_lsp(range: lsp2::Range) -> Range<Unclipped<PointUtf16>> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui2::TestAppContext;
|
||||
use gpui::TestAppContext;
|
||||
|
||||
#[gpui2::test(iterations = 10)]
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_first_line_pattern(cx: &mut TestAppContext) {
|
||||
let mut languages = LanguageRegistry::test();
|
||||
|
||||
@@ -1891,7 +1891,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 10)]
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_language_loading(cx: &mut TestAppContext) {
|
||||
let mut languages = LanguageRegistry::test();
|
||||
languages.set_executor(cx.executor().clone());
|
||||
|
||||
@@ -2,13 +2,13 @@ use crate::{File, Language};
|
||||
use anyhow::Result;
|
||||
use collections::{HashMap, HashSet};
|
||||
use globset::GlobMatcher;
|
||||
use gpui2::AppContext;
|
||||
use gpui::AppContext;
|
||||
use schemars::{
|
||||
schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
|
||||
JsonSchema,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings2::Settings;
|
||||
use settings::Settings;
|
||||
use std::{num::NonZeroU32, path::Path, sync::Arc};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
@@ -255,7 +255,7 @@ impl InlayHintKind {
|
||||
}
|
||||
}
|
||||
|
||||
impl settings2::Settings for AllLanguageSettings {
|
||||
impl settings::Settings for AllLanguageSettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
type FileContent = AllLanguageSettingsContent;
|
||||
@@ -332,7 +332,7 @@ impl settings2::Settings for AllLanguageSettings {
|
||||
|
||||
fn json_schema(
|
||||
generator: &mut schemars::gen::SchemaGenerator,
|
||||
params: &settings2::SettingsJsonSchemaParams,
|
||||
params: &settings::SettingsJsonSchemaParams,
|
||||
_: &AppContext,
|
||||
) -> schemars::schema::RootSchema {
|
||||
let mut root_schema = generator.root_schema_for::<Self::FileContent>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use fuzzy2::{StringMatch, StringMatchCandidate};
|
||||
use gpui2::{Executor, HighlightStyle};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{BackgroundExecutor, HighlightStyle};
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -57,11 +57,11 @@ impl<T> Outline<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn search(&self, query: &str, executor: Executor) -> Vec<StringMatch> {
|
||||
pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec<StringMatch> {
|
||||
let query = query.trim_start();
|
||||
let is_path_query = query.contains(' ');
|
||||
let smart_case = query.chars().any(|c| c.is_uppercase());
|
||||
let mut matches = fuzzy2::match_strings(
|
||||
let mut matches = fuzzy::match_strings(
|
||||
if is_path_query {
|
||||
&self.path_candidates
|
||||
} else {
|
||||
|
||||
@@ -4,8 +4,8 @@ use crate::{
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clock::ReplicaId;
|
||||
use lsp2::{DiagnosticSeverity, LanguageServerId};
|
||||
use rpc2::proto;
|
||||
use lsp::{DiagnosticSeverity, LanguageServerId};
|
||||
use rpc::proto;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use text::*;
|
||||
|
||||
|
||||
@@ -234,7 +234,6 @@ impl SyntaxMap {
|
||||
self.snapshot.interpolate(text);
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // todo!()
|
||||
#[cfg(test)]
|
||||
pub fn reparse(&mut self, language: Arc<Language>, text: &BufferSnapshot) {
|
||||
self.snapshot
|
||||
@@ -786,7 +785,6 @@ impl SyntaxSnapshot {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // todo!()
|
||||
#[cfg(test)]
|
||||
pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayerInfo> {
|
||||
self.layers_for_range(0..buffer.len(), buffer).collect()
|
||||
|
||||
@@ -78,7 +78,7 @@ fn test_splice_included_ranges() {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_syntax_map_layers_for_range() {
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let language = Arc::new(rust_lang());
|
||||
@@ -175,7 +175,7 @@ fn test_syntax_map_layers_for_range() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_dynamic_language_injection() {
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let markdown = Arc::new(markdown_lang());
|
||||
@@ -253,7 +253,7 @@ fn test_dynamic_language_injection() {
|
||||
assert!(!syntax_map.contains_unknown_injections());
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_typing_multiple_new_injections() {
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -282,7 +282,7 @@ fn test_typing_multiple_new_injections() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_pasting_new_injection_line_between_others() {
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -329,7 +329,7 @@ fn test_pasting_new_injection_line_between_others() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_joining_injections_with_child_injections() {
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -373,7 +373,7 @@ fn test_joining_injections_with_child_injections() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_editing_edges_of_injection() {
|
||||
test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -402,7 +402,7 @@ fn test_editing_edges_of_injection() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_edits_preceding_and_intersecting_injection() {
|
||||
test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -414,7 +414,7 @@ fn test_edits_preceding_and_intersecting_injection() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_non_local_changes_create_injections() {
|
||||
test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -433,7 +433,7 @@ fn test_non_local_changes_create_injections() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_creating_many_injections_in_one_edit() {
|
||||
test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -463,7 +463,7 @@ fn test_creating_many_injections_in_one_edit() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_editing_across_injection_boundary() {
|
||||
test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -491,7 +491,7 @@ fn test_editing_across_injection_boundary() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_removing_injection_by_replacing_across_boundary() {
|
||||
test_edit_sequence(
|
||||
"Rust",
|
||||
@@ -517,7 +517,7 @@ fn test_removing_injection_by_replacing_across_boundary() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_simple() {
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"ERB",
|
||||
@@ -564,7 +564,7 @@ fn test_combined_injections_simple() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_empty_ranges() {
|
||||
test_edit_sequence(
|
||||
"ERB",
|
||||
@@ -582,7 +582,7 @@ fn test_combined_injections_empty_ranges() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_edit_edges_of_ranges() {
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"ERB",
|
||||
@@ -613,7 +613,7 @@ fn test_combined_injections_edit_edges_of_ranges() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_splitting_some_injections() {
|
||||
let (_buffer, _syntax_map) = test_edit_sequence(
|
||||
"ERB",
|
||||
@@ -638,7 +638,7 @@ fn test_combined_injections_splitting_some_injections() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_editing_after_last_injection() {
|
||||
test_edit_sequence(
|
||||
"ERB",
|
||||
@@ -658,7 +658,7 @@ fn test_combined_injections_editing_after_last_injection() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_inside_injections() {
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"Markdown",
|
||||
@@ -734,7 +734,7 @@ fn test_combined_injections_inside_injections() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test]
|
||||
#[gpui::test]
|
||||
fn test_empty_combined_injections_inside_injections() {
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"Markdown",
|
||||
@@ -762,7 +762,7 @@ fn test_empty_combined_injections_inside_injections() {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 50)]
|
||||
#[gpui::test(iterations = 50)]
|
||||
fn test_random_syntax_map_edits_rust_macros(rng: StdRng) {
|
||||
let text = r#"
|
||||
fn test_something() {
|
||||
@@ -788,7 +788,7 @@ fn test_random_syntax_map_edits_rust_macros(rng: StdRng) {
|
||||
test_random_edits(text, registry, language, rng);
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 50)]
|
||||
#[gpui::test(iterations = 50)]
|
||||
fn test_random_syntax_map_edits_with_erb(rng: StdRng) {
|
||||
let text = r#"
|
||||
<div id="main">
|
||||
@@ -817,7 +817,7 @@ fn test_random_syntax_map_edits_with_erb(rng: StdRng) {
|
||||
test_random_edits(text, registry, language, rng);
|
||||
}
|
||||
|
||||
#[gpui2::test(iterations = 50)]
|
||||
#[gpui::test(iterations = 50)]
|
||||
fn test_random_syntax_map_edits_with_heex(rng: StdRng) {
|
||||
let text = r#"
|
||||
defmodule TheModule do
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "ce20dc083ee485524b802669890291c0d8090170",
|
||||
"version": "1.22.1"
|
||||
"revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e",
|
||||
"version": "1.21.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
2
crates/live_kit_client2/.cargo/config.toml
Normal file
2
crates/live_kit_client2/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[live_kit_client_test]
|
||||
rustflags = ["-C", "link-args=-ObjC"]
|
||||
71
crates/live_kit_client2/Cargo.toml
Normal file
71
crates/live_kit_client2/Cargo.toml
Normal file
@@ -0,0 +1,71 @@
|
||||
[package]
|
||||
name = "live_kit_client2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Bindings to LiveKit Swift client SDK"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/live_kit_client2.rs"
|
||||
doctest = false
|
||||
|
||||
[[example]]
|
||||
name = "test_app"
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"async-trait",
|
||||
"collections/test-support",
|
||||
"gpui2/test-support",
|
||||
"live_kit_server",
|
||||
"nanoid",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections", optional = true }
|
||||
gpui2 = { package = "gpui2", path = "../gpui2", optional = true }
|
||||
live_kit_server = { path = "../live_kit_server", optional = true }
|
||||
media = { path = "../media" }
|
||||
|
||||
anyhow.workspace = true
|
||||
async-broadcast = "0.4"
|
||||
core-foundation = "0.9.3"
|
||||
core-graphics = "0.22.3"
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
|
||||
async-trait = { workspace = true, optional = true }
|
||||
nanoid = { version ="0.4", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui2 = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
live_kit_server = { path = "../live_kit_server" }
|
||||
media = { path = "../media" }
|
||||
nanoid = "0.4"
|
||||
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
block = "0.1"
|
||||
bytes = "1.2"
|
||||
byteorder = "1.4"
|
||||
cocoa = "0.24"
|
||||
core-foundation = "0.9.3"
|
||||
core-graphics = "0.22.3"
|
||||
foreign-types = "0.3"
|
||||
futures.workspace = true
|
||||
hmac = "0.12"
|
||||
jwt = "0.16"
|
||||
objc = "0.2"
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
sha2 = "0.10"
|
||||
simplelog = "0.9"
|
||||
|
||||
[build-dependencies]
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
52
crates/live_kit_client2/LiveKitBridge2/Package.resolved
Normal file
52
crates/live_kit_client2/LiveKitBridge2/Package.resolved
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "LiveKit",
|
||||
"repositoryURL": "https://github.com/livekit/client-sdk-swift.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "7331b813a5ab8a95cfb81fb2b4ed10519428b9ff",
|
||||
"version": "1.0.12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Promises",
|
||||
"repositoryURL": "https://github.com/google/promises.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a",
|
||||
"version": "2.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "WebRTC",
|
||||
"repositoryURL": "https://github.com/webrtc-sdk/Specs.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "2f6bab30c8df0fe59ab3e58bc99097f757f85f65",
|
||||
"version": "104.5112.17"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-log",
|
||||
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "32e8d724467f8fe623624570367e3d50c5638e46",
|
||||
"version": "1.5.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftProtobuf",
|
||||
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "ce20dc083ee485524b802669890291c0d8090170",
|
||||
"version": "1.22.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user