Compare commits
19 Commits
gpui3
...
add-compon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8103b9ffa | ||
|
|
7719afeafa | ||
|
|
27b25100c9 | ||
|
|
4d16a3a2a8 | ||
|
|
d36230e46e | ||
|
|
8dddbf31bf | ||
|
|
0c8bb20424 | ||
|
|
5edb86511b | ||
|
|
7e75244620 | ||
|
|
175290c43a | ||
|
|
27c7541e5b | ||
|
|
ae9f51d0e0 | ||
|
|
f609429ac4 | ||
|
|
7cbf651cf9 | ||
|
|
81475ef182 | ||
|
|
9ce110f9ad | ||
|
|
f500de9c3b | ||
|
|
9d810a44fd | ||
|
|
3248bb9172 |
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -129,9 +129,8 @@ jobs:
|
||||
run: |
|
||||
cargo build --workspace --bins --all-features
|
||||
cargo check -p gpui --features "macos-blade"
|
||||
cargo check -p workspace
|
||||
cargo check -p workspace --features "livekit-cross-platform"
|
||||
cargo build -p remote_server
|
||||
script/check-rust-livekit-macos
|
||||
|
||||
linux_tests:
|
||||
timeout-minutes: 60
|
||||
@@ -163,10 +162,8 @@ jobs:
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
- name: Build other binaries and features
|
||||
run: |
|
||||
cargo build -p zed
|
||||
cargo check -p workspace
|
||||
- name: Build Zed
|
||||
run: cargo build -p zed
|
||||
|
||||
build_remote_server:
|
||||
timeout-minutes: 60
|
||||
|
||||
8
.github/workflows/deploy_cloudflare.yml
vendored
8
.github/workflows/deploy_cloudflare.yml
vendored
@@ -37,28 +37,28 @@ jobs:
|
||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||
|
||||
- name: Deploy Docs
|
||||
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy target/deploy --project-name=docs
|
||||
|
||||
- name: Deploy Install
|
||||
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
||||
|
||||
- name: Deploy Docs Workers
|
||||
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||
|
||||
- name: Deploy Install Workers
|
||||
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
|
||||
46
Cargo.lock
generated
46
Cargo.lock
generated
@@ -2862,6 +2862,29 @@ dependencies = [
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "component_preview"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"component_system",
|
||||
"gpui",
|
||||
"strum 0.25.0",
|
||||
"ui",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "component_system"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
"lazy_static",
|
||||
"linkme",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@@ -3480,9 +3503,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.9"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
@@ -3957,7 +3980,6 @@ dependencies = [
|
||||
"unindent",
|
||||
"url",
|
||||
"util",
|
||||
"uuid",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
@@ -7422,7 +7444,6 @@ dependencies = [
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
@@ -8579,9 +8600,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
|
||||
|
||||
[[package]]
|
||||
name = "pathfinder_geometry"
|
||||
@@ -10695,9 +10716,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.7"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519"
|
||||
checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
@@ -13811,9 +13832,14 @@ name = "ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"collections",
|
||||
"component_system",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
"lazy_static",
|
||||
"linkme",
|
||||
"menu",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
@@ -13839,6 +13865,7 @@ dependencies = [
|
||||
name = "ui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"component_system",
|
||||
"convert_case 0.6.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -15622,6 +15649,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"component_system",
|
||||
"db",
|
||||
"derive_more",
|
||||
"env_logger 0.11.5",
|
||||
@@ -16053,6 +16081,7 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"component_preview",
|
||||
"copilot",
|
||||
"db",
|
||||
"diagnostics",
|
||||
@@ -16085,7 +16114,6 @@ dependencies = [
|
||||
"languages",
|
||||
"libc",
|
||||
"log",
|
||||
"markdown",
|
||||
"markdown_preview",
|
||||
"menu",
|
||||
"mimalloc",
|
||||
|
||||
@@ -21,6 +21,8 @@ members = [
|
||||
"crates/collab",
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
"crates/component_preview",
|
||||
"crates/component_system",
|
||||
"crates/command_palette",
|
||||
"crates/command_palette_hooks",
|
||||
"crates/context_server",
|
||||
@@ -207,6 +209,8 @@ clock = { path = "crates/clock" }
|
||||
collab = { path = "crates/collab" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
component_system = { path = "crates/component_system" }
|
||||
component_preview = { path = "crates/component_preview" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
context_server = { path = "crates/context_server" }
|
||||
@@ -393,9 +397,11 @@ itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { version = "0.5.0" }
|
||||
jupyter-websocket-client = { version = "0.8.0" }
|
||||
lazy_static = "1.5.0"
|
||||
libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
linkme = "0.3"
|
||||
livekit = { git = "https://github.com/zed-industries/rust-sdks", rev="799f10133d93ba2a88642cd480d01ec4da53408c", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
@@ -506,7 +512,7 @@ unindent = "0.1.7"
|
||||
unicode-segmentation = "1.10"
|
||||
unicode-script = "0.5.7"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
||||
wasmparser = "0.215"
|
||||
wasm-encoder = "0.215"
|
||||
wasmtime = { version = "24", default-features = false, features = [
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[
|
||||
// Standard macOS bindings
|
||||
{
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "menu::SelectPrev",
|
||||
"shift-tab": "menu::SelectPrev",
|
||||
@@ -41,7 +40,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"backspace": "editor::Backspace",
|
||||
@@ -133,7 +131,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"shift-enter": "editor::Newline",
|
||||
@@ -151,23 +148,20 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full && inline_completion",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-tab": "editor::NextInlineCompletion",
|
||||
"alt-shift-tab": "editor::PreviousInlineCompletion",
|
||||
"alt-]": "editor::NextInlineCompletion",
|
||||
"alt-[": "editor::PreviousInlineCompletion",
|
||||
"ctrl-right": "editor::AcceptPartialInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !inline_completion",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-tab": "editor::ShowInlineCompletion"
|
||||
"alt-\\": "editor::ShowInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == auto_height",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-enter": "editor::Newline",
|
||||
"shift-enter": "editor::Newline",
|
||||
@@ -176,14 +170,12 @@
|
||||
},
|
||||
{
|
||||
"context": "Markdown",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-c": "markdown::Copy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && jupyter && !ContextEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-enter": "repl::Run",
|
||||
"ctrl-alt-enter": "repl::RunInPlace"
|
||||
@@ -191,7 +183,6 @@
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k c": "assistant::CopyCode",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
@@ -204,7 +195,6 @@
|
||||
},
|
||||
{
|
||||
"context": "ContextEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-enter": "assistant::Assist",
|
||||
"cmd-shift-enter": "assistant::Edit",
|
||||
@@ -219,7 +209,6 @@
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel2",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "assistant2::NewThread",
|
||||
"cmd-shift-h": "assistant2::OpenHistory"
|
||||
@@ -227,14 +216,12 @@
|
||||
},
|
||||
{
|
||||
"context": "MessageEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-enter": "assistant2::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptLibrary",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "prompt_library::NewPrompt",
|
||||
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
|
||||
@@ -243,7 +230,6 @@
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "buffer_search::Dismiss",
|
||||
"tab": "buffer_search::FocusEditor",
|
||||
@@ -257,7 +243,6 @@
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && in_replace > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "search::ReplaceNext",
|
||||
"cmd-enter": "search::ReplaceAll"
|
||||
@@ -265,7 +250,6 @@
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "search::PreviousHistoryQuery",
|
||||
"down": "search::NextHistoryQuery"
|
||||
@@ -273,7 +257,6 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchBar",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "project_search::ToggleFocus",
|
||||
"cmd-shift-j": "project_search::ToggleFilters",
|
||||
@@ -285,7 +268,6 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchBar > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "search::PreviousHistoryQuery",
|
||||
"down": "search::NextHistoryQuery"
|
||||
@@ -293,7 +275,6 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchBar && in_replace > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "search::ReplaceNext",
|
||||
"cmd-enter": "search::ReplaceAll"
|
||||
@@ -301,7 +282,6 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchView",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "project_search::ToggleFocus",
|
||||
"cmd-shift-j": "project_search::ToggleFilters",
|
||||
@@ -312,7 +292,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-{": "pane::ActivatePrevItem",
|
||||
"cmd-}": "pane::ActivateNextItem",
|
||||
@@ -341,7 +320,6 @@
|
||||
// Bindings from VS Code
|
||||
{
|
||||
"context": "Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-[": "editor::Outdent",
|
||||
"cmd-]": "editor::Indent",
|
||||
@@ -405,7 +383,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-o": "outline::Toggle",
|
||||
"ctrl-g": "go_to_line::Toggle"
|
||||
@@ -413,7 +390,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-1": ["pane::ActivateItem", 0],
|
||||
"ctrl-2": ["pane::ActivateItem", 1],
|
||||
@@ -433,7 +409,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
// Change the default action on `menu::Confirm` by setting the parameter
|
||||
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
|
||||
@@ -489,7 +464,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Workspace && !Terminal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-r": "task::Spawn",
|
||||
"cmd-alt-r": "task::Rerun",
|
||||
@@ -500,7 +474,6 @@
|
||||
// Bindings from Sublime Text
|
||||
{
|
||||
"context": "Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-j": "editor::JoinLines",
|
||||
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||
@@ -520,7 +493,6 @@
|
||||
// Bindings from Atom
|
||||
{
|
||||
"context": "Pane",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k up": "pane::SplitUp",
|
||||
"cmd-k down": "pane::SplitDown",
|
||||
@@ -531,14 +503,12 @@
|
||||
// Bindings that should be unified with bindings for more general actions
|
||||
{
|
||||
"context": "Editor && renaming",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmRename"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
@@ -546,21 +516,18 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor && inline_completion && !showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"tab": "editor::AcceptInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_code_actions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCodeAction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "editor::ContextMenuPrev",
|
||||
"ctrl-p": "editor::ContextMenuPrev",
|
||||
@@ -572,7 +539,6 @@
|
||||
},
|
||||
// Custom bindings
|
||||
{
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
||||
// TODO: Move this to a dock open action
|
||||
@@ -583,7 +549,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"shift-enter": "editor::ExpandExcerpts",
|
||||
@@ -595,7 +560,6 @@
|
||||
},
|
||||
{
|
||||
"context": "ProposedChangesEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-y": "editor::ApplyDiffHunk",
|
||||
"cmd-shift-a": "editor::ApplyAllDiffHunks"
|
||||
@@ -603,7 +567,6 @@
|
||||
},
|
||||
{
|
||||
"context": "PromptEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||
@@ -611,14 +574,12 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchBar && !in_replace",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-enter": "project_search::SearchInNew"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "OutlinePanel && not_editing",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel",
|
||||
"left": "outline_panel::CollapseSelectedEntry",
|
||||
@@ -635,7 +596,6 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"left": "project_panel::CollapseSelectedEntry",
|
||||
"right": "project_panel::ExpandSelectedEntry",
|
||||
@@ -665,14 +625,12 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel && not_editing",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"space": "project_panel::Open"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "CollabPanel && not_editing",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-backspace": "collab_panel::Remove",
|
||||
"space": "menu::Confirm"
|
||||
@@ -680,21 +638,18 @@
|
||||
},
|
||||
{
|
||||
"context": "(CollabPanel && editing) > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"space": "collab_panel::InsertSpace"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ChannelModal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"tab": "channel_modal::ToggleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Picker > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"tab": "picker::ConfirmCompletion",
|
||||
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
|
||||
@@ -703,21 +658,18 @@
|
||||
},
|
||||
{
|
||||
"context": "ChannelModal > Picker > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"tab": "channel_modal::ToggleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd": "file_finder::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && !menu_open",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-p": "file_finder::SelectPrev",
|
||||
"cmd-j": "pane::SplitDown",
|
||||
@@ -728,7 +680,6 @@
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && menu_open",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"j": "pane::SplitDown",
|
||||
"k": "pane::SplitUp",
|
||||
@@ -738,7 +689,6 @@
|
||||
},
|
||||
{
|
||||
"context": "TabSwitcher",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-up": "menu::SelectPrev",
|
||||
"ctrl-down": "menu::SelectNext",
|
||||
@@ -748,7 +698,6 @@
|
||||
},
|
||||
{
|
||||
"context": "Terminal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-cmd-space": "terminal::ShowCharacterPalette",
|
||||
"cmd-c": "terminal::Copy",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"context": "VimControl && !menu",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
@@ -187,6 +188,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == normal",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
@@ -241,6 +243,7 @@
|
||||
},
|
||||
{
|
||||
"context": "VimControl && VimCount",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"0": ["vim::Number", 0],
|
||||
":": "vim::CountCommand"
|
||||
@@ -248,6 +251,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == visual",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "vim::VisualCommand",
|
||||
"u": "vim::ConvertToLowerCase",
|
||||
@@ -297,6 +301,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
@@ -326,7 +331,6 @@
|
||||
"bindings": {
|
||||
"i": "vim::InsertBefore",
|
||||
"a": "vim::InsertAfter",
|
||||
"d": "vim::HelixDelete",
|
||||
"w": "vim::NextWordStart",
|
||||
"e": "vim::NextWordEnd",
|
||||
"b": "vim::PreviousWordStart",
|
||||
@@ -340,6 +344,7 @@
|
||||
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
@@ -347,6 +352,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
@@ -364,6 +370,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == waiting",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
@@ -377,6 +384,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == operator",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
@@ -386,6 +394,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||
@@ -416,6 +425,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == c",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
"d": "editor::Rename", // zed specific
|
||||
@@ -424,6 +434,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == d",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||
@@ -433,6 +444,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gu",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g u": "vim::CurrentLine",
|
||||
"u": "vim::CurrentLine"
|
||||
@@ -440,6 +452,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gU",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g shift-u": "vim::CurrentLine",
|
||||
"shift-u": "vim::CurrentLine"
|
||||
@@ -447,6 +460,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == g~",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g ~": "vim::CurrentLine",
|
||||
"~": "vim::CurrentLine"
|
||||
@@ -454,6 +468,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gq",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g q": "vim::CurrentLine",
|
||||
"q": "vim::CurrentLine",
|
||||
@@ -463,6 +478,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == y",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
|
||||
@@ -470,36 +486,42 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == ys",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"s": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == >",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
">": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == <",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"<": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == eq",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"=": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gc",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == literal",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"ctrl-@": ["vim::Literal", ["ctrl-@", "\u0000"]],
|
||||
"ctrl-a": ["vim::Literal", ["ctrl-a", "\u0001"]],
|
||||
@@ -543,6 +565,7 @@
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
@@ -550,6 +573,7 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
// window related commands (ctrl-w X)
|
||||
"ctrl-w": null,
|
||||
@@ -606,6 +630,7 @@
|
||||
},
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
@@ -614,6 +639,7 @@
|
||||
{
|
||||
// netrw compatibility
|
||||
"context": "ProjectPanel && not_editing",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"%": "project_panel::NewFile",
|
||||
@@ -647,6 +673,7 @@
|
||||
},
|
||||
{
|
||||
"context": "OutlinePanel && not_editing",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"j": "menu::SelectNext",
|
||||
"k": "menu::SelectPrev",
|
||||
|
||||
@@ -569,8 +569,7 @@
|
||||
// "Neighbour"
|
||||
"activate_on_close": "history",
|
||||
/// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||
/// Diagnostics are only shown when file icons are also active.
|
||||
/// This setting only works when can take the following three values:
|
||||
/// This setting can take the following three values:
|
||||
///
|
||||
/// 1. Do not mark any files:
|
||||
/// "off"
|
||||
@@ -578,7 +577,7 @@
|
||||
/// "errors"
|
||||
/// 3. Mark files with errors and warnings:
|
||||
/// "all"
|
||||
"show_diagnostics": "off"
|
||||
"show_diagnostics": "all"
|
||||
},
|
||||
// Settings related to preview tabs.
|
||||
"preview_tabs": {
|
||||
|
||||
@@ -3,9 +3,9 @@ use editor::Editor;
|
||||
use extension_host::ExtensionStore;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, percentage, Animation, AnimationExt as _, AppContext, AppContext, CursorStyle,
|
||||
EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Transformation, View, VisualContext as _,
|
||||
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
|
||||
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
|
||||
use lsp::LanguageServerName;
|
||||
@@ -46,38 +46,34 @@ struct PendingWork<'a> {
|
||||
struct Content {
|
||||
icon: Option<gpui::AnyElement>,
|
||||
message: String,
|
||||
on_click:
|
||||
Option<Arc<dyn Fn(&mut ActivityIndicator, &Model<ActivityIndicator>, &mut AppContext)>>,
|
||||
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
||||
}
|
||||
|
||||
impl ActivityIndicator {
|
||||
pub fn new(
|
||||
workspace: &mut Workspace,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
model: &Model<Workspace>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<ActivityIndicator> {
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> View<ActivityIndicator> {
|
||||
let project = workspace.project().clone();
|
||||
let auto_updater = AutoUpdater::get(cx);
|
||||
let this = cx.new_model(|model: &Model<Self>, cx: &mut AppContext| {
|
||||
let this = cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
let mut status_events = languages.language_server_binary_statuses();
|
||||
model
|
||||
.spawn(cx, |this, mut cx| async move {
|
||||
while let Some((name, status)) = status_events.next().await {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.statuses.retain(|s| s.name != name);
|
||||
this.statuses.push(LspStatus { name, status });
|
||||
model.notify(cx);
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
cx.observe(&project, |_, _, cx| model.notify(cx)).detach();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
while let Some((name, status)) = status_events.next().await {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.statuses.retain(|s| s.name != name);
|
||||
this.statuses.push(LspStatus { name, status });
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
||||
|
||||
if let Some(auto_updater) = auto_updater.as_ref() {
|
||||
cx.observe(auto_updater, |_, _, cx| model.notify(cx))
|
||||
.detach();
|
||||
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
|
||||
}
|
||||
|
||||
Self {
|
||||
@@ -90,8 +86,7 @@ impl ActivityIndicator {
|
||||
|
||||
cx.subscribe(&this, move |_, _, event, cx| match event {
|
||||
Event::ShowError { lsp_name, error } => {
|
||||
let create_buffer =
|
||||
project.update(cx, |project, model, cx| project.create_buffer(model, cx));
|
||||
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
|
||||
let project = project.clone();
|
||||
let error = error.clone();
|
||||
let lsp_name = lsp_name.clone();
|
||||
@@ -110,8 +105,8 @@ impl ActivityIndicator {
|
||||
})?;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(cx.new_model(|model, cx| {
|
||||
Editor::for_buffer(buffer, Some(project.clone()), model, cx)
|
||||
Box::new(cx.new_view(|cx| {
|
||||
Editor::for_buffer(buffer, Some(project.clone()), cx)
|
||||
})),
|
||||
None,
|
||||
true,
|
||||
@@ -128,44 +123,29 @@ impl ActivityIndicator {
|
||||
this
|
||||
}
|
||||
|
||||
fn show_error_message(
|
||||
&mut self,
|
||||
_: &ShowErrorMessage,
|
||||
model: &Model<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) {
|
||||
self.statuses.retain(|status| {
|
||||
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
|
||||
model.emit(
|
||||
cx,
|
||||
Event::ShowError {
|
||||
lsp_name: status.name.clone(),
|
||||
error: error.clone(),
|
||||
},
|
||||
);
|
||||
cx.emit(Event::ShowError {
|
||||
lsp_name: status.name.clone(),
|
||||
error: error.clone(),
|
||||
});
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn dismiss_error_message(
|
||||
&mut self,
|
||||
_: &DismissErrorMessage,
|
||||
model: &Model<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
|
||||
if let Some(updater) = &self.auto_updater {
|
||||
updater.update(cx, |updater, model, cx| {
|
||||
updater.dismiss_error(model, cx);
|
||||
updater.update(cx, |updater, cx| {
|
||||
updater.dismiss_error(cx);
|
||||
});
|
||||
}
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn pending_language_server_work<'a>(
|
||||
@@ -203,7 +183,7 @@ impl ActivityIndicator {
|
||||
self.project.read(cx).shell_environment_errors(cx)
|
||||
}
|
||||
|
||||
fn content_to_render(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Option<Content> {
|
||||
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Option<Content> {
|
||||
// Show if any direnv calls failed
|
||||
if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() {
|
||||
return Some(Content {
|
||||
@@ -214,7 +194,7 @@ impl ActivityIndicator {
|
||||
),
|
||||
message: error.0.clone(),
|
||||
on_click: Some(Arc::new(move |this, cx| {
|
||||
this.project.update(cx, |project, model, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.remove_environment_error(cx, worktree_id);
|
||||
});
|
||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||
@@ -372,7 +352,7 @@ impl ActivityIndicator {
|
||||
),
|
||||
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
||||
on_click: Some(Arc::new(|indicator, cx| {
|
||||
indicator.project.update(cx, |project, model, cx| {
|
||||
indicator.project.update(cx, |project, cx| {
|
||||
project.reset_last_formatting_failure(cx);
|
||||
});
|
||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||
@@ -462,12 +442,8 @@ impl ActivityIndicator {
|
||||
None
|
||||
}
|
||||
|
||||
fn toggle_language_server_work_context_menu(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.context_menu_handle.toggle(model, cx);
|
||||
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.context_menu_handle.toggle(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,20 +452,15 @@ impl EventEmitter<Event> for ActivityIndicator {}
|
||||
const MAX_MESSAGE_LEN: usize = 50;
|
||||
|
||||
impl Render for ActivityIndicator {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let result = h_flex()
|
||||
.id("activity-indicator")
|
||||
.on_action(model.listener(Self::show_error_message))
|
||||
.on_action(model.listener(Self::dismiss_error_message));
|
||||
let Some(content) = self.content_to_render(model, cx) else {
|
||||
.on_action(cx.listener(Self::show_error_message))
|
||||
.on_action(cx.listener(Self::dismiss_error_message));
|
||||
let Some(content) = self.content_to_render(cx) else {
|
||||
return result;
|
||||
};
|
||||
let this = model.downgrade();
|
||||
let this = cx.view().downgrade();
|
||||
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
|
||||
result.gap_2().child(
|
||||
PopoverMenu::new("activity-indicator-popover")
|
||||
@@ -509,15 +480,13 @@ impl Render for ActivityIndicator {
|
||||
))
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text(&content.message, cx)
|
||||
})
|
||||
.tooltip(move |cx| Tooltip::text(&content.message, cx))
|
||||
} else {
|
||||
button.child(Label::new(content.message).size(LabelSize::Small))
|
||||
}
|
||||
})
|
||||
.when_some(content.on_click, |this, handler| {
|
||||
this.on_click(model.listener(move |this, _, model, window, cx| {
|
||||
this.on_click(cx.listener(move |this, _, cx| {
|
||||
handler(this, cx);
|
||||
}))
|
||||
.cursor(CursorStyle::PointingHand)
|
||||
@@ -525,10 +494,10 @@ impl Render for ActivityIndicator {
|
||||
),
|
||||
)
|
||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||
.menu(move |window, cx| {
|
||||
.menu(move |cx| {
|
||||
let strong_this = this.upgrade()?;
|
||||
let mut has_work = false;
|
||||
let menu = ContextMenu::build(window, cx, |mut menu, model, window, cx| {
|
||||
let menu = ContextMenu::build(cx, |mut menu, cx| {
|
||||
for work in strong_this.read(cx).pending_language_server_work(cx) {
|
||||
has_work = true;
|
||||
let this = this.clone();
|
||||
@@ -553,17 +522,16 @@ impl Render for ActivityIndicator {
|
||||
.into_any_element()
|
||||
},
|
||||
move |cx| {
|
||||
this.update(cx, |this, model, cx| {
|
||||
this.project.update(cx, |project, model, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.cancel_language_server_work(
|
||||
language_server_id,
|
||||
Some(token.clone()),
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
this.context_menu_handle.hide(cx);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
@@ -586,11 +554,5 @@ impl Render for ActivityIndicator {
|
||||
}
|
||||
|
||||
impl StatusItemView for ActivityIndicator {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
_: Option<&dyn ItemHandle>,
|
||||
_: &Model<Self>,
|
||||
_: &mut AppContext,
|
||||
) {
|
||||
}
|
||||
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
|
||||
}
|
||||
|
||||
@@ -321,9 +321,9 @@ fn update_active_language_model_from_settings(cx: &mut AppContext) {
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, model, cx| {
|
||||
registry.select_active_model(&provider_name, &model_id, model, cx);
|
||||
registry.select_inline_alternative_models(inline_alternatives, model, cx);
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_active_model(&provider_name, &model_id, cx);
|
||||
registry.select_inline_alternative_models(inline_alternatives, cx);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ use std::{
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
|
||||
use ui::{Context as _, IconName};
|
||||
use ui::{Context as _, IconName, WindowContext};
|
||||
use unindent::Unindent;
|
||||
use util::{
|
||||
test::{generate_marked_text, marked_text_ranges},
|
||||
@@ -51,7 +51,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry,
|
||||
None,
|
||||
@@ -59,7 +59,6 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -71,7 +70,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
vec![(message_1.id, Role::User, 0..0)]
|
||||
);
|
||||
|
||||
let message_2 = context.update(cx, |context, model, cx| {
|
||||
let message_2 = context.update(cx, |context, cx| {
|
||||
context
|
||||
.insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
|
||||
.unwrap()
|
||||
@@ -84,8 +83,8 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
]
|
||||
);
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(0..0, "1"), (1..1, "2")], None, model, cx)
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "1"), (1..1, "2")], None, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
messages(&context, cx),
|
||||
@@ -95,7 +94,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
]
|
||||
);
|
||||
|
||||
let message_3 = context.update(cx, |context, model, cx| {
|
||||
let message_3 = context.update(cx, |context, cx| {
|
||||
context
|
||||
.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
|
||||
.unwrap()
|
||||
@@ -109,7 +108,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
]
|
||||
);
|
||||
|
||||
let message_4 = context.update(cx, |context, model, cx| {
|
||||
let message_4 = context.update(cx, |context, cx| {
|
||||
context
|
||||
.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
|
||||
.unwrap()
|
||||
@@ -124,8 +123,8 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
]
|
||||
);
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(4..4, "C"), (5..5, "D")], None, model, cx)
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(4..4, "C"), (5..5, "D")], None, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
messages(&context, cx),
|
||||
@@ -138,9 +137,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
);
|
||||
|
||||
// Deleting across message boundaries merges the messages.
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(1..4, "")], None, model, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(1..4, "")], None, cx));
|
||||
assert_eq!(
|
||||
messages(&context, cx),
|
||||
vec![
|
||||
@@ -150,7 +147,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
);
|
||||
|
||||
// Undoing the deletion should also undo the merge.
|
||||
buffer.update(cx, |buffer, model, cx| buffer.undo(cx));
|
||||
buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
||||
assert_eq!(
|
||||
messages(&context, cx),
|
||||
vec![
|
||||
@@ -162,7 +159,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
);
|
||||
|
||||
// Redoing the deletion should also redo the merge.
|
||||
buffer.update(cx, |buffer, model, cx| buffer.redo(cx));
|
||||
buffer.update(cx, |buffer, cx| buffer.redo(cx));
|
||||
assert_eq!(
|
||||
messages(&context, cx),
|
||||
vec![
|
||||
@@ -172,7 +169,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
);
|
||||
|
||||
// Ensure we can still insert after a merged message.
|
||||
let message_5 = context.update(cx, |context, model, cx| {
|
||||
let message_5 = context.update(cx, |context, cx| {
|
||||
context
|
||||
.insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
|
||||
.unwrap()
|
||||
@@ -196,7 +193,7 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
@@ -204,7 +201,6 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -216,11 +212,11 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
vec![(message_1.id, Role::User, 0..0)]
|
||||
);
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, model, cx)
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, cx)
|
||||
});
|
||||
|
||||
let (_, message_2) = context.update(cx, |context, model, cx| context.split_message(3..3, cx));
|
||||
let (_, message_2) = context.update(cx, |context, cx| context.split_message(3..3, cx));
|
||||
let message_2 = message_2.unwrap();
|
||||
|
||||
// We recycle newlines in the middle of a split message
|
||||
@@ -233,7 +229,7 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
]
|
||||
);
|
||||
|
||||
let (_, message_3) = context.update(cx, |context, model, cx| context.split_message(3..3, cx));
|
||||
let (_, message_3) = context.update(cx, |context, cx| context.split_message(3..3, cx));
|
||||
let message_3 = message_3.unwrap();
|
||||
|
||||
// We don't recycle newlines at the end of a split message
|
||||
@@ -247,7 +243,7 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
]
|
||||
);
|
||||
|
||||
let (_, message_4) = context.update(cx, |context, model, cx| context.split_message(9..9, cx));
|
||||
let (_, message_4) = context.update(cx, |context, cx| context.split_message(9..9, cx));
|
||||
let message_4 = message_4.unwrap();
|
||||
assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
|
||||
assert_eq!(
|
||||
@@ -260,7 +256,7 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
]
|
||||
);
|
||||
|
||||
let (_, message_5) = context.update(cx, |context, model, cx| context.split_message(9..9, cx));
|
||||
let (_, message_5) = context.update(cx, |context, cx| context.split_message(9..9, cx));
|
||||
let message_5 = message_5.unwrap();
|
||||
assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\nddd\n");
|
||||
assert_eq!(
|
||||
@@ -275,7 +271,7 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
);
|
||||
|
||||
let (message_6, message_7) =
|
||||
context.update(cx, |context, model, cx| context.split_message(14..16, cx));
|
||||
context.update(cx, |context, cx| context.split_message(14..16, cx));
|
||||
let message_6 = message_6.unwrap();
|
||||
let message_7 = message_7.unwrap();
|
||||
assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\ndd\nd\n");
|
||||
@@ -301,7 +297,7 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry,
|
||||
None,
|
||||
@@ -309,7 +305,6 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -321,26 +316,20 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
|
||||
vec![(message_1.id, Role::User, 0..0)]
|
||||
);
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(0..0, "aaa")], None, model, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
|
||||
let message_2 = context
|
||||
.update(cx, |context, model, cx| {
|
||||
.update(cx, |context, cx| {
|
||||
context.insert_message_after(message_1.id, Role::User, MessageStatus::Done, cx)
|
||||
})
|
||||
.unwrap();
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(4..4, "bbb")], None, model, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbb")], None, cx));
|
||||
|
||||
let message_3 = context
|
||||
.update(cx, |context, model, cx| {
|
||||
.update(cx, |context, cx| {
|
||||
context.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
|
||||
})
|
||||
.unwrap();
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(8..8, "ccc")], None, model, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(8..8, "ccc")], None, cx));
|
||||
|
||||
assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc");
|
||||
assert_eq!(
|
||||
@@ -362,7 +351,7 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
|
||||
);
|
||||
|
||||
let message_4 = context
|
||||
.update(cx, |context, model, cx| {
|
||||
.update(cx, |context, cx| {
|
||||
context.insert_message_after(message_3.id, Role::User, MessageStatus::Done, cx)
|
||||
})
|
||||
.unwrap();
|
||||
@@ -423,7 +412,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
@@ -431,7 +420,6 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -444,7 +432,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
let context_ranges = Rc::new(RefCell::new(ContextRanges::default()));
|
||||
context.update(cx, |_, model, cx| {
|
||||
context.update(cx, |_, cx| {
|
||||
cx.subscribe(&context, {
|
||||
let context_ranges = context_ranges.clone();
|
||||
move |context, _, event, _| {
|
||||
@@ -458,7 +446,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
}
|
||||
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||
for range in removed {
|
||||
context_ranges.parsed_commands.remove(&range);
|
||||
context_ranges.parsed_commands.remove(range);
|
||||
}
|
||||
for command in updated {
|
||||
context_ranges
|
||||
@@ -479,7 +467,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
let buffer = context.read_with(cx, |context, _| context.buffer.clone());
|
||||
|
||||
// Insert a slash command
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
|
||||
});
|
||||
assert_text_and_context_ranges(
|
||||
@@ -492,7 +480,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
// Edit the argument of the slash command.
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let edit_offset = buffer.text().find("lib.rs").unwrap();
|
||||
buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
|
||||
});
|
||||
@@ -506,7 +494,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
// Edit the name of the slash command, using one that doesn't exist.
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let edit_offset = buffer.text().find("/file").unwrap();
|
||||
buffer.edit(
|
||||
[(edit_offset..edit_offset + "/file".len(), "/unknown")],
|
||||
@@ -524,7 +512,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
// Undoing the insertion of an non-existent slash command resorts the previous one.
|
||||
buffer.update(cx, |buffer, model, cx| buffer.undo(cx));
|
||||
buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
@@ -535,7 +523,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
let (command_output_tx, command_output_rx) = mpsc::unbounded();
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
let command_source_range = context.parsed_slash_commands[0].source_range.clone();
|
||||
context.insert_command_output(
|
||||
command_source_range,
|
||||
@@ -631,7 +619,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
let mut actual_marked_text = String::new();
|
||||
buffer.update(cx, |buffer, model, _| {
|
||||
buffer.update(cx, |buffer, _| {
|
||||
struct Endpoint {
|
||||
offset: usize,
|
||||
marker: char,
|
||||
@@ -715,7 +703,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
|
||||
// Create a new context
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry.clone(),
|
||||
Some(project),
|
||||
@@ -723,13 +711,12 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Insert an assistant message to simulate a response.
|
||||
let assistant_message_id = context.update(cx, |context, model, cx| {
|
||||
let assistant_message_id = context.update(cx, |context, cx| {
|
||||
let user_message_id = context.messages(cx).next().unwrap().id;
|
||||
context
|
||||
.insert_message_after(user_message_id, Role::Assistant, MessageStatus::Done, cx)
|
||||
@@ -916,7 +903,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
// When setting the message role to User, the steps are cleared.
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.cycle_message_roles(HashSet::from_iter([assistant_message_id]), cx);
|
||||
context.cycle_message_roles(HashSet::from_iter([assistant_message_id]), cx);
|
||||
});
|
||||
@@ -945,7 +932,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
// When setting the message role back to Assistant, the steps are reparsed.
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.cycle_message_roles(HashSet::from_iter([assistant_message_id]), cx);
|
||||
});
|
||||
expect_patches(
|
||||
@@ -981,7 +968,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
|
||||
// Ensure steps are re-parsed when deserializing.
|
||||
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
|
||||
let deserialized_context = cx.new_model(|model, cx| {
|
||||
let deserialized_context = cx.new_model(|cx| {
|
||||
Context::deserialize(
|
||||
serialized_context,
|
||||
Default::default(),
|
||||
@@ -991,7 +978,6 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1027,14 +1013,9 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
fn edit(context: &Model<Context>, new_text_marked_with_edits: &str, cx: &mut TestAppContext) {
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit_via_marked_text(
|
||||
&new_text_marked_with_edits.unindent(),
|
||||
None,
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
context.update(cx, |context, cx| {
|
||||
context.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit_via_marked_text(&new_text_marked_with_edits.unindent(), None, cx);
|
||||
});
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -1050,7 +1031,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
let expected_marked_text = expected_marked_text.unindent();
|
||||
let (expected_text, _) = marked_text_ranges(&expected_marked_text, false);
|
||||
|
||||
let (buffer_text, ranges, patches) = context.update(cx, |context, model, cx| {
|
||||
let (buffer_text, ranges, patches) = context.update(cx, |context, cx| {
|
||||
context.buffer.read_with(cx, |buffer, _| {
|
||||
let ranges = context
|
||||
.patches
|
||||
@@ -1103,7 +1084,7 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
cx.update(assistant_panel::init);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
@@ -1111,32 +1092,31 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let buffer = context.read_with(cx, |context, _| context.buffer.clone());
|
||||
let message_0 = context.read_with(cx, |context, _| context.message_anchors[0].id);
|
||||
let message_1 = context.update(cx, |context, model, cx| {
|
||||
let message_1 = context.update(cx, |context, cx| {
|
||||
context
|
||||
.insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
|
||||
.unwrap()
|
||||
});
|
||||
let message_2 = context.update(cx, |context, model, cx| {
|
||||
let message_2 = context.update(cx, |context, cx| {
|
||||
context
|
||||
.insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
|
||||
.unwrap()
|
||||
});
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "a"), (1..1, "b\nc")], None, cx);
|
||||
buffer.finalize_last_transaction();
|
||||
});
|
||||
let _message_3 = context.update(cx, |context, model, cx| {
|
||||
let _message_3 = context.update(cx, |context, cx| {
|
||||
context
|
||||
.insert_message_after(message_2.id, Role::System, MessageStatus::Done, cx)
|
||||
.unwrap()
|
||||
});
|
||||
buffer.update(cx, |buffer, model, cx| buffer.undo(cx));
|
||||
buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
||||
assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "a\nb\nc\n");
|
||||
assert_eq!(
|
||||
cx.read(|cx| messages(&context, cx)),
|
||||
@@ -1148,7 +1128,7 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
|
||||
let deserialized_context = cx.new_model(|model, cx| {
|
||||
let deserialized_context = cx.new_model(|cx| {
|
||||
Context::deserialize(
|
||||
serialized_context,
|
||||
Default::default(),
|
||||
@@ -1158,7 +1138,6 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1208,7 +1187,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
let context_id = ContextId::new();
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
for i in 0..num_peers {
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::new(
|
||||
context_id.clone(),
|
||||
i as ReplicaId,
|
||||
@@ -1219,7 +1198,6 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1254,15 +1232,15 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
match rng.gen_range(0..100) {
|
||||
0..=29 if mutation_count > 0 => {
|
||||
log::info!("Context {}: edit buffer", context_index);
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.randomly_edit(&mut rng, 1, cx)
|
||||
});
|
||||
context.update(cx, |context, cx| {
|
||||
context
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.randomly_edit(&mut rng, 1, cx));
|
||||
});
|
||||
mutation_count -= 1;
|
||||
}
|
||||
30..=44 if mutation_count > 0 => {
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
let range = context.buffer.read(cx).random_byte_range(0, &mut rng);
|
||||
log::info!("Context {}: split message at {:?}", context_index, range);
|
||||
context.split_message(range, cx);
|
||||
@@ -1270,7 +1248,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
mutation_count -= 1;
|
||||
}
|
||||
45..=59 if mutation_count > 0 => {
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
if let Some(message) = context.messages(cx).choose(&mut rng) {
|
||||
let role = *[Role::User, Role::Assistant, Role::System]
|
||||
.choose(&mut rng)
|
||||
@@ -1287,7 +1265,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
mutation_count -= 1;
|
||||
}
|
||||
60..=74 if mutation_count > 0 => {
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
let command_text = "/".to_string()
|
||||
+ slash_commands
|
||||
.command_names()
|
||||
@@ -1296,7 +1274,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
.clone()
|
||||
.as_ref();
|
||||
|
||||
let command_range = context.buffer.update(cx, |buffer, model, cx| {
|
||||
let command_range = context.buffer.update(cx, |buffer, cx| {
|
||||
let offset = buffer.random_byte_range(0, &mut rng).start;
|
||||
buffer.edit(
|
||||
[(offset..offset, format!("\n{}\n", command_text))],
|
||||
@@ -1364,7 +1342,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
mutation_count -= 1;
|
||||
}
|
||||
75..=84 if mutation_count > 0 => {
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
if let Some(message) = context.messages(cx).choose(&mut rng) {
|
||||
let new_status = match rng.gen_range(0..3) {
|
||||
0 => MessageStatus::Done,
|
||||
@@ -1412,9 +1390,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
);
|
||||
|
||||
network.lock().broadcast(replica_id, ops_to_send);
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.apply_ops(ops_to_receive, cx)
|
||||
});
|
||||
context.update(cx, |context, cx| context.apply_ops(ops_to_receive, cx));
|
||||
} else if rng.gen_bool(0.1) && replica_id != 0 {
|
||||
log::info!("Context {}: disconnecting", context_index);
|
||||
network.lock().disconnect_peer(replica_id);
|
||||
@@ -1426,7 +1402,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
.map(ContextOperation::from_proto)
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.unwrap();
|
||||
context.update(cx, |context, model, cx| context.apply_ops(ops, cx));
|
||||
context.update(cx, |context, cx| context.apply_ops(ops, cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1473,7 +1449,7 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry,
|
||||
None,
|
||||
@@ -1481,7 +1457,6 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1496,7 +1471,7 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
|
||||
let message_1 = context.read(cx).message_anchors[0].clone();
|
||||
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.mark_cache_anchors(cache_configuration, false, cx)
|
||||
});
|
||||
|
||||
@@ -1509,28 +1484,22 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
"Empty messages should not have any cache anchors."
|
||||
);
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(0..0, "aaa")], None, model, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
|
||||
let message_2 = context
|
||||
.update(cx, |context, model, cx| {
|
||||
.update(cx, |context, cx| {
|
||||
context.insert_message_after(message_1.id, Role::User, MessageStatus::Pending, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(4..4, "bbbbbbb")], None, model, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbbbbbb")], None, cx));
|
||||
let message_3 = context
|
||||
.update(cx, |context, model, cx| {
|
||||
.update(cx, |context, cx| {
|
||||
context.insert_message_after(message_2.id, Role::User, MessageStatus::Pending, cx)
|
||||
})
|
||||
.unwrap();
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(12..12, "cccccc")], None, model, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(12..12, "cccccc")], None, cx));
|
||||
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.mark_cache_anchors(cache_configuration, false, cx)
|
||||
});
|
||||
assert_eq!(buffer.read(cx).text(), "aaa\nbbbbbbb\ncccccc");
|
||||
@@ -1542,12 +1511,12 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
0,
|
||||
"Messages should not be marked for cache before going over the token minimum."
|
||||
);
|
||||
context.update(cx, |context, model, _| {
|
||||
context.update(cx, |context, _| {
|
||||
context.token_count = Some(20);
|
||||
});
|
||||
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.mark_cache_anchors(cache_configuration, true, model, cx)
|
||||
context.update(cx, |context, cx| {
|
||||
context.mark_cache_anchors(cache_configuration, true, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
messages_cache(&context, cx)
|
||||
@@ -1559,12 +1528,12 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
);
|
||||
|
||||
context
|
||||
.update(cx, |context, model, cx| {
|
||||
.update(cx, |context, cx| {
|
||||
context.insert_message_after(message_3.id, Role::Assistant, MessageStatus::Pending, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.mark_cache_anchors(cache_configuration, false, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
@@ -1575,7 +1544,7 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
vec![false, true, true, false],
|
||||
"Most recent message should also be cached if not a speculative request."
|
||||
);
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.update_cache_status_for_completion(cx)
|
||||
});
|
||||
assert_eq!(
|
||||
@@ -1594,10 +1563,8 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
"All user messages prior to anchor should be marked as cached."
|
||||
);
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(14..14, "d")], None, model, cx)
|
||||
});
|
||||
context.update(cx, |context, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(14..14, "d")], None, cx));
|
||||
context.update(cx, |context, cx| {
|
||||
context.mark_cache_anchors(cache_configuration, false, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
@@ -1615,10 +1582,8 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
],
|
||||
"Modifying a message should invalidate it's cache but leave previous messages."
|
||||
);
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(2..2, "e")], None, model, cx)
|
||||
});
|
||||
context.update(cx, |context, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "e")], None, cx));
|
||||
context.update(cx, |context, cx| {
|
||||
context.mark_cache_anchors(cache_configuration, false, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
@@ -1677,9 +1642,8 @@ impl SlashCommand for FakeSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Ok(vec![]))
|
||||
}
|
||||
@@ -1693,10 +1657,9 @@ impl SlashCommand for FakeSlashCommand {
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakModel<Workspace>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
Task::ready(Ok(SlashCommandOutput {
|
||||
text: format!("Executed fake command: {}", self.0),
|
||||
|
||||
@@ -14,7 +14,9 @@ use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{AppContext, AsyncAppContext, Context as _, EventEmitter, Model, Task, WeakModel};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use paths::contexts_dir;
|
||||
use project::Project;
|
||||
@@ -107,16 +109,11 @@ impl ContextStore {
|
||||
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
|
||||
let this = cx.new_model(|model: &Model<Self>, cx: &mut AppContext| {
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||
let context_server_factory_registry =
|
||||
ContextServerFactoryRegistry::default_global(cx);
|
||||
let context_server_manager = cx.new_model(|model, cx| {
|
||||
ContextServerManager::new(
|
||||
context_server_factory_registry,
|
||||
project.clone(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
let context_server_manager = cx.new_model(|cx| {
|
||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||
});
|
||||
let mut this = Self {
|
||||
contexts: Vec::new(),
|
||||
@@ -130,10 +127,10 @@ impl ContextStore {
|
||||
slash_commands,
|
||||
tools,
|
||||
telemetry,
|
||||
_watch_updates: model.spawn(cx, |this, mut cx| {
|
||||
_watch_updates: cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
while events.next().await.is_some() {
|
||||
this.update(&mut cx, |this, model, cx| this.reload(model, cx))?
|
||||
this.update(&mut cx, |this, cx| this.reload(cx))?
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
@@ -151,12 +148,12 @@ impl ContextStore {
|
||||
project: project.clone(),
|
||||
prompt_builder,
|
||||
};
|
||||
this.handle_project_changed(project.clone(), model, cx);
|
||||
this.synchronize_contexts(model, cx);
|
||||
this.register_context_server_handlers(model, cx);
|
||||
this.handle_project_changed(project.clone(), cx);
|
||||
this.synchronize_contexts(cx);
|
||||
this.register_context_server_handlers(cx);
|
||||
this
|
||||
})?;
|
||||
this.update(&mut cx, |this, model, cx| this.reload(model, cx))?
|
||||
this.update(&mut cx, |this, cx| this.reload(cx))?
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
@@ -169,7 +166,7 @@ impl ContextStore {
|
||||
envelope: TypedEnvelope<proto::AdvertiseContexts>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.host_contexts = envelope
|
||||
.payload
|
||||
.contexts
|
||||
@@ -179,7 +176,7 @@ impl ContextStore {
|
||||
summary: context.summary,
|
||||
})
|
||||
.collect();
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -189,7 +186,7 @@ impl ContextStore {
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::OpenContextResponse> {
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
let operations = this.update(&mut cx, |this, model, cx| {
|
||||
let operations = this.update(&mut cx, |this, cx| {
|
||||
if this.project.read(cx).is_via_collab() {
|
||||
return Err(anyhow!("only the host contexts can be opened"));
|
||||
}
|
||||
@@ -218,14 +215,14 @@ impl ContextStore {
|
||||
_: TypedEnvelope<proto::CreateContext>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::CreateContextResponse> {
|
||||
let (context_id, operations) = this.update(&mut cx, |this, model, cx| {
|
||||
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
||||
if this.project.read(cx).is_via_collab() {
|
||||
return Err(anyhow!("can only create contexts as the host"));
|
||||
}
|
||||
|
||||
let context = this.create(model, cx);
|
||||
let context = this.create(cx);
|
||||
let context_id = context.read(cx).id().clone();
|
||||
model.emit(ContextStoreEvent::ContextCreated(context_id.clone()), cx);
|
||||
cx.emit(ContextStoreEvent::ContextCreated(context_id.clone()));
|
||||
|
||||
anyhow::Ok((
|
||||
context_id,
|
||||
@@ -246,14 +243,12 @@ impl ContextStore {
|
||||
envelope: TypedEnvelope<proto::UpdateContext>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
let operation_proto = envelope.payload.operation.context("invalid operation")?;
|
||||
let operation = ContextOperation::from_proto(operation_proto)?;
|
||||
context.update(cx, |context, model, cx| {
|
||||
context.apply_ops([operation], model, cx)
|
||||
});
|
||||
context.update(cx, |context, cx| context.apply_ops([operation], cx));
|
||||
}
|
||||
Ok(())
|
||||
})?
|
||||
@@ -264,7 +259,7 @@ impl ContextStore {
|
||||
envelope: TypedEnvelope<proto::SynchronizeContexts>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::SynchronizeContextsResponse> {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.project.read(cx).is_via_collab() {
|
||||
return Err(anyhow!("only the host can synchronize contexts"));
|
||||
}
|
||||
@@ -303,12 +298,7 @@ impl ContextStore {
|
||||
})?
|
||||
}
|
||||
|
||||
fn handle_project_changed(
|
||||
&mut self,
|
||||
_: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn handle_project_changed(&mut self, _: Model<Project>, cx: &mut ModelContext<Self>) {
|
||||
let is_shared = self.project.read(cx).is_shared();
|
||||
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
|
||||
if is_shared == was_shared {
|
||||
@@ -329,7 +319,7 @@ impl ContextStore {
|
||||
.client
|
||||
.subscribe_to_entity(remote_id)
|
||||
.log_err()
|
||||
.map(|subscription| subscription.set_model(model, &mut cx.to_async()));
|
||||
.map(|subscription| subscription.set_model(&cx.handle(), &mut cx.to_async()));
|
||||
self.advertise_contexts(cx);
|
||||
} else {
|
||||
self.client_subscription = None;
|
||||
@@ -340,23 +330,22 @@ impl ContextStore {
|
||||
&mut self,
|
||||
_: Model<Project>,
|
||||
event: &project::Event,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
project::Event::Reshared => {
|
||||
self.advertise_contexts(cx);
|
||||
}
|
||||
project::Event::HostReshared | project::Event::Rejoined => {
|
||||
self.synchronize_contexts(model, cx);
|
||||
self.synchronize_contexts(cx);
|
||||
}
|
||||
project::Event::DisconnectedFromHost => {
|
||||
self.contexts.retain_mut(|context| {
|
||||
if let Some(strong_context) = context.upgrade() {
|
||||
*context = ContextHandle::Weak(context.downgrade());
|
||||
strong_context.update(cx, |context, model, cx| {
|
||||
strong_context.update(cx, |context, cx| {
|
||||
if context.replica_id() != ReplicaId::default() {
|
||||
context.set_capability(language::Capability::ReadOnly, model, cx);
|
||||
context.set_capability(language::Capability::ReadOnly, cx);
|
||||
}
|
||||
});
|
||||
true
|
||||
@@ -365,14 +354,14 @@ impl ContextStore {
|
||||
}
|
||||
});
|
||||
self.host_contexts.clear();
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Model<Context> {
|
||||
let context = cx.new_model(|model, cx| {
|
||||
pub fn create(&mut self, cx: &mut ModelContext<Self>) -> Model<Context> {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
self.languages.clone(),
|
||||
Some(self.project.clone()),
|
||||
@@ -380,18 +369,16 @@ impl ContextStore {
|
||||
self.prompt_builder.clone(),
|
||||
self.slash_commands.clone(),
|
||||
self.tools.clone(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.register_context(&context, model, cx);
|
||||
self.register_context(&context, cx);
|
||||
context
|
||||
}
|
||||
|
||||
pub fn create_remote_context(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Context>>> {
|
||||
let project = self.project.read(cx);
|
||||
let Some(project_id) = project.remote_id() else {
|
||||
@@ -407,11 +394,11 @@ impl ContextStore {
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
let request = self.client.request(proto::CreateContext { project_id });
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
let context_id = ContextId::from_proto(response.context_id);
|
||||
let context_proto = response.context.context("invalid context")?;
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::new(
|
||||
context_id.clone(),
|
||||
replica_id,
|
||||
@@ -422,7 +409,6 @@ impl ContextStore {
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
@@ -437,12 +423,12 @@ impl ContextStore {
|
||||
})
|
||||
.await?;
|
||||
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, model, cx);
|
||||
this.synchronize_contexts(model, cx);
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
}
|
||||
})
|
||||
@@ -452,8 +438,7 @@ impl ContextStore {
|
||||
pub fn open_local_context(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<Model<Context>>> {
|
||||
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
|
||||
return Task::ready(Ok(existing_context));
|
||||
@@ -474,9 +459,9 @@ impl ContextStore {
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let saved_context = load.await?;
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::deserialize(
|
||||
saved_context,
|
||||
path.clone(),
|
||||
@@ -486,15 +471,14 @@ impl ContextStore {
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, model, cx);
|
||||
this.register_context(&context, cx);
|
||||
context
|
||||
}
|
||||
})
|
||||
@@ -530,8 +514,7 @@ impl ContextStore {
|
||||
pub fn open_remote_context(
|
||||
&mut self,
|
||||
context_id: ContextId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Context>>> {
|
||||
let project = self.project.read(cx);
|
||||
let Some(project_id) = project.remote_id() else {
|
||||
@@ -554,10 +537,10 @@ impl ContextStore {
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
let context_proto = response.context.context("invalid context")?;
|
||||
let context = cx.new_model(|model, cx| {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::new(
|
||||
context_id.clone(),
|
||||
replica_id,
|
||||
@@ -568,7 +551,6 @@ impl ContextStore {
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
@@ -583,24 +565,19 @@ impl ContextStore {
|
||||
})
|
||||
.await?;
|
||||
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, model, cx);
|
||||
this.synchronize_contexts(model, cx);
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn register_context(
|
||||
&mut self,
|
||||
context: &Model<Context>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn register_context(&mut self, context: &Model<Context>, cx: &mut ModelContext<Self>) {
|
||||
let handle = if self.project_is_shared {
|
||||
ContextHandle::Strong(context.clone())
|
||||
} else {
|
||||
@@ -615,8 +592,7 @@ impl ContextStore {
|
||||
&mut self,
|
||||
context: Model<Context>,
|
||||
event: &ContextEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||
return;
|
||||
@@ -675,7 +651,7 @@ impl ContextStore {
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn synchronize_contexts(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn synchronize_contexts(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||
return;
|
||||
};
|
||||
@@ -698,37 +674,36 @@ impl ContextStore {
|
||||
project_id,
|
||||
contexts,
|
||||
});
|
||||
model
|
||||
.spawn(cx, |this, cx| async move {
|
||||
let response = request.await?;
|
||||
cx.spawn(|this, cx| async move {
|
||||
let response = request.await?;
|
||||
|
||||
let mut context_ids = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
this.read_with(&cx, |this, cx| {
|
||||
for context_version_proto in response.contexts {
|
||||
let context_version = ContextVersion::from_proto(&context_version_proto);
|
||||
let context_id = ContextId::from_proto(context_version_proto.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
context_ids.push(context_id);
|
||||
operations.push(context.read(cx).serialize_ops(&context_version, cx));
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
let operations = futures::future::join_all(operations).await;
|
||||
for (context_id, operations) in context_ids.into_iter().zip(operations) {
|
||||
for operation in operations {
|
||||
client.send(proto::UpdateContext {
|
||||
project_id,
|
||||
context_id: context_id.to_proto(),
|
||||
operation: Some(operation),
|
||||
})?;
|
||||
let mut context_ids = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
this.read_with(&cx, |this, cx| {
|
||||
for context_version_proto in response.contexts {
|
||||
let context_version = ContextVersion::from_proto(&context_version_proto);
|
||||
let context_id = ContextId::from_proto(context_version_proto.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
context_ids.push(context_id);
|
||||
operations.push(context.read(cx).serialize_ops(&context_version, cx));
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
let operations = futures::future::join_all(operations).await;
|
||||
for (context_id, operations) in context_ids.into_iter().zip(operations) {
|
||||
for operation in operations {
|
||||
client.send(proto::UpdateContext {
|
||||
project_id,
|
||||
context_id: context_id.to_proto(),
|
||||
operation: Some(operation),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn search(&self, query: String, cx: &AppContext) -> Task<Vec<SavedContextMetadata>> {
|
||||
@@ -765,9 +740,9 @@ impl ContextStore {
|
||||
&self.host_contexts
|
||||
}
|
||||
|
||||
fn reload(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
fs.create_dir(contexts_dir()).await?;
|
||||
|
||||
let mut paths = fs.read_dir(contexts_dir()).await?;
|
||||
@@ -803,14 +778,14 @@ impl ContextStore {
|
||||
}
|
||||
contexts.sort_unstable_by_key(|context| Reverse(context.mtime));
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.contexts_metadata = contexts;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn restart_context_servers(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn restart_context_servers(&mut self, cx: &mut ModelContext<Self>) {
|
||||
cx.update_model(
|
||||
&self.context_server_manager,
|
||||
|context_server_manager, cx| {
|
||||
@@ -823,7 +798,7 @@ impl ContextStore {
|
||||
);
|
||||
}
|
||||
|
||||
fn register_context_server_handlers(&self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
|
||||
cx.subscribe(
|
||||
&self.context_server_manager.clone(),
|
||||
Self::handle_context_server_event,
|
||||
@@ -835,8 +810,7 @@ impl ContextStore {
|
||||
&mut self,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
event: &context_server::manager::Event,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let slash_command_working_set = self.slash_commands.clone();
|
||||
let tool_working_set = self.tools.clone();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -140,7 +140,7 @@ impl ResolvedPatch {
|
||||
edits.push((suggestion.range.clone(), suggestion.new_text.clone()));
|
||||
}
|
||||
}
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
edits,
|
||||
Some(AutoindentMode::Block {
|
||||
@@ -461,7 +461,7 @@ impl AssistantPatch {
|
||||
// Expand the context ranges of each edit and group edits with overlapping context ranges.
|
||||
let mut edit_groups_by_buffer = HashMap::default();
|
||||
for (buffer, edits) in edits_by_buffer {
|
||||
if let Ok(snapshot) = buffer.update(cx, |buffer, model, _| buffer.text_snapshot()) {
|
||||
if let Ok(snapshot) = buffer.update(cx, |buffer, _| buffer.text_snapshot()) {
|
||||
edit_groups_by_buffer.insert(buffer, Self::group_edits(edits, &snapshot));
|
||||
}
|
||||
}
|
||||
@@ -918,7 +918,7 @@ mod tests {
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let (text, _) = marked_text_ranges(text_with_expected_range, false);
|
||||
let buffer = cx.new_model(|model, cx| Buffer::local(text.clone(), model, cx));
|
||||
let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx));
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
|
||||
let text_with_actual_range = generate_marked_text(&text, &[range], false);
|
||||
@@ -932,9 +932,8 @@ mod tests {
|
||||
new_text: String,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let buffer = cx.new_model(|model, cx| {
|
||||
Buffer::local(old_text, model, cx).with_language(Arc::new(rust_lang()), model, cx)
|
||||
});
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::local(old_text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let resolved_edits = edits
|
||||
.into_iter()
|
||||
|
||||
@@ -38,8 +38,8 @@ use std::{
|
||||
use text::LineEnding;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
div, prelude::*, AppContext, IconButtonShape, KeyBinding, ListItem, ListItemSpacing,
|
||||
ParentElement, Render, SharedString, Styled, Tooltip, VisualContext,
|
||||
div, prelude::*, IconButtonShape, KeyBinding, ListItem, ListItemSpacing, ParentElement, Render,
|
||||
SharedString, Styled, Tooltip, ViewContext, VisualContext,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
@@ -88,7 +88,7 @@ pub fn open_prompt_library(
|
||||
.find_map(|window| window.downcast::<PromptLibrary>());
|
||||
if let Some(existing_window) = existing_window {
|
||||
existing_window
|
||||
.update(cx, |_, model, cx| cx.activate_window())
|
||||
.update(cx, |_, cx| cx.activate_window())
|
||||
.ok();
|
||||
Task::ready(Ok(existing_window))
|
||||
} else {
|
||||
@@ -109,11 +109,7 @@ pub fn open_prompt_library(
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| {
|
||||
cx.new_model(|model, cx| {
|
||||
PromptLibrary::new(store, language_registry, model, cx)
|
||||
})
|
||||
},
|
||||
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
|
||||
)
|
||||
})?
|
||||
})
|
||||
@@ -125,14 +121,14 @@ pub struct PromptLibrary {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
prompt_editors: HashMap<PromptId, PromptEditor>,
|
||||
active_prompt_id: Option<PromptId>,
|
||||
picker: Model<Picker<PromptPickerDelegate>>,
|
||||
picker: View<Picker<PromptPickerDelegate>>,
|
||||
pending_load: Task<()>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
struct PromptEditor {
|
||||
title_editor: Model<Editor>,
|
||||
body_editor: Model<Editor>,
|
||||
title_editor: View<Editor>,
|
||||
body_editor: View<Editor>,
|
||||
token_count: Option<usize>,
|
||||
pending_token_count: Task<Option<()>>,
|
||||
next_title_and_body_to_save: Option<(String, Rope)>,
|
||||
@@ -162,11 +158,7 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
self.matches.len()
|
||||
}
|
||||
|
||||
fn no_matches_text(
|
||||
&self,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
) -> SharedString {
|
||||
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
|
||||
if self.store.prompt_count() == 0 {
|
||||
"No prompts.".into()
|
||||
} else {
|
||||
@@ -178,31 +170,23 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix;
|
||||
if let Some(prompt) = self.matches.get(self.selected_index) {
|
||||
model.emit(
|
||||
cx,
|
||||
PromptPickerEvent::Selected {
|
||||
prompt_id: prompt.id,
|
||||
},
|
||||
);
|
||||
cx.emit(PromptPickerEvent::Selected {
|
||||
prompt_id: prompt.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut gpui::Window, _cx: &mut gpui::AppContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Search...".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<()> {
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let search = self.store.search(query);
|
||||
let prev_prompt_id = self.matches.get(self.selected_index).map(|mat| mat.id);
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (matches, selected_index) = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
@@ -217,34 +201,30 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate.matches = matches;
|
||||
this.delegate.set_selected_index(selected_index, cx);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(prompt) = self.matches.get(self.selected_index) {
|
||||
model.emit(
|
||||
cx,
|
||||
PromptPickerEvent::Confirmed {
|
||||
prompt_id: prompt.id,
|
||||
},
|
||||
);
|
||||
cx.emit(PromptPickerEvent::Confirmed {
|
||||
prompt_id: prompt.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, model: &Model<Picker>, _cx: &mut AppContext) {}
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let prompt = self.matches.get(ix)?;
|
||||
let default = prompt.default;
|
||||
@@ -261,9 +241,9 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
.selected(true)
|
||||
.icon_color(Color::Accent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |window, cx| Tooltip::text("Remove from Default Prompt", cx))
|
||||
.on_click(model.listener(move |_, _, model, window, cx| {
|
||||
model.emit(PromptPickerEvent::ToggledDefault { prompt_id }, cx)
|
||||
.tooltip(move |cx| Tooltip::text("Remove from Default Prompt", cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
|
||||
}))
|
||||
}))
|
||||
.end_hover_slot(
|
||||
@@ -273,12 +253,11 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
div()
|
||||
.id("built-in-prompt")
|
||||
.child(Icon::new(IconName::FileLock).color(Color::Muted))
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
"Built-in prompt",
|
||||
None,
|
||||
BUILT_IN_TOOLTIP_TEXT,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -287,9 +266,9 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
IconButton::new("delete-prompt", IconName::Trash)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |window, cx| Tooltip::text("Delete Prompt", cx))
|
||||
.on_click(model.listener(move |_, _, model, window, cx| {
|
||||
model.emit(PromptPickerEvent::Deleted { prompt_id }, cx)
|
||||
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(PromptPickerEvent::Deleted { prompt_id })
|
||||
}))
|
||||
.into_any_element()
|
||||
})
|
||||
@@ -299,7 +278,7 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
.selected_icon(IconName::SparkleFilled)
|
||||
.icon_color(if default { Color::Accent } else { Color::Muted })
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if default {
|
||||
"Remove from Default Prompt"
|
||||
@@ -309,20 +288,15 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(model.listener(move |_, _, model, window, cx| {
|
||||
model.emit(PromptPickerEvent::ToggledDefault { prompt_id }, cx)
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
|
||||
})),
|
||||
),
|
||||
);
|
||||
Some(element)
|
||||
}
|
||||
|
||||
fn render_editor(
|
||||
&self,
|
||||
editor: &Model<Editor>,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
) -> Div {
|
||||
fn render_editor(&self, editor: &View<Editor>, cx: &mut ViewContext<Picker<Self>>) -> Div {
|
||||
h_flex()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.rounded_md()
|
||||
@@ -339,8 +313,7 @@ impl PromptLibrary {
|
||||
fn new(
|
||||
store: Arc<PromptStore>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate = PromptPickerDelegate {
|
||||
store: store.clone(),
|
||||
@@ -348,11 +321,11 @@ impl PromptLibrary {
|
||||
matches: Vec::new(),
|
||||
};
|
||||
|
||||
let picker = cx.new_model(|model, cx| {
|
||||
let picker = Picker::uniform_list(delegate, model, cx)
|
||||
let picker = cx.new_view(|cx| {
|
||||
let picker = Picker::uniform_list(delegate, cx)
|
||||
.modal(false)
|
||||
.max_height(None);
|
||||
picker.focus(window, cx);
|
||||
picker.focus(cx);
|
||||
picker
|
||||
});
|
||||
Self {
|
||||
@@ -368,52 +341,47 @@ impl PromptLibrary {
|
||||
|
||||
fn handle_picker_event(
|
||||
&mut self,
|
||||
_: Model<Picker<PromptPickerDelegate>>,
|
||||
_: View<Picker<PromptPickerDelegate>>,
|
||||
event: &PromptPickerEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
PromptPickerEvent::Selected { prompt_id } => {
|
||||
self.load_prompt(*prompt_id, false, model, cx);
|
||||
self.load_prompt(*prompt_id, false, cx);
|
||||
}
|
||||
PromptPickerEvent::Confirmed { prompt_id } => {
|
||||
self.load_prompt(*prompt_id, true, model, cx);
|
||||
self.load_prompt(*prompt_id, true, cx);
|
||||
}
|
||||
PromptPickerEvent::ToggledDefault { prompt_id } => {
|
||||
self.toggle_default_for_prompt(*prompt_id, model, cx);
|
||||
self.toggle_default_for_prompt(*prompt_id, cx);
|
||||
}
|
||||
PromptPickerEvent::Deleted { prompt_id } => {
|
||||
self.delete_prompt(*prompt_id, model, cx);
|
||||
self.delete_prompt(*prompt_id, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_prompt(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn new_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
// If we already have an untitled prompt, use that instead
|
||||
// of creating a new one.
|
||||
if let Some(metadata) = self.store.first() {
|
||||
if metadata.title.is_none() {
|
||||
self.load_prompt(metadata.id, true, model, cx);
|
||||
self.load_prompt(metadata.id, true, cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let prompt_id = PromptId::new();
|
||||
let save = self.store.save(prompt_id, None, false, "".into());
|
||||
self.picker
|
||||
.update(cx, |picker, model, cx| picker.refresh(cx));
|
||||
model
|
||||
.spawn(cx, |this, mut cx| async move {
|
||||
save.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.load_prompt(prompt_id, true, cx)
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
self.picker.update(cx, |picker, cx| picker.refresh(cx));
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
save.await?;
|
||||
this.update(&mut cx, |this, cx| this.load_prompt(prompt_id, true, cx))
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn save_prompt(&mut self, prompt_id: PromptId, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn save_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||
const SAVE_THROTTLE: Duration = Duration::from_millis(500);
|
||||
|
||||
if prompt_id.is_built_in() {
|
||||
@@ -423,7 +391,7 @@ impl PromptLibrary {
|
||||
let prompt_metadata = self.store.metadata(prompt_id).unwrap();
|
||||
let prompt_editor = self.prompt_editors.get_mut(&prompt_id).unwrap();
|
||||
let title = prompt_editor.title_editor.read(cx).text(cx);
|
||||
let body = prompt_editor.body_editor.update(cx, |editor, model, cx| {
|
||||
let body = prompt_editor.body_editor.update(cx, |editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
@@ -439,10 +407,10 @@ impl PromptLibrary {
|
||||
|
||||
prompt_editor.next_title_and_body_to_save = Some((title, body));
|
||||
if prompt_editor.pending_save.is_none() {
|
||||
prompt_editor.pending_save = Some(model.spawn(cx, |this, mut cx| {
|
||||
prompt_editor.pending_save = Some(cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
loop {
|
||||
let title_and_body = this.update(&mut cx, |this, _, _| {
|
||||
let title_and_body = this.update(&mut cx, |this, _| {
|
||||
this.prompt_editors
|
||||
.get_mut(&prompt_id)?
|
||||
.next_title_and_body_to_save
|
||||
@@ -459,10 +427,9 @@ impl PromptLibrary {
|
||||
.save(prompt_id, title, prompt_metadata.default, body)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.picker
|
||||
.update(cx, |picker, model, cx| picker.refresh(cx));
|
||||
model.notify(cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.picker.update(cx, |picker, cx| picker.refresh(cx));
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
executor.timer(SAVE_THROTTLE).await;
|
||||
@@ -482,90 +449,77 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_active_prompt(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn delete_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt_id) = self.active_prompt_id {
|
||||
self.delete_prompt(active_prompt_id, model, cx);
|
||||
self.delete_prompt(active_prompt_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duplicate_active_prompt(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn duplicate_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt_id) = self.active_prompt_id {
|
||||
self.duplicate_prompt(active_prompt_id, model, cx);
|
||||
self.duplicate_prompt(active_prompt_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_default_for_active_prompt(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn toggle_default_for_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt_id) = self.active_prompt_id {
|
||||
self.toggle_default_for_prompt(active_prompt_id, model, cx);
|
||||
self.toggle_default_for_prompt(active_prompt_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_default_for_prompt(
|
||||
&mut self,
|
||||
prompt_id: PromptId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn toggle_default_for_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
|
||||
self.store
|
||||
.save_metadata(prompt_id, prompt_metadata.title, !prompt_metadata.default)
|
||||
.detach_and_log_err(cx);
|
||||
self.picker
|
||||
.update(cx, |picker, model, cx| picker.refresh(cx));
|
||||
model.notify(cx);
|
||||
self.picker.update(cx, |picker, cx| picker.refresh(cx));
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_prompt(
|
||||
&mut self,
|
||||
prompt_id: PromptId,
|
||||
focus: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn load_prompt(&mut self, prompt_id: PromptId, focus: bool, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
|
||||
if focus {
|
||||
prompt_editor
|
||||
.body_editor
|
||||
.update(cx, |editor, model, cx| editor.focus(window, cx));
|
||||
.update(cx, |editor, cx| editor.focus(cx));
|
||||
}
|
||||
self.set_active_prompt(Some(prompt_id), model, cx);
|
||||
self.set_active_prompt(Some(prompt_id), cx);
|
||||
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
|
||||
let language_registry = self.language_registry.clone();
|
||||
let prompt = self.store.load(prompt_id);
|
||||
self.pending_load = model.spawn(cx, |this, mut cx| async move {
|
||||
self.pending_load = cx.spawn(|this, mut cx| async move {
|
||||
let prompt = prompt.await;
|
||||
let markdown = language_registry.language_for_name("Markdown").await;
|
||||
this.update(&mut cx, |this, model, cx| match prompt {
|
||||
this.update(&mut cx, |this, cx| match prompt {
|
||||
Ok(prompt) => {
|
||||
let title_editor = cx.new_model(|model, cx| {
|
||||
let mut editor = Editor::auto_width(model, cx);
|
||||
editor.set_placeholder_text("Untitled", model, cx);
|
||||
editor.set_text(prompt_metadata.title.unwrap_or_default(), model, cx);
|
||||
let title_editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_width(cx);
|
||||
editor.set_placeholder_text("Untitled", cx);
|
||||
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
|
||||
if prompt_id.is_built_in() {
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), model, cx);
|
||||
editor.set_show_inline_completions(Some(false), cx);
|
||||
}
|
||||
editor
|
||||
});
|
||||
let body_editor = cx.new_model(|model, cx| {
|
||||
// todo!: Remove this type annotation on cx
|
||||
let buffer = cx.new_model(|model, cx: &mut AppContext| {
|
||||
let mut buffer = Buffer::local(prompt, model, cx);
|
||||
buffer.set_language(markdown.log_err(), model, cx);
|
||||
let body_editor = cx.new_view(|cx| {
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::local(prompt, cx);
|
||||
buffer.set_language(markdown.log_err(), cx);
|
||||
buffer.set_language_registry(language_registry);
|
||||
buffer
|
||||
});
|
||||
|
||||
let mut editor = Editor::for_buffer(buffer, None, model, cx);
|
||||
let mut editor = Editor::for_buffer(buffer, None, cx);
|
||||
if prompt_id.is_built_in() {
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), model, cx);
|
||||
editor.set_show_inline_completions(Some(false), cx);
|
||||
}
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, model, cx);
|
||||
editor.set_show_gutter(false, model, cx);
|
||||
editor.set_show_wrap_guides(false, model, cx);
|
||||
editor.set_show_indent_guides(false, model, cx);
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_show_wrap_guides(false, cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_use_modal_editing(false);
|
||||
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
|
||||
editor.set_completion_provider(Some(Box::new(
|
||||
@@ -576,7 +530,7 @@ impl PromptLibrary {
|
||||
),
|
||||
)));
|
||||
if focus {
|
||||
editor.focus(window, cx);
|
||||
editor.focus(cx);
|
||||
}
|
||||
editor
|
||||
});
|
||||
@@ -613,14 +567,9 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_active_prompt(
|
||||
&mut self,
|
||||
prompt_id: Option<PromptId>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn set_active_prompt(&mut self, prompt_id: Option<PromptId>, cx: &mut ViewContext<Self>) {
|
||||
self.active_prompt_id = prompt_id;
|
||||
self.picker.update(cx, |picker, model, cx| {
|
||||
self.picker.update(cx, |picker, cx| {
|
||||
if let Some(prompt_id) = prompt_id {
|
||||
if picker
|
||||
.delegate
|
||||
@@ -640,13 +589,13 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
picker.focus(window);
|
||||
picker.focus(cx);
|
||||
}
|
||||
});
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn delete_prompt(&mut self, prompt_id: PromptId, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn delete_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(metadata) = self.store.metadata(prompt_id) {
|
||||
let confirmation = cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
@@ -658,32 +607,25 @@ impl PromptLibrary {
|
||||
&["Delete", "Cancel"],
|
||||
);
|
||||
|
||||
model
|
||||
.spawn(cx, |this, mut cx| async move {
|
||||
if confirmation.await.ok() == Some(0) {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
if this.active_prompt_id == Some(prompt_id) {
|
||||
this.set_active_prompt(None, cx);
|
||||
}
|
||||
this.prompt_editors.remove(&prompt_id);
|
||||
this.store.delete(prompt_id).detach_and_log_err(cx);
|
||||
this.picker
|
||||
.update(cx, |picker, model, cx| picker.refresh(cx));
|
||||
model.notify(cx);
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if confirmation.await.ok() == Some(0) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.active_prompt_id == Some(prompt_id) {
|
||||
this.set_active_prompt(None, cx);
|
||||
}
|
||||
this.prompt_editors.remove(&prompt_id);
|
||||
this.store.delete(prompt_id).detach_and_log_err(cx);
|
||||
this.picker.update(cx, |picker, cx| picker.refresh(cx));
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duplicate_prompt(
|
||||
&mut self,
|
||||
prompt_id: PromptId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn duplicate_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt) = self.prompt_editors.get(&prompt_id) {
|
||||
const DUPLICATE_SUFFIX: &str = " copy";
|
||||
let title_to_duplicate = prompt.title_editor.read(cx).text(cx);
|
||||
@@ -713,39 +655,31 @@ impl PromptLibrary {
|
||||
let save = self
|
||||
.store
|
||||
.save(new_id, Some(title.into()), false, body.into());
|
||||
self.picker
|
||||
.update(cx, |picker, model, cx| picker.refresh(cx));
|
||||
model
|
||||
.spawn(cx, |this, mut cx| async move {
|
||||
save.await?;
|
||||
this.update(&mut cx, |prompt_library, cx| {
|
||||
prompt_library.load_prompt(new_id, true, cx)
|
||||
})
|
||||
self.picker.update(cx, |picker, cx| picker.refresh(cx));
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
save.await?;
|
||||
this.update(&mut cx, |prompt_library, cx| {
|
||||
prompt_library.load_prompt(new_id, true, cx)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_active_prompt(&mut self, _: &Tab, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt) = self.active_prompt_id {
|
||||
self.prompt_editors[&active_prompt]
|
||||
.body_editor
|
||||
.update(cx, |editor, model, cx| editor.focus(window, cx));
|
||||
.update(cx, |editor, cx| editor.focus(cx));
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_picker(&mut self, _: &menu::Cancel, model: &Model<Self>, cx: &mut AppContext) {
|
||||
self.picker
|
||||
.update(cx, |picker, model, cx| picker.focus(window));
|
||||
fn focus_picker(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
self.picker.update(cx, |picker, cx| picker.focus(cx));
|
||||
}
|
||||
|
||||
pub fn inline_assist(
|
||||
&mut self,
|
||||
action: &InlineAssist,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn inline_assist(&mut self, action: &InlineAssist, cx: &mut ViewContext<Self>) {
|
||||
let Some(active_prompt_id) = self.active_prompt_id else {
|
||||
cx.propagate();
|
||||
return;
|
||||
@@ -759,13 +693,13 @@ impl PromptLibrary {
|
||||
let initial_prompt = action.prompt.clone();
|
||||
if provider.is_authenticated(cx) {
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.assist(&prompt_editor, None, None, initial_prompt, window, cx)
|
||||
assistant.assist(&prompt_editor, None, None, initial_prompt, cx)
|
||||
})
|
||||
} else {
|
||||
for window in cx.windows() {
|
||||
if let Some(workspace) = window.downcast::<Workspace>() {
|
||||
let panel = workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
cx.activate_window();
|
||||
workspace.focus_panel::<AssistantPanel>(cx)
|
||||
})
|
||||
@@ -779,12 +713,7 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
fn move_down_from_title(
|
||||
&mut self,
|
||||
_: &editor::actions::MoveDown,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn move_down_from_title(&mut self, _: &editor::actions::MoveDown, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt_id) = self.active_prompt_id {
|
||||
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
|
||||
cx.focus_view(&prompt_editor.body_editor);
|
||||
@@ -792,12 +721,7 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
fn move_up_from_body(
|
||||
&mut self,
|
||||
_: &editor::actions::MoveUp,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn move_up_from_body(&mut self, _: &editor::actions::MoveUp, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt_id) = self.active_prompt_id {
|
||||
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
|
||||
cx.focus_view(&prompt_editor.title_editor);
|
||||
@@ -808,19 +732,18 @@ impl PromptLibrary {
|
||||
fn handle_prompt_title_editor_event(
|
||||
&mut self,
|
||||
prompt_id: PromptId,
|
||||
title_editor: Model<Editor>,
|
||||
title_editor: View<Editor>,
|
||||
event: &EditorEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::BufferEdited => {
|
||||
self.save_prompt(prompt_id, model, cx);
|
||||
self.count_tokens(prompt_id, model, cx);
|
||||
self.save_prompt(prompt_id, cx);
|
||||
self.count_tokens(prompt_id, cx);
|
||||
}
|
||||
EditorEvent::Blurred => {
|
||||
title_editor.update(cx, |title_editor, model, cx| {
|
||||
title_editor.change_selections(None, model, cx, |selections| {
|
||||
title_editor.update(cx, |title_editor, cx| {
|
||||
title_editor.change_selections(None, cx, |selections| {
|
||||
let cursor = selections.oldest_anchor().head();
|
||||
selections.select_anchor_ranges([cursor..cursor]);
|
||||
});
|
||||
@@ -833,19 +756,18 @@ impl PromptLibrary {
|
||||
fn handle_prompt_body_editor_event(
|
||||
&mut self,
|
||||
prompt_id: PromptId,
|
||||
body_editor: Model<Editor>,
|
||||
body_editor: View<Editor>,
|
||||
event: &EditorEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::BufferEdited => {
|
||||
self.save_prompt(prompt_id, model, cx);
|
||||
self.count_tokens(prompt_id, model, cx);
|
||||
self.save_prompt(prompt_id, cx);
|
||||
self.count_tokens(prompt_id, cx);
|
||||
}
|
||||
EditorEvent::Blurred => {
|
||||
body_editor.update(cx, |body_editor, model, cx| {
|
||||
body_editor.change_selections(None, model, cx, |selections| {
|
||||
body_editor.update(cx, |body_editor, cx| {
|
||||
body_editor.change_selections(None, cx, |selections| {
|
||||
let cursor = selections.oldest_anchor().head();
|
||||
selections.select_anchor_ranges([cursor..cursor]);
|
||||
});
|
||||
@@ -855,7 +777,7 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
fn count_tokens(&mut self, prompt_id: PromptId, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn count_tokens(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||
return;
|
||||
};
|
||||
@@ -863,7 +785,7 @@ impl PromptLibrary {
|
||||
let editor = &prompt.body_editor.read(cx);
|
||||
let buffer = &editor.buffer().read(cx).as_singleton().unwrap().read(cx);
|
||||
let body = buffer.as_rope().clone();
|
||||
prompt.pending_token_count = model.spawn(cx, |this, mut cx| {
|
||||
prompt.pending_token_count = cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
const DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
@@ -886,10 +808,10 @@ impl PromptLibrary {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let prompt_editor = this.prompt_editors.get_mut(&prompt_id).unwrap();
|
||||
prompt_editor.token_count = Some(token_count);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
.log_err()
|
||||
@@ -897,7 +819,7 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_list(&mut self, model: &Model<Self>, cx: &mut AppContext) -> impl IntoElement {
|
||||
fn render_prompt_list(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("prompt-list")
|
||||
.capture_action(cx.listener(Self::focus_active_prompt))
|
||||
@@ -917,9 +839,7 @@ impl PromptLibrary {
|
||||
IconButton::new("new-prompt", IconName::Plus)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("New Prompt", &NewPrompt, window, cx)
|
||||
})
|
||||
.tooltip(move |cx| Tooltip::for_action("New Prompt", &NewPrompt, cx))
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(NewPrompt));
|
||||
}),
|
||||
@@ -928,11 +848,7 @@ impl PromptLibrary {
|
||||
.child(div().flex_grow().child(self.picker.clone()))
|
||||
}
|
||||
|
||||
fn render_active_prompt(
|
||||
&mut self,
|
||||
model: &Model<PromptLibrary>,
|
||||
cx: &mut AppContext,
|
||||
) -> gpui::Stateful<Div> {
|
||||
fn render_active_prompt(&mut self, cx: &mut ViewContext<PromptLibrary>) -> gpui::Stateful<Div> {
|
||||
div()
|
||||
.w_2_3()
|
||||
.h_full()
|
||||
@@ -957,7 +873,7 @@ impl PromptLibrary {
|
||||
.overflow_hidden()
|
||||
.pl(DynamicSpacing::Base16.rems(cx))
|
||||
.pt(DynamicSpacing::Base08.rems(cx))
|
||||
.on_click(model.listener(move |_, _, model, window, cx| {
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.focus(&focus_handle);
|
||||
}))
|
||||
.child(
|
||||
@@ -1011,7 +927,7 @@ impl PromptLibrary {
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
status: cx.theme().status().clone(),
|
||||
inlay_hints_style:
|
||||
editor::make_inlay_hints_style(model, cx),
|
||||
editor::make_inlay_hints_style(cx),
|
||||
suggestions_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
..HighlightStyle::default()
|
||||
@@ -1043,7 +959,7 @@ impl PromptLibrary {
|
||||
|
||||
h_flex()
|
||||
.id("token_count")
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
let token_count =
|
||||
token_count.clone();
|
||||
|
||||
@@ -1062,7 +978,6 @@ impl PromptLibrary {
|
||||
.0)
|
||||
.unwrap_or_default()
|
||||
),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -1082,12 +997,11 @@ impl PromptLibrary {
|
||||
Icon::new(IconName::FileLock)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
"Built-in prompt",
|
||||
None,
|
||||
BUILT_IN_TOOLTIP_TEXT,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -1101,11 +1015,10 @@ impl PromptLibrary {
|
||||
.style(ButtonStyle::Transparent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.size(ButtonSize::Large)
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Delete Prompt",
|
||||
&DeletePrompt,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -1123,11 +1036,10 @@ impl PromptLibrary {
|
||||
.style(ButtonStyle::Transparent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.size(ButtonSize::Large)
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Duplicate Prompt",
|
||||
&DuplicatePrompt,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -1152,7 +1064,7 @@ impl PromptLibrary {
|
||||
})
|
||||
.shape(IconButtonShape::Square)
|
||||
.size(ButtonSize::Large)
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if prompt_metadata.default {
|
||||
"Remove from Default Prompt"
|
||||
@@ -1186,13 +1098,8 @@ impl PromptLibrary {
|
||||
}
|
||||
|
||||
impl Render for PromptLibrary {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(window, cx);
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(cx);
|
||||
let theme = cx.theme().clone();
|
||||
|
||||
h_flex()
|
||||
@@ -1208,7 +1115,7 @@ impl Render for PromptLibrary {
|
||||
.overflow_hidden()
|
||||
.font(ui_font)
|
||||
.text_color(theme.colors().text)
|
||||
.child(self.render_prompt_list(model, cx))
|
||||
.child(self.render_prompt_list(cx))
|
||||
.map(|el| {
|
||||
if self.store.prompt_count() == 0 {
|
||||
el.child(
|
||||
@@ -1244,7 +1151,7 @@ impl Render for PromptLibrary {
|
||||
Button::new("create-prompt", "New Prompt")
|
||||
.full_width()
|
||||
.key_binding(KeyBinding::for_action(
|
||||
&NewPrompt, window, cx,
|
||||
&NewPrompt, cx,
|
||||
))
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(NewPrompt.boxed_clone())
|
||||
@@ -1255,7 +1162,7 @@ impl Render for PromptLibrary {
|
||||
),
|
||||
)
|
||||
} else {
|
||||
el.child(self.render_active_prompt(model, cx))
|
||||
el.child(self.render_active_prompt(cx))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use assistant_slash_command::AfterCompletion;
|
||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput};
|
||||
use editor::{CompletionProvider, Editor};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{AppContext, AppContext, Model, Task, WeakView};
|
||||
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
|
||||
use language::{Anchor, Buffer, CodeLabel, Documentation, HighlightId, LanguageServerId, ToPoint};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use project::CompletionIntent;
|
||||
@@ -41,8 +41,8 @@ pub mod terminal_command;
|
||||
pub(crate) struct SlashCommandCompletionProvider {
|
||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakModel<ContextEditor>>,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
}
|
||||
|
||||
pub(crate) struct SlashCommandLine {
|
||||
@@ -55,8 +55,8 @@ pub(crate) struct SlashCommandLine {
|
||||
impl SlashCommandCompletionProvider {
|
||||
pub fn new(
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakModel<ContextEditor>>,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
|
||||
@@ -71,8 +71,7 @@ impl SlashCommandCompletionProvider {
|
||||
command_name: &str,
|
||||
command_range: Range<Anchor>,
|
||||
name_range: Range<Anchor>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<project::Completion>>> {
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let candidates = slash_commands
|
||||
@@ -121,12 +120,12 @@ impl SlashCommandCompletionProvider {
|
||||
let editor = editor.clone();
|
||||
let workspace = workspace.clone();
|
||||
Arc::new(
|
||||
move |intent: CompletionIntent, window: &mut gpui::Window, cx: &mut gpui::AppContext| {
|
||||
move |intent: CompletionIntent, cx: &mut WindowContext| {
|
||||
if !requires_argument
|
||||
&& (!accepts_arguments || intent.is_complete())
|
||||
{
|
||||
editor
|
||||
.update(cx, |editor, model, cx| {
|
||||
.update(cx, |editor, cx| {
|
||||
editor.run_command(
|
||||
command_range.clone(),
|
||||
&command_name,
|
||||
@@ -166,8 +165,7 @@ impl SlashCommandCompletionProvider {
|
||||
command_range: Range<Anchor>,
|
||||
argument_range: Range<Anchor>,
|
||||
last_argument_range: Range<Anchor>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<project::Completion>>> {
|
||||
let new_cancel_flag = Arc::new(AtomicBool::new(false));
|
||||
let mut flag = self.cancel_flag.lock();
|
||||
@@ -205,12 +203,12 @@ impl SlashCommandCompletionProvider {
|
||||
|
||||
let command_range = command_range.clone();
|
||||
let command_name = command_name.clone();
|
||||
move |intent: CompletionIntent, window: &mut gpui::Window, cx: &mut gpui::AppContext| {
|
||||
move |intent: CompletionIntent, cx: &mut WindowContext| {
|
||||
if new_argument.after_completion.run()
|
||||
|| intent.is_complete()
|
||||
{
|
||||
editor
|
||||
.update(cx, |editor, model, cx| {
|
||||
.update(cx, |editor, cx| {
|
||||
editor.run_command(
|
||||
command_range.clone(),
|
||||
&command_name,
|
||||
@@ -262,11 +260,10 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_position: Anchor,
|
||||
_: editor::CompletionContext,
|
||||
model: &Model<Editor>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Vec<project::Completion>>> {
|
||||
let Some((name, arguments, command_range, last_argument_range)) =
|
||||
buffer.update(cx, |buffer, model, _cx| {
|
||||
buffer.update(cx, |buffer, _cx| {
|
||||
let position = buffer_position.to_point(buffer);
|
||||
let line_start = Point::new(position.row, 0);
|
||||
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||
@@ -321,7 +318,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
self.complete_command_name(&name, command_range, last_argument_range, model, cx)
|
||||
self.complete_command_name(&name, command_range, last_argument_range, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,8 +327,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
_: Model<Buffer>,
|
||||
_: Vec<usize>,
|
||||
_: Arc<RwLock<Box<[project::Completion]>>>,
|
||||
_: &Model<Editor>,
|
||||
_: &mut AppContext,
|
||||
_: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<bool>> {
|
||||
Task::ready(Ok(true))
|
||||
}
|
||||
@@ -341,8 +337,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
_: Model<Buffer>,
|
||||
_: project::Completion,
|
||||
_: bool,
|
||||
_: &Model<Editor>,
|
||||
_: &mut AppContext,
|
||||
_: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Option<language::Transaction>>> {
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
@@ -353,8 +348,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
position: language::Anchor,
|
||||
_text: &str,
|
||||
_trigger_in_words: bool,
|
||||
model: &Model<Editor>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
let buffer = buffer.read(cx);
|
||||
let position = position.to_point(buffer);
|
||||
|
||||
@@ -14,7 +14,7 @@ use language_model::{
|
||||
use semantic_index::{FileSummary, SemanticDb};
|
||||
use smol::channel;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use ui::{prelude::*, BorrowAppContext};
|
||||
use ui::{prelude::*, BorrowAppContext, WindowContext};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -53,9 +53,8 @@ impl SlashCommand for AutoCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
// There's no autocomplete for a prompt, since it's arbitrary text.
|
||||
// However, we can use this opportunity to kick off a drain of the backlog.
|
||||
@@ -77,7 +76,7 @@ impl SlashCommand for AutoCommand {
|
||||
|
||||
let cx: &mut AppContext = cx;
|
||||
|
||||
cx.spawn(|cx: AsyncAppContext| async move {
|
||||
cx.spawn(|cx: gpui::AsyncAppContext| async move {
|
||||
let task = project_index.read_with(&cx, |project_index, cx| {
|
||||
project_index.flush_summary_backlogs(cx)
|
||||
})?;
|
||||
@@ -97,10 +96,9 @@ impl SlashCommand for AutoCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: language::BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||
@@ -117,7 +115,7 @@ impl SlashCommand for AutoCommand {
|
||||
return Task::ready(Err(anyhow!("no project indexer")));
|
||||
};
|
||||
|
||||
let task = cx.spawn(|cx: AsyncAppContext| async move {
|
||||
let task = cx.spawn(|cx: gpui::AsyncWindowContext| async move {
|
||||
let summaries = project_index
|
||||
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
|
||||
.await?;
|
||||
|
||||
@@ -107,9 +107,8 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
@@ -123,12 +122,11 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let output = workspace.update(cx, |workspace, model, cx| {
|
||||
let output = workspace.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let fs = workspace.project().read(cx).fs().clone();
|
||||
let path = Self::path_to_cargo_toml(project, cx);
|
||||
|
||||
@@ -8,7 +8,7 @@ use context_server::{
|
||||
manager::{ContextServer, ContextServerManager},
|
||||
types::Prompt,
|
||||
};
|
||||
use gpui::{AppContext, Model, Task, WeakView};
|
||||
use gpui::{AppContext, Model, Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
@@ -77,9 +77,8 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else {
|
||||
return Task::ready(Err(anyhow!("Failed to complete argument")));
|
||||
@@ -129,10 +128,9 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakModel<Workspace>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let server_id = self.server_id.clone();
|
||||
let prompt_name = self.prompt.name.clone();
|
||||
|
||||
@@ -36,9 +36,8 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancellation_flag: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
@@ -48,9 +47,9 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakModel<Workspace>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let store = PromptStore::global(cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
|
||||
@@ -6,7 +6,7 @@ use assistant_slash_command::{
|
||||
};
|
||||
use collections::HashSet;
|
||||
use futures::future;
|
||||
use gpui::{Task, WeakView};
|
||||
use gpui::{Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use text::OffsetRangeExt;
|
||||
@@ -40,9 +40,8 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancellation_flag: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
@@ -52,10 +51,9 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
_arguments: &[String],
|
||||
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let mut paths = HashSet::default();
|
||||
let mut file_command_old_outputs = Vec::new();
|
||||
@@ -79,7 +77,6 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
context_buffer.clone(),
|
||||
workspace.clone(),
|
||||
delegate.clone(),
|
||||
model,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ impl DiagnosticsSlashCommand {
|
||||
&self,
|
||||
query: String,
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: &Model<Workspace>,
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Vec<PathMatch>> {
|
||||
if query.is_empty() {
|
||||
@@ -118,8 +118,8 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
|
||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||
@@ -172,9 +172,9 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||
|
||||
@@ -43,7 +43,7 @@ impl DocsSlashCommand {
|
||||
/// access the workspace so we can read the project.
|
||||
fn ensure_rust_doc_providers_are_registered(
|
||||
&self,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
|
||||
@@ -164,9 +164,8 @@ impl SlashCommand for DocsSlashCommand {
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
self.ensure_rust_doc_providers_are_registered(workspace, cx);
|
||||
|
||||
@@ -273,10 +272,9 @@ impl SlashCommand for DocsSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakModel<Workspace>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
if arguments.is_empty() {
|
||||
return Task::ready(Err(anyhow!("missing an argument")));
|
||||
|
||||
@@ -124,9 +124,8 @@ impl SlashCommand for FetchSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
@@ -136,9 +135,9 @@ impl SlashCommand for FetchSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let Some(argument) = arguments.first() else {
|
||||
return Task::ready(Err(anyhow!("missing URL")));
|
||||
|
||||
@@ -28,7 +28,7 @@ impl FileSlashCommand {
|
||||
&self,
|
||||
query: String,
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: &Model<Workspace>,
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Vec<PathMatch>> {
|
||||
if query.is_empty() {
|
||||
@@ -134,8 +134,8 @@ impl SlashCommand for FileSlashCommand {
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
|
||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||
@@ -187,9 +187,9 @@ impl SlashCommand for FileSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||
|
||||
@@ -35,9 +35,8 @@ impl SlashCommand for NowSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
@@ -47,10 +46,9 @@ impl SlashCommand for NowSlashCommand {
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakModel<Workspace>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let now = Local::now();
|
||||
let text = format!("Today is {now}.", now = now.to_rfc2822());
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::PromptBuilder;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection, SlashCommandResult};
|
||||
use feature_flags::FeatureFlag;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use language::{Anchor, CodeLabel, LspAdapterDelegate};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelTool};
|
||||
use schemars::JsonSchema;
|
||||
@@ -67,9 +67,8 @@ impl SlashCommand for ProjectSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
@@ -79,10 +78,9 @@ impl SlashCommand for ProjectSlashCommand {
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<Anchor>],
|
||||
context_buffer: language::BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
let current_model = model_registry.active_model();
|
||||
|
||||
@@ -37,8 +37,8 @@ impl SlashCommand for PromptSlashCommand {
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_cancellation_flag: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let store = PromptStore::global(cx);
|
||||
let query = arguments.to_owned().join(" ");
|
||||
@@ -64,9 +64,9 @@ impl SlashCommand for PromptSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakModel<Workspace>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let title = arguments.to_owned().join(" ");
|
||||
if title.trim().is_empty() {
|
||||
|
||||
@@ -58,9 +58,8 @@ impl SlashCommand for SearchSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
@@ -70,9 +69,9 @@ impl SlashCommand for SearchSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: language::BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window, cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui::{AppContext, Task, WeakView};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use ui::{IconName, SharedString};
|
||||
use ui::{IconName, SharedString, WindowContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct SelectionCommand;
|
||||
@@ -47,9 +47,8 @@ impl SlashCommand for SelectionCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
@@ -59,10 +58,9 @@ impl SlashCommand for SelectionCommand {
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let mut events = vec![];
|
||||
|
||||
|
||||
@@ -45,9 +45,8 @@ impl SlashCommand for StreamingExampleSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
@@ -57,10 +56,9 @@ impl SlashCommand for StreamingExampleSlashCommand {
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakModel<Workspace>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let (events_tx, events_rx) = mpsc::unbounded();
|
||||
cx.background_executor()
|
||||
|
||||
@@ -8,7 +8,7 @@ use gpui::{Task, WeakView};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use std::sync::Arc;
|
||||
use std::{path::Path, sync::atomic::AtomicBool};
|
||||
use ui::IconName;
|
||||
use ui::{IconName, WindowContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct OutlineSlashCommand;
|
||||
@@ -34,9 +34,8 @@ impl SlashCommand for OutlineSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
@@ -50,12 +49,11 @@ impl SlashCommand for OutlineSlashCommand {
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let output = workspace.update(cx, |workspace, model, cx| {
|
||||
let output = workspace.update(cx, |workspace, cx| {
|
||||
let Some(active_item) = workspace.active_item(cx) else {
|
||||
return Task::ready(Err(anyhow!("no active tab")));
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ use std::{
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::{prelude::*, ActiveTheme};
|
||||
use ui::{prelude::*, ActiveTheme, WindowContext};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -51,9 +51,8 @@ impl SlashCommand for TabSlashCommand {
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let mut has_all_tabs_completion_item = false;
|
||||
let argument_set = arguments
|
||||
@@ -74,8 +73,8 @@ impl SlashCommand for TabSlashCommand {
|
||||
|
||||
let active_item_path = workspace.as_ref().and_then(|workspace| {
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
let snapshot = active_item_buffer(workspace, model, cx).ok()?;
|
||||
.update(cx, |workspace, cx| {
|
||||
let snapshot = active_item_buffer(workspace, cx).ok()?;
|
||||
snapshot.resolve_file_path(cx, true)
|
||||
})
|
||||
.ok()
|
||||
@@ -83,7 +82,7 @@ impl SlashCommand for TabSlashCommand {
|
||||
});
|
||||
let current_query = arguments.last().cloned().unwrap_or_default();
|
||||
let tab_items_search =
|
||||
tab_items_for_queries(workspace, &[current_query], cancel, false, model, cx);
|
||||
tab_items_for_queries(workspace, &[current_query], cancel, false, cx);
|
||||
|
||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||
cx.spawn(|_| async move {
|
||||
@@ -138,10 +137,9 @@ impl SlashCommand for TabSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let tab_items_search = tab_items_for_queries(
|
||||
Some(workspace),
|
||||
@@ -162,12 +160,11 @@ impl SlashCommand for TabSlashCommand {
|
||||
}
|
||||
|
||||
fn tab_items_for_queries(
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
queries: &[String],
|
||||
cancel: Arc<AtomicBool>,
|
||||
strict_match: bool,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<anyhow::Result<Vec<(Option<PathBuf>, BufferSnapshot, usize)>>> {
|
||||
let empty_query = queries.is_empty() || queries.iter().all(|query| query.trim().is_empty());
|
||||
let queries = queries.to_owned();
|
||||
@@ -177,7 +174,7 @@ fn tab_items_for_queries(
|
||||
.context("no workspace")?
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
if strict_match && empty_query {
|
||||
let snapshot = active_item_buffer(workspace, model, cx)?;
|
||||
let snapshot = active_item_buffer(workspace, cx)?;
|
||||
let full_path = snapshot.resolve_file_path(cx, true);
|
||||
return anyhow::Ok(vec![(full_path, snapshot, 0)]);
|
||||
}
|
||||
@@ -288,8 +285,7 @@ fn tab_items_for_queries(
|
||||
|
||||
fn active_item_buffer(
|
||||
workspace: &mut Workspace,
|
||||
model: &Model<Workspace>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ui::ViewContext<Workspace>,
|
||||
) -> anyhow::Result<BufferSnapshot> {
|
||||
let active_editor = workspace
|
||||
.active_item(cx)
|
||||
|
||||
@@ -53,9 +53,8 @@ impl SlashCommand for TerminalSlashCommand {
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
@@ -65,16 +64,15 @@ impl SlashCommand for TerminalSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||
};
|
||||
|
||||
let Some(active_terminal) = resolve_active_terminal(&workspace, model, cx) else {
|
||||
let Some(active_terminal) = resolve_active_terminal(&workspace, cx) else {
|
||||
return Task::ready(Err(anyhow::anyhow!("no active terminal")));
|
||||
};
|
||||
|
||||
@@ -109,10 +107,9 @@ impl SlashCommand for TerminalSlashCommand {
|
||||
}
|
||||
|
||||
fn resolve_active_terminal(
|
||||
workspace: &Model<Workspace>,
|
||||
window: &Window,
|
||||
cx: &AppContext,
|
||||
) -> Option<Model<TerminalView>> {
|
||||
workspace: &View<Workspace>,
|
||||
cx: &WindowContext,
|
||||
) -> Option<View<TerminalView>> {
|
||||
if let Some(terminal_view) = workspace
|
||||
.read(cx)
|
||||
.active_item(cx)
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::SlashCommandWorkingSet;
|
||||
#[derive(IntoElement)]
|
||||
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakModel<ContextEditor>,
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
trigger: T,
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ enum SlashCommandEntry {
|
||||
Info(SlashCommandInfo),
|
||||
Advert {
|
||||
name: SharedString,
|
||||
renderer: fn(&mut gpui::Window, &mut gpui::AppContext) -> AnyElement,
|
||||
on_confirm: fn(&mut gpui::Window, &mut gpui::AppContext),
|
||||
renderer: fn(&mut WindowContext<'_>) -> AnyElement,
|
||||
on_confirm: fn(&mut WindowContext<'_>),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -44,14 +44,14 @@ impl AsRef<str> for SlashCommandEntry {
|
||||
pub(crate) struct SlashCommandDelegate {
|
||||
all_commands: Vec<SlashCommandEntry>,
|
||||
filtered_commands: Vec<SlashCommandEntry>,
|
||||
active_context_editor: WeakModel<ContextEditor>,
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> SlashCommandSelector<T> {
|
||||
pub(crate) fn new(
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakModel<ContextEditor>,
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
trigger: T,
|
||||
) -> Self {
|
||||
SlashCommandSelector {
|
||||
@@ -73,23 +73,18 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1));
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut gpui::Window, _cx: &mut gpui::AppContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Select a command...".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<()> {
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let all_commands = self.all_commands.clone();
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let filtered_commands = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
@@ -109,10 +104,10 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate.filtered_commands = filtered_commands;
|
||||
this.delegate.set_selected_index(0, cx);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -144,31 +139,25 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
ret
|
||||
}
|
||||
|
||||
fn confirm(
|
||||
&mut self,
|
||||
_secondary: bool,
|
||||
model: &Model<Picker>,
|
||||
window: &mut Window,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(command) = self.filtered_commands.get(self.selected_index) {
|
||||
match command {
|
||||
SlashCommandEntry::Info(info) => {
|
||||
self.active_context_editor
|
||||
.update(cx, |context_editor, model, cx| {
|
||||
.update(cx, |context_editor, cx| {
|
||||
context_editor.insert_command(&info.name, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
SlashCommandEntry::Advert { on_confirm, .. } => {
|
||||
on_confirm(window, cx);
|
||||
on_confirm(cx);
|
||||
}
|
||||
}
|
||||
model.emit(DismissEvent, cx);
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, model: &Model<Picker>, _cx: &mut AppContext) {}
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn editor_position(&self) -> PickerEditorPosition {
|
||||
PickerEditorPosition::End
|
||||
@@ -178,8 +167,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let command_info = self.filtered_commands.get(ix)?;
|
||||
|
||||
@@ -191,10 +179,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
.selected(selected)
|
||||
.tooltip({
|
||||
let description = info.description.clone();
|
||||
move |cx| {
|
||||
cx.new_model(|_, _| Tooltip::new(description.clone()))
|
||||
.into()
|
||||
}
|
||||
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
@@ -245,14 +230,14 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Dense)
|
||||
.selected(selected)
|
||||
.child(renderer(window, cx)),
|
||||
.child(renderer(cx)),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::AppContext) -> impl IntoElement {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let all_models = self
|
||||
.working_set
|
||||
.featured_command_names(cx)
|
||||
@@ -320,15 +305,14 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
selected_index: 0,
|
||||
};
|
||||
|
||||
let picker_view = cx.new_model(|model, cx| {
|
||||
let picker =
|
||||
Picker::uniform_list(delegate, model, cx).max_height(Some(rems(20.).into()));
|
||||
let picker_view = cx.new_view(|cx| {
|
||||
let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into()));
|
||||
picker
|
||||
});
|
||||
|
||||
let handle = self
|
||||
.active_context_editor
|
||||
.update(cx, |this, model, _| this.slash_menu_handle.clone())
|
||||
.update(cx, |this, _| this.slash_menu_handle.clone())
|
||||
.ok();
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |_cx| Some(picker_view.clone()))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,13 +17,13 @@ use workspace::Workspace;
|
||||
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
||||
|
||||
pub struct ActiveThread {
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
thread: Model<Thread>,
|
||||
messages: Vec<MessageId>,
|
||||
list_state: ListState,
|
||||
rendered_messages_by_id: HashMap<MessageId, Model<Markdown>>,
|
||||
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
|
||||
last_error: Option<ThreadError>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -31,14 +31,13 @@ pub struct ActiveThread {
|
||||
impl ActiveThread {
|
||||
pub fn new(
|
||||
thread: Model<Thread>,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let subscriptions = vec![
|
||||
cx.observe(&thread, |_, _, cx| model.notify(cx)),
|
||||
cx.observe(&thread, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&thread, Self::handle_thread_event),
|
||||
];
|
||||
|
||||
@@ -50,9 +49,9 @@ impl ActiveThread {
|
||||
messages: Vec::new(),
|
||||
rendered_messages_by_id: HashMap::default(),
|
||||
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
|
||||
let this = model.downgrade();
|
||||
move |ix, window: &mut gpui::Window, cx: &mut gpui::AppContext| {
|
||||
this.update(cx, |this, model, cx| this.render_message(ix, model, cx))
|
||||
let this = cx.view().downgrade();
|
||||
move |ix, cx: &mut WindowContext| {
|
||||
this.update(cx, |this, cx| this.render_message(ix, cx))
|
||||
.unwrap()
|
||||
}
|
||||
}),
|
||||
@@ -61,7 +60,7 @@ impl ActiveThread {
|
||||
};
|
||||
|
||||
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
|
||||
this.push_message(&message.id, message.text.clone(), model, cx);
|
||||
this.push_message(&message.id, message.text.clone(), cx);
|
||||
}
|
||||
|
||||
this
|
||||
@@ -83,13 +82,7 @@ impl ActiveThread {
|
||||
self.last_error.take();
|
||||
}
|
||||
|
||||
fn push_message(
|
||||
&mut self,
|
||||
id: &MessageId,
|
||||
text: String,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn push_message(&mut self, id: &MessageId, text: String, cx: &mut ViewContext<Self>) {
|
||||
let old_len = self.messages.len();
|
||||
self.messages.push(*id);
|
||||
self.list_state.splice(old_len..old_len, 1);
|
||||
@@ -98,7 +91,7 @@ impl ActiveThread {
|
||||
let ui_font_size = TextSize::Default.rems(cx);
|
||||
let buffer_font_size = theme_settings.buffer_font_size;
|
||||
|
||||
let mut text_style = window.text_style();
|
||||
let mut text_style = cx.text_style();
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
font_size: Some(ui_font_size.into()),
|
||||
@@ -127,14 +120,12 @@ impl ActiveThread {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let markdown = cx.new_model(|model, cx| {
|
||||
let markdown = cx.new_view(|cx| {
|
||||
Markdown::new(
|
||||
text,
|
||||
markdown_style,
|
||||
Some(self.language_registry.clone()),
|
||||
None,
|
||||
model,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -145,8 +136,7 @@ impl ActiveThread {
|
||||
&mut self,
|
||||
_: Model<Thread>,
|
||||
event: &ThreadEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ThreadEvent::ShowError(error) => {
|
||||
@@ -156,8 +146,8 @@ impl ActiveThread {
|
||||
ThreadEvent::SummaryChanged => {}
|
||||
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
||||
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
|
||||
markdown.update(cx, |markdown, model, cx| {
|
||||
markdown.append(text, model, cx);
|
||||
markdown.update(cx, |markdown, cx| {
|
||||
markdown.append(text, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -168,10 +158,10 @@ impl ActiveThread {
|
||||
.message(*message_id)
|
||||
.map(|message| message.text.clone())
|
||||
{
|
||||
self.push_message(message_id, message_text, model, cx);
|
||||
self.push_message(message_id, message_text, cx);
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
ThreadEvent::UsePendingTools => {
|
||||
let pending_tool_uses = self
|
||||
@@ -185,14 +175,13 @@ impl ActiveThread {
|
||||
|
||||
for tool_use in pending_tool_uses {
|
||||
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), model, cx);
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
|
||||
|
||||
self.thread.update(cx, |thread, model, cx| {
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_tool_output(
|
||||
tool_use.assistant_message_id,
|
||||
tool_use.id.clone(),
|
||||
task,
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -203,7 +192,7 @@ impl ActiveThread {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_message(&self, ix: usize, model: &Model<Self>, cx: &mut AppContext) -> AnyElement {
|
||||
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
let message_id = self.messages[ix];
|
||||
let Some(message) = self.thread.read(cx).message(message_id) else {
|
||||
return Empty.into_any();
|
||||
@@ -247,12 +236,7 @@ impl ActiveThread {
|
||||
}
|
||||
|
||||
impl Render for ActiveThread {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
list(self.list_state.clone()).flex_1()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::zed_urls;
|
||||
use gpui::{
|
||||
prelude::*, px, svg, Action, AnyElement, AppContext, AppContext, EventEmitter, FocusHandle,
|
||||
FocusableView, FontWeight, Model, Pixels, Task, View, WeakView,
|
||||
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Model, Pixels, Task, View, ViewContext, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::LanguageModelRegistry;
|
||||
@@ -24,8 +25,8 @@ use crate::{NewThread, OpenHistory, ToggleFocus, ToggleModelSelector};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, model: &Model<Workspace>, _cx: &mut AppContext| {
|
||||
workspace.register_action(model, |workspace, _: &ToggleFocus, cx| {
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
});
|
||||
},
|
||||
@@ -39,23 +40,22 @@ enum ActiveView {
|
||||
}
|
||||
|
||||
pub struct AssistantPanel {
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
thread: Model<ActiveThread>,
|
||||
message_editor: Model<MessageEditor>,
|
||||
thread: View<ActiveThread>,
|
||||
message_editor: View<MessageEditor>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
local_timezone: UtcOffset,
|
||||
active_view: ActiveView,
|
||||
history: Model<ThreadHistory>,
|
||||
history: View<ThreadHistory>,
|
||||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
pub fn load(
|
||||
workspace: WeakModel<Workspace>,
|
||||
window: AnyWindowHandle,
|
||||
cx: AsyncAppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let tools = Arc::new(ToolWorkingSet::default());
|
||||
let thread_store = workspace
|
||||
@@ -66,7 +66,7 @@ impl AssistantPanel {
|
||||
.await?;
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
cx.new_model(|model, cx| Self::new(workspace, thread_store, tools, model, cx))
|
||||
cx.new_view(|cx| Self::new(workspace, thread_store, tools, cx))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -75,37 +75,34 @@ impl AssistantPanel {
|
||||
workspace: &Workspace,
|
||||
thread_store: Model<ThreadStore>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let thread = thread_store.update(cx, |this, model, cx| this.create_thread(model, cx));
|
||||
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
||||
let language_registry = workspace.project().read(cx).languages().clone();
|
||||
let workspace = workspace.weak_handle();
|
||||
let weak_self = model.downgrade();
|
||||
let weak_self = cx.view().downgrade();
|
||||
|
||||
Self {
|
||||
active_view: ActiveView::Thread,
|
||||
workspace: workspace.clone(),
|
||||
language_registry: language_registry.clone(),
|
||||
thread_store: thread_store.clone(),
|
||||
thread: cx.new_model(|model, cx| {
|
||||
thread: cx.new_view(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
workspace,
|
||||
language_registry,
|
||||
tools.clone(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
message_editor: cx.new_model(|model, cx| MessageEditor::new(thread.clone(), model, cx)),
|
||||
message_editor: cx.new_view(|cx| MessageEditor::new(thread.clone(), cx)),
|
||||
tools,
|
||||
local_timezone: UtcOffset::from_whole_seconds(
|
||||
chrono::Local::now().offset().local_minus_utc(),
|
||||
)
|
||||
.unwrap(),
|
||||
history: cx
|
||||
.new_model(|model, cx| ThreadHistory::new(weak_self, thread_store, model, cx)),
|
||||
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,63 +110,50 @@ impl AssistantPanel {
|
||||
self.local_timezone
|
||||
}
|
||||
|
||||
fn new_thread(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let thread = self
|
||||
.thread_store
|
||||
.update(cx, |this, model, cx| this.create_thread(model, cx));
|
||||
.update(cx, |this, cx| this.create_thread(cx));
|
||||
|
||||
self.active_view = ActiveView::Thread;
|
||||
self.thread = cx.new_model(|model, cx| {
|
||||
self.thread = cx.new_view(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
self.workspace.clone(),
|
||||
self.language_registry.clone(),
|
||||
self.tools.clone(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.message_editor = cx.new_model(|model, cx| MessageEditor::new(thread, model, cx));
|
||||
self.message_editor.focus_handle(cx).focus(window);
|
||||
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread, cx));
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
pub(crate) fn open_thread(
|
||||
&mut self,
|
||||
thread_id: &ThreadId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
|
||||
let Some(thread) = self
|
||||
.thread_store
|
||||
.update(cx, |this, model, cx| this.open_thread(thread_id, model, cx))
|
||||
.update(cx, |this, cx| this.open_thread(thread_id, cx))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.active_view = ActiveView::Thread;
|
||||
self.thread = cx.new_model(|model, cx| {
|
||||
self.thread = cx.new_view(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
self.workspace.clone(),
|
||||
self.language_registry.clone(),
|
||||
self.tools.clone(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.message_editor = cx.new_model(|model, cx| MessageEditor::new(thread, model, cx));
|
||||
self.message_editor.focus_handle(cx).focus(window);
|
||||
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread, cx));
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
pub(crate) fn delete_thread(
|
||||
&mut self,
|
||||
thread_id: &ThreadId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.thread_store.update(cx, |this, model, cx| {
|
||||
this.delete_thread(thread_id, model, cx)
|
||||
});
|
||||
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
|
||||
self.thread_store
|
||||
.update(cx, |this, cx| this.delete_thread(thread_id, cx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +173,7 @@ impl Panel for AssistantPanel {
|
||||
"AssistantPanel2"
|
||||
}
|
||||
|
||||
fn position(&self, _window: &Window, cx: &AppContext) -> DockPosition {
|
||||
fn position(&self, _cx: &WindowContext) -> DockPosition {
|
||||
DockPosition::Right
|
||||
}
|
||||
|
||||
@@ -197,26 +181,25 @@ impl Panel for AssistantPanel {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_position(&mut self, _position: DockPosition, model: &Model<Self>, _cx: &mut AppContext) {
|
||||
}
|
||||
fn set_position(&mut self, _position: DockPosition, _cx: &mut ViewContext<Self>) {}
|
||||
|
||||
fn size(&self, _window: &Window, cx: &AppContext) -> Pixels {
|
||||
fn size(&self, _cx: &WindowContext) -> Pixels {
|
||||
px(640.)
|
||||
}
|
||||
|
||||
fn set_size(&mut self, _size: Option<Pixels>, model: &Model<Self>, _cx: &mut AppContext) {}
|
||||
fn set_size(&mut self, _size: Option<Pixels>, _cx: &mut ViewContext<Self>) {}
|
||||
|
||||
fn set_active(&mut self, _active: bool, model: &Model<Self>, _cx: &mut AppContext) {}
|
||||
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
|
||||
|
||||
fn remote_id() -> Option<proto::PanelId> {
|
||||
Some(proto::PanelId::AssistantPanel)
|
||||
}
|
||||
|
||||
fn icon(&self, _window: &Window, cx: &AppContext) -> Option<IconName> {
|
||||
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
|
||||
Some(IconName::ZedAssistant)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _window: &Window, cx: &AppContext) -> Option<&'static str> {
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
Some("Assistant Panel")
|
||||
}
|
||||
|
||||
@@ -226,7 +209,7 @@ impl Panel for AssistantPanel {
|
||||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
fn render_toolbar(&self, model: &Model<Self>, cx: &mut AppContext) -> impl IntoElement {
|
||||
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
h_flex()
|
||||
@@ -242,7 +225,7 @@ impl AssistantPanel {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.child(self.render_language_model_selector(model, cx))
|
||||
.child(self.render_language_model_selector(cx))
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new("new-thread", IconName::Plus)
|
||||
@@ -256,7 +239,6 @@ impl AssistantPanel {
|
||||
"New Thread",
|
||||
&NewThread,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -277,7 +259,6 @@ impl AssistantPanel {
|
||||
"Open History",
|
||||
&OpenHistory,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -291,7 +272,7 @@ impl AssistantPanel {
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |window, cx| Tooltip::text("Configure Assistant", cx))
|
||||
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
|
||||
.on_click(move |_event, _cx| {
|
||||
println!("Configure Assistant");
|
||||
}),
|
||||
@@ -299,11 +280,7 @@ impl AssistantPanel {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_language_model_selector(
|
||||
&self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
|
||||
@@ -350,32 +327,22 @@ impl AssistantPanel {
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Change Model", &ToggleModelSelector, model, cx)
|
||||
}),
|
||||
.tooltip(move |cx| Tooltip::for_action("Change Model", &ToggleModelSelector, cx)),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_active_thread_or_empty_state(
|
||||
&self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> AnyElement {
|
||||
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
if self.thread.read(cx).is_empty() {
|
||||
return self.render_thread_empty_state(model, cx).into_any_element();
|
||||
return self.render_thread_empty_state(cx).into_any_element();
|
||||
}
|
||||
|
||||
self.thread.clone().into_any()
|
||||
}
|
||||
|
||||
fn render_thread_empty_state(
|
||||
&self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render_thread_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let recent_threads = self
|
||||
.thread_store
|
||||
.update(cx, |this, model, cx| this.recent_threads(3, model, cx));
|
||||
.update(cx, |this, cx| this.recent_threads(3, cx));
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
@@ -443,7 +410,7 @@ impl AssistantPanel {
|
||||
v_flex().gap_2().children(
|
||||
recent_threads
|
||||
.into_iter()
|
||||
.map(|thread| PastThread::new(thread, model.downgrade())),
|
||||
.map(|thread| PastThread::new(thread, cx.view().downgrade())),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -454,7 +421,6 @@ impl AssistantPanel {
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
model,
|
||||
cx,
|
||||
))
|
||||
.on_click(move |_event, cx| {
|
||||
@@ -465,7 +431,7 @@ impl AssistantPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_last_error(&self, model: &Model<Self>, cx: &mut AppContext) -> Option<AnyElement> {
|
||||
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
let last_error = self.thread.read(cx).last_error()?;
|
||||
|
||||
Some(
|
||||
@@ -479,23 +445,19 @@ impl AssistantPanel {
|
||||
.elevation_2(cx)
|
||||
.occlude()
|
||||
.child(match last_error {
|
||||
ThreadError::PaymentRequired => self.render_payment_required_error(model, cx),
|
||||
ThreadError::PaymentRequired => self.render_payment_required_error(cx),
|
||||
ThreadError::MaxMonthlySpendReached => {
|
||||
self.render_max_monthly_spend_reached_error(model, cx)
|
||||
self.render_max_monthly_spend_reached_error(cx)
|
||||
}
|
||||
ThreadError::Message(error_message) => {
|
||||
self.render_error_message(&error_message, model, cx)
|
||||
self.render_error_message(&error_message, cx)
|
||||
}
|
||||
})
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_payment_required_error(
|
||||
&self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> AnyElement {
|
||||
fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
|
||||
|
||||
v_flex()
|
||||
@@ -520,32 +482,28 @@ impl AssistantPanel {
|
||||
.mt_1()
|
||||
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
this.thread.update(cx, |this, model, _cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
cx.open_url(&zed_urls::account_url(cx));
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
},
|
||||
)))
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
this.thread.update(cx, |this, model, _cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
},
|
||||
))),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn render_max_monthly_spend_reached_error(
|
||||
&self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> AnyElement {
|
||||
fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
|
||||
|
||||
v_flex()
|
||||
@@ -570,23 +528,23 @@ impl AssistantPanel {
|
||||
.mt_1()
|
||||
.child(
|
||||
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
|
||||
model.listener(|this, model, _, cx| {
|
||||
this.thread.update(cx, |this, model, _cx| {
|
||||
cx.listener(|this, _, cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
cx.open_url(&zed_urls::account_url(cx));
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
this.thread.update(cx, |this, model, _cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
},
|
||||
))),
|
||||
)
|
||||
@@ -596,8 +554,7 @@ impl AssistantPanel {
|
||||
fn render_error_message(
|
||||
&self,
|
||||
error_message: &SharedString,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
@@ -624,11 +581,11 @@ impl AssistantPanel {
|
||||
.mt_1()
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
this.thread.update(cx, |this, model, _cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
},
|
||||
))),
|
||||
)
|
||||
@@ -637,12 +594,7 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
impl Render for AssistantPanel {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.key_context("AssistantPanel2")
|
||||
.justify_between()
|
||||
@@ -652,20 +604,20 @@ impl Render for AssistantPanel {
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
|
||||
this.active_view = ActiveView::History;
|
||||
this.history.focus_handle(cx).focus(window);
|
||||
model.notify(cx);
|
||||
this.history.focus_handle(cx).focus(cx);
|
||||
cx.notify();
|
||||
}))
|
||||
.child(self.render_toolbar(model, cx))
|
||||
.child(self.render_toolbar(cx))
|
||||
.map(|parent| match self.active_view {
|
||||
ActiveView::Thread => parent
|
||||
.child(self.render_active_thread_or_empty_state(model, cx))
|
||||
.child(self.render_active_thread_or_empty_state(cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(self.message_editor.clone()),
|
||||
)
|
||||
.children(self.render_last_error(model, cx)),
|
||||
.children(self.render_last_error(cx)),
|
||||
ActiveView::History => parent.child(self.history.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::message_editor::MessageEditor;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub(super) struct ContextPicker<T: PopoverTrigger> {
|
||||
message_editor: WeakModel<MessageEditor>,
|
||||
message_editor: WeakView<MessageEditor>,
|
||||
trigger: T,
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ struct ContextPickerEntry {
|
||||
pub(crate) struct ContextPickerDelegate {
|
||||
all_entries: Vec<ContextPickerEntry>,
|
||||
filtered_entries: Vec<ContextPickerEntry>,
|
||||
message_editor: WeakModel<MessageEditor>,
|
||||
message_editor: WeakView<MessageEditor>,
|
||||
selected_ix: usize,
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> ContextPicker<T> {
|
||||
pub(crate) fn new(message_editor: WeakModel<MessageEditor>, trigger: T) -> Self {
|
||||
pub(crate) fn new(message_editor: WeakView<MessageEditor>, trigger: T) -> Self {
|
||||
ContextPicker {
|
||||
message_editor,
|
||||
trigger,
|
||||
@@ -46,23 +46,18 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
self.selected_ix
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_ix = ix.min(self.filtered_entries.len().saturating_sub(1));
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut gpui::Window, _cx: &mut gpui::AppContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Select a context source…".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<()> {
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let all_commands = self.all_entries.clone();
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let filtered_commands = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
@@ -82,27 +77,27 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate.filtered_entries = filtered_commands;
|
||||
this.delegate.set_selected_index(0, cx);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(entry) = self.filtered_entries.get(self.selected_ix) {
|
||||
self.message_editor
|
||||
.update(cx, |_message_editor, model, _cx| {
|
||||
.update(cx, |_message_editor, _cx| {
|
||||
println!("Insert context from {}", entry.name);
|
||||
})
|
||||
.ok();
|
||||
model.emit(DismissEvent, cx);
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, model: &Model<Picker>, _cx: &mut AppContext) {}
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn editor_position(&self) -> PickerEditorPosition {
|
||||
PickerEditorPosition::End
|
||||
@@ -112,8 +107,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
model: &Model<Picker>,
|
||||
_cx: &mut AppContext,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let entry = self.filtered_entries.get(ix)?;
|
||||
|
||||
@@ -124,10 +118,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
.selected(selected)
|
||||
.tooltip({
|
||||
let description = entry.description.clone();
|
||||
move |cx| {
|
||||
cx.new_model(|_model, _cx| Tooltip::new(description.clone()))
|
||||
.into()
|
||||
}
|
||||
move |cx| cx.new_view(|_cx| Tooltip::new(description.clone())).into()
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
@@ -159,7 +150,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> RenderOnce for ContextPicker<T> {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::AppContext) -> impl IntoElement {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let entries = vec![
|
||||
ContextPickerEntry {
|
||||
name: "directory".into(),
|
||||
@@ -185,13 +176,12 @@ impl<T: PopoverTrigger> RenderOnce for ContextPicker<T> {
|
||||
selected_ix: 0,
|
||||
};
|
||||
|
||||
let picker = cx.new_model(|model, cx| {
|
||||
Picker::uniform_list(delegate, model, cx).max_height(Some(rems(20.).into()))
|
||||
});
|
||||
let picker =
|
||||
cx.new_view(|cx| Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())));
|
||||
|
||||
let handle = self
|
||||
.message_editor
|
||||
.update(cx, |this, model, _| this.context_picker_handle.clone())
|
||||
.update(cx, |this, _| this.context_picker_handle.clone())
|
||||
.ok();
|
||||
PopoverMenu::new("context-picker")
|
||||
.menu(move |_cx| Some(picker.clone()))
|
||||
|
||||
@@ -15,18 +15,18 @@ use crate::Chat;
|
||||
|
||||
pub struct MessageEditor {
|
||||
thread: Model<Thread>,
|
||||
editor: Model<Editor>,
|
||||
editor: View<Editor>,
|
||||
pub(crate) context_picker_handle: PopoverMenuHandle<Picker<ContextPickerDelegate>>,
|
||||
use_tools: bool,
|
||||
}
|
||||
|
||||
impl MessageEditor {
|
||||
pub fn new(thread: Model<Thread>, model: &Model<Self>, cx: &mut AppContext) -> Self {
|
||||
pub fn new(thread: Model<Thread>, cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
thread,
|
||||
editor: cx.new_model(|model, cx| {
|
||||
let mut editor = Editor::auto_height(80, model, cx);
|
||||
editor.set_placeholder_text("Ask anything…", model, cx);
|
||||
editor: cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(80, cx);
|
||||
editor.set_placeholder_text("Ask anything…", cx);
|
||||
|
||||
editor
|
||||
}),
|
||||
@@ -35,35 +35,34 @@ impl MessageEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn chat(&mut self, _: &Chat, model: &Model<Self>, cx: &mut AppContext) {
|
||||
self.send_to_model(RequestKind::Chat, model, cx);
|
||||
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
|
||||
self.send_to_model(RequestKind::Chat, cx);
|
||||
}
|
||||
|
||||
fn send_to_model(
|
||||
&mut self,
|
||||
request_kind: RequestKind,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<()> {
|
||||
let provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
if provider
|
||||
.as_ref()
|
||||
.map_or(false, |provider| provider.must_accept_terms(cx))
|
||||
{
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
return None;
|
||||
}
|
||||
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
let model = model_registry.active_model()?;
|
||||
|
||||
let user_message = self.editor.update(cx, |editor, model, cx| {
|
||||
let user_message = self.editor.update(cx, |editor, cx| {
|
||||
let text = editor.text(cx);
|
||||
editor.clear(cx);
|
||||
text
|
||||
});
|
||||
|
||||
self.thread.update(cx, |thread, model, cx| {
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_user_message(user_message, cx);
|
||||
let mut request = thread.to_completion_request(request_kind, cx);
|
||||
|
||||
@@ -94,12 +93,7 @@ impl FocusableView for MessageEditor {
|
||||
}
|
||||
|
||||
impl Render for MessageEditor {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let font_size = TextSize::Default.rems(cx);
|
||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
@@ -113,7 +107,7 @@ impl Render for MessageEditor {
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
h_flex().gap_2().child(ContextPicker::new(
|
||||
model.downgrade(),
|
||||
cx.view().downgrade(),
|
||||
IconButton::new("add-context", IconName::Plus)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small),
|
||||
@@ -166,11 +160,11 @@ impl Render for MessageEditor {
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Chat"))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, model, cx)
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Chat, model, cx);
|
||||
focus_handle.dispatch_action(&Chat, cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,7 +6,7 @@ use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use gpui::{AppContext, EventEmitter, SharedString, Task};
|
||||
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
||||
@@ -71,7 +71,7 @@ pub struct Thread {
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
pub fn new(tools: Arc<ToolWorkingSet>, model: &Model<Self>, _cx: &mut AppContext) -> Self {
|
||||
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
id: ThreadId::new(),
|
||||
updated_at: Utc::now(),
|
||||
@@ -108,14 +108,9 @@ impl Thread {
|
||||
self.summary.clone()
|
||||
}
|
||||
|
||||
pub fn set_summary(
|
||||
&mut self,
|
||||
summary: impl Into<SharedString>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut ModelContext<Self>) {
|
||||
self.summary = Some(summary.into());
|
||||
model.emit(ThreadEvent::SummaryChanged, cx);
|
||||
cx.emit(ThreadEvent::SummaryChanged);
|
||||
}
|
||||
|
||||
pub fn message(&self, id: MessageId) -> Option<&Message> {
|
||||
@@ -134,21 +129,15 @@ impl Thread {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn insert_user_message(
|
||||
&mut self,
|
||||
text: impl Into<String>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.insert_message(Role::User, text, model, cx)
|
||||
pub fn insert_user_message(&mut self, text: impl Into<String>, cx: &mut ModelContext<Self>) {
|
||||
self.insert_message(Role::User, text, cx)
|
||||
}
|
||||
|
||||
pub fn insert_message(
|
||||
&mut self,
|
||||
role: Role,
|
||||
text: impl Into<String>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let id = self.next_message_id.post_inc();
|
||||
self.messages.push(Message {
|
||||
@@ -157,7 +146,7 @@ impl Thread {
|
||||
text: text.into(),
|
||||
});
|
||||
self.touch_updated_at();
|
||||
model.emit(ThreadEvent::MessageAdded(id), cx);
|
||||
cx.emit(ThreadEvent::MessageAdded(id));
|
||||
}
|
||||
|
||||
pub fn to_completion_request(
|
||||
@@ -211,8 +200,7 @@ impl Thread {
|
||||
&mut self,
|
||||
request: LanguageModelRequest,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let pending_completion_id = post_inc(&mut self.completion_count);
|
||||
|
||||
@@ -237,13 +225,10 @@ impl Thread {
|
||||
if let Some(last_message) = thread.messages.last_mut() {
|
||||
if last_message.role == Role::Assistant {
|
||||
last_message.text.push_str(&chunk);
|
||||
model.emit(
|
||||
cx,
|
||||
ThreadEvent::StreamedAssistantText(
|
||||
last_message.id,
|
||||
chunk,
|
||||
),
|
||||
);
|
||||
cx.emit(ThreadEvent::StreamedAssistantText(
|
||||
last_message.id,
|
||||
chunk,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,8 +259,8 @@ impl Thread {
|
||||
}
|
||||
|
||||
thread.touch_updated_at();
|
||||
model.emit(ThreadEvent::StreamedCompletion, cx);
|
||||
model.notify(cx);
|
||||
cx.emit(ThreadEvent::StreamedCompletion);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
smol::future::yield_now().await;
|
||||
@@ -300,31 +285,25 @@ impl Thread {
|
||||
.update(&mut cx, |_thread, cx| match result.as_ref() {
|
||||
Ok(stop_reason) => match stop_reason {
|
||||
StopReason::ToolUse => {
|
||||
model.emit(ThreadEvent::UsePendingTools, cx);
|
||||
cx.emit(ThreadEvent::UsePendingTools);
|
||||
}
|
||||
StopReason::EndTurn => {}
|
||||
StopReason::MaxTokens => {}
|
||||
},
|
||||
Err(error) => {
|
||||
if error.is::<PaymentRequiredError>() {
|
||||
model.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired), cx);
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
|
||||
} else if error.is::<MaxMonthlySpendReachedError>() {
|
||||
model.emit(
|
||||
cx,
|
||||
ThreadEvent::ShowError(ThreadError::MaxMonthlySpendReached),
|
||||
);
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::MaxMonthlySpendReached));
|
||||
} else {
|
||||
let error_message = error
|
||||
.chain()
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
model.emit(
|
||||
cx,
|
||||
ThreadEvent::ShowError(ThreadError::Message(SharedString::from(
|
||||
error_message.clone(),
|
||||
))),
|
||||
);
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::Message(
|
||||
SharedString::from(error_message.clone()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -337,7 +316,7 @@ impl Thread {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn summarize(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn summarize(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
||||
return;
|
||||
};
|
||||
@@ -359,7 +338,7 @@ impl Thread {
|
||||
cache: false,
|
||||
});
|
||||
|
||||
self.pending_summary = model.spawn(cx, |this, mut cx| {
|
||||
self.pending_summary = cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
let stream = model.stream_completion_text(request, &cx);
|
||||
let mut messages = stream.await?;
|
||||
@@ -376,12 +355,12 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if !new_summary.is_empty() {
|
||||
this.summary = Some(new_summary.into());
|
||||
}
|
||||
|
||||
model.emit(ThreadEvent::SummaryChanged, cx);
|
||||
cx.emit(ThreadEvent::SummaryChanged);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
@@ -395,8 +374,7 @@ impl Thread {
|
||||
assistant_message_id: MessageId,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Task<Result<String>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let insert_output_task = cx.spawn(|thread, mut cx| {
|
||||
let tool_use_id = tool_use_id.clone();
|
||||
@@ -422,7 +400,7 @@ impl Thread {
|
||||
is_error: false,
|
||||
});
|
||||
|
||||
model.emit(ThreadEvent::ToolFinished { tool_use_id }, cx);
|
||||
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
|
||||
}
|
||||
Err(err) => {
|
||||
tool_results.push(LanguageModelToolResult {
|
||||
|
||||
@@ -10,20 +10,19 @@ use crate::AssistantPanel;
|
||||
|
||||
pub struct ThreadHistory {
|
||||
focus_handle: FocusHandle,
|
||||
assistant_panel: WeakModel<AssistantPanel>,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
}
|
||||
|
||||
impl ThreadHistory {
|
||||
pub(crate) fn new(
|
||||
assistant_panel: WeakModel<AssistantPanel>,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
focus_handle: window.focus_handle(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
assistant_panel,
|
||||
thread_store,
|
||||
scroll_handle: UniformListScrollHandle::default(),
|
||||
@@ -38,15 +37,8 @@ impl FocusableView for ThreadHistory {
|
||||
}
|
||||
|
||||
impl Render for ThreadHistory {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
let threads = self
|
||||
.thread_store
|
||||
.update(cx, |this, model, cx| this.threads(cx));
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
|
||||
|
||||
v_flex()
|
||||
.id("thread-history-container")
|
||||
@@ -93,11 +85,11 @@ impl Render for ThreadHistory {
|
||||
#[derive(IntoElement)]
|
||||
pub struct PastThread {
|
||||
thread: Model<Thread>,
|
||||
assistant_panel: WeakModel<AssistantPanel>,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
}
|
||||
|
||||
impl PastThread {
|
||||
pub fn new(thread: Model<Thread>, assistant_panel: WeakModel<AssistantPanel>) -> Self {
|
||||
pub fn new(thread: Model<Thread>, assistant_panel: WeakView<AssistantPanel>) -> Self {
|
||||
Self {
|
||||
thread,
|
||||
assistant_panel,
|
||||
@@ -106,7 +98,7 @@ impl PastThread {
|
||||
}
|
||||
|
||||
impl RenderOnce for PastThread {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::AppContext) -> impl IntoElement {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let (id, summary) = {
|
||||
const DEFAULT_SUMMARY: SharedString = SharedString::new_static("New Thread");
|
||||
let thread = self.thread.read(cx);
|
||||
@@ -121,7 +113,7 @@ impl RenderOnce for PastThread {
|
||||
.unwrap(),
|
||||
OffsetDateTime::now_utc(),
|
||||
self.assistant_panel
|
||||
.update(cx, |this, model, _cx| this.local_timezone())
|
||||
.update(cx, |this, _cx| this.local_timezone())
|
||||
.unwrap_or(UtcOffset::UTC),
|
||||
time_format::TimestampFormat::EnhancedAbsolute,
|
||||
);
|
||||
@@ -141,7 +133,7 @@ impl RenderOnce for PastThread {
|
||||
let id = id.clone();
|
||||
move |_event, cx| {
|
||||
assistant_panel
|
||||
.update(cx, |this, model, cx| {
|
||||
.update(cx, |this, cx| {
|
||||
this.delete_thread(&id, cx);
|
||||
})
|
||||
.ok();
|
||||
@@ -154,7 +146,7 @@ impl RenderOnce for PastThread {
|
||||
let id = id.clone();
|
||||
move |_event, cx| {
|
||||
assistant_panel
|
||||
.update(cx, |this, model, cx| {
|
||||
.update(cx, |this, cx| {
|
||||
this.open_thread(&id, cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -5,7 +5,7 @@ use assistant_tool::{ToolId, ToolWorkingSet};
|
||||
use collections::HashMap;
|
||||
use context_server::manager::ContextServerManager;
|
||||
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
||||
use gpui::{prelude::*, AppContext, Model, Task};
|
||||
use gpui::{prelude::*, AppContext, Model, ModelContext, Task};
|
||||
use project::Project;
|
||||
use unindent::Unindent;
|
||||
use util::ResultExt as _;
|
||||
@@ -28,16 +28,11 @@ impl ThreadStore {
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let this = cx.new_model(|model: &Model<Self>, cx: &mut AppContext| {
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||
let context_server_factory_registry =
|
||||
ContextServerFactoryRegistry::default_global(cx);
|
||||
let context_server_manager = cx.new_model(|model, cx| {
|
||||
ContextServerManager::new(
|
||||
context_server_factory_registry,
|
||||
project.clone(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
let context_server_manager = cx.new_model(|cx| {
|
||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||
});
|
||||
|
||||
let mut this = Self {
|
||||
@@ -57,7 +52,7 @@ impl ThreadStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn threads(&self, model: &Model<Self>, cx: &AppContext) -> Vec<Model<Thread>> {
|
||||
pub fn threads(&self, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
|
||||
let mut threads = self
|
||||
.threads
|
||||
.iter()
|
||||
@@ -68,38 +63,28 @@ impl ThreadStore {
|
||||
threads
|
||||
}
|
||||
|
||||
pub fn recent_threads(
|
||||
&self,
|
||||
limit: usize,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
) -> Vec<Model<Thread>> {
|
||||
pub fn recent_threads(&self, limit: usize, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
|
||||
self.threads(cx).into_iter().take(limit).collect()
|
||||
}
|
||||
|
||||
pub fn create_thread(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Model<Thread> {
|
||||
let thread = cx.new_model(|model, cx| Thread::new(self.tools.clone(), model, cx));
|
||||
pub fn create_thread(&mut self, cx: &mut ModelContext<Self>) -> Model<Thread> {
|
||||
let thread = cx.new_model(|cx| Thread::new(self.tools.clone(), cx));
|
||||
self.threads.push(thread.clone());
|
||||
thread
|
||||
}
|
||||
|
||||
pub fn open_thread(
|
||||
&self,
|
||||
id: &ThreadId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<Model<Thread>> {
|
||||
pub fn open_thread(&self, id: &ThreadId, cx: &mut ModelContext<Self>) -> Option<Model<Thread>> {
|
||||
self.threads
|
||||
.iter()
|
||||
.find(|thread| thread.read(cx).id() == id)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn delete_thread(&mut self, id: &ThreadId, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut ModelContext<Self>) {
|
||||
self.threads.retain(|thread| thread.read(cx).id() != id);
|
||||
}
|
||||
|
||||
fn register_context_server_handlers(&self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
|
||||
cx.subscribe(
|
||||
&self.context_server_manager.clone(),
|
||||
Self::handle_context_server_event,
|
||||
@@ -111,8 +96,7 @@ impl ThreadStore {
|
||||
&mut self,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
event: &context_server::manager::Event,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let tool_working_set = self.tools.clone();
|
||||
match event {
|
||||
@@ -169,23 +153,23 @@ impl ThreadStore {
|
||||
|
||||
impl ThreadStore {
|
||||
/// Creates some mocked recent threads for testing purposes.
|
||||
fn mock_recent_threads(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn mock_recent_threads(&mut self, cx: &mut ModelContext<Self>) {
|
||||
use language_model::Role;
|
||||
|
||||
self.threads.push(cx.new_model(|model, cx| {
|
||||
let mut thread = Thread::new(self.tools.clone(), model, cx);
|
||||
thread.set_summary("Introduction to quantum computing", model, cx);
|
||||
thread.insert_user_message("Hello! Can you help me understand quantum computing?", model, cx);
|
||||
thread.insert_message(Role::Assistant, "Of course! I'd be happy to help you understand quantum computing. Quantum computing is a fascinating field that uses the principles of quantum mechanics to process information. Unlike classical computers that use bits (0s and 1s), quantum computers use quantum bits or 'qubits'. These qubits can exist in multiple states simultaneously, a property called superposition. This allows quantum computers to perform certain calculations much faster than classical computers. What specific aspect of quantum computing would you like to know more about?", model, cx);
|
||||
thread.insert_user_message("That's interesting! Can you explain how quantum entanglement is used in quantum computing?", model, cx);
|
||||
thread.insert_message(Role::Assistant, "Certainly! Quantum entanglement is a key principle used in quantum computing. When two qubits become entangled, the state of one qubit is directly related to the state of the other, regardless of the distance between them. This property is used in quantum computing to create complex quantum states and to perform operations on multiple qubits simultaneously. Entanglement allows quantum computers to process information in ways that classical computers cannot, potentially solving certain problems much more efficiently. For example, it's crucial in quantum error correction and in algorithms like quantum teleportation, which is important for quantum communication.", model, cx);
|
||||
self.threads.push(cx.new_model(|cx| {
|
||||
let mut thread = Thread::new(self.tools.clone(), cx);
|
||||
thread.set_summary("Introduction to quantum computing", cx);
|
||||
thread.insert_user_message("Hello! Can you help me understand quantum computing?", cx);
|
||||
thread.insert_message(Role::Assistant, "Of course! I'd be happy to help you understand quantum computing. Quantum computing is a fascinating field that uses the principles of quantum mechanics to process information. Unlike classical computers that use bits (0s and 1s), quantum computers use quantum bits or 'qubits'. These qubits can exist in multiple states simultaneously, a property called superposition. This allows quantum computers to perform certain calculations much faster than classical computers. What specific aspect of quantum computing would you like to know more about?", cx);
|
||||
thread.insert_user_message("That's interesting! Can you explain how quantum entanglement is used in quantum computing?", cx);
|
||||
thread.insert_message(Role::Assistant, "Certainly! Quantum entanglement is a key principle used in quantum computing. When two qubits become entangled, the state of one qubit is directly related to the state of the other, regardless of the distance between them. This property is used in quantum computing to create complex quantum states and to perform operations on multiple qubits simultaneously. Entanglement allows quantum computers to process information in ways that classical computers cannot, potentially solving certain problems much more efficiently. For example, it's crucial in quantum error correction and in algorithms like quantum teleportation, which is important for quantum communication.", cx);
|
||||
thread
|
||||
}));
|
||||
|
||||
self.threads.push(cx.new_model(|model, cx| {
|
||||
let mut thread = Thread::new(self.tools.clone(), model, cx);
|
||||
thread.set_summary("Rust web development and async programming", model, cx);
|
||||
thread.insert_user_message("Can you show me an example of Rust code for a simple web server?", model, cx);
|
||||
self.threads.push(cx.new_model(|cx| {
|
||||
let mut thread = Thread::new(self.tools.clone(), cx);
|
||||
thread.set_summary("Rust web development and async programming", cx);
|
||||
thread.insert_user_message("Can you show me an example of Rust code for a simple web server?", cx);
|
||||
thread.insert_message(Role::Assistant, "Certainly! Here's an example of a simple web server in Rust using the `actix-web` framework:
|
||||
|
||||
```rust
|
||||
@@ -221,8 +205,8 @@ impl ThreadStore {
|
||||
actix-web = \"4.0\"
|
||||
```
|
||||
|
||||
Then you can run the server with `cargo run` and access it at `http://localhost:8080`.".unindent(), model, cx);
|
||||
thread.insert_user_message("That's great! Can you explain more about async functions in Rust?", model, cx);
|
||||
Then you can run the server with `cargo run` and access it at `http://localhost:8080`.".unindent(), cx);
|
||||
thread.insert_user_message("That's great! Can you explain more about async functions in Rust?", cx);
|
||||
thread.insert_message(Role::Assistant, "Certainly! Async functions are a key feature in Rust for writing efficient, non-blocking code, especially for I/O-bound operations. Here's an overview:
|
||||
|
||||
1. **Syntax**: Async functions are declared using the `async` keyword:
|
||||
@@ -251,7 +235,7 @@ impl ThreadStore {
|
||||
|
||||
6. **Error Handling**: Async functions work well with Rust's `?` operator for error handling.
|
||||
|
||||
Async programming in Rust provides a powerful way to write concurrent code that's both safe and efficient. It's particularly useful for servers, network programming, and any application that deals with many concurrent operations.".unindent(), model, cx);
|
||||
Async programming in Rust provides a powerful way to write concurrent code that's both safe and efficient. It's particularly useful for servers, network programming, and any application that deals with many concurrent operations.".unindent(), cx);
|
||||
thread
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ pub use crate::slash_command_registry::*;
|
||||
use anyhow::Result;
|
||||
use futures::stream::{self, BoxStream};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView};
|
||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||
pub use language_model::Role;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -78,9 +78,8 @@ pub trait SlashCommand: 'static + Send + Sync {
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>>;
|
||||
fn requires_argument(&self) -> bool;
|
||||
fn accepts_arguments(&self) -> bool {
|
||||
@@ -91,27 +90,21 @@ pub trait SlashCommand: 'static + Send + Sync {
|
||||
arguments: &[String],
|
||||
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
context_buffer: BufferSnapshot,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
// TODO: We're just using the `LspAdapterDelegate` here because that is
|
||||
// what the extension API is already expecting.
|
||||
//
|
||||
// It may be that `LspAdapterDelegate` needs a more general name, or
|
||||
// perhaps another kind of delegate is needed here.
|
||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult>;
|
||||
}
|
||||
|
||||
pub type RenderFoldPlaceholder = Arc<
|
||||
dyn Send
|
||||
+ Sync
|
||||
+ Fn(
|
||||
ElementId,
|
||||
Arc<dyn Fn(&mut gpui::Window, &mut gpui::AppContext)>,
|
||||
&mut gpui::Window,
|
||||
&mut gpui::AppContext,
|
||||
) -> AnyElement,
|
||||
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
||||
>;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::{atomic::AtomicBool, Arc};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
@@ -97,9 +97,8 @@ impl SlashCommand for ExtensionSlashCommand {
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakModel<Workspace>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let command = self.command.clone();
|
||||
let arguments = arguments.to_owned();
|
||||
@@ -128,10 +127,9 @@ impl SlashCommand for ExtensionSlashCommand {
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakModel<Workspace>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let command = self.command.clone();
|
||||
let arguments = arguments.to_owned();
|
||||
|
||||
@@ -4,7 +4,7 @@ mod tool_working_set;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub use crate::tool_registry::*;
|
||||
@@ -31,8 +31,7 @@ pub trait Tool: 'static + Send + Sync {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
input: serde_json::Value,
|
||||
workspace: WeakModel<Workspace>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<String>>;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_tool::Tool;
|
||||
use chrono::{Local, Utc};
|
||||
use gpui::{Task, WeakView};
|
||||
use gpui::{Task, WeakView, WindowContext};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -41,9 +41,8 @@ impl Tool for NowTool {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
input: serde_json::Value,
|
||||
_workspace: WeakModel<workspace::Workspace>,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::AppContext,
|
||||
_workspace: WeakView<workspace::Workspace>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<String>> {
|
||||
let input: FileToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
|
||||
@@ -3,7 +3,8 @@ use client::{Client, TelemetrySettings};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use db::RELEASE_CHANNEL;
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, SemanticVersion, Task,
|
||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
||||
SemanticVersion, Task, WindowContext,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use paths::remote_servers_dir;
|
||||
@@ -130,16 +131,16 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
||||
AutoUpdateSetting::register(cx);
|
||||
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace.register_action(model, |_, action: &Check, cx| check(action, model, cx));
|
||||
workspace.register_action(|_, action: &Check, cx| check(action, cx));
|
||||
|
||||
workspace.register_action(model, |_, action, cx| {
|
||||
workspace.register_action(|_, action, cx| {
|
||||
view_release_notes(action, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
let version = release_channel::AppVersion::global(cx);
|
||||
let auto_updater = cx.new_model(|model, cx| {
|
||||
let auto_updater = cx.new_model(|cx| {
|
||||
let updater = AutoUpdater::new(version, http_client);
|
||||
|
||||
let poll_for_updates = ReleaseChannel::try_global(cx)
|
||||
@@ -152,7 +153,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
||||
{
|
||||
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
||||
.0
|
||||
.then(|| updater.start_polling(model, cx));
|
||||
.then(|| updater.start_polling(cx));
|
||||
|
||||
cx.observe_global::<SettingsStore>(move |updater, cx| {
|
||||
if AutoUpdateSetting::get_global(cx).0 {
|
||||
@@ -171,7 +172,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
||||
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
|
||||
}
|
||||
|
||||
pub fn check(_: &Check, window: &mut gpui::Window, cx: &mut gpui::AppContext) {
|
||||
pub fn check(_: &Check, cx: &mut WindowContext) {
|
||||
if let Some(message) = option_env!("ZED_UPDATE_EXPLANATION") {
|
||||
drop(cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
@@ -200,7 +201,7 @@ pub fn check(_: &Check, window: &mut gpui::Window, cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
if let Some(updater) = AutoUpdater::get(cx) {
|
||||
updater.update(cx, |updater, model, cx| updater.poll(model, cx));
|
||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||
} else {
|
||||
drop(cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
@@ -248,30 +249,30 @@ impl AutoUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_polling(&self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
pub fn start_polling(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
loop {
|
||||
this.update(&mut cx, |this, model, cx| this.poll(model, cx))?;
|
||||
this.update(&mut cx, |this, cx| this.poll(cx))?;
|
||||
cx.background_executor().timer(POLL_INTERVAL).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn poll(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn poll(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if self.pending_poll.is_some() || self.status.is_updated() {
|
||||
return;
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
self.pending_poll = Some(model.spawn(cx, |this, mut cx| async move {
|
||||
self.pending_poll = Some(cx.spawn(|this, mut cx| async move {
|
||||
let result = Self::update(this.upgrade()?, cx.clone()).await;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_poll = None;
|
||||
if let Err(error) = result {
|
||||
log::error!("auto-update failed: error:{:?}", error);
|
||||
this.status = AutoUpdateStatus::Errored;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
@@ -286,9 +287,9 @@ impl AutoUpdater {
|
||||
self.status.clone()
|
||||
}
|
||||
|
||||
pub fn dismiss_error(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn dismiss_error(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.status = AutoUpdateStatus::Idle;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// If you are packaging Zed and need to override the place it downloads SSH remotes from,
|
||||
@@ -431,16 +432,15 @@ impl AutoUpdater {
|
||||
}
|
||||
|
||||
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
|
||||
let (client, current_version, release_channel) =
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.status = AutoUpdateStatus::Checking;
|
||||
model.notify(cx);
|
||||
(
|
||||
this.http_client.clone(),
|
||||
this.current_version,
|
||||
ReleaseChannel::try_global(cx),
|
||||
)
|
||||
})?;
|
||||
let (client, current_version, release_channel) = this.update(&mut cx, |this, cx| {
|
||||
this.status = AutoUpdateStatus::Checking;
|
||||
cx.notify();
|
||||
(
|
||||
this.http_client.clone(),
|
||||
this.current_version,
|
||||
ReleaseChannel::try_global(cx),
|
||||
)
|
||||
})?;
|
||||
|
||||
let release =
|
||||
Self::get_latest_release(&this, "zed", OS, ARCH, release_channel, &mut cx).await?;
|
||||
@@ -455,16 +455,16 @@ impl AutoUpdater {
|
||||
};
|
||||
|
||||
if !should_download {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.status = AutoUpdateStatus::Idle;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.status = AutoUpdateStatus::Downloading;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
let temp_dir = tempfile::Builder::new()
|
||||
@@ -485,9 +485,9 @@ impl AutoUpdater {
|
||||
let downloaded_asset = temp_dir.path().join(filename);
|
||||
download_release(&downloaded_asset, release, client, &cx).await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.status = AutoUpdateStatus::Installing;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
let binary_path = match OS {
|
||||
@@ -496,11 +496,11 @@ impl AutoUpdater {
|
||||
_ => Err(anyhow!("not supported: {:?}", OS)),
|
||||
}?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.set_should_show_update_notification(true, cx)
|
||||
.detach_and_log_err(cx);
|
||||
this.status = AutoUpdateStatus::Updated { binary_path };
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -2,7 +2,7 @@ mod update_notification;
|
||||
|
||||
use auto_update::AutoUpdater;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use gpui::{actions, prelude::*, AppContext, Model, SharedString, View};
|
||||
use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext};
|
||||
use http_client::HttpClient;
|
||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
@@ -18,8 +18,8 @@ actions!(auto_update, [ViewReleaseNotesLocally]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace.register_action(model, |workspace, _: &ViewReleaseNotesLocally, cx| {
|
||||
view_release_notes_locally(workspace, model, cx);
|
||||
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
|
||||
view_release_notes_locally(workspace, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
@@ -31,11 +31,7 @@ struct ReleaseNotesBody {
|
||||
release_notes: String,
|
||||
}
|
||||
|
||||
fn view_release_notes_locally(
|
||||
workspace: &mut Workspace,
|
||||
model: &Model<Workspace>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
|
||||
let url = match release_channel {
|
||||
@@ -64,7 +60,7 @@ fn view_release_notes_locally(
|
||||
.language_for_name("Markdown");
|
||||
|
||||
workspace
|
||||
.with_local_workspace(model, cx, move |_, model, cx| {
|
||||
.with_local_workspace(cx, move |_, cx| {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let markdown = markdown.await.log_err();
|
||||
let response = client.get(&url, Default::default(), true).await;
|
||||
@@ -82,29 +78,27 @@ fn view_release_notes_locally(
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let buffer = project.update(cx, |project, model, cx| {
|
||||
let buffer = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer("", markdown, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, body.release_notes)], None, cx)
|
||||
});
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
|
||||
let buffer =
|
||||
cx.new_model(|model, cx| MultiBuffer::singleton(buffer, model, cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let tab_description = SharedString::from(body.title.to_string());
|
||||
let editor = cx.new_model(|model, cx| {
|
||||
Editor::for_multibuffer(buffer, Some(project), true, model, cx)
|
||||
let editor = cx.new_view(|cx| {
|
||||
Editor::for_multibuffer(buffer, Some(project), true, cx)
|
||||
});
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let view: Model<MarkdownPreviewView> = MarkdownPreviewView::new(
|
||||
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
|
||||
MarkdownPreviewMode::Default,
|
||||
editor,
|
||||
workspace_handle,
|
||||
language_registry,
|
||||
Some(tab_description),
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
workspace.add_item_to_active_pane(
|
||||
@@ -113,7 +107,7 @@ fn view_release_notes_locally(
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
@@ -123,7 +117,7 @@ fn view_release_notes_locally(
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn notify_of_any_new_update(model: &Model<Workspace>, cx: &mut AppContext) -> Option<()> {
|
||||
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
|
||||
let updater = AutoUpdater::get(cx)?;
|
||||
let version = updater.read(cx).current_version();
|
||||
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
|
||||
@@ -136,9 +130,9 @@ pub fn notify_of_any_new_update(model: &Model<Workspace>, cx: &mut AppContext) -
|
||||
workspace.show_notification(
|
||||
NotificationId::unique::<UpdateNotification>(),
|
||||
cx,
|
||||
|cx| cx.new_model(|_, _| UpdateNotification::new(version, workspace_handle)),
|
||||
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
|
||||
);
|
||||
updater.update(cx, |updater, model, cx| {
|
||||
updater.update(cx, |updater, cx| {
|
||||
updater
|
||||
.set_should_show_update_notification(false, cx)
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
div, AppContext, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement,
|
||||
Render, SemanticVersion, StatefulInteractiveElement, Styled, WeakView,
|
||||
div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render,
|
||||
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, WeakView,
|
||||
};
|
||||
use menu::Cancel;
|
||||
use release_channel::ReleaseChannel;
|
||||
@@ -12,18 +12,13 @@ use workspace::{
|
||||
|
||||
pub struct UpdateNotification {
|
||||
version: SemanticVersion,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for UpdateNotification {}
|
||||
|
||||
impl Render for UpdateNotification {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
let app_name = ReleaseChannel::global(cx).display_name();
|
||||
|
||||
v_flex()
|
||||
@@ -42,10 +37,7 @@ impl Render for UpdateNotification {
|
||||
.id("cancel")
|
||||
.child(Icon::new(IconName::Close))
|
||||
.cursor_pointer()
|
||||
.on_click(
|
||||
model
|
||||
.listener(|this, model, _, cx| this.dismiss(&menu::Cancel, cx)),
|
||||
),
|
||||
.on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -53,10 +45,10 @@ impl Render for UpdateNotification {
|
||||
.id("notes")
|
||||
.child(Label::new("View the release notes"))
|
||||
.cursor_pointer()
|
||||
.on_click(model.listener(|this, model, _, cx| {
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
crate::view_release_notes_locally(workspace, model, cx);
|
||||
.update(cx, |workspace, cx| {
|
||||
crate::view_release_notes_locally(workspace, cx);
|
||||
})
|
||||
.log_err();
|
||||
this.dismiss(&menu::Cancel, cx)
|
||||
@@ -66,11 +58,11 @@ impl Render for UpdateNotification {
|
||||
}
|
||||
|
||||
impl UpdateNotification {
|
||||
pub fn new(version: SemanticVersion, workspace: WeakModel<Workspace>) -> Self {
|
||||
pub fn new(version: SemanticVersion, workspace: WeakView<Workspace>) -> Self {
|
||||
Self { version, workspace }
|
||||
}
|
||||
|
||||
pub fn dismiss(&mut self, _: &Cancel, model: &Model<Self>, cx: &mut AppContext) {
|
||||
model.emit(DismissEvent, cx);
|
||||
pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
AppContext, Element, EventEmitter, FocusableView, IntoElement, Model, ParentElement, Render,
|
||||
StyledText, Subscription,
|
||||
Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText,
|
||||
Subscription, ViewContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use std::cmp;
|
||||
@@ -37,12 +37,7 @@ impl Breadcrumbs {
|
||||
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
|
||||
|
||||
impl Render for Breadcrumbs {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
const MAX_SEGMENTS: usize = 12;
|
||||
let element = h_flex().text_ui(cx);
|
||||
let Some(active_item) = self.active_item.as_ref() else {
|
||||
@@ -69,7 +64,7 @@ impl Render for Breadcrumbs {
|
||||
}
|
||||
|
||||
let highlighted_segments = segments.into_iter().map(|segment| {
|
||||
let mut text_style = window.text_style();
|
||||
let mut text_style = cx.text_style();
|
||||
if let Some(font) = segment.font {
|
||||
text_style.font_family = font.family;
|
||||
text_style.font_features = font.features;
|
||||
@@ -99,25 +94,23 @@ impl Render for Breadcrumbs {
|
||||
let editor = editor.clone();
|
||||
move |_, cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
outline::toggle(editor, &editor::actions::ToggleOutline, model, cx)
|
||||
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
let focus_handle = editor.read(cx).focus_handle(cx);
|
||||
Tooltip::for_action_in(
|
||||
"Show symbol outline",
|
||||
&editor::actions::ToggleOutline,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::for_action(
|
||||
"Show symbol outline",
|
||||
&editor::actions::ToggleOutline,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -135,30 +128,26 @@ impl ToolbarItemView for Breadcrumbs {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
self.active_item = None;
|
||||
|
||||
let Some(item) = active_pane_item else {
|
||||
return ToolbarItemLocation::Hidden;
|
||||
};
|
||||
|
||||
let this = model.downgrade();
|
||||
let this = cx.view().downgrade();
|
||||
self.subscription = Some(item.subscribe_to_item_events(
|
||||
cx,
|
||||
Box::new(move |event, cx| {
|
||||
if let ItemEvent::UpdateBreadcrumbs = event {
|
||||
this.update(cx, |this, model, cx| {
|
||||
model.notify(cx);
|
||||
this.update(cx, |this, cx| {
|
||||
cx.notify();
|
||||
if let Some(active_item) = this.active_item.as_ref() {
|
||||
model.emit(
|
||||
cx,
|
||||
ToolbarItemEvent::ChangeLocation(
|
||||
active_item.breadcrumb_location(cx),
|
||||
),
|
||||
)
|
||||
cx.emit(ToolbarItemEvent::ChangeLocation(
|
||||
active_item.breadcrumb_location(cx),
|
||||
))
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
@@ -169,7 +158,7 @@ impl ToolbarItemView for Breadcrumbs {
|
||||
item.breadcrumb_location(cx)
|
||||
}
|
||||
|
||||
fn pane_focus_update(&mut self, pane_focused: bool, _: &Model<Self>, _: &mut AppContext) {
|
||||
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
|
||||
self.pane_focused = pane_focused;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ test-support = [
|
||||
"project/test-support",
|
||||
"util/test-support"
|
||||
]
|
||||
livekit-macos = ["livekit_client_macos"]
|
||||
livekit-cross-platform = ["livekit_client"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -40,12 +42,8 @@ serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
settings.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
livekit_client_macos = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
livekit_client = { workspace = true }
|
||||
livekit_client_macos = { workspace = true, optional = true }
|
||||
livekit_client = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,13 +1,41 @@
|
||||
pub mod call_settings;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(any(
|
||||
all(target_os = "macos", feature = "livekit-macos"),
|
||||
all(
|
||||
not(target_os = "macos"),
|
||||
feature = "livekit-macos",
|
||||
not(feature = "livekit-cross-platform")
|
||||
)
|
||||
))]
|
||||
mod macos;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(any(
|
||||
all(target_os = "macos", feature = "livekit-macos"),
|
||||
all(
|
||||
not(target_os = "macos"),
|
||||
feature = "livekit-macos",
|
||||
not(feature = "livekit-cross-platform")
|
||||
)
|
||||
))]
|
||||
pub use macos::*;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(any(
|
||||
all(
|
||||
target_os = "macos",
|
||||
feature = "livekit-cross-platform",
|
||||
not(feature = "livekit-macos"),
|
||||
),
|
||||
all(not(target_os = "macos"), feature = "livekit-cross-platform"),
|
||||
))]
|
||||
mod cross_platform;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(any(
|
||||
all(
|
||||
target_os = "macos",
|
||||
feature = "livekit-cross-platform",
|
||||
not(feature = "livekit-macos"),
|
||||
),
|
||||
all(not(target_os = "macos"), feature = "livekit-cross-platform"),
|
||||
))]
|
||||
pub use cross_platform::*;
|
||||
|
||||
@@ -8,8 +8,8 @@ use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAY
|
||||
use collections::HashSet;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, Subscription, Task,
|
||||
WeakModel,
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Subscription,
|
||||
Task, WeakModel,
|
||||
};
|
||||
use postage::watch;
|
||||
use project::Project;
|
||||
@@ -34,7 +34,7 @@ pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppConte
|
||||
);
|
||||
CallSettings::register(cx);
|
||||
|
||||
let active_call = cx.new_model(|model, cx| ActiveCall::new(client, user_store, model, cx));
|
||||
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||
cx.set_global(GlobalActiveCall(active_call));
|
||||
}
|
||||
|
||||
@@ -96,12 +96,7 @@ pub struct ActiveCall {
|
||||
impl EventEmitter<Event> for ActiveCall {}
|
||||
|
||||
impl ActiveCall {
|
||||
fn new(
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
model: &Model<Self>,
|
||||
_: &mut AppContext,
|
||||
) -> Self {
|
||||
fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
room: None,
|
||||
pending_room_creation: None,
|
||||
@@ -110,8 +105,8 @@ impl ActiveCall {
|
||||
incoming_call: watch::channel(),
|
||||
_join_debouncer: OneAtATime { cancel: None },
|
||||
_subscriptions: vec![
|
||||
client.add_request_handler(model.downgrade(), Self::handle_incoming_call),
|
||||
client.add_message_handler(model.downgrade(), Self::handle_call_canceled),
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_call_canceled),
|
||||
],
|
||||
client,
|
||||
user_store,
|
||||
@@ -127,22 +122,22 @@ impl ActiveCall {
|
||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let user_store = this.update(&mut cx, |this, _, _| this.user_store.clone())?;
|
||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||
let call = IncomingCall {
|
||||
room_id: envelope.payload.room_id,
|
||||
participants: user_store
|
||||
.update(&mut cx, |user_store, model, cx| {
|
||||
user_store.get_users(envelope.payload.participant_user_ids, model, cx)
|
||||
.update(&mut cx, |user_store, cx| {
|
||||
user_store.get_users(envelope.payload.participant_user_ids, cx)
|
||||
})?
|
||||
.await?,
|
||||
calling_user: user_store
|
||||
.update(&mut cx, |user_store, model, cx| {
|
||||
user_store.get_user(envelope.payload.calling_user_id, model, cx)
|
||||
.update(&mut cx, |user_store, cx| {
|
||||
user_store.get_user(envelope.payload.calling_user_id, cx)
|
||||
})?
|
||||
.await?,
|
||||
initial_project: envelope.payload.initial_project,
|
||||
};
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
*this.incoming_call.0.borrow_mut() = Some(call);
|
||||
})?;
|
||||
|
||||
@@ -154,7 +149,7 @@ impl ActiveCall {
|
||||
envelope: TypedEnvelope<proto::CallCanceled>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
let mut incoming_call = this.incoming_call.0.borrow_mut();
|
||||
if incoming_call
|
||||
.as_ref()
|
||||
@@ -179,13 +174,12 @@ impl ActiveCall {
|
||||
&mut self,
|
||||
called_user_id: u64,
|
||||
initial_project: Option<Model<Project>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if !self.pending_invites.insert(called_user_id) {
|
||||
return Task::ready(Err(anyhow!("user was already invited")));
|
||||
}
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
if self._join_debouncer.running() {
|
||||
return Task::ready(Ok(()));
|
||||
@@ -198,22 +192,20 @@ impl ActiveCall {
|
||||
};
|
||||
|
||||
let invite = if let Some(room) = room {
|
||||
cx.spawn(move |mut cx| async move {
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
|
||||
|
||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||
Some(
|
||||
room.update(&mut cx, |room, model, cx| {
|
||||
room.share_project(initial_project, model, cx)
|
||||
})?
|
||||
.await?,
|
||||
room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))?
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
room.update(&mut cx, move |room, model, cx| {
|
||||
room.call(called_user_id, initial_project_id, model, cx)
|
||||
room.update(&mut cx, move |room, cx| {
|
||||
room.call(called_user_id, initial_project_id, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
@@ -222,8 +214,8 @@ impl ActiveCall {
|
||||
} else {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
let room = model
|
||||
.spawn(cx, move |this, mut cx| async move {
|
||||
let room = cx
|
||||
.spawn(move |this, mut cx| async move {
|
||||
let create_room = async {
|
||||
let room = cx
|
||||
.update(|cx| {
|
||||
@@ -237,16 +229,14 @@ impl ActiveCall {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.set_room(Some(room.clone()), model, cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))?
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(room)
|
||||
};
|
||||
|
||||
let room = create_room.await;
|
||||
this.update(&mut cx, |this, _, _| this.pending_room_creation = None)?;
|
||||
this.update(&mut cx, |this, _| this.pending_room_creation = None)?;
|
||||
room.map_err(Arc::new)
|
||||
})
|
||||
.shared();
|
||||
@@ -257,20 +247,18 @@ impl ActiveCall {
|
||||
})
|
||||
};
|
||||
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = invite.await;
|
||||
if result.is_ok() {
|
||||
this.update(&mut cx, |this, _model, cx| {
|
||||
this.report_call_event("invite", cx)
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
|
||||
} else {
|
||||
//TODO: report collaboration error
|
||||
log::error!("invite failed: {:?}", result);
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_invites.remove(&called_user_id);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
result
|
||||
})
|
||||
@@ -279,8 +267,7 @@ impl ActiveCall {
|
||||
pub fn cancel_invite(
|
||||
&mut self,
|
||||
called_user_id: u64,
|
||||
_: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let room_id = if let Some(room) = self.room() {
|
||||
room.read(cx).id()
|
||||
@@ -304,11 +291,7 @@ impl ActiveCall {
|
||||
self.incoming_call.1.clone()
|
||||
}
|
||||
|
||||
pub fn accept_incoming(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn accept_incoming(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.room.is_some() {
|
||||
return Task::ready(Err(anyhow!("cannot join while on another call")));
|
||||
}
|
||||
@@ -330,20 +313,18 @@ impl ActiveCall {
|
||||
._join_debouncer
|
||||
.spawn(cx, move |cx| Room::join(room_id, client, user_store, cx));
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let room = join.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.set_room(room.clone(), model, cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("accept incoming", cx)
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decline_incoming(&mut self, _: &Model<Self>, _: &mut AppContext) -> Result<()> {
|
||||
pub fn decline_incoming(&mut self, _: &mut ModelContext<Self>) -> Result<()> {
|
||||
let call = self
|
||||
.incoming_call
|
||||
.0
|
||||
@@ -360,14 +341,13 @@ impl ActiveCall {
|
||||
pub fn join_channel(
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Option<Model<Room>>>> {
|
||||
if let Some(room) = self.room().cloned() {
|
||||
if room.read(cx).channel_id() == Some(channel_id) {
|
||||
return Task::ready(Ok(Some(room)));
|
||||
} else {
|
||||
room.update(cx, |room, model, cx| room.clear_state(cx));
|
||||
room.update(cx, |room, cx| room.clear_state(cx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,29 +361,27 @@ impl ActiveCall {
|
||||
Room::join_channel(channel_id, client, user_store, cx).await
|
||||
});
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let room = join.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.set_room(room.clone(), model, cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("join channel", cx)
|
||||
})?;
|
||||
Ok(room)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hang_up(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
model.notify(cx);
|
||||
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
cx.notify();
|
||||
self.report_call_event("hang up", cx);
|
||||
|
||||
Audio::end_call(cx);
|
||||
|
||||
let channel_id = self.channel_id(cx);
|
||||
if let Some((room, _)) = self.room.take() {
|
||||
model.emit(Event::RoomLeft { channel_id }, cx);
|
||||
room.update(cx, |room, model, cx| room.leave(model, cx))
|
||||
cx.emit(Event::RoomLeft { channel_id });
|
||||
room.update(cx, |room, cx| room.leave(cx))
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
@@ -412,12 +390,11 @@ impl ActiveCall {
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("share project", cx);
|
||||
room.update(cx, |room, model, cx| room.share_project(project, model, cx))
|
||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("no active call")))
|
||||
}
|
||||
@@ -426,14 +403,11 @@ impl ActiveCall {
|
||||
pub fn unshare_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("unshare project", cx);
|
||||
room.update(cx, |room, model, cx| {
|
||||
room.unshare_project(project, model, cx)
|
||||
})
|
||||
room.update(cx, |room, cx| room.unshare_project(project, cx))
|
||||
} else {
|
||||
Err(anyhow!("no active call"))
|
||||
}
|
||||
@@ -446,13 +420,12 @@ impl ActiveCall {
|
||||
pub fn set_location(
|
||||
&mut self,
|
||||
project: Option<&Model<Project>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if project.is_some() || !*ZED_ALWAYS_ACTIVE {
|
||||
self.location = project.map(|project| project.downgrade());
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
return room.update(cx, |room, model, cx| room.set_location(project, model, cx));
|
||||
return room.update(cx, |room, cx| room.set_location(project, cx));
|
||||
}
|
||||
}
|
||||
Task::ready(Ok(()))
|
||||
@@ -461,29 +434,26 @@ impl ActiveCall {
|
||||
fn set_room(
|
||||
&mut self,
|
||||
room: Option<Model<Room>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if room.as_ref() == self.room.as_ref().map(|room| &room.0) {
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
if let Some(room) = room {
|
||||
if room.read(cx).status().is_offline() {
|
||||
self.room = None;
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
let subscriptions = vec![
|
||||
model.observe(&room, cx, |this, room, model, cx| {
|
||||
cx.observe(&room, |this, room, cx| {
|
||||
if room.read(cx).status().is_offline() {
|
||||
this.set_room(None, model, cx).detach_and_log_err(cx)
|
||||
this.set_room(None, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
}),
|
||||
model.subscribe(&room, cx, |_, _, event, model, cx| {
|
||||
model.emit(event.clone(), cx)
|
||||
cx.notify();
|
||||
}),
|
||||
cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
|
||||
];
|
||||
self.room = Some((room.clone(), subscriptions));
|
||||
let location = self
|
||||
@@ -491,10 +461,8 @@ impl ActiveCall {
|
||||
.as_ref()
|
||||
.and_then(|location| location.upgrade());
|
||||
let channel_id = room.read(cx).channel_id();
|
||||
model.emit(Event::RoomJoined { channel_id }, cx);
|
||||
room.update(cx, |room, model, cx| {
|
||||
room.set_location(location.as_ref(), model, cx)
|
||||
})
|
||||
cx.emit(Event::RoomJoined { channel_id });
|
||||
room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
|
||||
}
|
||||
} else {
|
||||
self.room = None;
|
||||
|
||||
@@ -13,7 +13,9 @@ use client::{
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, Task, WeakModel};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use livekit::{
|
||||
@@ -119,12 +121,11 @@ impl Room {
|
||||
livekit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
spawn_room_connection(livekit_connection_info, model, cx);
|
||||
spawn_room_connection(livekit_connection_info, cx);
|
||||
|
||||
let maintain_connection = model.spawn(cx, {
|
||||
let maintain_connection = cx.spawn({
|
||||
let client = client.clone();
|
||||
move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
|
||||
});
|
||||
@@ -146,11 +147,11 @@ impl Room {
|
||||
pending_participants: Default::default(),
|
||||
pending_call_count: 0,
|
||||
client_subscriptions: vec![
|
||||
client.add_message_handler(model.downgrade(), Self::handle_room_updated)
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_room_updated)
|
||||
],
|
||||
_subscriptions: vec![
|
||||
model.on_release(cx, Self::released),
|
||||
model.on_app_quit(cx, Self::app_will_quit),
|
||||
cx.on_release(Self::released),
|
||||
cx.on_app_quit(Self::app_will_quit),
|
||||
],
|
||||
leave_when_empty: false,
|
||||
pending_room_update: None,
|
||||
@@ -173,14 +174,13 @@ impl Room {
|
||||
cx.spawn(move |mut cx| async move {
|
||||
let response = client.request(proto::CreateRoom {}).await?;
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
let room = cx.new_model(|model, cx| {
|
||||
let room = cx.new_model(|cx| {
|
||||
let mut room = Self::new(
|
||||
room_proto.id,
|
||||
None,
|
||||
response.live_kit_connection_info,
|
||||
client,
|
||||
user_store,
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
if let Some(participant) = room_proto.participants.first() {
|
||||
@@ -191,8 +191,8 @@ impl Room {
|
||||
|
||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||
let initial_project_id = room
|
||||
.update(&mut cx, |room, model, cx| {
|
||||
room.share_project(initial_project.clone(), model, cx)
|
||||
.update(&mut cx, |room, cx| {
|
||||
room.share_project(initial_project.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
Some(initial_project_id)
|
||||
@@ -201,9 +201,9 @@ impl Room {
|
||||
};
|
||||
|
||||
let did_join = room
|
||||
.update(&mut cx, |room, model, cx| {
|
||||
.update(&mut cx, |room, cx| {
|
||||
room.leave_when_empty = true;
|
||||
room.call(called_user_id, initial_project_id, model, cx)
|
||||
room.call(called_user_id, initial_project_id, cx)
|
||||
})?
|
||||
.await;
|
||||
match did_join {
|
||||
@@ -251,11 +251,7 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
fn app_will_quit(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> impl Future<Output = ()> {
|
||||
fn app_will_quit(&mut self, cx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
|
||||
let task = if self.status.is_online() {
|
||||
let leave = self.leave_internal(cx);
|
||||
Some(cx.background_executor().spawn(async move {
|
||||
@@ -283,20 +279,19 @@ impl Room {
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
let room = cx.new_model(|model, cx| {
|
||||
let room = cx.new_model(|cx| {
|
||||
Self::new(
|
||||
room_proto.id,
|
||||
response.channel_id.map(ChannelId),
|
||||
response.live_kit_connection_info,
|
||||
client,
|
||||
user_store,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
room.update(&mut cx, |room, model, cx| {
|
||||
room.update(&mut cx, |room, cx| {
|
||||
room.leave_when_empty = room.channel_id.is_none();
|
||||
room.apply_room_update(room_proto, model, cx)?;
|
||||
room.apply_room_update(room_proto, cx)?;
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
Ok(room)
|
||||
@@ -310,8 +305,8 @@ impl Room {
|
||||
&& self.pending_call_count == 0
|
||||
}
|
||||
|
||||
pub(crate) fn leave(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
model.notify(cx);
|
||||
pub(crate) fn leave(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
cx.notify();
|
||||
self.leave_internal(cx)
|
||||
}
|
||||
|
||||
@@ -335,16 +330,16 @@ impl Room {
|
||||
pub(crate) fn clear_state(&mut self, cx: &mut AppContext) {
|
||||
for project in self.shared_projects.drain() {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.unshare(model, cx).log_err();
|
||||
project.update(cx, |project, cx| {
|
||||
project.unshare(cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
for project in self.joined_projects.drain() {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.disconnected_from_host(model, cx);
|
||||
project.close(model, cx);
|
||||
project.update(cx, |project, cx| {
|
||||
project.disconnected_from_host(cx);
|
||||
project.close(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -374,9 +369,9 @@ impl Room {
|
||||
|
||||
this.upgrade()
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.update(&mut cx, |this, model, cx| {
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.status = RoomStatus::Rejoining;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
// Wait for client to re-establish a connection to the server.
|
||||
@@ -390,8 +385,7 @@ impl Room {
|
||||
log::info!("client reconnected, attempting to rejoin room");
|
||||
|
||||
let Some(this) = this.upgrade() else { break };
|
||||
match this.update(&mut cx, |this, model, cx| this.rejoin(model, cx))
|
||||
{
|
||||
match this.update(&mut cx, |this, cx| this.rejoin(cx)) {
|
||||
Ok(task) => {
|
||||
if task.await.log_err().is_some() {
|
||||
return true;
|
||||
@@ -440,15 +434,14 @@ impl Room {
|
||||
// we leave the room and return an error.
|
||||
if let Some(this) = this.upgrade() {
|
||||
log::info!("reconnection failed, leaving room");
|
||||
this.update(&mut cx, |this, model, cx| this.leave(model, cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.leave(cx))?.await?;
|
||||
}
|
||||
Err(anyhow!(
|
||||
"can't reconnect to room: client failed to re-establish connection"
|
||||
))
|
||||
}
|
||||
|
||||
fn rejoin(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
fn rejoin(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let mut projects = HashMap::default();
|
||||
let mut reshared_projects = Vec::new();
|
||||
let mut rejoined_projects = Vec::new();
|
||||
@@ -496,29 +489,27 @@ impl Room {
|
||||
rejoined_projects,
|
||||
});
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = response.await?;
|
||||
let message_id = response.message_id;
|
||||
let response = response.payload;
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.status = RoomStatus::Online;
|
||||
this.apply_room_update(room_proto, model, cx)?;
|
||||
this.apply_room_update(room_proto, cx)?;
|
||||
|
||||
for reshared_project in response.reshared_projects {
|
||||
if let Some(project) = projects.get(&reshared_project.id) {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.reshared(reshared_project, model, cx).log_err();
|
||||
project.update(cx, |project, cx| {
|
||||
project.reshared(reshared_project, cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for rejoined_project in response.rejoined_projects {
|
||||
if let Some(project) = projects.get(&rejoined_project.id) {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project
|
||||
.rejoined(rejoined_project, message_id, model, cx)
|
||||
.log_err();
|
||||
project.update(cx, |project, cx| {
|
||||
project.rejoined(rejoined_project, message_id, cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -576,13 +567,12 @@ impl Room {
|
||||
&mut self,
|
||||
user_id: u64,
|
||||
role: proto::ChannelRole,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
let room_id = self.id;
|
||||
let role = role.into();
|
||||
cx.spawn(|_| async move {
|
||||
cx.spawn(|_, _| async move {
|
||||
client
|
||||
.request(proto::SetRoomParticipantRole {
|
||||
room_id,
|
||||
@@ -654,26 +644,19 @@ impl Room {
|
||||
.payload
|
||||
.room
|
||||
.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.apply_room_update(room, model, cx)
|
||||
})?
|
||||
this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))?
|
||||
}
|
||||
|
||||
fn apply_room_update(
|
||||
&mut self,
|
||||
room: proto::Room,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Result<()> {
|
||||
fn apply_room_update(&mut self, room: proto::Room, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
log::trace!(
|
||||
"client {:?}. room update: {:?}",
|
||||
self.client.user_id(),
|
||||
&room
|
||||
);
|
||||
|
||||
self.pending_room_update = Some(self.start_room_connection(room, model, cx));
|
||||
self.pending_room_update = Some(self.start_room_connection(room, cx));
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -692,8 +675,7 @@ impl Room {
|
||||
fn start_room_connection(
|
||||
&self,
|
||||
mut room: proto::Room,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<()> {
|
||||
Task::ready(())
|
||||
}
|
||||
@@ -702,8 +684,7 @@ impl Room {
|
||||
fn start_room_connection(
|
||||
&self,
|
||||
mut room: proto::Room,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<()> {
|
||||
// Filter ourselves out from the room's participants.
|
||||
let local_participant_ix = room
|
||||
@@ -725,17 +706,17 @@ impl Room {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (remote_participants, pending_participants) =
|
||||
self.user_store.update(cx, move |user_store, model, cx| {
|
||||
self.user_store.update(cx, move |user_store, cx| {
|
||||
(
|
||||
user_store.get_users(remote_participant_user_ids, model, cx),
|
||||
user_store.get_users(pending_participant_user_ids, model, cx),
|
||||
user_store.get_users(remote_participant_user_ids, cx),
|
||||
user_store.get_users(pending_participant_user_ids, cx),
|
||||
)
|
||||
});
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (remote_participants, pending_participants) =
|
||||
futures::join!(remote_participants, pending_participants);
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.participant_user_ids.clear();
|
||||
|
||||
if let Some(participant) = local_participant {
|
||||
@@ -747,20 +728,18 @@ impl Room {
|
||||
if role == proto::ChannelRole::Guest {
|
||||
for project in mem::take(&mut this.shared_projects) {
|
||||
if let Some(project) = project.upgrade() {
|
||||
this.unshare_project(project, model, cx).log_err();
|
||||
this.unshare_project(project, cx).log_err();
|
||||
}
|
||||
}
|
||||
this.local_participant.projects.clear();
|
||||
if let Some(livekit_room) = &mut this.live_kit {
|
||||
livekit_room.stop_publishing(model, cx);
|
||||
livekit_room.stop_publishing(cx);
|
||||
}
|
||||
}
|
||||
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.set_role(role, model, cx)
|
||||
});
|
||||
project.update(cx, |project, cx| project.set_role(role, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -799,23 +778,20 @@ impl Room {
|
||||
|
||||
for project in &participant.projects {
|
||||
if !old_projects.contains(&project.id) {
|
||||
model.emit(
|
||||
Event::RemoteProjectShared {
|
||||
owner: user.clone(),
|
||||
project_id: project.id,
|
||||
worktree_root_names: project.worktree_root_names.clone(),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteProjectShared {
|
||||
owner: user.clone(),
|
||||
project_id: project.id,
|
||||
worktree_root_names: project.worktree_root_names.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for unshared_project_id in old_projects.difference(&new_projects) {
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
if project.remote_id() == Some(*unshared_project_id) {
|
||||
project.disconnected_from_host(model, cx);
|
||||
project.disconnected_from_host(cx);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
@@ -825,12 +801,9 @@ impl Room {
|
||||
false
|
||||
}
|
||||
});
|
||||
model.emit(
|
||||
Event::RemoteProjectUnshared {
|
||||
project_id: *unshared_project_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteProjectUnshared {
|
||||
project_id: *unshared_project_id,
|
||||
});
|
||||
}
|
||||
|
||||
let role = participant.role();
|
||||
@@ -847,12 +820,9 @@ impl Room {
|
||||
{
|
||||
remote_participant.location = location;
|
||||
remote_participant.role = role;
|
||||
model.emit(
|
||||
Event::ParticipantLocationChanged {
|
||||
participant_id: peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::ParticipantLocationChanged {
|
||||
participant_id: peer_id,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.remote_participants.insert(
|
||||
@@ -887,7 +857,6 @@ impl Room {
|
||||
publication,
|
||||
participant: livekit_participant.clone(),
|
||||
},
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
.warn_on_err();
|
||||
@@ -903,12 +872,9 @@ impl Room {
|
||||
true
|
||||
} else {
|
||||
for project in &participant.projects {
|
||||
model.emit(
|
||||
Event::RemoteProjectUnshared {
|
||||
project_id: project.id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteProjectUnshared {
|
||||
project_id: project.id,
|
||||
});
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -946,21 +912,21 @@ impl Room {
|
||||
this.pending_room_update.take();
|
||||
if this.should_leave() {
|
||||
log::info!("room is empty, leaving");
|
||||
this.leave(model, cx).detach();
|
||||
this.leave(cx).detach();
|
||||
}
|
||||
|
||||
this.user_store.update(cx, |user_store, model, cx| {
|
||||
this.user_store.update(cx, |user_store, cx| {
|
||||
let participant_indices_by_user_id = this
|
||||
.remote_participants
|
||||
.iter()
|
||||
.map(|(user_id, participant)| (*user_id, participant.participant_index))
|
||||
.collect();
|
||||
user_store.set_participant_indices(participant_indices_by_user_id, model, cx);
|
||||
user_store.set_participant_indices(participant_indices_by_user_id, cx);
|
||||
});
|
||||
|
||||
this.check_invariants();
|
||||
this.room_update_completed_tx.try_send(Some(())).ok();
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -969,8 +935,7 @@ impl Room {
|
||||
fn livekit_room_updated(
|
||||
&mut self,
|
||||
event: RoomEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
log::trace!(
|
||||
"client {:?}. livekit event: {:?}",
|
||||
@@ -998,23 +963,17 @@ impl Room {
|
||||
}
|
||||
match track {
|
||||
livekit::track::RemoteTrack::Audio(track) => {
|
||||
model.emit(
|
||||
Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
let stream = play_remote_audio_track(&track, cx.background_executor())?;
|
||||
participant.audio_tracks.insert(track_id, (track, stream));
|
||||
participant.muted = publication.is_muted();
|
||||
}
|
||||
livekit::track::RemoteTrack::Video(track) => {
|
||||
model.emit(
|
||||
Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
participant.video_tracks.insert(track_id, track);
|
||||
}
|
||||
}
|
||||
@@ -1035,21 +994,15 @@ impl Room {
|
||||
livekit::track::RemoteTrack::Audio(track) => {
|
||||
participant.audio_tracks.remove(&track.sid());
|
||||
participant.muted = true;
|
||||
model.emit(
|
||||
Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
}
|
||||
livekit::track::RemoteTrack::Video(track) => {
|
||||
participant.video_tracks.remove(&track.sid());
|
||||
model.emit(
|
||||
Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1127,12 +1080,12 @@ impl Room {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
RoomEvent::Disconnected { reason } => {
|
||||
log::info!("disconnected from room: {reason:?}");
|
||||
self.leave(model, cx).detach_and_log_err(cx);
|
||||
self.leave(cx).detach_and_log_err(cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1160,18 +1113,17 @@ impl Room {
|
||||
&mut self,
|
||||
called_user_id: u64,
|
||||
initial_project_id: Option<u64>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
let client = self.client.clone();
|
||||
let room_id = self.id;
|
||||
self.pending_call_count += 1;
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::Call {
|
||||
room_id,
|
||||
@@ -1179,10 +1131,10 @@ impl Room {
|
||||
initial_project_id,
|
||||
})
|
||||
.await;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_call_count -= 1;
|
||||
if this.should_leave() {
|
||||
this.leave(model, cx).detach_and_log_err(cx);
|
||||
this.leave(cx).detach_and_log_err(cx);
|
||||
}
|
||||
})?;
|
||||
result?;
|
||||
@@ -1195,17 +1147,16 @@ impl Room {
|
||||
id: u64,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Project>>> {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
model.emit(Event::RemoteProjectJoined { project_id: id }, cx);
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.emit(Event::RemoteProjectJoined { project_id: id });
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let project =
|
||||
Project::in_room(id, client, user_store, language_registry, fs, cx.clone()).await?;
|
||||
|
||||
this.update(&mut cx, |this, _, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
!project.read(cx).is_disconnected(cx)
|
||||
@@ -1222,8 +1173,7 @@ impl Room {
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some(project_id) = project.read(cx).remote_id() {
|
||||
return Task::ready(Ok(project_id));
|
||||
@@ -1235,19 +1185,19 @@ impl Room {
|
||||
is_ssh_project: project.read(cx).is_via_ssh(),
|
||||
});
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
|
||||
project.update(&mut cx, |project, model, cx| {
|
||||
project.shared(response.project_id, model, cx)
|
||||
project.update(&mut cx, |project, cx| {
|
||||
project.shared(response.project_id, cx)
|
||||
})??;
|
||||
|
||||
// If the user's location is in this project, it changes from UnsharedProject to SharedProject.
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.shared_projects.insert(project.downgrade());
|
||||
let active_project = this.local_participant.active_project.as_ref();
|
||||
if active_project.map_or(false, |location| *location == project) {
|
||||
this.set_location(Some(&project), model, cx)
|
||||
this.set_location(Some(&project), cx)
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
@@ -1261,8 +1211,7 @@ impl Room {
|
||||
pub(crate) fn unshare_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
let project_id = match project.read(cx).remote_id() {
|
||||
Some(project_id) => project_id,
|
||||
@@ -1270,11 +1219,10 @@ impl Room {
|
||||
};
|
||||
|
||||
self.client.send(proto::UnshareProject { project_id })?;
|
||||
project.update(cx, |this, model, cx| this.unshare(model, cx))?;
|
||||
project.update(cx, |this, cx| this.unshare(cx))?;
|
||||
|
||||
if self.local_participant.active_project == Some(project.downgrade()) {
|
||||
self.set_location(Some(&project), model, cx)
|
||||
.detach_and_log_err(cx);
|
||||
self.set_location(Some(&project), cx).detach_and_log_err(cx);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1282,8 +1230,7 @@ impl Room {
|
||||
pub(crate) fn set_location(
|
||||
&mut self,
|
||||
project: Option<&Model<Project>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
@@ -1307,7 +1254,7 @@ impl Room {
|
||||
proto::participant_location::Variant::External(proto::participant_location::External {})
|
||||
};
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::UpdateParticipantLocation {
|
||||
@@ -1377,21 +1324,13 @@ impl Room {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn share_microphone(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
Task::ready(Err(anyhow!("Windows is not supported yet")))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[track_caller]
|
||||
pub fn share_microphone(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
@@ -1399,13 +1338,13 @@ impl Room {
|
||||
let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.microphone_track = LocalTrack::Pending { publish_id };
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
(live_kit.room.local_participant(), publish_id)
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
};
|
||||
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let (track, stream) = capture_local_audio_track(cx.background_executor())?.await;
|
||||
|
||||
let publication = participant
|
||||
@@ -1418,7 +1357,7 @@ impl Room {
|
||||
)
|
||||
.await
|
||||
.map_err(|error| anyhow!("failed to publish track: {error}"));
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let live_kit = this
|
||||
.live_kit
|
||||
.as_mut()
|
||||
@@ -1449,7 +1388,7 @@ impl Room {
|
||||
track_publication: publication,
|
||||
_stream: Box::new(stream),
|
||||
};
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1458,7 +1397,7 @@ impl Room {
|
||||
Ok(())
|
||||
} else {
|
||||
live_kit.microphone_track = LocalTrack::None;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
@@ -1468,12 +1407,12 @@ impl Room {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn share_screen(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
Task::ready(Err(anyhow!("Windows is not supported yet")))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn share_screen(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
@@ -1484,7 +1423,7 @@ impl Room {
|
||||
let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.screen_track = LocalTrack::Pending { publish_id };
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
(live_kit.room.local_participant(), publish_id)
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
@@ -1492,7 +1431,7 @@ impl Room {
|
||||
|
||||
let sources = cx.screen_capture_sources();
|
||||
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let sources = sources.await??;
|
||||
let source = sources.first().ok_or_else(|| anyhow!("no display found"))?;
|
||||
|
||||
@@ -1510,7 +1449,7 @@ impl Room {
|
||||
.await
|
||||
.map_err(|error| anyhow!("error publishing screen track {error:?}"));
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let live_kit = this
|
||||
.live_kit
|
||||
.as_mut()
|
||||
@@ -1538,7 +1477,7 @@ impl Room {
|
||||
track_publication: publication,
|
||||
_stream: Box::new(stream),
|
||||
};
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
Audio::play_sound(Sound::StartScreenshare, cx);
|
||||
@@ -1549,7 +1488,7 @@ impl Room {
|
||||
Ok(())
|
||||
} else {
|
||||
live_kit.screen_track = LocalTrack::None;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
@@ -1558,7 +1497,7 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn toggle_mute(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
// When unmuting, undeafen if the user was deafened before.
|
||||
let was_deafened = live_kit.deafened;
|
||||
@@ -1574,17 +1513,17 @@ impl Room {
|
||||
let muted = live_kit.muted_by_user;
|
||||
let should_undeafen = was_deafened && !live_kit.deafened;
|
||||
|
||||
if let Some(task) = self.set_mute(muted, model, cx) {
|
||||
if let Some(task) = self.set_mute(muted, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
if should_undeafen {
|
||||
self.set_deafened(false, model, cx);
|
||||
self.set_deafened(false, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_deafen(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
// When deafening, mute the microphone if it was not already muted.
|
||||
// When un-deafening, unmute the microphone, unless it was explicitly muted.
|
||||
@@ -1592,17 +1531,17 @@ impl Room {
|
||||
live_kit.deafened = deafened;
|
||||
let should_change_mute = !live_kit.muted_by_user;
|
||||
|
||||
self.set_deafened(deafened, model, cx);
|
||||
self.set_deafened(deafened, cx);
|
||||
|
||||
if should_change_mute {
|
||||
if let Some(task) = self.set_mute(deafened, model, cx) {
|
||||
if let Some(task) = self.set_mute(deafened, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unshare_screen(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Result<()> {
|
||||
pub fn unshare_screen(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
if self.status.is_offline() {
|
||||
return Err(anyhow!("room is offline"));
|
||||
}
|
||||
@@ -1614,7 +1553,7 @@ impl Room {
|
||||
match mem::take(&mut live_kit.screen_track) {
|
||||
LocalTrack::None => Err(anyhow!("screen was not shared")),
|
||||
LocalTrack::Pending { .. } => {
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
LocalTrack::Published {
|
||||
@@ -1627,7 +1566,7 @@ impl Room {
|
||||
cx.background_executor()
|
||||
.spawn(async move { local_participant.unpublish_track(&sid).await })
|
||||
.detach_and_log_err(cx);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
Audio::play_sound(Sound::StopScreenshare, cx);
|
||||
Ok(())
|
||||
@@ -1635,16 +1574,11 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_deafened(
|
||||
&mut self,
|
||||
deafened: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<()> {
|
||||
fn set_deafened(&mut self, deafened: bool, cx: &mut ModelContext<Self>) -> Option<()> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let live_kit = self.live_kit.as_mut()?;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
for (_, participant) in live_kit.room.remote_participants() {
|
||||
for (_, publication) in participant.track_publications() {
|
||||
if publication.kind() == TrackKind::Audio {
|
||||
@@ -1660,11 +1594,10 @@ impl Room {
|
||||
fn set_mute(
|
||||
&mut self,
|
||||
should_mute: bool,
|
||||
model: &Model<Room>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Room>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let live_kit = self.live_kit.as_mut()?;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
if should_mute {
|
||||
Audio::play_sound(Sound::Mute, cx);
|
||||
@@ -1677,7 +1610,7 @@ impl Room {
|
||||
if should_mute {
|
||||
None
|
||||
} else {
|
||||
Some(self.share_microphone(model, cx))
|
||||
Some(self.share_microphone(cx))
|
||||
}
|
||||
}
|
||||
LocalTrack::Pending { .. } => None,
|
||||
@@ -1701,62 +1634,59 @@ impl Room {
|
||||
#[cfg(target_os = "windows")]
|
||||
fn spawn_room_connection(
|
||||
livekit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||
model: &Model<Room>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<'_, Room>,
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn spawn_room_connection(
|
||||
livekit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||
model: &Model<Room>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<'_, Room>,
|
||||
) {
|
||||
if let Some(connection_info) = livekit_connection_info {
|
||||
model
|
||||
.spawn(cx, |this, mut cx| async move {
|
||||
let (room, mut events) = livekit::Room::connect(
|
||||
&connection_info.server_url,
|
||||
&connection_info.token,
|
||||
RoomOptions::default(),
|
||||
)
|
||||
.await?;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (room, mut events) = livekit::Room::connect(
|
||||
&connection_info.server_url,
|
||||
&connection_info.token,
|
||||
RoomOptions::default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
let _handle_updates = model.spawn(cx, |this, mut cx| async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
if this
|
||||
.update(&mut cx, |this, model, cx| {
|
||||
this.livekit_room_updated(event, model, cx).warn_on_err();
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let _handle_updates = cx.spawn(|this, mut cx| async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
if this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.livekit_room_updated(event, cx).warn_on_err();
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
let muted_by_user = Room::mute_on_join(cx);
|
||||
this.live_kit = Some(LiveKitRoom {
|
||||
room: Arc::new(room),
|
||||
screen_track: LocalTrack::None,
|
||||
microphone_track: LocalTrack::None,
|
||||
next_publish_id: 0,
|
||||
muted_by_user,
|
||||
deafened: false,
|
||||
speaking: false,
|
||||
_handle_updates,
|
||||
});
|
||||
|
||||
if !muted_by_user && this.can_use_microphone(cx) {
|
||||
this.share_microphone(model, cx)
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
})?
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
|
||||
let muted_by_user = Room::mute_on_join(cx);
|
||||
this.live_kit = Some(LiveKitRoom {
|
||||
room: Arc::new(room),
|
||||
screen_track: LocalTrack::None,
|
||||
microphone_track: LocalTrack::None,
|
||||
next_publish_id: 0,
|
||||
muted_by_user,
|
||||
deafened: false,
|
||||
speaking: false,
|
||||
_handle_updates,
|
||||
});
|
||||
|
||||
if !muted_by_user && this.can_use_microphone(cx) {
|
||||
this.share_microphone(cx)
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
})?
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1774,17 +1704,17 @@ struct LiveKitRoom {
|
||||
|
||||
impl LiveKitRoom {
|
||||
#[cfg(target_os = "windows")]
|
||||
fn stop_publishing(&mut self, model: &Model<Room>, _cx: &mut AppContext) {}
|
||||
fn stop_publishing(&mut self, _cx: &mut ModelContext<Room>) {}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn stop_publishing(&mut self, model: &Model<Room>, cx: &mut AppContext) {
|
||||
fn stop_publishing(&mut self, cx: &mut ModelContext<Room>) {
|
||||
let mut tracks_to_unpublish = Vec::new();
|
||||
if let LocalTrack::Published {
|
||||
track_publication, ..
|
||||
} = mem::replace(&mut self.microphone_track, LocalTrack::None)
|
||||
{
|
||||
tracks_to_unpublish.push(track_publication.sid());
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if let LocalTrack::Published {
|
||||
@@ -1792,7 +1722,7 @@ impl LiveKitRoom {
|
||||
} = mem::replace(&mut self.screen_track, LocalTrack::None)
|
||||
{
|
||||
tracks_to_unpublish.push(track_publication.sid());
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
let participant = self.room.local_participant();
|
||||
|
||||
@@ -8,8 +8,8 @@ use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAY
|
||||
use collections::HashSet;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, Subscription, Task,
|
||||
WeakModel,
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Subscription,
|
||||
Task, WeakModel,
|
||||
};
|
||||
use postage::watch;
|
||||
use project::Project;
|
||||
@@ -27,7 +27,7 @@ impl Global for GlobalActiveCall {}
|
||||
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||
CallSettings::register(cx);
|
||||
|
||||
let active_call = cx.new_model(|model, cx| ActiveCall::new(client, user_store, model, cx));
|
||||
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||
cx.set_global(GlobalActiveCall(active_call));
|
||||
}
|
||||
|
||||
@@ -89,12 +89,7 @@ pub struct ActiveCall {
|
||||
impl EventEmitter<Event> for ActiveCall {}
|
||||
|
||||
impl ActiveCall {
|
||||
fn new(
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Self {
|
||||
fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
room: None,
|
||||
pending_room_creation: None,
|
||||
@@ -103,8 +98,8 @@ impl ActiveCall {
|
||||
incoming_call: watch::channel(),
|
||||
_join_debouncer: OneAtATime { cancel: None },
|
||||
_subscriptions: vec![
|
||||
client.add_request_handler(model.downgrade(), Self::handle_incoming_call),
|
||||
client.add_message_handler(model.downgrade(), Self::handle_call_canceled),
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_call_canceled),
|
||||
],
|
||||
client,
|
||||
user_store,
|
||||
@@ -120,22 +115,22 @@ impl ActiveCall {
|
||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let user_store = this.update(&mut cx, |this, _, _| this.user_store.clone())?;
|
||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||
let call = IncomingCall {
|
||||
room_id: envelope.payload.room_id,
|
||||
participants: user_store
|
||||
.update(&mut cx, |user_store, model, cx| {
|
||||
user_store.get_users(envelope.payload.participant_user_ids, model, cx)
|
||||
.update(&mut cx, |user_store, cx| {
|
||||
user_store.get_users(envelope.payload.participant_user_ids, cx)
|
||||
})?
|
||||
.await?,
|
||||
calling_user: user_store
|
||||
.update(&mut cx, |user_store, model, cx| {
|
||||
user_store.get_user(envelope.payload.calling_user_id, model, cx)
|
||||
.update(&mut cx, |user_store, cx| {
|
||||
user_store.get_user(envelope.payload.calling_user_id, cx)
|
||||
})?
|
||||
.await?,
|
||||
initial_project: envelope.payload.initial_project,
|
||||
};
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
*this.incoming_call.0.borrow_mut() = Some(call);
|
||||
})?;
|
||||
|
||||
@@ -147,7 +142,7 @@ impl ActiveCall {
|
||||
envelope: TypedEnvelope<proto::CallCanceled>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
let mut incoming_call = this.incoming_call.0.borrow_mut();
|
||||
if incoming_call
|
||||
.as_ref()
|
||||
@@ -172,13 +167,12 @@ impl ActiveCall {
|
||||
&mut self,
|
||||
called_user_id: u64,
|
||||
initial_project: Option<Model<Project>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if !self.pending_invites.insert(called_user_id) {
|
||||
return Task::ready(Err(anyhow!("user was already invited")));
|
||||
}
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
if self._join_debouncer.running() {
|
||||
return Task::ready(Ok(()));
|
||||
@@ -191,22 +185,20 @@ impl ActiveCall {
|
||||
};
|
||||
|
||||
let invite = if let Some(room) = room {
|
||||
cx.spawn(move |mut cx| async move {
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
|
||||
|
||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||
Some(
|
||||
room.update(&mut cx, |room, model, cx| {
|
||||
room.share_project(initial_project, model, cx)
|
||||
})?
|
||||
.await?,
|
||||
room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))?
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
room.update(&mut cx, move |room, model, cx| {
|
||||
room.call(called_user_id, initial_project_id, model, cx)
|
||||
room.update(&mut cx, move |room, cx| {
|
||||
room.call(called_user_id, initial_project_id, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
@@ -215,8 +207,8 @@ impl ActiveCall {
|
||||
} else {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
let room = model
|
||||
.spawn(cx, move |this, mut cx| async move {
|
||||
let room = cx
|
||||
.spawn(move |this, mut cx| async move {
|
||||
let create_room = async {
|
||||
let room = cx
|
||||
.update(|cx| {
|
||||
@@ -230,16 +222,14 @@ impl ActiveCall {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.set_room(Some(room.clone()), model, cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))?
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(room)
|
||||
};
|
||||
|
||||
let room = create_room.await;
|
||||
this.update(&mut cx, |this, _, _| this.pending_room_creation = None)?;
|
||||
this.update(&mut cx, |this, _| this.pending_room_creation = None)?;
|
||||
room.map_err(Arc::new)
|
||||
})
|
||||
.shared();
|
||||
@@ -250,20 +240,18 @@ impl ActiveCall {
|
||||
})
|
||||
};
|
||||
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = invite.await;
|
||||
if result.is_ok() {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.report_call_event("invite", cx)
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
|
||||
} else {
|
||||
//TODO: report collaboration error
|
||||
log::error!("invite failed: {:?}", result);
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_invites.remove(&called_user_id);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
result
|
||||
})
|
||||
@@ -272,8 +260,7 @@ impl ActiveCall {
|
||||
pub fn cancel_invite(
|
||||
&mut self,
|
||||
called_user_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let room_id = if let Some(room) = self.room() {
|
||||
room.read(cx).id()
|
||||
@@ -297,11 +284,7 @@ impl ActiveCall {
|
||||
self.incoming_call.1.clone()
|
||||
}
|
||||
|
||||
pub fn accept_incoming(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn accept_incoming(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.room.is_some() {
|
||||
return Task::ready(Err(anyhow!("cannot join while on another call")));
|
||||
}
|
||||
@@ -323,20 +306,18 @@ impl ActiveCall {
|
||||
._join_debouncer
|
||||
.spawn(cx, move |cx| Room::join(room_id, client, user_store, cx));
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let room = join.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.set_room(room.clone(), model, cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("accept incoming", cx)
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decline_incoming(&mut self, _: &Model<Self>, _: &mut AppContext) -> Result<()> {
|
||||
pub fn decline_incoming(&mut self, _: &mut ModelContext<Self>) -> Result<()> {
|
||||
let call = self
|
||||
.incoming_call
|
||||
.0
|
||||
@@ -353,14 +334,13 @@ impl ActiveCall {
|
||||
pub fn join_channel(
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Option<Model<Room>>>> {
|
||||
if let Some(room) = self.room().cloned() {
|
||||
if room.read(cx).channel_id() == Some(channel_id) {
|
||||
return Task::ready(Ok(Some(room)));
|
||||
} else {
|
||||
room.update(cx, |room, model, cx| room.clear_state(cx));
|
||||
room.update(cx, |room, cx| room.clear_state(cx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,29 +354,27 @@ impl ActiveCall {
|
||||
Room::join_channel(channel_id, client, user_store, cx).await
|
||||
});
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let room = join.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.set_room(room.clone(), model, cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("join channel", cx)
|
||||
})?;
|
||||
Ok(room)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hang_up(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
model.notify(cx);
|
||||
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
cx.notify();
|
||||
self.report_call_event("hang up", cx);
|
||||
|
||||
Audio::end_call(cx);
|
||||
|
||||
let channel_id = self.channel_id(cx);
|
||||
if let Some((room, _)) = self.room.take() {
|
||||
model.emit(Event::RoomLeft { channel_id }, cx);
|
||||
room.update(cx, |room, model, cx| room.leave(model, cx))
|
||||
cx.emit(Event::RoomLeft { channel_id });
|
||||
room.update(cx, |room, cx| room.leave(cx))
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
@@ -405,12 +383,11 @@ impl ActiveCall {
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("share project", cx);
|
||||
room.update(cx, |room, model, cx| room.share_project(project, model, cx))
|
||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("no active call")))
|
||||
}
|
||||
@@ -419,14 +396,11 @@ impl ActiveCall {
|
||||
pub fn unshare_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("unshare project", cx);
|
||||
room.update(cx, |room, model, cx| {
|
||||
room.unshare_project(project, model, cx)
|
||||
})
|
||||
room.update(cx, |room, cx| room.unshare_project(project, cx))
|
||||
} else {
|
||||
Err(anyhow!("no active call"))
|
||||
}
|
||||
@@ -439,13 +413,12 @@ impl ActiveCall {
|
||||
pub fn set_location(
|
||||
&mut self,
|
||||
project: Option<&Model<Project>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if project.is_some() || !*ZED_ALWAYS_ACTIVE {
|
||||
self.location = project.map(|project| project.downgrade());
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
return room.update(cx, |room, model, cx| room.set_location(project, model, cx));
|
||||
return room.update(cx, |room, cx| room.set_location(project, cx));
|
||||
}
|
||||
}
|
||||
Task::ready(Ok(()))
|
||||
@@ -454,29 +427,26 @@ impl ActiveCall {
|
||||
fn set_room(
|
||||
&mut self,
|
||||
room: Option<Model<Room>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if room.as_ref() == self.room.as_ref().map(|room| &room.0) {
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
if let Some(room) = room {
|
||||
if room.read(cx).status().is_offline() {
|
||||
self.room = None;
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
let subscriptions = vec![
|
||||
model.observe(&room, cx, |this, room, model, cx| {
|
||||
cx.observe(&room, |this, room, cx| {
|
||||
if room.read(cx).status().is_offline() {
|
||||
this.set_room(None, model, cx).detach_and_log_err(cx);
|
||||
this.set_room(None, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
}),
|
||||
model.subscribe(&room, cx, |_, _, event, model, cx| {
|
||||
model.emit(event.clone(), cx)
|
||||
cx.notify();
|
||||
}),
|
||||
cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
|
||||
];
|
||||
self.room = Some((room.clone(), subscriptions));
|
||||
let location = self
|
||||
@@ -484,10 +454,8 @@ impl ActiveCall {
|
||||
.as_ref()
|
||||
.and_then(|location| location.upgrade());
|
||||
let channel_id = room.read(cx).channel_id();
|
||||
model.emit(Event::RoomJoined { channel_id }, cx);
|
||||
room.update(cx, |room, model, cx| {
|
||||
room.set_location(location.as_ref(), model, cx)
|
||||
})
|
||||
cx.emit(Event::RoomJoined { channel_id });
|
||||
room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
|
||||
}
|
||||
} else {
|
||||
self.room = None;
|
||||
|
||||
@@ -11,7 +11,9 @@ use client::{
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, Task, WeakModel};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use livekit_client_macos::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate};
|
||||
use postage::{sink::Sink, stream::Stream, watch};
|
||||
@@ -108,15 +110,14 @@ impl Room {
|
||||
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
||||
let room = livekit_client_macos::Room::new();
|
||||
let mut status = room.status();
|
||||
// Consume the initial status of the room.
|
||||
let _ = status.try_recv();
|
||||
let _maintain_room = model.spawn(cx, |this, mut cx| async move {
|
||||
let _maintain_room = cx.spawn(|this, mut cx| async move {
|
||||
while let Some(status) = status.next().await {
|
||||
let this = if let Some(this) = this.upgrade() {
|
||||
this
|
||||
@@ -125,14 +126,14 @@ impl Room {
|
||||
};
|
||||
|
||||
if status == livekit_client_macos::ConnectionState::Disconnected {
|
||||
this.update(&mut cx, |this, model, cx| this.leave(model, cx).log_err())
|
||||
this.update(&mut cx, |this, cx| this.leave(cx).log_err())
|
||||
.ok();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _handle_updates = model.spawn(cx, {
|
||||
let _handle_updates = cx.spawn({
|
||||
let room = room.clone();
|
||||
move |this, mut cx| async move {
|
||||
let mut updates = room.updates();
|
||||
@@ -143,8 +144,8 @@ impl Room {
|
||||
break;
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.live_kit_room_updated(update, model, cx).log_err()
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.live_kit_room_updated(update, cx).log_err()
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -152,22 +153,21 @@ impl Room {
|
||||
});
|
||||
|
||||
let connect = room.connect(&connection_info.server_url, &connection_info.token);
|
||||
model
|
||||
.spawn(cx, |this, mut cx| async move {
|
||||
connect.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
if this.can_use_microphone(cx) {
|
||||
if let Some(live_kit) = &this.live_kit {
|
||||
if !live_kit.muted_by_user && !live_kit.deafened {
|
||||
return this.share_microphone(model, cx);
|
||||
}
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
connect.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.can_use_microphone(cx) {
|
||||
if let Some(live_kit) = &this.live_kit {
|
||||
if !live_kit.muted_by_user && !live_kit.deafened {
|
||||
return this.share_microphone(cx);
|
||||
}
|
||||
}
|
||||
Task::ready(Ok(()))
|
||||
})?
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
Task::ready(Ok(()))
|
||||
})?
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
Some(LiveKitRoom {
|
||||
room,
|
||||
@@ -184,7 +184,7 @@ impl Room {
|
||||
None
|
||||
};
|
||||
|
||||
let maintain_connection = model.spawn(cx, {
|
||||
let maintain_connection = cx.spawn({
|
||||
let client = client.clone();
|
||||
move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
|
||||
});
|
||||
@@ -206,11 +206,11 @@ impl Room {
|
||||
pending_participants: Default::default(),
|
||||
pending_call_count: 0,
|
||||
client_subscriptions: vec![
|
||||
client.add_message_handler(model.downgrade(), Self::handle_room_updated)
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_room_updated)
|
||||
],
|
||||
_subscriptions: vec![
|
||||
model.on_release(cx, Self::released),
|
||||
model.on_app_quit(cx, Self::app_will_quit),
|
||||
cx.on_release(Self::released),
|
||||
cx.on_app_quit(Self::app_will_quit),
|
||||
],
|
||||
leave_when_empty: false,
|
||||
pending_room_update: None,
|
||||
@@ -233,14 +233,13 @@ impl Room {
|
||||
cx.spawn(move |mut cx| async move {
|
||||
let response = client.request(proto::CreateRoom {}).await?;
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
let room = cx.new_model(|model, cx| {
|
||||
let room = cx.new_model(|cx| {
|
||||
let mut room = Self::new(
|
||||
room_proto.id,
|
||||
None,
|
||||
response.live_kit_connection_info,
|
||||
client,
|
||||
user_store,
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
if let Some(participant) = room_proto.participants.first() {
|
||||
@@ -251,8 +250,8 @@ impl Room {
|
||||
|
||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||
let initial_project_id = room
|
||||
.update(&mut cx, |room, model, cx| {
|
||||
room.share_project(initial_project.clone(), model, cx)
|
||||
.update(&mut cx, |room, cx| {
|
||||
room.share_project(initial_project.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
Some(initial_project_id)
|
||||
@@ -261,9 +260,9 @@ impl Room {
|
||||
};
|
||||
|
||||
let did_join = room
|
||||
.update(&mut cx, |room, model, cx| {
|
||||
.update(&mut cx, |room, cx| {
|
||||
room.leave_when_empty = true;
|
||||
room.call(called_user_id, initial_project_id, model, cx)
|
||||
room.call(called_user_id, initial_project_id, cx)
|
||||
})?
|
||||
.await;
|
||||
match did_join {
|
||||
@@ -311,11 +310,7 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
fn app_will_quit(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> impl Future<Output = ()> {
|
||||
fn app_will_quit(&mut self, cx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
|
||||
let task = if self.status.is_online() {
|
||||
let leave = self.leave_internal(cx);
|
||||
Some(cx.background_executor().spawn(async move {
|
||||
@@ -343,20 +338,19 @@ impl Room {
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
let room = cx.new_model(|model, cx| {
|
||||
let room = cx.new_model(|cx| {
|
||||
Self::new(
|
||||
room_proto.id,
|
||||
response.channel_id.map(ChannelId),
|
||||
response.live_kit_connection_info,
|
||||
client,
|
||||
user_store,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
room.update(&mut cx, |room, model, cx| {
|
||||
room.update(&mut cx, |room, cx| {
|
||||
room.leave_when_empty = room.channel_id.is_none();
|
||||
room.apply_room_update(room_proto, model, cx)?;
|
||||
room.apply_room_update(room_proto, cx)?;
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
Ok(room)
|
||||
@@ -370,8 +364,8 @@ impl Room {
|
||||
&& self.pending_call_count == 0
|
||||
}
|
||||
|
||||
pub(crate) fn leave(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
model.notify(cx);
|
||||
pub(crate) fn leave(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
cx.notify();
|
||||
self.leave_internal(cx)
|
||||
}
|
||||
|
||||
@@ -395,16 +389,16 @@ impl Room {
|
||||
pub(crate) fn clear_state(&mut self, cx: &mut AppContext) {
|
||||
for project in self.shared_projects.drain() {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.unshare(model, cx).log_err();
|
||||
project.update(cx, |project, cx| {
|
||||
project.unshare(cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
for project in self.joined_projects.drain() {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.disconnected_from_host(model, cx);
|
||||
project.close(model, cx);
|
||||
project.update(cx, |project, cx| {
|
||||
project.disconnected_from_host(cx);
|
||||
project.close(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -434,9 +428,9 @@ impl Room {
|
||||
|
||||
this.upgrade()
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.update(&mut cx, |this, model, cx| {
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.status = RoomStatus::Rejoining;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
// Wait for client to re-establish a connection to the server.
|
||||
@@ -450,8 +444,7 @@ impl Room {
|
||||
log::info!("client reconnected, attempting to rejoin room");
|
||||
|
||||
let Some(this) = this.upgrade() else { break };
|
||||
match this.update(&mut cx, |this, model, cx| this.rejoin(model, cx))
|
||||
{
|
||||
match this.update(&mut cx, |this, cx| this.rejoin(cx)) {
|
||||
Ok(task) => {
|
||||
if task.await.log_err().is_some() {
|
||||
return true;
|
||||
@@ -500,15 +493,14 @@ impl Room {
|
||||
// we leave the room and return an error.
|
||||
if let Some(this) = this.upgrade() {
|
||||
log::info!("reconnection failed, leaving room");
|
||||
this.update(&mut cx, |this, model, cx| this.leave(model, cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.leave(cx))?.await?;
|
||||
}
|
||||
Err(anyhow!(
|
||||
"can't reconnect to room: client failed to re-establish connection"
|
||||
))
|
||||
}
|
||||
|
||||
fn rejoin(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
fn rejoin(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let mut projects = HashMap::default();
|
||||
let mut reshared_projects = Vec::new();
|
||||
let mut rejoined_projects = Vec::new();
|
||||
@@ -556,29 +548,27 @@ impl Room {
|
||||
rejoined_projects,
|
||||
});
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = response.await?;
|
||||
let message_id = response.message_id;
|
||||
let response = response.payload;
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.status = RoomStatus::Online;
|
||||
this.apply_room_update(room_proto, model, cx)?;
|
||||
this.apply_room_update(room_proto, cx)?;
|
||||
|
||||
for reshared_project in response.reshared_projects {
|
||||
if let Some(project) = projects.get(&reshared_project.id) {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.reshared(reshared_project, model, cx).log_err();
|
||||
project.update(cx, |project, cx| {
|
||||
project.reshared(reshared_project, cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for rejoined_project in response.rejoined_projects {
|
||||
if let Some(project) = projects.get(&rejoined_project.id) {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project
|
||||
.rejoined(rejoined_project, message_id, model, cx)
|
||||
.log_err();
|
||||
project.update(cx, |project, cx| {
|
||||
project.rejoined(rejoined_project, message_id, cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -636,13 +626,12 @@ impl Room {
|
||||
&mut self,
|
||||
user_id: u64,
|
||||
role: proto::ChannelRole,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
let room_id = self.id;
|
||||
let role = role.into();
|
||||
cx.spawn(|_| async move {
|
||||
cx.spawn(|_, _| async move {
|
||||
client
|
||||
.request(proto::SetRoomParticipantRole {
|
||||
room_id,
|
||||
@@ -714,16 +703,13 @@ impl Room {
|
||||
.payload
|
||||
.room
|
||||
.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.apply_room_update(room, model, cx)
|
||||
})?
|
||||
this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))?
|
||||
}
|
||||
|
||||
fn apply_room_update(
|
||||
&mut self,
|
||||
mut room: proto::Room,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
// Filter ourselves out from the room's participants.
|
||||
let local_participant_ix = room
|
||||
@@ -745,18 +731,18 @@ impl Room {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (remote_participants, pending_participants) =
|
||||
self.user_store.update(cx, move |user_store, model, cx| {
|
||||
self.user_store.update(cx, move |user_store, cx| {
|
||||
(
|
||||
user_store.get_users(remote_participant_user_ids, model, cx),
|
||||
user_store.get_users(pending_participant_user_ids, model, cx),
|
||||
user_store.get_users(remote_participant_user_ids, cx),
|
||||
user_store.get_users(pending_participant_user_ids, cx),
|
||||
)
|
||||
});
|
||||
|
||||
self.pending_room_update = Some(model.spawn(cx, |this, mut cx| async move {
|
||||
self.pending_room_update = Some(cx.spawn(|this, mut cx| async move {
|
||||
let (remote_participants, pending_participants) =
|
||||
futures::join!(remote_participants, pending_participants);
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.participant_user_ids.clear();
|
||||
|
||||
if let Some(participant) = local_participant {
|
||||
@@ -768,20 +754,18 @@ impl Room {
|
||||
if role == proto::ChannelRole::Guest {
|
||||
for project in mem::take(&mut this.shared_projects) {
|
||||
if let Some(project) = project.upgrade() {
|
||||
this.unshare_project(project, model, cx).log_err();
|
||||
this.unshare_project(project, cx).log_err();
|
||||
}
|
||||
}
|
||||
this.local_participant.projects.clear();
|
||||
if let Some(live_kit_room) = &mut this.live_kit {
|
||||
live_kit_room.stop_publishing(model, cx);
|
||||
live_kit_room.stop_publishing(cx);
|
||||
}
|
||||
}
|
||||
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.set_role(role, model, cx)
|
||||
});
|
||||
project.update(cx, |project, cx| project.set_role(role, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -815,23 +799,20 @@ impl Room {
|
||||
|
||||
for project in &participant.projects {
|
||||
if !old_projects.contains(&project.id) {
|
||||
model.emit(
|
||||
Event::RemoteProjectShared {
|
||||
owner: user.clone(),
|
||||
project_id: project.id,
|
||||
worktree_root_names: project.worktree_root_names.clone(),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteProjectShared {
|
||||
owner: user.clone(),
|
||||
project_id: project.id,
|
||||
worktree_root_names: project.worktree_root_names.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for unshared_project_id in old_projects.difference(&new_projects) {
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, model, cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
if project.remote_id() == Some(*unshared_project_id) {
|
||||
project.disconnected_from_host(model, cx);
|
||||
project.disconnected_from_host(cx);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
@@ -841,12 +822,9 @@ impl Room {
|
||||
false
|
||||
}
|
||||
});
|
||||
model.emit(
|
||||
Event::RemoteProjectUnshared {
|
||||
project_id: *unshared_project_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteProjectUnshared {
|
||||
project_id: *unshared_project_id,
|
||||
});
|
||||
}
|
||||
|
||||
let role = participant.role();
|
||||
@@ -863,12 +841,9 @@ impl Room {
|
||||
{
|
||||
remote_participant.location = location;
|
||||
remote_participant.role = role;
|
||||
model.emit(
|
||||
Event::ParticipantLocationChanged {
|
||||
participant_id: peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::ParticipantLocationChanged {
|
||||
participant_id: peer_id,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.remote_participants.insert(
|
||||
@@ -901,7 +876,6 @@ impl Room {
|
||||
for track in video_tracks {
|
||||
this.live_kit_room_updated(
|
||||
RoomUpdate::SubscribedToRemoteVideoTrack(track),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
@@ -915,7 +889,6 @@ impl Room {
|
||||
track.clone(),
|
||||
publication.clone(),
|
||||
),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
@@ -929,12 +902,9 @@ impl Room {
|
||||
true
|
||||
} else {
|
||||
for project in &participant.projects {
|
||||
model.emit(
|
||||
Event::RemoteProjectUnshared {
|
||||
project_id: project.id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteProjectUnshared {
|
||||
project_id: project.id,
|
||||
});
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -972,26 +942,26 @@ impl Room {
|
||||
this.pending_room_update.take();
|
||||
if this.should_leave() {
|
||||
log::info!("room is empty, leaving");
|
||||
this.leave(model, cx).detach();
|
||||
this.leave(cx).detach();
|
||||
}
|
||||
|
||||
this.user_store.update(cx, |user_store, model, cx| {
|
||||
this.user_store.update(cx, |user_store, cx| {
|
||||
let participant_indices_by_user_id = this
|
||||
.remote_participants
|
||||
.iter()
|
||||
.map(|(user_id, participant)| (*user_id, participant.participant_index))
|
||||
.collect();
|
||||
user_store.set_participant_indices(participant_indices_by_user_id, model, cx);
|
||||
user_store.set_participant_indices(participant_indices_by_user_id, cx);
|
||||
});
|
||||
|
||||
this.check_invariants();
|
||||
this.room_update_completed_tx.try_send(Some(())).ok();
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}));
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1009,8 +979,7 @@ impl Room {
|
||||
fn live_kit_room_updated(
|
||||
&mut self,
|
||||
update: RoomUpdate,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
match update {
|
||||
RoomUpdate::SubscribedToRemoteVideoTrack(track) => {
|
||||
@@ -1021,12 +990,9 @@ impl Room {
|
||||
.get_mut(&user_id)
|
||||
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||
participant.video_tracks.insert(track_id.clone(), track);
|
||||
model.emit(
|
||||
Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
}
|
||||
|
||||
RoomUpdate::UnsubscribedFromRemoteVideoTrack {
|
||||
@@ -1039,12 +1005,9 @@ impl Room {
|
||||
.get_mut(&user_id)
|
||||
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
|
||||
participant.video_tracks.remove(&track_id);
|
||||
model.emit(
|
||||
Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
}
|
||||
|
||||
RoomUpdate::ActiveSpeakersChanged { speakers } => {
|
||||
@@ -1098,12 +1061,9 @@ impl Room {
|
||||
participant.audio_tracks.insert(track_id.clone(), track);
|
||||
participant.muted = publication.is_muted();
|
||||
|
||||
model.emit(
|
||||
Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
}
|
||||
|
||||
RoomUpdate::UnsubscribedFromRemoteAudioTrack {
|
||||
@@ -1116,12 +1076,9 @@ impl Room {
|
||||
.get_mut(&user_id)
|
||||
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
|
||||
participant.audio_tracks.remove(&track_id);
|
||||
model.emit(
|
||||
Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
}
|
||||
|
||||
RoomUpdate::LocalAudioTrackUnpublished { publication } => {
|
||||
@@ -1147,7 +1104,7 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1175,18 +1132,17 @@ impl Room {
|
||||
&mut self,
|
||||
called_user_id: u64,
|
||||
initial_project_id: Option<u64>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
let client = self.client.clone();
|
||||
let room_id = self.id;
|
||||
self.pending_call_count += 1;
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::Call {
|
||||
room_id,
|
||||
@@ -1194,10 +1150,10 @@ impl Room {
|
||||
initial_project_id,
|
||||
})
|
||||
.await;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_call_count -= 1;
|
||||
if this.should_leave() {
|
||||
this.leave(model, cx).detach_and_log_err(cx);
|
||||
this.leave(cx).detach_and_log_err(cx);
|
||||
}
|
||||
})?;
|
||||
result?;
|
||||
@@ -1210,17 +1166,16 @@ impl Room {
|
||||
id: u64,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Project>>> {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
model.emit(Event::RemoteProjectJoined { project_id: id }, cx);
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.emit(Event::RemoteProjectJoined { project_id: id });
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let project =
|
||||
Project::in_room(id, client, user_store, language_registry, fs, cx.clone()).await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
!project.read(cx).is_disconnected(cx)
|
||||
@@ -1237,8 +1192,7 @@ impl Room {
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some(project_id) = project.read(cx).remote_id() {
|
||||
return Task::ready(Ok(project_id));
|
||||
@@ -1250,19 +1204,19 @@ impl Room {
|
||||
is_ssh_project: project.read(cx).is_via_ssh(),
|
||||
});
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
|
||||
project.update(&mut cx, |project, model, cx| {
|
||||
project.shared(response.project_id, model, cx)
|
||||
project.update(&mut cx, |project, cx| {
|
||||
project.shared(response.project_id, cx)
|
||||
})??;
|
||||
|
||||
// If the user's location is in this project, it changes from UnsharedProject to SharedProject.
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.shared_projects.insert(project.downgrade());
|
||||
let active_project = this.local_participant.active_project.as_ref();
|
||||
if active_project.map_or(false, |location| *location == project) {
|
||||
this.set_location(Some(&project), model, cx)
|
||||
this.set_location(Some(&project), cx)
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
@@ -1276,8 +1230,7 @@ impl Room {
|
||||
pub(crate) fn unshare_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
let project_id = match project.read(cx).remote_id() {
|
||||
Some(project_id) => project_id,
|
||||
@@ -1285,11 +1238,10 @@ impl Room {
|
||||
};
|
||||
|
||||
self.client.send(proto::UnshareProject { project_id })?;
|
||||
project.update(cx, |this, model, cx| this.unshare(model, cx))?;
|
||||
project.update(cx, |this, cx| this.unshare(cx))?;
|
||||
|
||||
if self.local_participant.active_project == Some(project.downgrade()) {
|
||||
self.set_location(Some(&project), model, cx)
|
||||
.detach_and_log_err(cx);
|
||||
self.set_location(Some(&project), cx).detach_and_log_err(cx);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1297,8 +1249,7 @@ impl Room {
|
||||
pub(crate) fn set_location(
|
||||
&mut self,
|
||||
project: Option<&Model<Project>>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
@@ -1322,7 +1273,7 @@ impl Room {
|
||||
proto::participant_location::Variant::External(proto::participant_location::External {})
|
||||
};
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::UpdateParticipantLocation {
|
||||
@@ -1383,11 +1334,7 @@ impl Room {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn share_microphone(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
@@ -1395,18 +1342,18 @@ impl Room {
|
||||
let publish_id = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.microphone_track = LocalTrack::Pending { publish_id };
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
publish_id
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
};
|
||||
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let publish_track = async {
|
||||
let track = LocalAudioTrack::create();
|
||||
this.upgrade()
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.update(&mut cx, |this, model, _| {
|
||||
.update(&mut cx, |this, _| {
|
||||
this.live_kit
|
||||
.as_ref()
|
||||
.map(|live_kit| live_kit.room.publish_audio_track(track))
|
||||
@@ -1417,7 +1364,7 @@ impl Room {
|
||||
let publication = publish_track.await;
|
||||
this.upgrade()
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.update(&mut cx, |this, model, cx| {
|
||||
.update(&mut cx, |this, cx| {
|
||||
let live_kit = this
|
||||
.live_kit
|
||||
.as_mut()
|
||||
@@ -1445,7 +1392,7 @@ impl Room {
|
||||
live_kit.microphone_track = LocalTrack::Published {
|
||||
track_publication: publication,
|
||||
};
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1454,7 +1401,7 @@ impl Room {
|
||||
Ok(())
|
||||
} else {
|
||||
live_kit.microphone_track = LocalTrack::None;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
@@ -1463,7 +1410,7 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn share_screen(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
} else if self.is_screen_sharing() {
|
||||
@@ -1473,13 +1420,13 @@ impl Room {
|
||||
let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.screen_track = LocalTrack::Pending { publish_id };
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
(live_kit.room.display_sources(), publish_id)
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
};
|
||||
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let publish_track = async {
|
||||
let displays = displays.await?;
|
||||
let display = displays
|
||||
@@ -1488,7 +1435,7 @@ impl Room {
|
||||
let track = LocalVideoTrack::screen_share_for_display(display);
|
||||
this.upgrade()
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.update(&mut cx, |this, model, _| {
|
||||
.update(&mut cx, |this, _| {
|
||||
this.live_kit
|
||||
.as_ref()
|
||||
.map(|live_kit| live_kit.room.publish_video_track(track))
|
||||
@@ -1500,7 +1447,7 @@ impl Room {
|
||||
let publication = publish_track.await;
|
||||
this.upgrade()
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.update(&mut cx, |this, model, cx| {
|
||||
.update(&mut cx, |this, cx| {
|
||||
let live_kit = this
|
||||
.live_kit
|
||||
.as_mut()
|
||||
@@ -1523,7 +1470,7 @@ impl Room {
|
||||
live_kit.screen_track = LocalTrack::Published {
|
||||
track_publication: publication,
|
||||
};
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
Audio::play_sound(Sound::StartScreenshare, cx);
|
||||
@@ -1535,7 +1482,7 @@ impl Room {
|
||||
Ok(())
|
||||
} else {
|
||||
live_kit.screen_track = LocalTrack::None;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
@@ -1544,7 +1491,7 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn toggle_mute(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
// When unmuting, undeafen if the user was deafened before.
|
||||
let was_deafened = live_kit.deafened;
|
||||
@@ -1560,19 +1507,19 @@ impl Room {
|
||||
let muted = live_kit.muted_by_user;
|
||||
let should_undeafen = was_deafened && !live_kit.deafened;
|
||||
|
||||
if let Some(task) = self.set_mute(muted, model, cx) {
|
||||
if let Some(task) = self.set_mute(muted, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
if should_undeafen {
|
||||
if let Some(task) = self.set_deafened(false, model, cx) {
|
||||
if let Some(task) = self.set_deafened(false, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_deafen(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
// When deafening, mute the microphone if it was not already muted.
|
||||
// When un-deafening, unmute the microphone, unless it was explicitly muted.
|
||||
@@ -1580,19 +1527,19 @@ impl Room {
|
||||
live_kit.deafened = deafened;
|
||||
let should_change_mute = !live_kit.muted_by_user;
|
||||
|
||||
if let Some(task) = self.set_deafened(deafened, model, cx) {
|
||||
if let Some(task) = self.set_deafened(deafened, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
if should_change_mute {
|
||||
if let Some(task) = self.set_mute(deafened, model, cx) {
|
||||
if let Some(task) = self.set_mute(deafened, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unshare_screen(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Result<()> {
|
||||
pub fn unshare_screen(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
if self.status.is_offline() {
|
||||
return Err(anyhow!("room is offline"));
|
||||
}
|
||||
@@ -1604,14 +1551,14 @@ impl Room {
|
||||
match mem::take(&mut live_kit.screen_track) {
|
||||
LocalTrack::None => Err(anyhow!("screen was not shared")),
|
||||
LocalTrack::Pending { .. } => {
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
LocalTrack::Published {
|
||||
track_publication, ..
|
||||
} => {
|
||||
live_kit.room.unpublish_track(track_publication);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
Audio::play_sound(Sound::StopScreenshare, cx);
|
||||
Ok(())
|
||||
@@ -1622,11 +1569,10 @@ impl Room {
|
||||
fn set_deafened(
|
||||
&mut self,
|
||||
deafened: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let live_kit = self.live_kit.as_mut()?;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
let mut track_updates = Vec::new();
|
||||
for participant in self.remote_participants.values() {
|
||||
@@ -1657,11 +1603,10 @@ impl Room {
|
||||
fn set_mute(
|
||||
&mut self,
|
||||
should_mute: bool,
|
||||
model: &Model<Room>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Room>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let live_kit = self.live_kit.as_mut()?;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
if should_mute {
|
||||
Audio::play_sound(Sound::Mute, cx);
|
||||
@@ -1674,7 +1619,7 @@ impl Room {
|
||||
if should_mute {
|
||||
None
|
||||
} else {
|
||||
Some(self.share_microphone(model, cx))
|
||||
Some(self.share_microphone(cx))
|
||||
}
|
||||
}
|
||||
LocalTrack::Pending { .. } => None,
|
||||
@@ -1709,13 +1654,13 @@ struct LiveKitRoom {
|
||||
}
|
||||
|
||||
impl LiveKitRoom {
|
||||
fn stop_publishing(&mut self, model: &Model<Room>, cx: &mut AppContext) {
|
||||
fn stop_publishing(&mut self, cx: &mut ModelContext<Room>) {
|
||||
if let LocalTrack::Published {
|
||||
track_publication, ..
|
||||
} = mem::replace(&mut self.microphone_track, LocalTrack::None)
|
||||
{
|
||||
self.room.unpublish_track(track_publication);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if let LocalTrack::Published {
|
||||
@@ -1723,7 +1668,7 @@ impl LiveKitRoom {
|
||||
} = mem::replace(&mut self.screen_track, LocalTrack::None)
|
||||
{
|
||||
self.room.unpublish_track(track_publication);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{Channel, ChannelStore};
|
||||
use anyhow::Result;
|
||||
use client::{ChannelId, Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, Task};
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
||||
use language::proto::serialize_version;
|
||||
use rpc::{
|
||||
proto::{self, PeerId},
|
||||
@@ -62,21 +62,17 @@ impl ChannelBuffer {
|
||||
.map(language::proto::deserialize_operation)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let buffer = cx.new_model(|model, cx| {
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let capability = channel_store.read(cx).channel_capability(channel.id);
|
||||
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
|
||||
})?;
|
||||
buffer.update(&mut cx, |buffer, model, cx| {
|
||||
buffer.apply_ops(operations, model, cx)
|
||||
})?;
|
||||
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
||||
|
||||
let subscription = client.subscribe_to_entity(channel.id.0)?;
|
||||
|
||||
anyhow::Ok(cx.new_model(|model, cx| {
|
||||
model
|
||||
.subscribe(&buffer, cx, Self::on_buffer_update)
|
||||
.detach();
|
||||
model.on_release(cx, Self::release).detach();
|
||||
anyhow::Ok(cx.new_model(|cx| {
|
||||
cx.subscribe(&buffer, Self::on_buffer_update).detach();
|
||||
cx.on_release(Self::release).detach();
|
||||
let mut this = Self {
|
||||
buffer,
|
||||
buffer_epoch: response.epoch,
|
||||
@@ -85,11 +81,11 @@ impl ChannelBuffer {
|
||||
collaborators: Default::default(),
|
||||
acknowledge_task: None,
|
||||
channel_id: channel.id,
|
||||
subscription: Some(subscription.set_model(model, &mut cx.to_async())),
|
||||
subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())),
|
||||
user_store,
|
||||
channel_store,
|
||||
};
|
||||
this.replace_collaborators(response.collaborators, model, cx);
|
||||
this.replace_collaborators(response.collaborators, cx);
|
||||
this
|
||||
})?)
|
||||
}
|
||||
@@ -118,8 +114,7 @@ impl ChannelBuffer {
|
||||
pub(crate) fn replace_collaborators(
|
||||
&mut self,
|
||||
collaborators: Vec<proto::Collaborator>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let mut new_collaborators = HashMap::default();
|
||||
for collaborator in collaborators {
|
||||
@@ -130,14 +125,14 @@ impl ChannelBuffer {
|
||||
|
||||
for (_, old_collaborator) in &self.collaborators {
|
||||
if !new_collaborators.contains_key(&old_collaborator.peer_id) {
|
||||
self.buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.remove_peer(old_collaborator.replica_id, model, cx)
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
buffer.remove_peer(old_collaborator.replica_id, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
self.collaborators = new_collaborators;
|
||||
model.emit(ChannelBufferEvent::CollaboratorsChanged, cx);
|
||||
model.notify(cx);
|
||||
cx.emit(ChannelBufferEvent::CollaboratorsChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
async fn handle_update_channel_buffer(
|
||||
@@ -152,10 +147,10 @@ impl ChannelBuffer {
|
||||
.map(language::proto::deserialize_operation)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
model.notify(cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.notify();
|
||||
this.buffer
|
||||
.update(cx, |buffer, model, cx| buffer.apply_ops(ops, model, cx))
|
||||
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
@@ -166,10 +161,10 @@ impl ChannelBuffer {
|
||||
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.replace_collaborators(message.payload.collaborators, model, cx);
|
||||
model.emit(ChannelBufferEvent::CollaboratorsChanged, cx);
|
||||
model.notify(cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.replace_collaborators(message.payload.collaborators, cx);
|
||||
cx.emit(ChannelBufferEvent::CollaboratorsChanged);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -177,8 +172,7 @@ impl ChannelBuffer {
|
||||
&mut self,
|
||||
_: Model<language::Buffer>,
|
||||
event: &language::BufferEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
language::BufferEvent::Operation {
|
||||
@@ -201,20 +195,20 @@ impl ChannelBuffer {
|
||||
.log_err();
|
||||
}
|
||||
language::BufferEvent::Edited => {
|
||||
model.emit(ChannelBufferEvent::BufferEdited, cx);
|
||||
cx.emit(ChannelBufferEvent::BufferEdited);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn acknowledge_buffer_version(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let version = buffer.version();
|
||||
let buffer_id = buffer.remote_id().into();
|
||||
let client = self.client.clone();
|
||||
let epoch = self.epoch();
|
||||
|
||||
self.acknowledge_task = Some(cx.spawn(move |cx| async move {
|
||||
self.acknowledge_task = Some(cx.spawn(move |_, cx| async move {
|
||||
cx.background_executor()
|
||||
.timer(ACKNOWLEDGE_DEBOUNCE_INTERVAL)
|
||||
.await;
|
||||
@@ -248,19 +242,19 @@ impl ChannelBuffer {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(crate) fn disconnect(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) {
|
||||
log::info!("channel buffer {} disconnected", self.channel_id);
|
||||
if self.connected {
|
||||
self.connected = false;
|
||||
self.subscription.take();
|
||||
model.emit(ChannelBufferEvent::Disconnected, cx);
|
||||
model.notify(cx)
|
||||
cx.emit(ChannelBufferEvent::Disconnected);
|
||||
cx.notify()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn channel_changed(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
model.emit(ChannelBufferEvent::ChannelChanged, cx);
|
||||
model.notify(cx)
|
||||
pub(crate) fn channel_changed(&mut self, cx: &mut ModelContext<Self>) {
|
||||
cx.emit(ChannelBufferEvent::ChannelChanged);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
|
||||
@@ -7,7 +7,9 @@ use client::{
|
||||
};
|
||||
use collections::HashSet;
|
||||
use futures::lock::Mutex;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, Task, WeakModel};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::AnyProtoClient;
|
||||
use std::{
|
||||
@@ -117,8 +119,8 @@ impl ChannelChat {
|
||||
})
|
||||
.await?;
|
||||
|
||||
let handle = cx.new_model(|model, cx| {
|
||||
model.on_release(cx, Self::release).detach();
|
||||
let handle = cx.new_model(|cx| {
|
||||
cx.on_release(Self::release).detach();
|
||||
Self {
|
||||
channel_id: channel.id,
|
||||
user_store: user_store.clone(),
|
||||
@@ -132,7 +134,7 @@ impl ChannelChat {
|
||||
last_acknowledged_id: None,
|
||||
rng: StdRng::from_entropy(),
|
||||
first_loaded_message_id: None,
|
||||
_subscription: subscription.set_model(model, &mut cx.to_async()),
|
||||
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
|
||||
}
|
||||
})?;
|
||||
Self::handle_loaded_messages(
|
||||
@@ -169,8 +171,7 @@ impl ChannelChat {
|
||||
pub fn send_message(
|
||||
&mut self,
|
||||
message: MessageParams,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<Task<Result<u64>>> {
|
||||
if message.text.trim().is_empty() {
|
||||
Err(anyhow!("message body can't be empty"))?;
|
||||
@@ -199,7 +200,6 @@ impl ChannelChat {
|
||||
},
|
||||
&(),
|
||||
),
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
let user_store = self.user_store.clone();
|
||||
@@ -207,7 +207,7 @@ impl ChannelChat {
|
||||
let outgoing_messages_lock = self.outgoing_messages_lock.clone();
|
||||
|
||||
// todo - handle messages that fail to send (e.g. >1024 chars)
|
||||
Ok(model.spawn(cx, move |this, mut cx| async move {
|
||||
Ok(cx.spawn(move |this, mut cx| async move {
|
||||
let outgoing_message_guard = outgoing_messages_lock.lock().await;
|
||||
let request = rpc.request(proto::SendChannelMessage {
|
||||
channel_id: channel_id.0,
|
||||
@@ -221,8 +221,8 @@ impl ChannelChat {
|
||||
let response = response.message.ok_or_else(|| anyhow!("invalid message"))?;
|
||||
let id = response.id;
|
||||
let message = ChannelMessage::from_proto(response, &user_store, &mut cx).await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.insert_messages(SumTree::from_item(message, &()), model, cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.insert_messages(SumTree::from_item(message, &()), cx);
|
||||
if this.first_loaded_message_id.is_none() {
|
||||
this.first_loaded_message_id = Some(id);
|
||||
}
|
||||
@@ -231,20 +231,15 @@ impl ChannelChat {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn remove_message(
|
||||
&mut self,
|
||||
id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let response = self.rpc.request(proto::RemoveChannelMessage {
|
||||
channel_id: self.channel_id.0,
|
||||
message_id: id,
|
||||
});
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
response.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.message_removed(id, model, cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.message_removed(id, cx);
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -254,15 +249,13 @@ impl ChannelChat {
|
||||
&mut self,
|
||||
id: u64,
|
||||
message: MessageParams,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<Task<Result<()>>> {
|
||||
self.message_update(
|
||||
ChannelMessageId::Saved(id),
|
||||
message.text.clone(),
|
||||
message.mentions.clone(),
|
||||
Some(OffsetDateTime::now_utc()),
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -275,17 +268,13 @@ impl ChannelChat {
|
||||
nonce: Some(nonce.into()),
|
||||
mentions: mentions_to_proto(&message.mentions),
|
||||
});
|
||||
Ok(cx.spawn(move |_| async move {
|
||||
Ok(cx.spawn(move |_, _| async move {
|
||||
request.await?;
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn load_more_messages(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<Task<Option<()>>> {
|
||||
pub fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<Option<()>>> {
|
||||
if self.loaded_all_messages {
|
||||
return None;
|
||||
}
|
||||
@@ -294,7 +283,7 @@ impl ChannelChat {
|
||||
let user_store = self.user_store.clone();
|
||||
let channel_id = self.channel_id;
|
||||
let before_message_id = self.first_loaded_message_id()?;
|
||||
Some(model.spawn(cx, move |this, mut cx| {
|
||||
Some(cx.spawn(move |this, mut cx| {
|
||||
async move {
|
||||
let response = rpc
|
||||
.request(proto::GetChannelMessages {
|
||||
@@ -340,7 +329,7 @@ impl ChannelChat {
|
||||
) -> Option<usize> {
|
||||
loop {
|
||||
let step = chat
|
||||
.update(&mut cx, |chat, model, cx| {
|
||||
.update(&mut cx, |chat, cx| {
|
||||
if let Some(first_id) = chat.first_loaded_message_id() {
|
||||
if first_id <= message_id {
|
||||
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>(&());
|
||||
@@ -358,7 +347,7 @@ impl ChannelChat {
|
||||
);
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(chat.load_more_messages(model, cx))
|
||||
ControlFlow::Continue(chat.load_more_messages(cx))
|
||||
})
|
||||
.log_err()?;
|
||||
match step {
|
||||
@@ -368,7 +357,7 @@ impl ChannelChat {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn acknowledge_last_message(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn acknowledge_last_message(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let ChannelMessageId::Saved(latest_message_id) = self.messages.summary().max_id {
|
||||
if self
|
||||
.last_acknowledged_id
|
||||
@@ -381,8 +370,8 @@ impl ChannelChat {
|
||||
})
|
||||
.ok();
|
||||
self.last_acknowledged_id = Some(latest_message_id);
|
||||
self.channel_store.update(cx, |store, model, cx| {
|
||||
store.acknowledge_message_id(self.channel_id, latest_message_id, model, cx);
|
||||
self.channel_store.update(cx, |store, cx| {
|
||||
store.acknowledge_message_id(self.channel_id, latest_message_id, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -399,7 +388,7 @@ impl ChannelChat {
|
||||
let loaded_messages = messages_from_proto(proto_messages, &user_store, cx).await?;
|
||||
|
||||
let first_loaded_message_id = loaded_messages.first().map(|m| m.id);
|
||||
let loaded_message_ids = this.update(cx, |this, model, _| {
|
||||
let loaded_message_ids = this.update(cx, |this, _| {
|
||||
let mut loaded_message_ids: HashSet<u64> = HashSet::default();
|
||||
for message in loaded_messages.iter() {
|
||||
if let Some(saved_message_id) = message.id.into() {
|
||||
@@ -436,69 +425,68 @@ impl ChannelChat {
|
||||
.await?;
|
||||
Some(messages_from_proto(response.messages, &user_store, cx).await?)
|
||||
};
|
||||
this.update(cx, |this, model, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.first_loaded_message_id = first_loaded_message_id.and_then(|msg_id| msg_id.into());
|
||||
this.loaded_all_messages = loaded_all_messages;
|
||||
this.insert_messages(loaded_messages, model, cx);
|
||||
this.insert_messages(loaded_messages, cx);
|
||||
if let Some(loaded_ancestors) = loaded_ancestors {
|
||||
this.insert_messages(loaded_ancestors, model, cx);
|
||||
this.insert_messages(loaded_ancestors, cx);
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rejoin(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn rejoin(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let user_store = self.user_store.clone();
|
||||
let rpc = self.rpc.clone();
|
||||
let channel_id = self.channel_id;
|
||||
model
|
||||
.spawn(cx, move |this, mut cx| {
|
||||
async move {
|
||||
let response = rpc
|
||||
.request(proto::JoinChannelChat {
|
||||
channel_id: channel_id.0,
|
||||
})
|
||||
.await?;
|
||||
Self::handle_loaded_messages(
|
||||
this.clone(),
|
||||
user_store.clone(),
|
||||
rpc.clone(),
|
||||
response.messages,
|
||||
response.done,
|
||||
cx.spawn(move |this, mut cx| {
|
||||
async move {
|
||||
let response = rpc
|
||||
.request(proto::JoinChannelChat {
|
||||
channel_id: channel_id.0,
|
||||
})
|
||||
.await?;
|
||||
Self::handle_loaded_messages(
|
||||
this.clone(),
|
||||
user_store.clone(),
|
||||
rpc.clone(),
|
||||
response.messages,
|
||||
response.done,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let pending_messages = this.update(&mut cx, |this, _| {
|
||||
this.pending_messages().cloned().collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
for pending_message in pending_messages {
|
||||
let request = rpc.request(proto::SendChannelMessage {
|
||||
channel_id: channel_id.0,
|
||||
body: pending_message.body,
|
||||
mentions: mentions_to_proto(&pending_message.mentions),
|
||||
nonce: Some(pending_message.nonce.into()),
|
||||
reply_to_message_id: pending_message.reply_to_message_id,
|
||||
});
|
||||
let response = request.await?;
|
||||
let message = ChannelMessage::from_proto(
|
||||
response.message.ok_or_else(|| anyhow!("invalid message"))?,
|
||||
&user_store,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let pending_messages = this.update(&mut cx, |this, _, _| {
|
||||
this.pending_messages().cloned().collect::<Vec<_>>()
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.insert_messages(SumTree::from_item(message, &()), cx);
|
||||
})?;
|
||||
|
||||
for pending_message in pending_messages {
|
||||
let request = rpc.request(proto::SendChannelMessage {
|
||||
channel_id: channel_id.0,
|
||||
body: pending_message.body,
|
||||
mentions: mentions_to_proto(&pending_message.mentions),
|
||||
nonce: Some(pending_message.nonce.into()),
|
||||
reply_to_message_id: pending_message.reply_to_message_id,
|
||||
});
|
||||
let response = request.await?;
|
||||
let message = ChannelMessage::from_proto(
|
||||
response.message.ok_or_else(|| anyhow!("invalid message"))?,
|
||||
&user_store,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.insert_messages(SumTree::from_item(message, &()), model, cx);
|
||||
})?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
})
|
||||
.detach();
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn message_count(&self) -> usize {
|
||||
@@ -543,7 +531,7 @@ impl ChannelChat {
|
||||
message: TypedEnvelope<proto::ChannelMessageSent>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let user_store = this.update(&mut cx, |this, _, _| this.user_store.clone())?;
|
||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||
let message = message
|
||||
.payload
|
||||
.message
|
||||
@@ -551,15 +539,12 @@ impl ChannelChat {
|
||||
let message_id = message.id;
|
||||
|
||||
let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.insert_messages(SumTree::from_item(message, &()), model, cx);
|
||||
model.emit(
|
||||
ChannelChatEvent::NewMessage {
|
||||
channel_id: this.channel_id,
|
||||
message_id,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.insert_messages(SumTree::from_item(message, &()), cx);
|
||||
cx.emit(ChannelChatEvent::NewMessage {
|
||||
channel_id: this.channel_id,
|
||||
message_id,
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
@@ -570,8 +555,8 @@ impl ChannelChat {
|
||||
message: TypedEnvelope<proto::RemoveChannelMessage>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.message_removed(message.payload.message_id, model, cx)
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.message_removed(message.payload.message_id, cx)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -581,7 +566,7 @@ impl ChannelChat {
|
||||
message: TypedEnvelope<proto::ChannelMessageUpdate>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let user_store = this.update(&mut cx, |this, _, _| this.user_store.clone())?;
|
||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||
let message = message
|
||||
.payload
|
||||
.message
|
||||
@@ -589,25 +574,19 @@ impl ChannelChat {
|
||||
|
||||
let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.message_update(
|
||||
message.id,
|
||||
message.body,
|
||||
message.mentions,
|
||||
message.edited_at,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_messages(
|
||||
&mut self,
|
||||
messages: SumTree<ChannelMessage>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
|
||||
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
|
||||
let nonces = messages
|
||||
.cursor::<()>(&())
|
||||
@@ -652,27 +631,21 @@ impl ChannelChat {
|
||||
self.messages = new_messages;
|
||||
|
||||
for range in ranges.into_iter().rev() {
|
||||
model.emit(
|
||||
ChannelChatEvent::MessagesUpdated {
|
||||
old_range: range,
|
||||
new_count: 0,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(ChannelChatEvent::MessagesUpdated {
|
||||
old_range: range,
|
||||
new_count: 0,
|
||||
});
|
||||
}
|
||||
model.emit(
|
||||
ChannelChatEvent::MessagesUpdated {
|
||||
old_range: start_ix..end_ix,
|
||||
new_count,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(ChannelChatEvent::MessagesUpdated {
|
||||
old_range: start_ix..end_ix,
|
||||
new_count,
|
||||
});
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn message_removed(&mut self, id: u64, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn message_removed(&mut self, id: u64, cx: &mut ModelContext<Self>) {
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
|
||||
let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
|
||||
if let Some(item) = cursor.item() {
|
||||
@@ -685,7 +658,7 @@ impl ChannelChat {
|
||||
|
||||
// If the message that was deleted was the last acknowledged message,
|
||||
// replace the acknowledged message with an earlier one.
|
||||
self.channel_store.update(cx, |store, model, _| {
|
||||
self.channel_store.update(cx, |store, _| {
|
||||
let summary = self.messages.summary();
|
||||
if summary.count == 0 {
|
||||
store.set_acknowledged_message_id(self.channel_id, None);
|
||||
@@ -696,13 +669,10 @@ impl ChannelChat {
|
||||
}
|
||||
});
|
||||
|
||||
model.emit(
|
||||
ChannelChatEvent::MessagesUpdated {
|
||||
old_range: deleted_message_ix..deleted_message_ix + 1,
|
||||
new_count: 0,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(ChannelChatEvent::MessagesUpdated {
|
||||
old_range: deleted_message_ix..deleted_message_ix + 1,
|
||||
new_count: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -713,8 +683,7 @@ impl ChannelChat {
|
||||
body: String,
|
||||
mentions: Vec<(Range<usize>, u64)>,
|
||||
edited_at: Option<OffsetDateTime>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
|
||||
let mut messages = cursor.slice(&id, Bias::Left, &());
|
||||
@@ -732,15 +701,12 @@ impl ChannelChat {
|
||||
drop(cursor);
|
||||
self.messages = messages;
|
||||
|
||||
model.emit(
|
||||
ChannelChatEvent::UpdateMessage {
|
||||
message_ix: ix,
|
||||
message_id: id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(ChannelChatEvent::UpdateMessage {
|
||||
message_ix: ix,
|
||||
message_id: id,
|
||||
});
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,8 +728,8 @@ impl ChannelMessage {
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
let sender = user_store
|
||||
.update(cx, |user_store, model, cx| {
|
||||
user_store.get_user(message.sender_id, model, cx)
|
||||
.update(cx, |user_store, cx| {
|
||||
user_store.get_user(message.sender_id, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
@@ -813,8 +779,8 @@ impl ChannelMessage {
|
||||
.into_iter()
|
||||
.collect();
|
||||
user_store
|
||||
.update(cx, |user_store, model, cx| {
|
||||
user_store.get_users(unique_user_ids, model, cx)
|
||||
.update(cx, |user_store, cx| {
|
||||
user_store.get_users(unique_user_ids, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, User
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, SharedString, Task,
|
||||
WeakModel,
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, SharedString,
|
||||
Task, WeakModel,
|
||||
};
|
||||
use language::Capability;
|
||||
use rpc::{
|
||||
@@ -23,7 +23,7 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||
let channel_store =
|
||||
cx.new_model(|model, cx| ChannelStore::new(client.clone(), user_store.clone(), model, cx));
|
||||
cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
|
||||
cx.set_global(GlobalChannelStore(channel_store));
|
||||
}
|
||||
|
||||
@@ -160,37 +160,32 @@ impl ChannelStore {
|
||||
pub fn new(
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let rpc_subscriptions = [
|
||||
client.add_message_handler(model.downgrade(), Self::handle_update_channels),
|
||||
client.add_message_handler(model.downgrade(), Self::handle_update_user_channels),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_update_channels),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_update_user_channels),
|
||||
];
|
||||
|
||||
let mut connection_status = client.status();
|
||||
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
|
||||
let watch_connection_status = model.spawn(cx, |this, mut cx| async move {
|
||||
let watch_connection_status = cx.spawn(|this, mut cx| async move {
|
||||
while let Some(status) = connection_status.next().await {
|
||||
let this = this.upgrade()?;
|
||||
match status {
|
||||
client::Status::Connected { .. } => {
|
||||
this.update(&mut cx, |this, model, cx| this.handle_connect(model, cx))
|
||||
this.update(&mut cx, |this, cx| this.handle_connect(cx))
|
||||
.ok()?
|
||||
.await
|
||||
.log_err()?;
|
||||
}
|
||||
client::Status::SignedOut | client::Status::UpgradeRequired => {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.handle_disconnect(false, model, cx)
|
||||
})
|
||||
.ok();
|
||||
this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx))
|
||||
.ok();
|
||||
}
|
||||
_ => {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.handle_disconnect(true, model, cx)
|
||||
})
|
||||
.ok();
|
||||
this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,12 +205,12 @@ impl ChannelStore {
|
||||
_rpc_subscriptions: rpc_subscriptions,
|
||||
_watch_connection_status: watch_connection_status,
|
||||
disconnect_channel_buffers_task: None,
|
||||
_update_channels: model.spawn(cx, |this, mut cx| async move {
|
||||
_update_channels: cx.spawn(|this, mut cx| async move {
|
||||
maybe!(async move {
|
||||
while let Some(update_channels) = update_channels_rx.next().await {
|
||||
if let Some(this) = this.upgrade() {
|
||||
let update_task = this.update(&mut cx, |this, model, cx| {
|
||||
this.update_channels(update_channels, model, cx)
|
||||
let update_task = this.update(&mut cx, |this, cx| {
|
||||
this.update_channels(update_channels, cx)
|
||||
})?;
|
||||
if let Some(update_task) = update_task {
|
||||
update_task.await.log_err();
|
||||
@@ -312,17 +307,15 @@ impl ChannelStore {
|
||||
pub fn open_channel_buffer(
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<ChannelBuffer>>> {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
let channel_store = model.clone();
|
||||
let channel_store = cx.handle();
|
||||
self.open_channel_resource(
|
||||
channel_id,
|
||||
|this| &mut this.opened_buffers,
|
||||
|channel, cx| ChannelBuffer::new(channel, client, user_store, channel_store, cx),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -330,8 +323,7 @@ impl ChannelStore {
|
||||
pub fn fetch_channel_messages(
|
||||
&self,
|
||||
message_ids: Vec<u64>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<ChannelMessage>>> {
|
||||
let request = if message_ids.is_empty() {
|
||||
None
|
||||
@@ -341,13 +333,13 @@ impl ChannelStore {
|
||||
.request(proto::GetChannelMessagesById { message_ids }),
|
||||
)
|
||||
};
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some(request) = request {
|
||||
let response = request.await?;
|
||||
let this = this
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("channel store dropped"))?;
|
||||
let user_store = this.update(&mut cx, |this, _, _| this.user_store.clone())?;
|
||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||
ChannelMessage::from_proto_vec(response.messages, &user_store, &mut cx).await
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
@@ -392,28 +384,26 @@ impl ChannelStore {
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
message_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_default()
|
||||
.acknowledge_message_id(message_id);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn update_latest_message_id(
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
message_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_default()
|
||||
.update_latest_message_id(message_id);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn acknowledge_notes_version(
|
||||
@@ -421,14 +411,13 @@ impl ChannelStore {
|
||||
channel_id: ChannelId,
|
||||
epoch: u64,
|
||||
version: &clock::Global,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_default()
|
||||
.acknowledge_notes_version(epoch, version);
|
||||
model.notify(cx)
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub fn update_latest_notes_version(
|
||||
@@ -436,30 +425,27 @@ impl ChannelStore {
|
||||
channel_id: ChannelId,
|
||||
epoch: u64,
|
||||
version: &clock::Global,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_default()
|
||||
.update_latest_notes_version(epoch, version);
|
||||
model.notify(cx)
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub fn open_channel_chat(
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<ChannelChat>>> {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
let this = model.clone();
|
||||
let this = cx.handle();
|
||||
self.open_channel_resource(
|
||||
channel_id,
|
||||
|this| &mut this.opened_chats,
|
||||
|channel, cx| ChannelChat::new(channel, this, user_store, client, cx),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -474,8 +460,7 @@ impl ChannelStore {
|
||||
channel_id: ChannelId,
|
||||
get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
|
||||
load: F,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<T>>>
|
||||
where
|
||||
F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
|
||||
@@ -498,9 +483,9 @@ impl ChannelStore {
|
||||
}
|
||||
},
|
||||
hash_map::Entry::Vacant(e) => {
|
||||
let task = model
|
||||
.spawn(cx, move |this, mut cx| async move {
|
||||
let channel = this.update(&mut cx, |this, _, _| {
|
||||
let task = cx
|
||||
.spawn(move |this, mut cx| async move {
|
||||
let channel = this.update(&mut cx, |this, _| {
|
||||
this.channel_for_id(channel_id).cloned().ok_or_else(|| {
|
||||
Arc::new(anyhow!("no channel for id: {}", channel_id))
|
||||
})
|
||||
@@ -511,26 +496,25 @@ impl ChannelStore {
|
||||
.shared();
|
||||
|
||||
e.insert(OpenedModelHandle::Loading(task.clone()));
|
||||
model
|
||||
.spawn(cx, {
|
||||
let task = task.clone();
|
||||
move |this, mut cx| async move {
|
||||
let result = task.await;
|
||||
this.update(&mut cx, |this, _, _| match result {
|
||||
Ok(model) => {
|
||||
get_map(this).insert(
|
||||
channel_id,
|
||||
OpenedModelHandle::Open(model.downgrade()),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
get_map(this).remove(&channel_id);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
cx.spawn({
|
||||
let task = task.clone();
|
||||
move |this, mut cx| async move {
|
||||
let result = task.await;
|
||||
this.update(&mut cx, |this, _| match result {
|
||||
Ok(model) => {
|
||||
get_map(this).insert(
|
||||
channel_id,
|
||||
OpenedModelHandle::Open(model.downgrade()),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
get_map(this).remove(&channel_id);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
break task;
|
||||
}
|
||||
}
|
||||
@@ -588,12 +572,11 @@ impl ChannelStore {
|
||||
&self,
|
||||
name: &str,
|
||||
parent_id: Option<ChannelId>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<ChannelId>> {
|
||||
let client = self.client.clone();
|
||||
let name = name.trim_start_matches('#').to_owned();
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let response = client
|
||||
.request(proto::CreateChannel {
|
||||
name,
|
||||
@@ -606,13 +589,12 @@ impl ChannelStore {
|
||||
.ok_or_else(|| anyhow!("missing channel in response"))?;
|
||||
let channel_id = ChannelId(channel.id);
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let task = this.update_channels(
|
||||
proto::UpdateChannels {
|
||||
channels: vec![channel],
|
||||
..Default::default()
|
||||
},
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
assert!(task.is_none());
|
||||
@@ -621,7 +603,7 @@ impl ChannelStore {
|
||||
// before this frame is rendered. But we can't guarantee that the collab panel's future
|
||||
// will resolve before this flush_effects finishes. Synchronously emitting this event
|
||||
// ensures that the collab panel will observe this creation before the frame completes
|
||||
model.emit(ChannelEvent::ChannelCreated(channel_id), cx);
|
||||
cx.emit(ChannelEvent::ChannelCreated(channel_id));
|
||||
})?;
|
||||
|
||||
Ok(channel_id)
|
||||
@@ -632,11 +614,10 @@ impl ChannelStore {
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
to: ChannelId,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
cx.spawn(move |_| async move {
|
||||
cx.spawn(move |_, _| async move {
|
||||
let _ = client
|
||||
.request(proto::MoveChannel {
|
||||
channel_id: channel_id.0,
|
||||
@@ -652,11 +633,10 @@ impl ChannelStore {
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
visibility: ChannelVisibility,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
cx.spawn(move |_| async move {
|
||||
cx.spawn(move |_, _| async move {
|
||||
let _ = client
|
||||
.request(proto::SetChannelVisibility {
|
||||
channel_id: channel_id.0,
|
||||
@@ -673,16 +653,15 @@ impl ChannelStore {
|
||||
channel_id: ChannelId,
|
||||
user_id: UserId,
|
||||
role: proto::ChannelRole,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if !self.outgoing_invites.insert((channel_id, user_id)) {
|
||||
return Task::ready(Err(anyhow!("invite request already in progress")));
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
let client = self.client.clone();
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::InviteChannelMember {
|
||||
channel_id: channel_id.0,
|
||||
@@ -691,9 +670,9 @@ impl ChannelStore {
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.outgoing_invites.remove(&(channel_id, user_id));
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
result?;
|
||||
@@ -706,16 +685,15 @@ impl ChannelStore {
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
user_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if !self.outgoing_invites.insert((channel_id, user_id)) {
|
||||
return Task::ready(Err(anyhow!("invite request already in progress")));
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
let client = self.client.clone();
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::RemoveChannelMember {
|
||||
channel_id: channel_id.0,
|
||||
@@ -723,9 +701,9 @@ impl ChannelStore {
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.outgoing_invites.remove(&(channel_id, user_id));
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
result?;
|
||||
Ok(())
|
||||
@@ -737,16 +715,15 @@ impl ChannelStore {
|
||||
channel_id: ChannelId,
|
||||
user_id: UserId,
|
||||
role: proto::ChannelRole,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if !self.outgoing_invites.insert((channel_id, user_id)) {
|
||||
return Task::ready(Err(anyhow!("member request already in progress")));
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
let client = self.client.clone();
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::SetChannelMemberRole {
|
||||
channel_id: channel_id.0,
|
||||
@@ -755,9 +732,9 @@ impl ChannelStore {
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.outgoing_invites.remove(&(channel_id, user_id));
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
result?;
|
||||
@@ -769,12 +746,11 @@ impl ChannelStore {
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
new_name: &str,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
let name = new_name.to_string();
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let channel = client
|
||||
.request(proto::RenameChannel {
|
||||
channel_id: channel_id.0,
|
||||
@@ -783,13 +759,12 @@ impl ChannelStore {
|
||||
.await?
|
||||
.channel
|
||||
.ok_or_else(|| anyhow!("missing channel in response"))?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let task = this.update_channels(
|
||||
proto::UpdateChannels {
|
||||
channels: vec![channel],
|
||||
..Default::default()
|
||||
},
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
assert!(task.is_none());
|
||||
@@ -798,7 +773,7 @@ impl ChannelStore {
|
||||
// before this frame is rendered. But we can't guarantee that the collab panel's future
|
||||
// will resolve before this flush_effects finishes. Synchronously emitting this event
|
||||
// ensures that the collab panel will observe this creation before the frame complete
|
||||
model.emit(ChannelEvent::ChannelRenamed(channel_id), cx)
|
||||
cx.emit(ChannelEvent::ChannelRenamed(channel_id))
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -808,8 +783,7 @@ impl ChannelStore {
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
accept: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
@@ -827,12 +801,11 @@ impl ChannelStore {
|
||||
channel_id: ChannelId,
|
||||
query: String,
|
||||
limit: u16,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<ChannelMembership>>> {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.downgrade();
|
||||
cx.spawn(move |mut cx| async move {
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let response = client
|
||||
.request(proto::GetChannelMembers {
|
||||
channel_id: channel_id.0,
|
||||
@@ -840,7 +813,7 @@ impl ChannelStore {
|
||||
limit: limit as u64,
|
||||
})
|
||||
.await?;
|
||||
user_store.update(&mut cx, |user_store, model, _| {
|
||||
user_store.update(&mut cx, |user_store, _| {
|
||||
user_store.insert(response.users);
|
||||
response
|
||||
.members
|
||||
@@ -882,7 +855,7 @@ impl ChannelStore {
|
||||
message: TypedEnvelope<proto::UpdateChannels>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.update_channels_tx
|
||||
.unbounded_send(message.payload)
|
||||
.unwrap();
|
||||
@@ -895,14 +868,13 @@ impl ChannelStore {
|
||||
message: TypedEnvelope<proto::UpdateUserChannels>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for buffer_version in message.payload.observed_channel_buffer_version {
|
||||
let version = language::proto::deserialize_version(&buffer_version.version);
|
||||
this.acknowledge_notes_version(
|
||||
ChannelId(buffer_version.channel_id),
|
||||
buffer_version.epoch,
|
||||
&version,
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -910,7 +882,6 @@ impl ChannelStore {
|
||||
this.acknowledge_message_id(
|
||||
ChannelId(message_id.channel_id),
|
||||
message_id.message_id,
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -925,7 +896,7 @@ impl ChannelStore {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_connect(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
self.channel_index.clear();
|
||||
self.channel_invitations.clear();
|
||||
self.channel_participants.clear();
|
||||
@@ -936,8 +907,8 @@ impl ChannelStore {
|
||||
for chat in self.opened_chats.values() {
|
||||
if let OpenedModelHandle::Open(chat) = chat {
|
||||
if let Some(chat) = chat.upgrade() {
|
||||
chat.update(cx, |chat, model, cx| {
|
||||
chat.rejoin(model, cx);
|
||||
chat.update(cx, |chat, cx| {
|
||||
chat.rejoin(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -966,17 +937,17 @@ impl ChannelStore {
|
||||
buffers: buffer_versions,
|
||||
});
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let mut response = response.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.opened_buffers.retain(|_, buffer| match buffer {
|
||||
OpenedModelHandle::Open(channel_buffer) => {
|
||||
let Some(channel_buffer) = channel_buffer.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
channel_buffer.update(cx, |channel_buffer, model, cx| {
|
||||
channel_buffer.update(cx, |channel_buffer, cx| {
|
||||
let channel_id = channel_buffer.channel_id;
|
||||
if let Some(remote_buffer) = response
|
||||
.buffers
|
||||
@@ -989,13 +960,12 @@ impl ChannelStore {
|
||||
|
||||
channel_buffer.replace_collaborators(
|
||||
mem::take(&mut remote_buffer.collaborators),
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
|
||||
let operations = channel_buffer
|
||||
.buffer()
|
||||
.update(cx, |buffer, model, cx| {
|
||||
.update(cx, |buffer, cx| {
|
||||
let outgoing_operations =
|
||||
buffer.serialize_ops(Some(remote_version), cx);
|
||||
let incoming_operations =
|
||||
@@ -1003,7 +973,7 @@ impl ChannelStore {
|
||||
.into_iter()
|
||||
.map(language::proto::deserialize_operation)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
buffer.apply_ops(incoming_operations, model, cx);
|
||||
buffer.apply_ops(incoming_operations, cx);
|
||||
anyhow::Ok(outgoing_operations)
|
||||
})
|
||||
.log_err();
|
||||
@@ -1029,7 +999,7 @@ impl ChannelStore {
|
||||
}
|
||||
}
|
||||
|
||||
channel_buffer.disconnect(model, cx);
|
||||
channel_buffer.disconnect(cx);
|
||||
false
|
||||
})
|
||||
}
|
||||
@@ -1041,28 +1011,21 @@ impl ChannelStore {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_disconnect(
|
||||
&mut self,
|
||||
wait_for_reconnect: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
model.notify(cx);
|
||||
fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext<Self>) {
|
||||
cx.notify();
|
||||
self.did_subscribe = false;
|
||||
self.disconnect_channel_buffers_task.get_or_insert_with(|| {
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if wait_for_reconnect {
|
||||
cx.background_executor().timer(RECONNECT_TIMEOUT).await;
|
||||
}
|
||||
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for (_, buffer) in this.opened_buffers.drain() {
|
||||
if let OpenedModelHandle::Open(buffer) = buffer {
|
||||
if let Some(buffer) = buffer.upgrade() {
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.disconnect(model, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1076,8 +1039,7 @@ impl ChannelStore {
|
||||
pub(crate) fn update_channels(
|
||||
&mut self,
|
||||
payload: proto::UpdateChannels,
|
||||
model: &Model<ChannelStore>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<ChannelStore>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if !payload.remove_channel_invitations.is_empty() {
|
||||
self.channel_invitations
|
||||
@@ -1165,7 +1127,7 @@ impl ChannelStore {
|
||||
}
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
if payload.channel_participants.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -1180,13 +1142,13 @@ impl ChannelStore {
|
||||
}
|
||||
}
|
||||
|
||||
let users = self.user_store.update(cx, |user_store, model, cx| {
|
||||
user_store.get_users(all_user_ids, model, cx)
|
||||
});
|
||||
Some(model.spawn(cx, |this, mut cx| async move {
|
||||
let users = self
|
||||
.user_store
|
||||
.update(cx, |user_store, cx| user_store.get_users(all_user_ids, cx));
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
let users = users.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for entry in &channel_participants {
|
||||
let mut participants: Vec<_> = entry
|
||||
.participant_user_ids
|
||||
@@ -1205,7 +1167,7 @@ impl ChannelStore {
|
||||
.insert(ChannelId(entry.channel_id), participants);
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
let user_id = 5;
|
||||
let channel_id = 5;
|
||||
let channel_store = cx.update(init_test);
|
||||
let client = channel_store.update(cx, |s, model, _| s.client());
|
||||
let client = channel_store.update(cx, |s, _| s.client());
|
||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||
|
||||
// Get the available channels.
|
||||
@@ -169,9 +169,9 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
// Join a channel and populate its existing messages.
|
||||
let channel = channel_store.update(cx, |store, model, cx| {
|
||||
let channel = channel_store.update(cx, |store, cx| {
|
||||
let channel_id = store.ordered_channels().next().unwrap().1.id;
|
||||
store.open_channel_chat(channel_id, model, cx)
|
||||
store.open_channel_chat(channel_id, cx)
|
||||
});
|
||||
let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
|
||||
server.respond(
|
||||
@@ -221,7 +221,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
let channel = channel.await.unwrap();
|
||||
channel.update(cx, |channel, model, _| {
|
||||
channel.update(cx, |channel, _| {
|
||||
assert_eq!(
|
||||
channel
|
||||
.messages_in_range(0..2)
|
||||
@@ -270,7 +270,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
new_count: 1,
|
||||
}
|
||||
);
|
||||
channel.update(cx, |channel, model, _| {
|
||||
channel.update(cx, |channel, _| {
|
||||
assert_eq!(
|
||||
channel
|
||||
.messages_in_range(2..3)
|
||||
@@ -281,8 +281,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
// Scroll up to view older messages.
|
||||
channel.update(cx, |channel, model, cx| {
|
||||
channel.load_more_messages(model, cx).unwrap().detach();
|
||||
channel.update(cx, |channel, cx| {
|
||||
channel.load_more_messages(cx).unwrap().detach();
|
||||
});
|
||||
let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
|
||||
assert_eq!(get_messages.payload.channel_id, 5);
|
||||
@@ -323,7 +323,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
new_count: 2,
|
||||
}
|
||||
);
|
||||
channel.update(cx, |channel, model, _| {
|
||||
channel.update(cx, |channel, _| {
|
||||
assert_eq!(
|
||||
channel
|
||||
.messages_in_range(0..2)
|
||||
@@ -346,7 +346,7 @@ fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
let client = Client::new(clock, http.clone(), cx);
|
||||
let user_store = cx.new_model(|model, cx| UserStore::new(client.clone(), model, cx));
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||
|
||||
client::init(&client, cx);
|
||||
crate::init(&client, user_store, cx);
|
||||
@@ -359,9 +359,7 @@ fn update_channels(
|
||||
message: proto::UpdateChannels,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let task = channel_store.update(cx, |store, model, cx| {
|
||||
store.update_channels(message, model, cx)
|
||||
});
|
||||
let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
|
||||
assert!(task.is_none());
|
||||
}
|
||||
|
||||
@@ -371,7 +369,7 @@ fn assert_channels(
|
||||
expected_channels: &[(usize, String)],
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let actual = channel_store.update(cx, |store, model, _| {
|
||||
let actual = channel_store.update(cx, |store, _| {
|
||||
store
|
||||
.ordered_channels()
|
||||
.map(|(depth, channel)| (depth, channel.name.to_string()))
|
||||
|
||||
@@ -1962,7 +1962,7 @@ mod tests {
|
||||
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
||||
AnyProtoClient::from(client.clone()).add_model_message_handler(
|
||||
move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, mut cx| {
|
||||
match model.update(&mut cx, |model, _, _| model.id).unwrap() {
|
||||
match model.update(&mut cx, |model, _| model.id).unwrap() {
|
||||
1 => done_tx1.try_send(()).unwrap(),
|
||||
2 => done_tx2.try_send(()).unwrap(),
|
||||
_ => unreachable!(),
|
||||
@@ -1970,15 +1970,15 @@ mod tests {
|
||||
async { Ok(()) }
|
||||
},
|
||||
);
|
||||
let model1 = cx.new_model(|_, _| TestModel {
|
||||
let model1 = cx.new_model(|_| TestModel {
|
||||
id: 1,
|
||||
subscription: None,
|
||||
});
|
||||
let model2 = cx.new_model(|_, _| TestModel {
|
||||
let model2 = cx.new_model(|_| TestModel {
|
||||
id: 2,
|
||||
subscription: None,
|
||||
});
|
||||
let model3 = cx.new_model(|_, _| TestModel {
|
||||
let model3 = cx.new_model(|_| TestModel {
|
||||
id: 3,
|
||||
subscription: None,
|
||||
});
|
||||
@@ -2018,7 +2018,7 @@ mod tests {
|
||||
});
|
||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||
|
||||
let model = cx.new_model(|_, _| TestModel::default());
|
||||
let model = cx.new_model(|_| TestModel::default());
|
||||
let (done_tx1, _done_rx1) = smol::channel::unbounded();
|
||||
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
||||
let subscription1 = client.add_message_handler(
|
||||
@@ -2053,20 +2053,20 @@ mod tests {
|
||||
});
|
||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||
|
||||
let model = cx.new_model(|_, _| TestModel::default());
|
||||
let model = cx.new_model(|_| TestModel::default());
|
||||
let (done_tx, mut done_rx) = smol::channel::unbounded();
|
||||
let subscription = client.add_message_handler(
|
||||
model.clone().downgrade(),
|
||||
move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, mut cx| {
|
||||
model
|
||||
.update(&mut cx, |this, model, _| this.subscription.take())
|
||||
.update(&mut cx, |model, _| model.subscription.take())
|
||||
.unwrap();
|
||||
done_tx.try_send(()).unwrap();
|
||||
async { Ok(()) }
|
||||
},
|
||||
);
|
||||
model.update(cx, |this, model, _| {
|
||||
this.subscription = Some(subscription);
|
||||
model.update(cx, |model, _| {
|
||||
model.subscription = Some(subscription);
|
||||
});
|
||||
server.send(proto::Ping {});
|
||||
done_rx.next().await.unwrap();
|
||||
|
||||
@@ -204,7 +204,7 @@ impl FakeServer {
|
||||
client: Arc<Client>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Model<UserStore> {
|
||||
let user_store = cx.new_model(|model, cx| UserStore::new(client, model, cx));
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client, cx));
|
||||
assert_eq!(
|
||||
self.receive::<proto::GetUsers>()
|
||||
.await
|
||||
|
||||
@@ -5,7 +5,8 @@ use collections::{hash_map::Entry, HashMap, HashSet};
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use futures::{channel::mpsc, Future, StreamExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, EventEmitter, Model, SharedString, SharedUri, Task, WeakModel,
|
||||
AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, SharedUri, Task,
|
||||
WeakModel,
|
||||
};
|
||||
use postage::{sink::Sink, watch};
|
||||
use rpc::proto::{RequestMessage, UsersResponse};
|
||||
@@ -135,14 +136,14 @@ enum UpdateContacts {
|
||||
}
|
||||
|
||||
impl UserStore {
|
||||
pub fn new(client: Arc<Client>, model: &Model<Self>, cx: &AppContext) -> Self {
|
||||
pub fn new(client: Arc<Client>, cx: &ModelContext<Self>) -> Self {
|
||||
let (mut current_user_tx, current_user_rx) = watch::channel();
|
||||
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
|
||||
let rpc_subscriptions = vec![
|
||||
client.add_message_handler(model.downgrade(), Self::handle_update_plan),
|
||||
client.add_message_handler(model.downgrade(), Self::handle_update_contacts),
|
||||
client.add_message_handler(model.downgrade(), Self::handle_update_invite_info),
|
||||
client.add_message_handler(model.downgrade(), Self::handle_show_contacts),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_update_plan),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_update_contacts),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_update_invite_info),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_show_contacts),
|
||||
];
|
||||
Self {
|
||||
users: Default::default(),
|
||||
@@ -157,19 +158,19 @@ impl UserStore {
|
||||
invite_info: None,
|
||||
client: Arc::downgrade(&client),
|
||||
update_contacts_tx,
|
||||
_maintain_contacts: model.spawn(cx, |this, mut cx| async move {
|
||||
_maintain_contacts: cx.spawn(|this, mut cx| async move {
|
||||
let _subscriptions = rpc_subscriptions;
|
||||
while let Some(message) = update_contacts_rx.next().await {
|
||||
if let Ok(task) = this.update(&mut cx, |this, model, cx| {
|
||||
this.update_contacts(message, model, cx)
|
||||
}) {
|
||||
if let Ok(task) =
|
||||
this.update(&mut cx, |this, cx| this.update_contacts(message, cx))
|
||||
{
|
||||
task.log_err().await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}),
|
||||
_maintain_current_user: model.spawn(cx, |this, mut cx| async move {
|
||||
_maintain_current_user: cx.spawn(|this, mut cx| async move {
|
||||
let mut status = client.status();
|
||||
let weak = Arc::downgrade(&client);
|
||||
drop(client);
|
||||
@@ -182,8 +183,8 @@ impl UserStore {
|
||||
Status::Connected { .. } => {
|
||||
if let Some(user_id) = client.user_id() {
|
||||
let fetch_user = if let Ok(fetch_user) = this
|
||||
.update(&mut cx, |this, model, cx| {
|
||||
this.get_user(user_id, model, cx).log_err()
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.get_user(user_id, cx).log_err()
|
||||
}) {
|
||||
fetch_user
|
||||
} else {
|
||||
@@ -205,7 +206,7 @@ impl UserStore {
|
||||
staff,
|
||||
);
|
||||
|
||||
this.update(cx, |this, model, _| {
|
||||
this.update(cx, |this, _| {
|
||||
this.set_current_user_accepted_tos_at(
|
||||
info.accepted_tos_at,
|
||||
);
|
||||
@@ -217,20 +218,20 @@ impl UserStore {
|
||||
|
||||
current_user_tx.send(user).await.ok();
|
||||
|
||||
this.update(&mut cx, |_, model, cx| model.notify(cx))?;
|
||||
this.update(&mut cx, |_, cx| cx.notify())?;
|
||||
}
|
||||
}
|
||||
Status::SignedOut => {
|
||||
current_user_tx.send(None).await.ok();
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
model.notify(cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.notify();
|
||||
this.clear_contacts()
|
||||
})?
|
||||
.await;
|
||||
}
|
||||
Status::ConnectionLost => {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
model.notify(cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.notify();
|
||||
this.clear_contacts()
|
||||
})?
|
||||
.await;
|
||||
@@ -241,7 +242,7 @@ impl UserStore {
|
||||
Ok(())
|
||||
}),
|
||||
pending_contact_requests: Default::default(),
|
||||
weak_self: model.downgrade(),
|
||||
weak_self: cx.weak_model(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,12 +257,12 @@ impl UserStore {
|
||||
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.invite_info = Some(InviteInfo {
|
||||
url: Arc::from(message.payload.url),
|
||||
count: message.payload.count,
|
||||
});
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -271,7 +272,7 @@ impl UserStore {
|
||||
_: TypedEnvelope<proto::ShowContacts>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |_, model, cx| model.emit(Event::ShowContacts, cx))?;
|
||||
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -284,7 +285,7 @@ impl UserStore {
|
||||
message: TypedEnvelope<proto::UpdateContacts>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.update_contacts_tx
|
||||
.unbounded_send(UpdateContacts::Update(message.payload))
|
||||
.unwrap();
|
||||
@@ -297,9 +298,9 @@ impl UserStore {
|
||||
message: TypedEnvelope<proto::UpdateUserPlan>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.current_plan = Some(message.payload.plan());
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -307,8 +308,7 @@ impl UserStore {
|
||||
fn update_contacts(
|
||||
&mut self,
|
||||
message: UpdateContacts,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
match message {
|
||||
UpdateContacts::Wait(barrier) => {
|
||||
@@ -330,8 +330,8 @@ impl UserStore {
|
||||
user_ids.extend(message.incoming_requests.iter().map(|req| req.requester_id));
|
||||
user_ids.extend(message.outgoing_requests.iter());
|
||||
|
||||
let load_users = self.get_users(user_ids.into_iter().collect(), model, cx);
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
let load_users = self.get_users(user_ids.into_iter().collect(), cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
load_users.await?;
|
||||
|
||||
// Users are fetched in parallel above and cached in call to get_users
|
||||
@@ -349,8 +349,8 @@ impl UserStore {
|
||||
let mut incoming_requests = Vec::new();
|
||||
for request in message.incoming_requests {
|
||||
incoming_requests.push({
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.get_user(request.requester_id, model, cx)
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.get_user(request.requester_id, cx)
|
||||
})?
|
||||
.await?
|
||||
});
|
||||
@@ -359,10 +359,8 @@ impl UserStore {
|
||||
let mut outgoing_requests = Vec::new();
|
||||
for requested_user_id in message.outgoing_requests {
|
||||
outgoing_requests.push(
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.get_user(requested_user_id, model, cx)
|
||||
})?
|
||||
.await?,
|
||||
this.update(&mut cx, |this, cx| this.get_user(requested_user_id, cx))?
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -373,7 +371,7 @@ impl UserStore {
|
||||
let removed_outgoing_requests =
|
||||
HashSet::<u64>::from_iter(message.remove_outgoing_requests.iter().copied());
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
// Remove contacts
|
||||
this.contacts
|
||||
.retain(|contact| !removed_contacts.contains(&contact.user.id));
|
||||
@@ -391,13 +389,10 @@ impl UserStore {
|
||||
// Remove incoming contact requests
|
||||
this.incoming_contact_requests.retain(|user| {
|
||||
if removed_incoming_requests.contains(&user.id) {
|
||||
model.emit(
|
||||
Event::Contact {
|
||||
user: user.clone(),
|
||||
kind: ContactEventKind::Cancelled,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.emit(Event::Contact {
|
||||
user: user.clone(),
|
||||
kind: ContactEventKind::Cancelled,
|
||||
});
|
||||
false
|
||||
} else {
|
||||
true
|
||||
@@ -430,7 +425,7 @@ impl UserStore {
|
||||
}
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
@@ -488,24 +483,17 @@ impl UserStore {
|
||||
pub fn request_contact(
|
||||
&mut self,
|
||||
responder_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.perform_contact_request(
|
||||
responder_id,
|
||||
proto::RequestContact { responder_id },
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
self.perform_contact_request(responder_id, proto::RequestContact { responder_id }, cx)
|
||||
}
|
||||
|
||||
pub fn remove_contact(
|
||||
&mut self,
|
||||
user_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.perform_contact_request(user_id, proto::RemoveContact { user_id }, model, cx)
|
||||
self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
|
||||
}
|
||||
|
||||
pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
|
||||
@@ -518,8 +506,7 @@ impl UserStore {
|
||||
&mut self,
|
||||
requester_id: u64,
|
||||
accept: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.perform_contact_request(
|
||||
requester_id,
|
||||
@@ -531,7 +518,6 @@ impl UserStore {
|
||||
proto::ContactRequestResponse::Decline
|
||||
} as i32,
|
||||
},
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -539,11 +525,10 @@ impl UserStore {
|
||||
pub fn dismiss_contact_request(
|
||||
&self,
|
||||
requester_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.upgrade();
|
||||
cx.spawn(move |_| async move {
|
||||
cx.spawn(move |_, _| async move {
|
||||
client
|
||||
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
|
||||
.request(proto::RespondToContactRequest {
|
||||
@@ -559,19 +544,18 @@ impl UserStore {
|
||||
&mut self,
|
||||
user_id: u64,
|
||||
request: T,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.upgrade();
|
||||
*self.pending_contact_requests.entry(user_id).or_insert(0) += 1;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let response = client
|
||||
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
|
||||
.request(request)
|
||||
.await;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Entry::Occupied(mut request_count) =
|
||||
this.pending_contact_requests.entry(user_id)
|
||||
{
|
||||
@@ -580,7 +564,7 @@ impl UserStore {
|
||||
request_count.remove();
|
||||
}
|
||||
}
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
response?;
|
||||
Ok(())
|
||||
@@ -610,27 +594,25 @@ impl UserStore {
|
||||
pub fn get_users(
|
||||
&self,
|
||||
user_ids: Vec<u64>,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Arc<User>>>> {
|
||||
let mut user_ids_to_fetch = user_ids.clone();
|
||||
user_ids_to_fetch.retain(|id| !self.users.contains_key(id));
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if !user_ids_to_fetch.is_empty() {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.load_users(
|
||||
proto::GetUsers {
|
||||
user_ids: user_ids_to_fetch,
|
||||
},
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
user_ids
|
||||
.iter()
|
||||
.map(|user_id| {
|
||||
@@ -647,44 +629,33 @@ impl UserStore {
|
||||
pub fn fuzzy_search_users(
|
||||
&self,
|
||||
query: String,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Arc<User>>>> {
|
||||
self.load_users(proto::FuzzySearchUsers { query }, model, cx)
|
||||
self.load_users(proto::FuzzySearchUsers { query }, cx)
|
||||
}
|
||||
|
||||
pub fn get_cached_user(&self, user_id: u64) -> Option<Arc<User>> {
|
||||
self.users.get(&user_id).cloned()
|
||||
}
|
||||
|
||||
pub fn get_user_optimistic(
|
||||
&self,
|
||||
user_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
) -> Option<Arc<User>> {
|
||||
pub fn get_user_optimistic(&self, user_id: u64, cx: &ModelContext<Self>) -> Option<Arc<User>> {
|
||||
if let Some(user) = self.users.get(&user_id).cloned() {
|
||||
return Some(user);
|
||||
}
|
||||
|
||||
self.get_user(user_id, model, cx).detach_and_log_err(cx);
|
||||
self.get_user(user_id, cx).detach_and_log_err(cx);
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_user(
|
||||
&self,
|
||||
user_id: u64,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<Arc<User>>> {
|
||||
pub fn get_user(&self, user_id: u64, cx: &ModelContext<Self>) -> Task<Result<Arc<User>>> {
|
||||
if let Some(user) = self.users.get(&user_id).cloned() {
|
||||
return Task::ready(Ok(user));
|
||||
}
|
||||
|
||||
let load_users = self.get_users(vec![user_id], model, cx);
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
let load_users = self.get_users(vec![user_id], cx);
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
load_users.await?;
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.users
|
||||
.get(&user_id)
|
||||
.cloned()
|
||||
@@ -716,24 +687,20 @@ impl UserStore {
|
||||
.map(|accepted_tos_at| accepted_tos_at.is_some())
|
||||
}
|
||||
|
||||
pub fn accept_terms_of_service(
|
||||
&self,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn accept_terms_of_service(&self, cx: &ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.current_user().is_none() {
|
||||
return Task::ready(Err(anyhow!("no current user")));
|
||||
};
|
||||
|
||||
let client = self.client.clone();
|
||||
model.spawn(cx, move |this, mut cx| async move {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(client) = client.upgrade() {
|
||||
let response = client
|
||||
.request(proto::AcceptTermsOfService {})
|
||||
.await
|
||||
.context("error accepting tos")?;
|
||||
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.set_current_user_accepted_tos_at(Some(response.accepted_tos_at))
|
||||
})
|
||||
} else {
|
||||
@@ -751,16 +718,15 @@ impl UserStore {
|
||||
fn load_users(
|
||||
&self,
|
||||
request: impl RequestMessage<Response = UsersResponse>,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Arc<User>>>> {
|
||||
let client = self.client.clone();
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some(rpc) = client.upgrade() {
|
||||
let response = rpc.request(request).await.context("error loading users")?;
|
||||
let users = response.users;
|
||||
|
||||
this.update(&mut cx, |this, _, _| this.insert(users))
|
||||
this.update(&mut cx, |this, _| this.insert(users))
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
@@ -786,12 +752,11 @@ impl UserStore {
|
||||
pub fn set_participant_indices(
|
||||
&mut self,
|
||||
participant_indices: HashMap<u64, ParticipantIndex>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if participant_indices != self.participant_indices {
|
||||
self.participant_indices = participant_indices;
|
||||
model.emit(Event::ParticipantIndicesChanged, cx);
|
||||
cx.emit(Event::ParticipantIndicesChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,10 +781,8 @@ impl UserStore {
|
||||
if !missing_user_ids.is_empty() {
|
||||
let this = self.weak_self.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.get_users(missing_user_ids, model, cx)
|
||||
})?
|
||||
.await
|
||||
this.update(&mut cx, |this, cx| this.get_users(missing_user_ids, cx))?
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
@@ -844,8 +807,8 @@ impl Contact {
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
let user = user_store
|
||||
.update(cx, |user_store, model, cx| {
|
||||
user_store.get_user(contact.user_id, model, cx)
|
||||
.update(cx, |user_store, cx| {
|
||||
user_store.get_user(contact.user_id, cx)
|
||||
})?
|
||||
.await?;
|
||||
Ok(Self {
|
||||
|
||||
@@ -9,7 +9,6 @@ use collections::HashSet;
|
||||
use reqwest::StatusCode;
|
||||
use sea_orm::ActiveValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::{str::FromStr, sync::Arc, time::Duration};
|
||||
use stripe::{
|
||||
BillingPortalSession, CreateBillingPortalSession, CreateBillingPortalSessionFlowData,
|
||||
@@ -20,7 +19,6 @@ use stripe::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::api::events::SnowflakeRow;
|
||||
use crate::llm::{DEFAULT_MAX_MONTHLY_SPEND, FREE_TIER_MONTHLY_SPENDING_LIMIT};
|
||||
use crate::rpc::{ResultExt as _, Server};
|
||||
use crate::{
|
||||
@@ -126,20 +124,6 @@ async fn update_billing_preferences(
|
||||
.await?
|
||||
};
|
||||
|
||||
SnowflakeRow::new(
|
||||
"Spend Limit Updated",
|
||||
Some(user.metrics_id),
|
||||
user.admin,
|
||||
None,
|
||||
json!({
|
||||
"user_id": user.id,
|
||||
"max_monthly_llm_usage_spending_in_cents": billing_preferences.max_monthly_llm_usage_spending_in_cents,
|
||||
}),
|
||||
)
|
||||
.write(&app.kinesis_client, &app.config.kinesis_stream)
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
rpc_server.refresh_llm_tokens_for_user(user.id).await;
|
||||
|
||||
Ok(Json(BillingPreferencesResponse {
|
||||
|
||||
@@ -9,7 +9,7 @@ use collab_ui::channel_view::ChannelView;
|
||||
use collections::HashMap;
|
||||
use editor::{Anchor, Editor, ToOffset};
|
||||
use futures::future;
|
||||
use gpui::{AppContext, BackgroundExecutor, Model, TestAppContext};
|
||||
use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
|
||||
use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
|
||||
use serde_json::json;
|
||||
use std::ops::Range;
|
||||
@@ -161,43 +161,43 @@ async fn test_channel_notes_participant_indices(
|
||||
|
||||
// Clients A, B, and C open the channel notes
|
||||
let channel_view_a = cx_a
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_a.clone(), model, cx))
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel_view_b = cx_b
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_b.clone(), model, cx))
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_b.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel_view_c = cx_c
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_c.clone(), model, cx))
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_c.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Clients A, B, and C all insert and select some text
|
||||
channel_view_a.update(cx_a, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, model, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.insert("a", cx);
|
||||
editor.change_selections(None, model, cx, |selections| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![0..1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
channel_view_b.update(cx_b, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, model, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&Default::default(), cx);
|
||||
editor.insert("b", cx);
|
||||
editor.change_selections(None, model, cx, |selections| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![1..2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
channel_view_c.update(cx_c, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, model, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&Default::default(), cx);
|
||||
editor.insert("c", cx);
|
||||
editor.change_selections(None, model, cx, |selections| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![2..3]);
|
||||
});
|
||||
});
|
||||
@@ -207,8 +207,8 @@ async fn test_channel_notes_participant_indices(
|
||||
// in a call together.
|
||||
executor.run_until_parked();
|
||||
channel_view_a.update(cx_a, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, model, cx| {
|
||||
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], model, cx);
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -223,21 +223,19 @@ async fn test_channel_notes_participant_indices(
|
||||
// still doesn't have a color.
|
||||
executor.run_until_parked();
|
||||
channel_view_a.update(cx_a, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, model, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
assert_remote_selections(
|
||||
editor,
|
||||
&[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
});
|
||||
channel_view_b.update(cx_b, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, model, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
assert_remote_selections(
|
||||
editor,
|
||||
&[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -272,12 +270,12 @@ async fn test_channel_notes_participant_indices(
|
||||
.unwrap();
|
||||
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |selections| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![0..1]);
|
||||
});
|
||||
});
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |selections| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![2..3]);
|
||||
});
|
||||
});
|
||||
@@ -285,10 +283,10 @@ async fn test_channel_notes_participant_indices(
|
||||
|
||||
// Clients A and B see each other with the same colors as in the channel notes.
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], model, cx);
|
||||
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], model, cx);
|
||||
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -296,10 +294,9 @@ async fn test_channel_notes_participant_indices(
|
||||
fn assert_remote_selections(
|
||||
editor: &mut Editor,
|
||||
expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
|
||||
model: &Model<Editor>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let snapshot = editor.snapshot(model, cx);
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let range = Anchor::min()..Anchor::max();
|
||||
let remote_selections = snapshot
|
||||
.remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
|
||||
@@ -347,7 +344,7 @@ async fn test_multiple_handles_to_channel_buffer(
|
||||
assert_eq!(channel_buffer, channel_buffer_3);
|
||||
|
||||
channel_buffer.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, model, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "hello")], None, cx);
|
||||
})
|
||||
});
|
||||
@@ -368,7 +365,7 @@ async fn test_multiple_handles_to_channel_buffer(
|
||||
.unwrap();
|
||||
assert_ne!(channel_buffer.entity_id(), channel_buffer_model_id);
|
||||
channel_buffer.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, model, _| {
|
||||
buffer.buffer().update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "hello");
|
||||
})
|
||||
});
|
||||
@@ -468,7 +465,7 @@ async fn test_rejoin_channel_buffer(
|
||||
.unwrap();
|
||||
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, model, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "1")], None, cx);
|
||||
})
|
||||
});
|
||||
@@ -480,12 +477,12 @@ async fn test_rejoin_channel_buffer(
|
||||
|
||||
// Both clients make an edit.
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, model, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(1..1, "2")], None, cx);
|
||||
})
|
||||
});
|
||||
channel_buffer_b.update(cx_b, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, model, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "0")], None, cx);
|
||||
})
|
||||
});
|
||||
@@ -555,7 +552,7 @@ async fn test_channel_buffers_and_server_restarts(
|
||||
.unwrap();
|
||||
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, model, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "1")], None, cx);
|
||||
})
|
||||
});
|
||||
@@ -570,12 +567,12 @@ async fn test_channel_buffers_and_server_restarts(
|
||||
|
||||
// While the server is down, both clients make an edit.
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, model, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(1..1, "2")], None, cx);
|
||||
})
|
||||
});
|
||||
channel_buffer_b.update(cx_b, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, model, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "0")], None, cx);
|
||||
})
|
||||
});
|
||||
@@ -645,8 +642,8 @@ async fn test_channel_buffer_changes(
|
||||
|
||||
// Closing the buffer should re-enable change tracking
|
||||
cx_b.update(|cx| {
|
||||
workspace_b.update(cx, |workspace, model, cx| {
|
||||
workspace.close_all_items_and_panes(&Default::default(), model, cx)
|
||||
workspace_b.update(cx, |workspace, cx| {
|
||||
workspace.close_all_items_and_panes(&Default::default(), cx)
|
||||
});
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
@@ -118,7 +118,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
|
||||
// B is promoted
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| {
|
||||
call.room().unwrap().update(cx, |room, model, cx| {
|
||||
call.room().unwrap().update(cx, |room, cx| {
|
||||
room.set_participant_role(
|
||||
client_b.user_id().unwrap(),
|
||||
proto::ChannelRole::Member,
|
||||
@@ -146,7 +146,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
|
||||
// B is demoted
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| {
|
||||
call.room().unwrap().update(cx, |room, model, cx| {
|
||||
call.room().unwrap().update(cx, |room, cx| {
|
||||
room.set_participant_role(
|
||||
client_b.user_id().unwrap(),
|
||||
proto::ChannelRole::Guest,
|
||||
@@ -238,7 +238,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
// yet signed the zed CLA.
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| {
|
||||
call.room().unwrap().update(cx, |room, model, cx| {
|
||||
call.room().unwrap().update(cx, |room, cx| {
|
||||
room.set_participant_role(
|
||||
client_b.user_id().unwrap(),
|
||||
proto::ChannelRole::Member,
|
||||
@@ -258,7 +258,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
// yet signed the zed CLA.
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| {
|
||||
call.room().unwrap().update(cx, |room, model, cx| {
|
||||
call.room().unwrap().update(cx, |room, cx| {
|
||||
room.set_participant_role(
|
||||
client_b.user_id().unwrap(),
|
||||
proto::ChannelRole::Talker,
|
||||
@@ -285,7 +285,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
// A can now grant write access to B.
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| {
|
||||
call.room().unwrap().update(cx, |room, model, cx| {
|
||||
call.room().unwrap().update(cx, |room, cx| {
|
||||
room.set_participant_role(
|
||||
client_b.user_id().unwrap(),
|
||||
proto::ChannelRole::Member,
|
||||
|
||||
@@ -82,15 +82,8 @@ async fn test_host_disconnect(
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
|
||||
let workspace_b = cx_b.add_window(|cx| {
|
||||
Workspace::new(
|
||||
None,
|
||||
project_b.clone(),
|
||||
client_b.app_state.clone(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let workspace_b = cx_b
|
||||
.add_window(|cx| Workspace::new(None, project_b.clone(), client_b.app_state.clone(), cx));
|
||||
let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
|
||||
let workspace_b_view = workspace_b.root_view(cx_b).unwrap();
|
||||
|
||||
@@ -105,7 +98,7 @@ async fn test_host_disconnect(
|
||||
.unwrap();
|
||||
|
||||
//TODO: focus
|
||||
assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(window)));
|
||||
assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
|
||||
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
|
||||
|
||||
cx_b.update(|cx| {
|
||||
@@ -208,7 +201,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
|
||||
.await
|
||||
.unwrap();
|
||||
let cx_a = cx_a.add_empty_window();
|
||||
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), model, cx));
|
||||
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
|
||||
|
||||
let mut editor_cx_a = EditorTestContext {
|
||||
cx: cx_a.clone(),
|
||||
@@ -223,7 +216,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), model, cx));
|
||||
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
|
||||
|
||||
let mut editor_cx_b = EditorTestContext {
|
||||
cx: cx_b.clone(),
|
||||
@@ -324,8 +317,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
.await
|
||||
.unwrap();
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
let editor_b = cx_b
|
||||
.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), model, cx));
|
||||
let editor_b =
|
||||
cx_b.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx));
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
cx_a.background_executor.run_until_parked();
|
||||
@@ -336,7 +329,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
|
||||
// Type a completion trigger character as the guest.
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([13..13]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(".", cx);
|
||||
});
|
||||
cx_b.focus_view(&editor_b);
|
||||
@@ -449,7 +442,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
// Now we do a second completion, this time to ensure that documentation/snippets are
|
||||
// resolved
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([46..46]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([46..46]));
|
||||
editor.handle_input("; a", cx);
|
||||
editor.handle_input(".", cx);
|
||||
});
|
||||
@@ -601,7 +594,7 @@ async fn test_collaborating_with_code_actions(
|
||||
|
||||
// Move cursor to a location that contains code actions.
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
|
||||
});
|
||||
});
|
||||
@@ -682,7 +675,7 @@ async fn test_collaborating_with_code_actions(
|
||||
// Confirming the code action will trigger a resolve request.
|
||||
let confirm_action = editor_b
|
||||
.update(cx_b, |editor, cx| {
|
||||
Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, model, cx)
|
||||
Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, cx)
|
||||
})
|
||||
.unwrap();
|
||||
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|
||||
@@ -803,7 +796,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
|
||||
// Move cursor to a location that can be renamed.
|
||||
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([7..7]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
|
||||
editor.rename(&Rename, cx).unwrap()
|
||||
});
|
||||
|
||||
@@ -828,14 +821,14 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
|
||||
6..9
|
||||
);
|
||||
rename.editor.update(cx, |rename_editor, model, cx| {
|
||||
rename.editor.update(cx, |rename_editor, cx| {
|
||||
let rename_selection = rename_editor.selections.newest::<usize>(cx);
|
||||
assert_eq!(
|
||||
rename_selection.range(),
|
||||
0..3,
|
||||
"Rename that was triggered from zero selection caret, should propose the whole word."
|
||||
);
|
||||
rename_editor.buffer().update(cx, |rename_buffer, model, cx| {
|
||||
rename_editor.buffer().update(cx, |rename_buffer, cx| {
|
||||
rename_buffer.edit([(0..3, "THREE")], None, cx);
|
||||
});
|
||||
});
|
||||
@@ -846,7 +839,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
editor.cancel(&editor::actions::Cancel, cx);
|
||||
});
|
||||
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([7..8]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([7..8]));
|
||||
editor.rename(&Rename, cx).unwrap()
|
||||
});
|
||||
|
||||
@@ -870,21 +863,21 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
let lsp_rename_start = rename.range.start.to_offset(&buffer);
|
||||
let lsp_rename_end = rename.range.end.to_offset(&buffer);
|
||||
assert_eq!(lsp_rename_start..lsp_rename_end, 6..9);
|
||||
rename.editor.update(cx, |rename_editor, model, cx| {
|
||||
rename.editor.update(cx, |rename_editor, cx| {
|
||||
let rename_selection = rename_editor.selections.newest::<usize>(cx);
|
||||
assert_eq!(
|
||||
rename_selection.range(),
|
||||
1..2,
|
||||
"Rename that was triggered from a selection, should have the same selection range in the rename proposal"
|
||||
);
|
||||
rename_editor.buffer().update(cx, |rename_buffer, model, cx| {
|
||||
rename_editor.buffer().update(cx, |rename_buffer, cx| {
|
||||
rename_buffer.edit([(0..lsp_rename_end - lsp_rename_start, "THREE")], None, cx);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let confirm_rename = editor_b.update(cx_b, |editor, cx| {
|
||||
Editor::confirm_rename(editor, &ConfirmRename, model, cx).unwrap()
|
||||
Editor::confirm_rename(editor, &ConfirmRename, cx).unwrap()
|
||||
});
|
||||
fake_language_server
|
||||
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
|
||||
@@ -1198,7 +1191,7 @@ async fn test_share_project(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, model, cx));
|
||||
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, cx));
|
||||
|
||||
// Client A sees client B's selection
|
||||
executor.run_until_parked();
|
||||
@@ -1303,8 +1296,7 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
.await
|
||||
.unwrap();
|
||||
let cx_a = cx_a.add_empty_window();
|
||||
let editor_a =
|
||||
cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), model, cx));
|
||||
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx));
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
executor.run_until_parked();
|
||||
@@ -1338,7 +1330,7 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
cx_a.focus_view(&editor_a);
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([13..13]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(">", cx);
|
||||
});
|
||||
|
||||
@@ -1424,8 +1416,7 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
.await
|
||||
.unwrap();
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
let editor_b =
|
||||
cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), model, cx));
|
||||
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), cx));
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
executor.run_until_parked();
|
||||
@@ -1433,7 +1424,7 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
cx_b.focus_view(&editor_b);
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([13..13]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(":", cx);
|
||||
});
|
||||
|
||||
@@ -1677,7 +1668,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
|
||||
let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([13..13].clone()));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
|
||||
editor.handle_input(":", cx);
|
||||
});
|
||||
cx_b.focus_view(&editor_b);
|
||||
@@ -1702,7 +1693,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
|
||||
let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([13..13]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input("a change to increment both buffers' versions", cx);
|
||||
});
|
||||
cx_a.focus_view(&editor_a);
|
||||
@@ -2079,7 +2070,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
|
||||
editor_b.update(cx_b, |editor_b, cx| {
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, model, cx| {
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>()
|
||||
@@ -2095,7 +2086,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
]
|
||||
);
|
||||
|
||||
blame.update(cx, |blame, model, _| {
|
||||
blame.update(cx, |blame, _| {
|
||||
for (idx, entry) in entries.iter().flatten().enumerate() {
|
||||
let details = blame.details_for_entry(entry).unwrap();
|
||||
assert_eq!(details.message, format!("message for idx-{}", idx));
|
||||
@@ -2118,7 +2109,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
|
||||
editor_b.update(cx_b, |editor_b, cx| {
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, model, cx| {
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>()
|
||||
@@ -2145,7 +2136,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
|
||||
editor_b.update(cx_b, |editor_b, cx| {
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, model, cx| {
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>()
|
||||
@@ -2215,9 +2206,9 @@ async fn test_collaborating_with_editorconfig(
|
||||
.unwrap();
|
||||
let cx_a = cx_a.add_empty_window();
|
||||
let main_editor_a =
|
||||
cx_a.new_view(|cx| Editor::for_buffer(main_buffer_a, Some(project_a.clone()), model, cx));
|
||||
cx_a.new_view(|cx| Editor::for_buffer(main_buffer_a, Some(project_a.clone()), cx));
|
||||
let other_editor_a =
|
||||
cx_a.new_view(|cx| Editor::for_buffer(other_buffer_a, Some(project_a), model, cx));
|
||||
cx_a.new_view(|cx| Editor::for_buffer(other_buffer_a, Some(project_a), cx));
|
||||
let mut main_editor_cx_a = EditorTestContext {
|
||||
cx: cx_a.clone(),
|
||||
window: cx_a.handle(),
|
||||
@@ -2247,9 +2238,9 @@ async fn test_collaborating_with_editorconfig(
|
||||
.unwrap();
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
let main_editor_b =
|
||||
cx_b.new_view(|cx| Editor::for_buffer(main_buffer_b, Some(project_b.clone()), model, cx));
|
||||
cx_b.new_view(|cx| Editor::for_buffer(main_buffer_b, Some(project_b.clone()), cx));
|
||||
let other_editor_b =
|
||||
cx_b.new_view(|cx| Editor::for_buffer(other_buffer_b, Some(project_b.clone()), model, cx));
|
||||
cx_b.new_view(|cx| Editor::for_buffer(other_buffer_b, Some(project_b.clone()), cx));
|
||||
let mut main_editor_cx_b = EditorTestContext {
|
||||
cx: cx_b.clone(),
|
||||
window: cx_b.handle(),
|
||||
|
||||
@@ -8,8 +8,8 @@ use collab_ui::{
|
||||
};
|
||||
use editor::{Editor, ExcerptRange, MultiBuffer};
|
||||
use gpui::{
|
||||
point, AppContext, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString,
|
||||
TestAppContext, View, VisualContext, VisualTestContext,
|
||||
point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext,
|
||||
View, VisualContext, VisualTestContext,
|
||||
};
|
||||
use language::Capability;
|
||||
use project::WorktreeSettings;
|
||||
@@ -273,12 +273,12 @@ async fn test_basic_following(
|
||||
|
||||
// When client A opens a multibuffer, client B does so as well.
|
||||
let multibuffer_a = cx_a.new_model(|cx| {
|
||||
let buffer_a1 = project_a.update(cx, |project, model, cx| {
|
||||
let buffer_a1 = project_a.update(cx, |project, cx| {
|
||||
project
|
||||
.get_open_buffer(&(worktree_id, "1.txt").into(), cx)
|
||||
.unwrap()
|
||||
});
|
||||
let buffer_a2 = project_a.update(cx, |project, model, cx| {
|
||||
let buffer_a2 = project_a.update(cx, |project, cx| {
|
||||
project
|
||||
.get_open_buffer(&(worktree_id, "2.txt").into(), cx)
|
||||
.unwrap()
|
||||
@@ -290,7 +290,6 @@ async fn test_basic_following(
|
||||
context: 0..3,
|
||||
primary: None,
|
||||
}],
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
result.push_excerpts(
|
||||
@@ -299,16 +298,15 @@ async fn test_basic_following(
|
||||
context: 4..7,
|
||||
primary: None,
|
||||
}],
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
result
|
||||
});
|
||||
let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
|
||||
let editor = cx.new_model(|model, cx| {
|
||||
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, model, cx)
|
||||
let editor = cx.new_view(|cx| {
|
||||
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, cx)
|
||||
});
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, model, cx);
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
|
||||
editor
|
||||
});
|
||||
executor.run_until_parked();
|
||||
@@ -369,7 +367,7 @@ async fn test_basic_following(
|
||||
|
||||
// Changes to client A's editor are reflected on client B.
|
||||
editor_a1.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([1..1, 2..2]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
executor.run_until_parked();
|
||||
@@ -384,7 +382,7 @@ async fn test_basic_following(
|
||||
editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
|
||||
|
||||
editor_a1.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([3..3]));
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
|
||||
editor.set_scroll_position(point(0., 100.), cx);
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -446,7 +444,7 @@ async fn test_basic_following(
|
||||
.update(cx_b, |call, cx| {
|
||||
call.room()
|
||||
.unwrap()
|
||||
.update(cx, |room, model, cx| room.share_screen(cx))
|
||||
.update(cx, |room, cx| room.share_screen(cx))
|
||||
})
|
||||
.await
|
||||
.unwrap(); // This is what breaks
|
||||
@@ -515,7 +513,7 @@ async fn test_basic_following(
|
||||
// so the previously-opened screen-sharing item gets activated.
|
||||
let unfollowable_item = cx_b.new_view(TestItem::new);
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.active_pane().update(cx, |pane, model, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
|
||||
})
|
||||
});
|
||||
@@ -607,8 +605,8 @@ async fn test_following_tab_order(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pane_paths = |pane: &Model<workspace::Pane>, cx: &mut VisualTestContext| {
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
let pane_paths = |pane: &View<workspace::Pane>, cx: &mut VisualTestContext| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.items()
|
||||
.map(|item| {
|
||||
item.project_path(cx)
|
||||
@@ -926,7 +924,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
// Client B focuses a file that they previously followed A to, breaking
|
||||
// the follow.
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.active_pane().update(cx, |pane, model, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.activate_prev_item(true, cx);
|
||||
});
|
||||
});
|
||||
@@ -977,7 +975,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
// Client B closes tabs, some of which were originally opened by client A,
|
||||
// and some of which were originally opened by client B.
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.active_pane().update(cx, |pane, model, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.close_inactive_items(&Default::default(), cx)
|
||||
.unwrap()
|
||||
.detach();
|
||||
@@ -1030,7 +1028,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
executor.run_until_parked();
|
||||
// Client A cycles through some tabs.
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
workspace.active_pane().update(cx, |pane, model, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.activate_prev_item(true, cx);
|
||||
});
|
||||
});
|
||||
@@ -1074,7 +1072,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
);
|
||||
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
workspace.active_pane().update(cx, |pane, model, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.activate_prev_item(true, cx);
|
||||
});
|
||||
});
|
||||
@@ -1121,7 +1119,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
);
|
||||
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
workspace.active_pane().update(cx, |pane, model, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.activate_prev_item(true, cx);
|
||||
});
|
||||
});
|
||||
@@ -1610,7 +1608,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
|
||||
// b should follow a to position 1
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([1..1]))
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([1..1]))
|
||||
});
|
||||
cx_a.executor()
|
||||
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -1620,18 +1618,17 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
});
|
||||
|
||||
// a unshares the project
|
||||
// todo! why do i need the annotation
|
||||
cx_a.update(|cx: &mut AppContext| {
|
||||
cx_a.update(|cx| {
|
||||
let project = workspace_a.read(cx).project().clone();
|
||||
ActiveCall::global(cx).update(cx, |call, model, cx| {
|
||||
call.unshare_project(project, model, cx).unwrap();
|
||||
ActiveCall::global(cx).update(cx, |call, cx| {
|
||||
call.unshare_project(project, cx).unwrap();
|
||||
})
|
||||
});
|
||||
cx_a.run_until_parked();
|
||||
|
||||
// b should not follow a to position 2
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, model, cx, |s| s.select_ranges([2..2]))
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([2..2]))
|
||||
});
|
||||
cx_a.executor()
|
||||
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -1779,11 +1776,11 @@ async fn test_following_into_excluded_file(
|
||||
|
||||
fn visible_push_notifications(
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<gpui::Model<ProjectSharedNotification>> {
|
||||
) -> Vec<gpui::View<ProjectSharedNotification>> {
|
||||
let mut ret = Vec::new();
|
||||
for window in cx.windows() {
|
||||
window
|
||||
.update(cx, |window, model, _| {
|
||||
.update(cx, |window, _| {
|
||||
if let Ok(handle) = window.downcast::<ProjectSharedNotification>() {
|
||||
ret.push(handle)
|
||||
}
|
||||
@@ -1824,8 +1821,8 @@ fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec
|
||||
})
|
||||
}
|
||||
|
||||
fn pane_summaries(workspace: &Model<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
fn pane_summaries(workspace: &View<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let active_pane = workspace.active_pane();
|
||||
workspace
|
||||
.panes()
|
||||
@@ -1927,14 +1924,14 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
|
||||
// Client A opens the notes for channel 1.
|
||||
let channel_notes_1_a = cx_a
|
||||
.update(|cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), model, cx))
|
||||
.update(|cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
channel_notes_1_a.update(cx_a, |notes, cx| {
|
||||
assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
|
||||
notes.editor.update(cx, |editor, model, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.insert("Hello from A.", cx);
|
||||
editor.change_selections(None, model, cx, |selections| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![3..4]);
|
||||
});
|
||||
});
|
||||
@@ -1966,7 +1963,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
});
|
||||
channel_notes_1_b.update(cx_b, |notes, cx| {
|
||||
assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
|
||||
notes.editor.update(cx, |editor, model, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Hello from A.");
|
||||
assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
|
||||
})
|
||||
@@ -1974,7 +1971,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
|
||||
// Client A opens the notes for channel 2.
|
||||
let channel_notes_2_a = cx_a
|
||||
.update(|cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), model, cx))
|
||||
.update(|cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
channel_notes_2_a.update(cx_a, |notes, cx| {
|
||||
@@ -2030,12 +2027,12 @@ pub(crate) async fn join_channel(
|
||||
}
|
||||
|
||||
async fn share_workspace(
|
||||
workspace: &Model<Workspace>,
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> anyhow::Result<u64> {
|
||||
let project = workspace.update(cx, |workspace, model, _| workspace.project().clone());
|
||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
|
||||
cx.read(ActiveCall::global)
|
||||
.update(cx, |call, model, cx| call.share_project(project, model, cx))
|
||||
.update(cx, |call, cx| call.share_project(project, cx))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -2074,7 +2071,7 @@ async fn test_following_to_channel_notes_other_workspace(
|
||||
let (workspace_a2, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
|
||||
cx_a2.update(|cx| cx.activate_window());
|
||||
cx_a2
|
||||
.update(|cx| ChannelView::open(channel, None, workspace_a2, model, cx))
|
||||
.update(|cx| ChannelView::open(channel, None, workspace_a2, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a2.run_until_parked();
|
||||
|
||||
@@ -252,7 +252,7 @@ async fn test_basic_calls(
|
||||
.update(cx_a, |call, cx| {
|
||||
call.room()
|
||||
.unwrap()
|
||||
.update(cx, |room, model, cx| room.share_screen(cx))
|
||||
.update(cx, |room, cx| room.share_screen(cx))
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6078,7 +6078,7 @@ async fn test_join_call_after_screen_was_shared(
|
||||
.update(cx_a, |call, cx| {
|
||||
call.room()
|
||||
.unwrap()
|
||||
.update(cx, |room, model, cx| room.share_screen(cx))
|
||||
.update(cx, |room, cx| room.share_screen(cx))
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6173,11 +6173,11 @@ async fn test_pane_split_left(cx: &mut TestAppContext) {
|
||||
let (workspace, cx) = client.build_test_workspace(cx).await;
|
||||
|
||||
cx.simulate_keystrokes("cmd-n");
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 1);
|
||||
});
|
||||
cx.simulate_keystrokes("cmd-k left");
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
|
||||
});
|
||||
cx.simulate_keystrokes("cmd-k");
|
||||
@@ -6185,7 +6185,7 @@ async fn test_pane_split_left(cx: &mut TestAppContext) {
|
||||
// to verify that it doesn't fire in this case.
|
||||
cx.executor().advance_clock(Duration::from_secs(2));
|
||||
cx.simulate_keystrokes("left");
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
|
||||
});
|
||||
}
|
||||
@@ -6207,9 +6207,9 @@ async fn test_join_after_restart(cx1: &mut TestAppContext, cx2: &mut TestAppCont
|
||||
async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
let (_server, client) = TestServer::start1(cx).await;
|
||||
let (workspace, cx) = client.build_test_workspace(cx).await;
|
||||
let project = workspace.update(cx, |workspace, model, _| workspace.project().clone());
|
||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
|
||||
|
||||
let worktree_id = project.update(cx, |project, model, cx| {
|
||||
let worktree_id = project.update(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
|
||||
@@ -6226,7 +6226,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
path: Path::new("3.rs").into(),
|
||||
};
|
||||
|
||||
let pane = workspace.update(cx, |workspace, model, _| workspace.active_pane().clone());
|
||||
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
|
||||
|
||||
let get_path = |pane: &Pane, idx: usize, cx: &AppContext| {
|
||||
pane.item_for_index(idx).unwrap().project_path(cx).unwrap()
|
||||
@@ -6234,13 +6234,13 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
|
||||
// Opening item 3 as a "permanent" tab
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.open_path(path_3.clone(), None, false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(pane.preview_item_id(), None);
|
||||
@@ -6251,13 +6251,13 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
|
||||
// Open item 1 as preview
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.open_path_preview(path_1.clone(), None, true, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_1.clone());
|
||||
@@ -6272,13 +6272,13 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
|
||||
// Open item 2 as preview
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_2.clone());
|
||||
@@ -6293,11 +6293,11 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
|
||||
// Going back should show item 1 as preview
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| workspace.go_back(pane.downgrade(), cx))
|
||||
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_1.clone());
|
||||
@@ -6311,7 +6311,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
// Closing item 1
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(
|
||||
pane.active_item().unwrap().item_id(),
|
||||
workspace::SaveIntent::Skip,
|
||||
@@ -6321,7 +6321,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(pane.preview_item_id(), None);
|
||||
@@ -6332,11 +6332,11 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
|
||||
// Going back should show item 1 as preview
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| workspace.go_back(pane.downgrade(), cx))
|
||||
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_1.clone());
|
||||
@@ -6350,14 +6350,14 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
// Close permanent tab
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
let id = pane.items().next().unwrap().item_id();
|
||||
pane.close_item_by_id(id, workspace::SaveIntent::Skip, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
@@ -6370,13 +6370,13 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
// Split pane to the right
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.split(workspace::SplitDirection::Right, cx);
|
||||
});
|
||||
|
||||
let right_pane = workspace.update(cx, |workspace, model, _| workspace.active_pane().clone());
|
||||
let right_pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
@@ -6388,7 +6388,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert!(pane.can_navigate_forward());
|
||||
});
|
||||
|
||||
right_pane.update(cx, |pane, model, cx| {
|
||||
right_pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(pane.preview_item_id(), None);
|
||||
@@ -6399,13 +6399,13 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
|
||||
// Open item 2 as preview in right pane
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
@@ -6417,7 +6417,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert!(pane.can_navigate_forward());
|
||||
});
|
||||
|
||||
right_pane.update(cx, |pane, model, cx| {
|
||||
right_pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_2.clone());
|
||||
@@ -6431,19 +6431,19 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
// Focus left pane
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, cx)
|
||||
});
|
||||
|
||||
// Open item 2 as preview in left pane
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pane.update(cx, |pane, model, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_2.clone());
|
||||
assert_eq!(
|
||||
@@ -6455,7 +6455,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert!(!pane.can_navigate_forward());
|
||||
});
|
||||
|
||||
right_pane.update(cx, |pane, model, cx| {
|
||||
right_pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_2.clone());
|
||||
@@ -6545,12 +6545,12 @@ async fn test_context_collaboration_with_reconnect(
|
||||
|
||||
// Host and guest make changes
|
||||
context_a.update(cx_a, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, model, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Host change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
context_b.update(cx_b, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, model, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Guest change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
@@ -6568,12 +6568,12 @@ async fn test_context_collaboration_with_reconnect(
|
||||
server.disconnect_client(client_a.peer_id().unwrap());
|
||||
server.forbid_connections();
|
||||
context_a.update(cx_a, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, model, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Host offline change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
context_b.update(cx_b, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, model, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Guest offline change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
@@ -6643,7 +6643,7 @@ async fn test_remote_git_branches(
|
||||
executor.run_until_parked();
|
||||
|
||||
let branches_b = cx_b
|
||||
.update(|cx| project_b.update(cx, |project, model, cx| project.branches(root_path.clone(), cx)))
|
||||
.update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -6657,7 +6657,7 @@ async fn test_remote_git_branches(
|
||||
assert_eq!(&branches_b, &branches);
|
||||
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, model, cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
|
||||
})
|
||||
})
|
||||
@@ -6667,8 +6667,8 @@ async fn test_remote_git_branches(
|
||||
executor.run_until_parked();
|
||||
|
||||
let host_branch = cx_a.update(|cx| {
|
||||
project_a.update(cx, |project, model, cx| {
|
||||
project.worktree_store().update(cx, |worktree_store, model, cx| {
|
||||
project_a.update(cx, |project, cx| {
|
||||
project.worktree_store().update(cx, |worktree_store, cx| {
|
||||
worktree_store
|
||||
.current_branch(root_path.clone(), cx)
|
||||
.unwrap()
|
||||
@@ -6680,7 +6680,7 @@ async fn test_remote_git_branches(
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, model, cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
|
||||
})
|
||||
})
|
||||
@@ -6690,8 +6690,8 @@ async fn test_remote_git_branches(
|
||||
executor.run_until_parked();
|
||||
|
||||
let host_branch = cx_a.update(|cx| {
|
||||
project_a.update(cx, |project, model, cx| {
|
||||
project.worktree_store().update(cx, |worktree_store, model, cx| {
|
||||
project_a.update(cx, |project, cx| {
|
||||
project.worktree_store().update(cx, |worktree_store, cx| {
|
||||
worktree_store.current_branch(root_path, cx).unwrap()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,14 +21,14 @@ async fn test_notifications(
|
||||
let notification_events_b = Arc::new(Mutex::new(Vec::new()));
|
||||
client_a.notification_store().update(cx_a, |_, cx| {
|
||||
let events = notification_events_a.clone();
|
||||
cx.subscribe(model, move |_, _, event, _| {
|
||||
cx.subscribe(&cx.handle(), move |_, _, event, _| {
|
||||
events.lock().push(event.clone());
|
||||
})
|
||||
.detach()
|
||||
});
|
||||
client_b.notification_store().update(cx_b, |_, cx| {
|
||||
let events = notification_events_b.clone();
|
||||
cx.subscribe(model, move |_, _, event, _| {
|
||||
cx.subscribe(&cx.handle(), move |_, _, event, _| {
|
||||
events.lock().push(event.clone());
|
||||
})
|
||||
.detach()
|
||||
|
||||
@@ -133,7 +133,7 @@ impl RandomizedTest for RandomChannelBufferTest {
|
||||
) -> Result<(), TestError> {
|
||||
match operation {
|
||||
ChannelBufferOperation::JoinChannelNotes { channel_name } => {
|
||||
let buffer = client.channel_store().update(cx, |store, model, cx| {
|
||||
let buffer = client.channel_store().update(cx, |store, cx| {
|
||||
let channel_id = store
|
||||
.ordered_channels()
|
||||
.find(|(_, c)| c.name == channel_name)
|
||||
@@ -198,9 +198,9 @@ impl RandomizedTest for RandomChannelBufferTest {
|
||||
edits
|
||||
);
|
||||
|
||||
channel_buffer.update(cx, |buffer, model, cx| {
|
||||
channel_buffer.update(cx, |buffer, cx| {
|
||||
let buffer = buffer.buffer();
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot();
|
||||
buffer.edit(
|
||||
edits.into_iter().map(|(range, text)| {
|
||||
|
||||
@@ -508,7 +508,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
|
||||
log::info!("{}: accepting incoming call", client.username);
|
||||
active_call
|
||||
.update(cx, |call, model, cx| call.accept_incoming(model, cx))
|
||||
.update(cx, |call, cx| call.accept_incoming(cx))
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -519,7 +519,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
}
|
||||
|
||||
log::info!("{}: declining incoming call", client.username);
|
||||
active_call.update(cx, |call, model, cx| call.decline_incoming(model, cx))?;
|
||||
active_call.update(cx, |call, cx| call.decline_incoming(cx))?;
|
||||
}
|
||||
|
||||
ClientOperation::LeaveCall => {
|
||||
@@ -529,9 +529,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
}
|
||||
|
||||
log::info!("{}: hanging up", client.username);
|
||||
active_call
|
||||
.update(cx, |call, model, cx| call.hang_up(model, cx))
|
||||
.await?;
|
||||
active_call.update(cx, |call, cx| call.hang_up(cx)).await?;
|
||||
}
|
||||
|
||||
ClientOperation::InviteContactToCall { user_id } => {
|
||||
@@ -539,9 +537,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
|
||||
log::info!("{}: inviting {}", client.username, user_id,);
|
||||
active_call
|
||||
.update(cx, |call, model, cx| {
|
||||
call.invite(user_id.to_proto(), None, model, cx)
|
||||
})
|
||||
.update(cx, |call, cx| call.invite(user_id.to_proto(), None, cx))
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
@@ -584,8 +580,8 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
client.fs().create_dir(&new_root_path).await.unwrap();
|
||||
}
|
||||
project
|
||||
.update(cx, |project, model, cx| {
|
||||
project.find_or_create_worktree(&new_root_path, true, model, cx)
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(&new_root_path, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -619,7 +615,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
} => {
|
||||
let active_call = cx.read(ActiveCall::global);
|
||||
let project = active_call
|
||||
.update(cx, |call, model, cx| {
|
||||
.update(cx, |call, cx| {
|
||||
let room = call.room().cloned()?;
|
||||
let participant = room
|
||||
.read(cx)
|
||||
@@ -630,12 +626,11 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
.iter()
|
||||
.find(|project| project.worktree_root_names[0] == first_root_name)?
|
||||
.id;
|
||||
Some(room.update(cx, |room, model, cx| {
|
||||
Some(room.update(cx, |room, cx| {
|
||||
room.join_project(
|
||||
project_id,
|
||||
client.language_registry().clone(),
|
||||
FakeFs::new(cx.background_executor().clone()),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
@@ -675,9 +670,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
|
||||
ensure_project_shared(&project, client, cx).await;
|
||||
project
|
||||
.update(cx, |p, model, cx| {
|
||||
p.create_entry(project_path, is_dir, model, cx)
|
||||
})
|
||||
.update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -701,9 +694,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
|
||||
ensure_project_shared(&project, client, cx).await;
|
||||
let buffer = project
|
||||
.update(cx, |project, model, cx| {
|
||||
project.open_buffer(project_path, model, cx)
|
||||
})
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))
|
||||
.await?;
|
||||
client.buffers_for_project(&project).insert(buffer);
|
||||
}
|
||||
@@ -729,7 +720,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
);
|
||||
|
||||
ensure_project_shared(&project, client, cx).await;
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot();
|
||||
buffer.edit(
|
||||
edits.into_iter().map(|(range, text)| {
|
||||
@@ -738,7 +729,6 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
(start..end, text)
|
||||
}),
|
||||
None,
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -791,9 +781,8 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
|
||||
ensure_project_shared(&project, client, cx).await;
|
||||
let requested_version = buffer.read_with(cx, |buffer, _| buffer.version());
|
||||
let save = project.update(cx, |project, model, cx| {
|
||||
project.save_buffer(buffer.clone(), model, cx)
|
||||
});
|
||||
let save =
|
||||
project.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx));
|
||||
let save = cx.spawn(|cx| async move {
|
||||
save.await
|
||||
.map_err(|err| anyhow!("save request failed: {:?}", err))?;
|
||||
@@ -836,25 +825,25 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
use futures::{FutureExt as _, TryFutureExt as _};
|
||||
let offset = buffer.read_with(cx, |b, _| b.clip_offset(offset, Bias::Left));
|
||||
|
||||
let process_lsp_request = project.update(cx, |project, model, cx| match kind {
|
||||
let process_lsp_request = project.update(cx, |project, cx| match kind {
|
||||
LspRequestKind::Rename => project
|
||||
.prepare_rename(buffer, offset, model, cx)
|
||||
.prepare_rename(buffer, offset, cx)
|
||||
.map_ok(|_| ())
|
||||
.boxed(),
|
||||
LspRequestKind::Completion => project
|
||||
.completions(&buffer, offset, DEFAULT_COMPLETION_CONTEXT, model, cx)
|
||||
.completions(&buffer, offset, DEFAULT_COMPLETION_CONTEXT, cx)
|
||||
.map_ok(|_| ())
|
||||
.boxed(),
|
||||
LspRequestKind::CodeAction => project
|
||||
.code_actions(&buffer, offset..offset, None, model, cx)
|
||||
.code_actions(&buffer, offset..offset, None, cx)
|
||||
.map(|_| Ok(()))
|
||||
.boxed(),
|
||||
LspRequestKind::Definition => project
|
||||
.definition(&buffer, offset, model, cx)
|
||||
.definition(&buffer, offset, cx)
|
||||
.map_ok(|_| ())
|
||||
.boxed(),
|
||||
LspRequestKind::Highlights => project
|
||||
.document_highlights(&buffer, offset, model, cx)
|
||||
.document_highlights(&buffer, offset, cx)
|
||||
.map_ok(|_| ())
|
||||
.boxed(),
|
||||
});
|
||||
@@ -884,7 +873,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
if detach { "detaching" } else { "awaiting" }
|
||||
);
|
||||
|
||||
let mut search = project.update(cx, |project, model, cx| {
|
||||
let mut search = project.update(cx, |project, cx| {
|
||||
project.search(
|
||||
SearchQuery::text(
|
||||
query,
|
||||
@@ -896,7 +885,6 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1585,9 +1573,7 @@ async fn ensure_project_shared(
|
||||
&& project.read_with(cx, |project, _| project.is_local() && !project.is_shared())
|
||||
{
|
||||
match active_call
|
||||
.update(cx, |call, model, cx| {
|
||||
call.share_project(project.clone(), model, cx)
|
||||
})
|
||||
.update(cx, |call, cx| call.share_project(project.clone(), cx))
|
||||
.await
|
||||
{
|
||||
Ok(project_id) => {
|
||||
|
||||
@@ -73,7 +73,7 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
let remote_http_client = Arc::new(BlockedHttpClient);
|
||||
let node = NodeRuntime::unavailable();
|
||||
let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
|
||||
let _headless_project = server_cx.new_model(|model, cx| {
|
||||
let _headless_project = server_cx.new_model(|cx| {
|
||||
client::init_settings(cx);
|
||||
HeadlessProject::new(
|
||||
HeadlessAppState {
|
||||
@@ -236,7 +236,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
let remote_http_client = Arc::new(BlockedHttpClient);
|
||||
let node = NodeRuntime::unavailable();
|
||||
let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
|
||||
let headless_project = server_cx.new_model(|model, cx| {
|
||||
let headless_project = server_cx.new_model(|cx| {
|
||||
client::init_settings(cx);
|
||||
HeadlessProject::new(
|
||||
HeadlessAppState {
|
||||
@@ -273,7 +273,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
let root_path = ProjectPath::root_path(worktree_id);
|
||||
|
||||
let branches_b = cx_b
|
||||
.update(|cx| project_b.update(cx, |project, model, cx| project.branches(root_path.clone(), cx)))
|
||||
.update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -287,7 +287,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
assert_eq!(&branches_b, &branches);
|
||||
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, model, cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
|
||||
})
|
||||
})
|
||||
@@ -297,10 +297,10 @@ async fn test_ssh_collaboration_git_branches(
|
||||
executor.run_until_parked();
|
||||
|
||||
let server_branch = server_cx.update(|cx| {
|
||||
headless_project.update(cx, |headless_project, model, cx| {
|
||||
headless_project.update(cx, |headless_project, cx| {
|
||||
headless_project
|
||||
.worktree_store
|
||||
.update(cx, |worktree_store, model, cx| {
|
||||
.update(cx, |worktree_store, cx| {
|
||||
worktree_store
|
||||
.current_branch(root_path.clone(), cx)
|
||||
.unwrap()
|
||||
@@ -312,7 +312,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, model, cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
|
||||
})
|
||||
})
|
||||
@@ -322,10 +322,10 @@ async fn test_ssh_collaboration_git_branches(
|
||||
executor.run_until_parked();
|
||||
|
||||
let server_branch = server_cx.update(|cx| {
|
||||
headless_project.update(cx, |headless_project, model, cx| {
|
||||
headless_project.update(cx, |headless_project, cx| {
|
||||
headless_project
|
||||
.worktree_store
|
||||
.update(cx, |worktree_store, model, cx| {
|
||||
.update(cx, |worktree_store, cx| {
|
||||
worktree_store.current_branch(root_path, cx).unwrap()
|
||||
})
|
||||
})
|
||||
@@ -394,7 +394,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||
// User A connects to the remote project via SSH.
|
||||
server_cx.update(HeadlessProject::init);
|
||||
let remote_http_client = Arc::new(BlockedHttpClient);
|
||||
let _headless_project = server_cx.new_model(|model, cx| {
|
||||
let _headless_project = server_cx.new_model(|cx| {
|
||||
client::init_settings(cx);
|
||||
HeadlessProject::new(
|
||||
HeadlessAppState {
|
||||
|
||||
@@ -273,11 +273,10 @@ impl TestServer {
|
||||
git_hosting_provider_registry
|
||||
.register_hosting_provider(Arc::new(git_hosting_providers::Github));
|
||||
|
||||
let user_store = cx.new_model(|model, cx| UserStore::new(client.clone(), model, cx));
|
||||
let workspace_store =
|
||||
cx.new_model(|model, cx| WorkspaceStore::new(client.clone(), model, cx));
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||
let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let session = cx.new_model(|model, cx| AppSession::new(Session::test(), model, cx));
|
||||
let session = cx.new_model(|cx| AppSession::new(Session::test(), cx));
|
||||
let app_state = Arc::new(workspace::AppState {
|
||||
client: client.clone(),
|
||||
user_store: user_store.clone(),
|
||||
@@ -436,11 +435,10 @@ impl TestServer {
|
||||
|
||||
client
|
||||
.channel_store()
|
||||
.update(cx, |channel_store, model, cx| {
|
||||
.update(cx, |channel_store, cx| {
|
||||
channel_store.set_channel_visibility(
|
||||
channel_id,
|
||||
proto::ChannelVisibility::Public,
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -636,7 +634,7 @@ impl TestClient {
|
||||
pub async fn clear_contacts(&self, cx: &mut TestAppContext) {
|
||||
self.app_state
|
||||
.user_store
|
||||
.update(cx, |store, model, _| store.clear_contacts())
|
||||
.update(cx, |store, _| store.clear_contacts())
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -707,9 +705,7 @@ impl TestClient {
|
||||
) -> (Model<Project>, WorktreeId) {
|
||||
let project = self.build_empty_local_project(cx);
|
||||
let (worktree, _) = project
|
||||
.update(cx, |p, model, cx| {
|
||||
p.find_or_create_worktree(root_path, true, model, cx)
|
||||
})
|
||||
.update(cx, |p, cx| p.find_or_create_worktree(root_path, true, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
worktree
|
||||
@@ -736,9 +732,7 @@ impl TestClient {
|
||||
)
|
||||
});
|
||||
let (worktree, _) = project
|
||||
.update(cx, |p, model, cx| {
|
||||
p.find_or_create_worktree(root_path, true, model, cx)
|
||||
})
|
||||
.update(cx, |p, cx| p.find_or_create_worktree(root_path, true, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
(project, worktree.read_with(cx, |tree, _| tree.id()))
|
||||
@@ -760,22 +754,20 @@ impl TestClient {
|
||||
|
||||
pub async fn host_workspace(
|
||||
&self,
|
||||
workspace: &Model<Workspace>,
|
||||
workspace: &View<Workspace>,
|
||||
channel_id: ChannelId,
|
||||
cx: &mut VisualTestContext,
|
||||
) {
|
||||
cx.update(|cx| {
|
||||
let active_call = ActiveCall::global(cx);
|
||||
active_call.update(cx, |call, model, cx| {
|
||||
call.join_channel(channel_id, model, cx)
|
||||
})
|
||||
active_call.update(cx, |call, cx| call.join_channel(channel_id, cx))
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.update(|cx| {
|
||||
let active_call = ActiveCall::global(cx);
|
||||
let project = workspace.read(cx).project().clone();
|
||||
active_call.update(cx, |call, model, cx| call.share_project(project, model, cx))
|
||||
active_call.update(cx, |call, cx| call.share_project(project, cx))
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -786,7 +778,7 @@ impl TestClient {
|
||||
&'a self,
|
||||
channel_id: ChannelId,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (Model<Workspace>, &'a mut VisualTestContext) {
|
||||
) -> (View<Workspace>, &'a mut VisualTestContext) {
|
||||
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -832,42 +824,28 @@ impl TestClient {
|
||||
&'a self,
|
||||
project: &Model<Project>,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (Model<Workspace>, &'a mut VisualTestContext) {
|
||||
) -> (View<Workspace>, &'a mut VisualTestContext) {
|
||||
cx.add_window_view(|cx| {
|
||||
cx.activate_window();
|
||||
Workspace::new(
|
||||
None,
|
||||
project.clone(),
|
||||
self.app_state.clone(),
|
||||
model,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
Workspace::new(None, project.clone(), self.app_state.clone(), cx)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn build_test_workspace<'a>(
|
||||
&'a self,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (Model<Workspace>, &'a mut VisualTestContext) {
|
||||
) -> (View<Workspace>, &'a mut VisualTestContext) {
|
||||
let project = self.build_test_project(cx).await;
|
||||
cx.add_window_view(|cx| {
|
||||
cx.activate_window();
|
||||
Workspace::new(
|
||||
None,
|
||||
project.clone(),
|
||||
self.app_state.clone(),
|
||||
model,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
Workspace::new(None, project.clone(), self.app_state.clone(), cx)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn active_workspace<'a>(
|
||||
&'a self,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (Model<Workspace>, &'a mut VisualTestContext) {
|
||||
) -> (View<Workspace>, &'a mut VisualTestContext) {
|
||||
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||
|
||||
let view = window.root_view(cx).unwrap();
|
||||
@@ -880,11 +858,11 @@ impl TestClient {
|
||||
pub fn open_channel_notes(
|
||||
channel_id: ChannelId,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Task<anyhow::Result<Model<ChannelView>>> {
|
||||
) -> Task<anyhow::Result<View<ChannelView>>> {
|
||||
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||
let view = window.root_view(cx).unwrap();
|
||||
|
||||
cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), model, cx))
|
||||
cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
|
||||
}
|
||||
|
||||
impl Drop for TestClient {
|
||||
|
||||
@@ -11,9 +11,9 @@ use editor::{
|
||||
EditorEvent,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AnyView, AppContext, AppContext, ClipboardItem, Entity as _, EventEmitter,
|
||||
FocusableView, Model, Pixels, Point, Render, Subscription, Task, View, VisualContext as _,
|
||||
WeakView,
|
||||
actions, AnyView, AppContext, ClipboardItem, Entity as _, EventEmitter, FocusableView, Model,
|
||||
Pixels, Point, Render, Subscription, Task, View, ViewContext, VisualContext as _, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use project::Project;
|
||||
use rpc::proto::ChannelVisibility;
|
||||
@@ -38,8 +38,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
pub struct ChannelView {
|
||||
pub editor: Model<Editor>,
|
||||
workspace: WeakModel<Workspace>,
|
||||
pub editor: View<Editor>,
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_buffer: Model<ChannelBuffer>,
|
||||
@@ -52,17 +52,15 @@ impl ChannelView {
|
||||
pub fn open(
|
||||
channel_id: ChannelId,
|
||||
link_position: Option<String>,
|
||||
workspace: Model<Workspace>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
let pane = workspace.read(cx).active_pane().clone();
|
||||
let channel_view = Self::open_in_pane(
|
||||
channel_id,
|
||||
link_position,
|
||||
pane.clone(),
|
||||
workspace.clone(),
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|mut cx| async move {
|
||||
@@ -83,12 +81,11 @@ impl ChannelView {
|
||||
pub fn open_in_pane(
|
||||
channel_id: ChannelId,
|
||||
link_position: Option<String>,
|
||||
pane: Model<Pane>,
|
||||
workspace: Model<Workspace>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
let channel_view = Self::load(channel_id, workspace, model, cx);
|
||||
pane: View<Pane>,
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
let channel_view = Self::load(channel_id, workspace, cx);
|
||||
cx.spawn(|mut cx| async move {
|
||||
let channel_view = channel_view.await?;
|
||||
|
||||
@@ -104,7 +101,7 @@ impl ChannelView {
|
||||
if existing_view.read(cx).channel_buffer == channel_view.read(cx).channel_buffer
|
||||
{
|
||||
if let Some(link_position) = link_position {
|
||||
existing_view.update(cx, |channel_view, model, cx| {
|
||||
existing_view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
});
|
||||
}
|
||||
@@ -123,8 +120,8 @@ impl ChannelView {
|
||||
}
|
||||
|
||||
if let Some(link_position) = link_position {
|
||||
channel_view.update(cx, |channel_view, model, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, model, cx)
|
||||
channel_view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -135,26 +132,24 @@ impl ChannelView {
|
||||
|
||||
pub fn load(
|
||||
channel_id: ChannelId,
|
||||
workspace: Model<Workspace>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
let weak_workspace = workspace.downgrade();
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().to_owned();
|
||||
let channel_store = ChannelStore::global(cx);
|
||||
let language_registry = workspace.app_state().languages.clone();
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
let channel_buffer = channel_store.update(cx, |store, model, cx| {
|
||||
store.open_channel_buffer(channel_id, model, cx)
|
||||
});
|
||||
let channel_buffer =
|
||||
channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let channel_buffer = channel_buffer.await?;
|
||||
let markdown = markdown.await.log_err();
|
||||
|
||||
channel_buffer.update(&mut cx, |channel_buffer, cx| {
|
||||
channel_buffer.buffer().update(cx, |buffer, model, cx| {
|
||||
channel_buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.set_language_registry(language_registry);
|
||||
let Some(markdown) = markdown else {
|
||||
return;
|
||||
@@ -163,16 +158,10 @@ impl ChannelView {
|
||||
})
|
||||
})?;
|
||||
|
||||
cx.new_model(|model, cx| {
|
||||
let mut this = Self::new(
|
||||
project,
|
||||
weak_workspace,
|
||||
channel_store,
|
||||
channel_buffer,
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
this.acknowledge_buffer_version(model, cx);
|
||||
cx.new_view(|cx| {
|
||||
let mut this =
|
||||
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
|
||||
this.acknowledge_buffer_version(cx);
|
||||
this
|
||||
})
|
||||
})
|
||||
@@ -180,39 +169,31 @@ impl ChannelView {
|
||||
|
||||
pub fn new(
|
||||
project: Model<Project>,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_buffer: Model<ChannelBuffer>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let buffer = channel_buffer.read(cx).buffer();
|
||||
let this = model.downgrade();
|
||||
let editor = cx.new_model(|model, cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, None, model, cx);
|
||||
let this = cx.view().downgrade();
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, None, cx);
|
||||
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
|
||||
channel_buffer.clone(),
|
||||
)));
|
||||
editor.set_custom_context_menu(move |_, position, cx| {
|
||||
let this = this.clone();
|
||||
Some(ui::ContextMenu::build(
|
||||
window,
|
||||
cx,
|
||||
move |menu, model, window, cx| {
|
||||
menu.entry("Copy link to section", None, move |cx| {
|
||||
this.update(cx, |this, model, cx| {
|
||||
this.copy_link_for_position(position, cx)
|
||||
})
|
||||
Some(ui::ContextMenu::build(cx, move |menu, _| {
|
||||
menu.entry("Copy link to section", None, move |cx| {
|
||||
this.update(cx, |this, cx| this.copy_link_for_position(position, cx))
|
||||
.ok();
|
||||
})
|
||||
},
|
||||
))
|
||||
})
|
||||
}))
|
||||
});
|
||||
editor
|
||||
});
|
||||
let _editor_event_subscription = cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| {
|
||||
model.emit(e.clone(), cx)
|
||||
});
|
||||
let _editor_event_subscription =
|
||||
cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| cx.emit(e.clone()));
|
||||
|
||||
cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
|
||||
.detach();
|
||||
@@ -233,13 +214,10 @@ impl ChannelView {
|
||||
&mut self,
|
||||
position: String,
|
||||
first_attempt: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let position = Channel::slug(&position).to_lowercase();
|
||||
let snapshot = self
|
||||
.editor
|
||||
.update(cx, |editor, model, cx| editor.snapshot(model, cx));
|
||||
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
|
||||
if let Some(outline) = snapshot.buffer_snapshot.outline(None) {
|
||||
if let Some(item) = outline
|
||||
@@ -247,8 +225,8 @@ impl ChannelView {
|
||||
.iter()
|
||||
.find(|item| &Channel::slug(&item.text).to_lowercase() == &position)
|
||||
{
|
||||
self.editor.update(cx, |editor, model, cx| {
|
||||
editor.change_selections(Some(Autoscroll::focused()), model, cx, |s| {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
|
||||
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)])
|
||||
})
|
||||
});
|
||||
@@ -276,22 +254,15 @@ impl ChannelView {
|
||||
));
|
||||
}
|
||||
|
||||
fn copy_link(&mut self, _: &CopyLink, model: &Model<Self>, cx: &mut AppContext) {
|
||||
let position = self.editor.update(cx, |editor, model, cx| {
|
||||
editor.selections.newest_display(cx).start
|
||||
});
|
||||
self.copy_link_for_position(position, model, cx)
|
||||
fn copy_link(&mut self, _: &CopyLink, cx: &mut ViewContext<Self>) {
|
||||
let position = self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.selections.newest_display(cx).start);
|
||||
self.copy_link_for_position(position, cx)
|
||||
}
|
||||
|
||||
fn copy_link_for_position(
|
||||
&self,
|
||||
position: DisplayPoint,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let snapshot = self
|
||||
.editor
|
||||
.update(cx, |editor, model, cx| editor.snapshot(model, cx));
|
||||
fn copy_link_for_position(&self, position: DisplayPoint, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
|
||||
let mut closest_heading = None;
|
||||
|
||||
@@ -311,7 +282,7 @@ impl ChannelView {
|
||||
let link = channel.notes_link(closest_heading.map(|heading| heading.text), cx);
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(link));
|
||||
self.workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
struct CopyLinkForPositionToast;
|
||||
|
||||
workspace.show_toast(
|
||||
@@ -333,31 +304,29 @@ impl ChannelView {
|
||||
&mut self,
|
||||
_: Model<ChannelBuffer>,
|
||||
event: &ChannelBufferEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, model, cx| {
|
||||
ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
|
||||
editor.set_read_only(true);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}),
|
||||
ChannelBufferEvent::ChannelChanged => {
|
||||
self.editor.update(cx, |_, model, cx| {
|
||||
model.emit(editor::EditorEvent::TitleChanged, cx);
|
||||
model.notify(cx)
|
||||
self.editor.update(cx, |_, cx| {
|
||||
cx.emit(editor::EditorEvent::TitleChanged);
|
||||
cx.notify()
|
||||
});
|
||||
}
|
||||
ChannelBufferEvent::BufferEdited => {
|
||||
if self.editor.read(cx).is_focused(window) {
|
||||
self.acknowledge_buffer_version(model, cx);
|
||||
if self.editor.read(cx).is_focused(cx) {
|
||||
self.acknowledge_buffer_version(cx);
|
||||
} else {
|
||||
self.channel_store.update(cx, |store, model, cx| {
|
||||
self.channel_store.update(cx, |store, cx| {
|
||||
let channel_buffer = self.channel_buffer.read(cx);
|
||||
store.update_latest_notes_version(
|
||||
channel_buffer.channel_id,
|
||||
channel_buffer.epoch(),
|
||||
&channel_buffer.buffer().read(cx).version(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -367,19 +336,18 @@ impl ChannelView {
|
||||
}
|
||||
}
|
||||
|
||||
fn acknowledge_buffer_version(&mut self, model: &Model<ChannelView>, cx: &mut AppContext) {
|
||||
self.channel_store.update(cx, |store, model, cx| {
|
||||
fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<ChannelView>) {
|
||||
self.channel_store.update(cx, |store, cx| {
|
||||
let channel_buffer = self.channel_buffer.read(cx);
|
||||
store.acknowledge_notes_version(
|
||||
channel_buffer.channel_id,
|
||||
channel_buffer.epoch(),
|
||||
&channel_buffer.buffer().read(cx).version(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.channel_buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.acknowledge_buffer_version(model, cx);
|
||||
self.channel_buffer.update(cx, |buffer, cx| {
|
||||
buffer.acknowledge_buffer_version(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -387,12 +355,7 @@ impl ChannelView {
|
||||
impl EventEmitter<EditorEvent> for ChannelView {}
|
||||
|
||||
impl Render for ChannelView {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::copy_link))
|
||||
@@ -412,19 +375,19 @@ impl Item for ChannelView {
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
self_handle: &'a Model<Self>,
|
||||
self_handle: &'a View<Self>,
|
||||
_: &'a AppContext,
|
||||
) -> Option<AnyView> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.model())
|
||||
Some(self_handle.to_any())
|
||||
} else if type_id == TypeId::of::<Editor>() {
|
||||
Some(self.editor.model())
|
||||
Some(self.editor.to_any())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_icon(&self, window: &Window, cx: &AppContext) -> Option<Icon> {
|
||||
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
|
||||
let channel = self.channel(cx)?;
|
||||
let icon = match channel.visibility {
|
||||
ChannelVisibility::Public => IconName::Public,
|
||||
@@ -434,12 +397,7 @@ impl Item for ChannelView {
|
||||
Some(Icon::new(icon))
|
||||
}
|
||||
|
||||
fn tab_content(
|
||||
&self,
|
||||
params: TabContentParams,
|
||||
window: &Window,
|
||||
cx: &AppContext,
|
||||
) -> gpui::AnyElement {
|
||||
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> gpui::AnyElement {
|
||||
let (channel_name, status) = if let Some(channel) = self.channel(cx) {
|
||||
let status = match (
|
||||
self.channel_buffer.read(cx).buffer().read(cx).read_only(),
|
||||
@@ -479,16 +437,14 @@ impl Item for ChannelView {
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_: Option<WorkspaceId>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<Model<Self>> {
|
||||
Some(cx.new_model(|model, cx| {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<View<Self>> {
|
||||
Some(cx.new_view(|cx| {
|
||||
Self::new(
|
||||
self.project.clone(),
|
||||
self.workspace.clone(),
|
||||
self.channel_store.clone(),
|
||||
self.channel_buffer.clone(),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
@@ -498,27 +454,21 @@ impl Item for ChannelView {
|
||||
false
|
||||
}
|
||||
|
||||
fn navigate(&mut self, data: Box<dyn Any>, model: &Model<Self>, cx: &mut AppContext) -> bool {
|
||||
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
|
||||
self.editor
|
||||
.update(cx, |editor, model, cx| editor.navigate(data, model, cx))
|
||||
.update(cx, |editor, cx| editor.navigate(data, cx))
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, Item::deactivated)
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
history: ItemNavHistory,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.editor.update(cx, |editor, model, cx| {
|
||||
Item::set_nav_history(editor, history, model, cx)
|
||||
})
|
||||
fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Model<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
@@ -540,7 +490,7 @@ impl FollowableItem for ChannelView {
|
||||
self.remote_id
|
||||
}
|
||||
|
||||
fn to_state_proto(&self, window: &Window, cx: &AppContext) -> Option<proto::view::Variant> {
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
|
||||
let channel_buffer = self.channel_buffer.read(cx);
|
||||
if !channel_buffer.is_connected() {
|
||||
return None;
|
||||
@@ -550,7 +500,7 @@ impl FollowableItem for ChannelView {
|
||||
proto::view::ChannelView {
|
||||
channel_id: channel_buffer.channel_id.0,
|
||||
editor: if let Some(proto::view::Variant::Editor(proto)) =
|
||||
self.editor.read(cx).to_state_proto(model, cx)
|
||||
self.editor.read(cx).to_state_proto(cx)
|
||||
{
|
||||
Some(proto)
|
||||
} else {
|
||||
@@ -561,12 +511,11 @@ impl FollowableItem for ChannelView {
|
||||
}
|
||||
|
||||
fn from_state_proto(
|
||||
workspace: Model<workspace::Workspace>,
|
||||
workspace: View<workspace::Workspace>,
|
||||
remote_id: workspace::ViewId,
|
||||
state: &mut Option<proto::view::Variant>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> Option<gpui::Task<anyhow::Result<Model<Self>>>> {
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<gpui::Task<anyhow::Result<View<Self>>>> {
|
||||
let Some(proto::view::Variant::ChannelView(_)) = state else {
|
||||
return None;
|
||||
};
|
||||
@@ -574,16 +523,16 @@ impl FollowableItem for ChannelView {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let open = ChannelView::load(ChannelId(state.channel_id), workspace, model, cx);
|
||||
let open = ChannelView::load(ChannelId(state.channel_id), workspace, cx);
|
||||
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
let this = open.await?;
|
||||
|
||||
let task = this.update(&mut cx, |this, model, cx| {
|
||||
let task = this.update(&mut cx, |this, cx| {
|
||||
this.remote_id = Some(remote_id);
|
||||
|
||||
if let Some(state) = state.editor {
|
||||
Some(this.editor.update(cx, |editor, model, cx| {
|
||||
Some(this.editor.update(cx, |editor, cx| {
|
||||
editor.apply_update_proto(
|
||||
&this.project,
|
||||
proto::update_view::Variant::Editor(proto::update_view::Editor {
|
||||
@@ -594,7 +543,6 @@ impl FollowableItem for ChannelView {
|
||||
scroll_y: state.scroll_y,
|
||||
..Default::default()
|
||||
}),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
@@ -615,38 +563,31 @@ impl FollowableItem for ChannelView {
|
||||
&self,
|
||||
event: &EditorEvent,
|
||||
update: &mut Option<proto::update_view::Variant>,
|
||||
window: &Window,
|
||||
cx: &AppContext,
|
||||
cx: &WindowContext,
|
||||
) -> bool {
|
||||
self.editor
|
||||
.read(cx)
|
||||
.add_event_to_update_proto(event, update, model, cx)
|
||||
.add_event_to_update_proto(event, update, cx)
|
||||
}
|
||||
|
||||
fn apply_update_proto(
|
||||
&mut self,
|
||||
project: &Model<Project>,
|
||||
message: proto::update_view::Variant,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> gpui::Task<anyhow::Result<()>> {
|
||||
self.editor.update(cx, |editor, model, cx| {
|
||||
editor.apply_update_proto(project, message, model, cx)
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.apply_update_proto(project, message, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn set_leader_peer_id(
|
||||
&mut self,
|
||||
leader_peer_id: Option<PeerId>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.editor.update(cx, |editor, model, cx| {
|
||||
editor.set_leader_peer_id(leader_peer_id, model, cx)
|
||||
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_leader_peer_id(leader_peer_id, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_project_item(&self, _window: &Window, cx: &AppContext) -> bool {
|
||||
fn is_project_item(&self, _cx: &WindowContext) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -654,7 +595,7 @@ impl FollowableItem for ChannelView {
|
||||
Editor::to_follow_event(event)
|
||||
}
|
||||
|
||||
fn dedup(&self, existing: &Self, window: &Window, cx: &AppContext) -> Option<Dedup> {
|
||||
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
|
||||
let existing = existing.channel_buffer.read(cx);
|
||||
if self.channel_buffer.read(cx).channel_id == existing.channel_id {
|
||||
if existing.is_connected() {
|
||||
|
||||
@@ -7,10 +7,10 @@ use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::{actions, Editor};
|
||||
use gpui::{
|
||||
actions, div, list, prelude::*, px, Action, AppContext, AppContext, ClipboardItem, CursorStyle,
|
||||
DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, HighlightStyle,
|
||||
ListOffset, ListScrollEvent, ListState, Model, Render, Stateful, Subscription, Task, View,
|
||||
VisualContext, WeakView,
|
||||
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, ClipboardItem,
|
||||
CursorStyle, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight,
|
||||
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, Stateful, Subscription,
|
||||
Task, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use menu::Confirm;
|
||||
@@ -38,7 +38,7 @@ const CHAT_PANEL_KEY: &str = "ChatPanel";
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
||||
workspace.register_action(model, |workspace, _: &ToggleFocus, cx| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<ChatPanel>(cx);
|
||||
});
|
||||
})
|
||||
@@ -51,7 +51,7 @@ pub struct ChatPanel {
|
||||
languages: Arc<LanguageRegistry>,
|
||||
message_list: ListState,
|
||||
active_chat: Option<(Model<ChannelChat>, Subscription)>,
|
||||
message_editor: Model<MessageEditor>,
|
||||
message_editor: View<MessageEditor>,
|
||||
local_timezone: UtcOffset,
|
||||
fs: Arc<dyn Fs>,
|
||||
width: Option<Pixels>,
|
||||
@@ -74,35 +74,30 @@ struct SerializedChatPanel {
|
||||
actions!(chat_panel, [ToggleFocus]);
|
||||
|
||||
impl ChatPanel {
|
||||
pub fn new(
|
||||
workspace: &mut Workspace,
|
||||
model: &Model<Workspace>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self> {
|
||||
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let client = workspace.app_state().client.clone();
|
||||
let channel_store = ChannelStore::global(cx);
|
||||
let user_store = workspace.app_state().user_store.clone();
|
||||
let languages = workspace.app_state().languages.clone();
|
||||
|
||||
let input_editor = cx.new_model(|model, cx| {
|
||||
let input_editor = cx.new_view(|cx| {
|
||||
MessageEditor::new(
|
||||
languages.clone(),
|
||||
user_store.clone(),
|
||||
None,
|
||||
cx.new_model(|model, cx| Editor::auto_height(4, model, cx)),
|
||||
model,
|
||||
cx.new_view(|cx| Editor::auto_height(4, cx)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.new_model(|model: &Model<Self>, cx: &mut AppContext| {
|
||||
let view = model.downgrade();
|
||||
cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
let view = cx.view().downgrade();
|
||||
let message_list =
|
||||
ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| {
|
||||
if let Some(view) = view.upgrade() {
|
||||
view.update(cx, |view, model, cx| {
|
||||
view.render_message(ix, model, cx).into_any_element()
|
||||
view.update(cx, |view, cx| {
|
||||
view.render_message(ix, cx).into_any_element()
|
||||
})
|
||||
} else {
|
||||
div().into_any()
|
||||
@@ -132,7 +127,7 @@ impl ChatPanel {
|
||||
active: false,
|
||||
width: None,
|
||||
markdown_data: Default::default(),
|
||||
focus_handle: window.focus_handle(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
open_context_menu: None,
|
||||
highlighted_message: None,
|
||||
last_acknowledged_message_id: None,
|
||||
@@ -143,7 +138,7 @@ impl ChatPanel {
|
||||
.room()
|
||||
.and_then(|room| room.read(cx).channel_id())
|
||||
{
|
||||
this.select_channel(channel_id, None, model, cx)
|
||||
this.select_channel(channel_id, None, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
@@ -152,7 +147,7 @@ impl ChatPanel {
|
||||
move |this: &mut Self, call, event: &room::Event, cx| match event {
|
||||
room::Event::RoomJoined { channel_id } => {
|
||||
if let Some(channel_id) = channel_id {
|
||||
this.select_channel(*channel_id, None, model, cx)
|
||||
this.select_channel(*channel_id, None, cx)
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
if call
|
||||
@@ -160,13 +155,13 @@ impl ChatPanel {
|
||||
.room()
|
||||
.is_some_and(|room| room.read(cx).contains_guests())
|
||||
{
|
||||
model.emit(PanelEvent::Activate, cx)
|
||||
cx.emit(PanelEvent::Activate)
|
||||
}
|
||||
}
|
||||
}
|
||||
room::Event::RoomLeft { channel_id } => {
|
||||
if channel_id == &this.channel_id(cx) {
|
||||
model.emit(PanelEvent::Close, cx)
|
||||
cx.emit(PanelEvent::Close)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -192,10 +187,9 @@ impl ChatPanel {
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
workspace: WeakModel<Workspace>,
|
||||
window: AnyWindowHandle,
|
||||
cx: AsyncAppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let serialized_panel = if let Some(panel) = cx
|
||||
.background_executor()
|
||||
@@ -210,11 +204,11 @@ impl ChatPanel {
|
||||
};
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let panel = Self::new(workspace, model, cx);
|
||||
let panel = Self::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, model, cx| {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width.map(|r| r.round());
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
panel
|
||||
@@ -222,7 +216,7 @@ impl ChatPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let width = self.width;
|
||||
self.pending_serialization = cx.background_executor().spawn(
|
||||
async move {
|
||||
@@ -238,23 +232,18 @@ impl ChatPanel {
|
||||
);
|
||||
}
|
||||
|
||||
fn set_active_chat(
|
||||
&mut self,
|
||||
chat: Model<ChannelChat>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
|
||||
if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
|
||||
self.markdown_data.clear();
|
||||
self.message_list.reset(chat.read(cx).message_count());
|
||||
self.message_editor.update(cx, |editor, model, cx| {
|
||||
editor.set_channel_chat(chat.clone(), model, cx);
|
||||
self.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_channel_chat(chat.clone(), cx);
|
||||
editor.clear_reply_to_message_id();
|
||||
});
|
||||
let subscription = cx.subscribe(&chat, Self::channel_did_change);
|
||||
self.active_chat = Some((chat, subscription));
|
||||
self.acknowledge_last_message(model, cx);
|
||||
model.notify(cx);
|
||||
self.acknowledge_last_message(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,8 +251,7 @@ impl ChatPanel {
|
||||
&mut self,
|
||||
_: Model<ChannelChat>,
|
||||
event: &ChannelChatEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ChannelChatEvent::MessagesUpdated {
|
||||
@@ -272,7 +260,7 @@ impl ChatPanel {
|
||||
} => {
|
||||
self.message_list.splice(old_range.clone(), *new_count);
|
||||
if self.active {
|
||||
self.acknowledge_last_message(model, cx);
|
||||
self.acknowledge_last_message(cx);
|
||||
}
|
||||
}
|
||||
ChannelChatEvent::UpdateMessage {
|
||||
@@ -287,16 +275,16 @@ impl ChatPanel {
|
||||
message_id,
|
||||
} => {
|
||||
if !self.active {
|
||||
self.channel_store.update(cx, |store, model, cx| {
|
||||
store.update_latest_message_id(*channel_id, *message_id, model, cx)
|
||||
self.channel_store.update(cx, |store, cx| {
|
||||
store.update_latest_message_id(*channel_id, *message_id, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn acknowledge_last_message(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn acknowledge_last_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if self.active && self.is_scrolled_to_bottom {
|
||||
if let Some((chat, _)) = &self.active_chat {
|
||||
if let Some(channel_id) = self.channel_id(cx) {
|
||||
@@ -306,8 +294,8 @@ impl ChatPanel {
|
||||
.last_acknowledge_message_id(channel_id);
|
||||
}
|
||||
|
||||
chat.update(cx, |chat, model, cx| {
|
||||
chat.acknowledge_last_message(model, cx);
|
||||
chat.update(cx, |chat, cx| {
|
||||
chat.acknowledge_last_message(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -317,8 +305,7 @@ impl ChatPanel {
|
||||
&mut self,
|
||||
message_id: Option<ChannelMessageId>,
|
||||
reply_to_message: &Option<ChannelMessage>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let reply_to_message = match reply_to_message {
|
||||
None => {
|
||||
@@ -382,8 +369,8 @@ impl ChatPanel {
|
||||
),
|
||||
)
|
||||
.cursor(CursorStyle::PointingHand)
|
||||
.tooltip(|window, cx| Tooltip::text("Go to message", cx))
|
||||
.on_click(model.listener(move |chat_panel, _, cx| {
|
||||
.tooltip(|cx| Tooltip::text("Go to message", cx))
|
||||
.on_click(cx.listener(move |chat_panel, _, cx| {
|
||||
if let Some(channel_id) = current_channel_id {
|
||||
chat_panel
|
||||
.select_channel(channel_id, reply_to_message_id.into(), cx)
|
||||
@@ -393,15 +380,10 @@ impl ChatPanel {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_message(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_chat = &self.active_chat.as_ref().unwrap().0;
|
||||
let (message, is_continuation_from_previous, is_admin) =
|
||||
active_chat.update(cx, |active_chat, model, cx| {
|
||||
active_chat.update(cx, |active_chat, cx| {
|
||||
let is_admin = self
|
||||
.channel_store
|
||||
.read(cx)
|
||||
@@ -499,7 +481,6 @@ impl ChatPanel {
|
||||
el.child(self.render_replied_to_message(
|
||||
Some(message.id),
|
||||
&reply_to_message,
|
||||
model,
|
||||
cx,
|
||||
))
|
||||
.when(is_continuation_from_previous, |this| this.mt_2())
|
||||
@@ -549,7 +530,7 @@ impl ChatPanel {
|
||||
.w_full()
|
||||
.text_ui_sm(cx)
|
||||
.id(element_id)
|
||||
.child(text.element("body".into(), model, cx)),
|
||||
.child(text.element("body".into(), cx)),
|
||||
)
|
||||
.when(self.has_open_menu(message_id), |el| {
|
||||
el.bg(cx.theme().colors().element_selected)
|
||||
@@ -579,14 +560,8 @@ impl ChatPanel {
|
||||
},
|
||||
)
|
||||
.child(
|
||||
self.render_popover_buttons(
|
||||
window,
|
||||
cx,
|
||||
message_id,
|
||||
can_delete_message,
|
||||
can_edit_message,
|
||||
)
|
||||
.mt_neg_2p5(),
|
||||
self.render_popover_buttons(cx, message_id, can_delete_message, can_edit_message)
|
||||
.mt_neg_2p5(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -597,12 +572,7 @@ impl ChatPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_popover_button(
|
||||
&self,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
child: Stateful<Div>,
|
||||
) -> Div {
|
||||
fn render_popover_button(&self, cx: &ViewContext<Self>, child: Stateful<Div>) -> Div {
|
||||
div()
|
||||
.w_6()
|
||||
.bg(cx.theme().colors().element_background)
|
||||
@@ -612,8 +582,7 @@ impl ChatPanel {
|
||||
|
||||
fn render_popover_buttons(
|
||||
&self,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
cx: &ViewContext<Self>,
|
||||
message_id: Option<u64>,
|
||||
can_delete_message: bool,
|
||||
can_edit_message: bool,
|
||||
@@ -632,22 +601,21 @@ impl ChatPanel {
|
||||
.when_some(message_id, |el, message_id| {
|
||||
el.child(
|
||||
self.render_popover_button(
|
||||
window,
|
||||
cx,
|
||||
div()
|
||||
.id("reply")
|
||||
.child(
|
||||
IconButton::new(("reply", message_id), IconName::ReplyArrowRight)
|
||||
.on_click(model.listener(move |this, _, cx| {
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.cancel_edit_message(cx);
|
||||
|
||||
this.message_editor.update(cx, |editor, model, cx| {
|
||||
this.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_reply_to_message_id(message_id);
|
||||
editor.focus_handle(cx).focus(window);
|
||||
editor.focus_handle(cx).focus(cx);
|
||||
})
|
||||
})),
|
||||
)
|
||||
.tooltip(|window, cx| Tooltip::text("Reply", cx)),
|
||||
.tooltip(|cx| Tooltip::text("Reply", cx)),
|
||||
),
|
||||
)
|
||||
})
|
||||
@@ -655,14 +623,13 @@ impl ChatPanel {
|
||||
el.when(can_edit_message, |el| {
|
||||
el.child(
|
||||
self.render_popover_button(
|
||||
window,
|
||||
cx,
|
||||
div()
|
||||
.id("edit")
|
||||
.child(
|
||||
IconButton::new(("edit", message_id), IconName::Pencil)
|
||||
.on_click(model.listener(move |this, _, cx| {
|
||||
this.message_editor.update(cx, |editor, model, cx| {
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.message_editor.update(cx, |editor, cx| {
|
||||
editor.clear_reply_to_message_id();
|
||||
|
||||
let message = this
|
||||
@@ -683,17 +650,17 @@ impl ChatPanel {
|
||||
.as_singleton()
|
||||
.expect("message editor must be singleton");
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_text(message.body.clone(), cx)
|
||||
});
|
||||
|
||||
editor.set_edit_message_id(message_id);
|
||||
editor.focus_handle(cx).focus(window);
|
||||
editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
})
|
||||
})),
|
||||
)
|
||||
.tooltip(|window, cx| Tooltip::text("Edit", cx)),
|
||||
.tooltip(|cx| Tooltip::text("Edit", cx)),
|
||||
),
|
||||
)
|
||||
})
|
||||
@@ -703,7 +670,6 @@ impl ChatPanel {
|
||||
|
||||
el.child(
|
||||
self.render_popover_button(
|
||||
window,
|
||||
cx,
|
||||
div()
|
||||
.child(
|
||||
@@ -712,32 +678,30 @@ impl ChatPanel {
|
||||
("trigger", message_id),
|
||||
IconName::Ellipsis,
|
||||
))
|
||||
.menu(move |window, cx| {
|
||||
.menu(move |cx| {
|
||||
Some(Self::render_message_menu(
|
||||
&this,
|
||||
message_id,
|
||||
can_delete_message,
|
||||
model,
|
||||
cx,
|
||||
))
|
||||
}),
|
||||
)
|
||||
.id("more")
|
||||
.tooltip(|window, cx| Tooltip::text("More", cx)),
|
||||
.tooltip(|cx| Tooltip::text("More", cx)),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_message_menu(
|
||||
this: &Model<Self>,
|
||||
this: &View<Self>,
|
||||
message_id: u64,
|
||||
can_delete_message: bool,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> Model<ContextMenu> {
|
||||
cx: &mut WindowContext,
|
||||
) -> View<ContextMenu> {
|
||||
let menu = {
|
||||
ContextMenu::build(window, cx, move |menu, model, window, _cx| {
|
||||
ContextMenu::build(cx, move |menu, cx| {
|
||||
menu.entry(
|
||||
"Copy message text",
|
||||
None,
|
||||
@@ -759,7 +723,7 @@ impl ChatPanel {
|
||||
})
|
||||
})
|
||||
};
|
||||
this.update(cx, |this, model, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
let subscription = cx.subscribe(&menu, |this: &mut Self, _, _: &DismissEvent, _| {
|
||||
this.open_context_menu = None;
|
||||
});
|
||||
@@ -821,27 +785,25 @@ impl ChatPanel {
|
||||
rich_text
|
||||
}
|
||||
|
||||
fn send(&mut self, _: &Confirm, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
if let Some((chat, _)) = self.active_chat.as_ref() {
|
||||
let message = self
|
||||
.message_editor
|
||||
.update(cx, |editor, model, cx| editor.take_message(model, cx));
|
||||
.update(cx, |editor, cx| editor.take_message(cx));
|
||||
|
||||
if let Some(id) = self.message_editor.read(cx).edit_message_id() {
|
||||
self.message_editor.update(cx, |editor, model, _| {
|
||||
self.message_editor.update(cx, |editor, _| {
|
||||
editor.clear_edit_message_id();
|
||||
});
|
||||
|
||||
if let Some(task) = chat
|
||||
.update(cx, |chat, model, cx| {
|
||||
chat.update_message(id, message, model, cx)
|
||||
})
|
||||
.update(cx, |chat, cx| chat.update_message(id, message, cx))
|
||||
.log_err()
|
||||
{
|
||||
task.detach();
|
||||
}
|
||||
} else if let Some(task) = chat
|
||||
.update(cx, |chat, model, cx| chat.send_message(message, model, cx))
|
||||
.update(cx, |chat, cx| chat.send_message(message, cx))
|
||||
.log_err()
|
||||
{
|
||||
task.detach();
|
||||
@@ -849,18 +811,16 @@ impl ChatPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_message(&mut self, id: u64, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn remove_message(&mut self, id: u64, cx: &mut ViewContext<Self>) {
|
||||
if let Some((chat, _)) = self.active_chat.as_ref() {
|
||||
chat.update(cx, |chat, model, cx| {
|
||||
chat.remove_message(id, model, cx).detach()
|
||||
})
|
||||
chat.update(cx, |chat, cx| chat.remove_message(id, cx).detach())
|
||||
}
|
||||
}
|
||||
|
||||
fn load_more_messages(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn load_more_messages(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some((chat, _)) = self.active_chat.as_ref() {
|
||||
chat.update(cx, |channel, model, cx| {
|
||||
if let Some(task) = channel.load_more_messages(model, cx) {
|
||||
chat.update(cx, |channel, cx| {
|
||||
if let Some(task) = channel.load_more_messages(cx) {
|
||||
task.detach();
|
||||
}
|
||||
})
|
||||
@@ -871,8 +831,7 @@ impl ChatPanel {
|
||||
&mut self,
|
||||
selected_channel_id: ChannelId,
|
||||
scroll_to_message_id: Option<u64>,
|
||||
model: &Model<ChatPanel>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<ChatPanel>,
|
||||
) -> Task<Result<()>> {
|
||||
let open_chat = self
|
||||
.active_chat
|
||||
@@ -882,16 +841,16 @@ impl ChatPanel {
|
||||
.then(|| Task::ready(anyhow::Ok(chat.clone())))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
self.channel_store.update(cx, |store, model, cx| {
|
||||
store.open_channel_chat(selected_channel_id, model, cx)
|
||||
self.channel_store.update(cx, |store, cx| {
|
||||
store.open_channel_chat(selected_channel_id, cx)
|
||||
})
|
||||
});
|
||||
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let chat = open_chat.await?;
|
||||
let highlight_message_id = scroll_to_message_id;
|
||||
let scroll_to_message_id = this.update(&mut cx, |this, model, cx| {
|
||||
this.set_active_chat(chat.clone(), model, cx);
|
||||
let scroll_to_message_id = this.update(&mut cx, |this, cx| {
|
||||
this.set_active_chat(chat.clone(), cx);
|
||||
|
||||
scroll_to_message_id.or(this.last_acknowledged_message_id)
|
||||
})?;
|
||||
@@ -901,14 +860,14 @@ impl ChatPanel {
|
||||
ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone())
|
||||
.await
|
||||
{
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(highlight_message_id) = highlight_message_id {
|
||||
let task = cx.spawn({
|
||||
|this, mut cx| async move {
|
||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.highlighted_message.take();
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -922,7 +881,7 @@ impl ChatPanel {
|
||||
item_ix,
|
||||
offset_in_item: px(0.0),
|
||||
});
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
})?;
|
||||
}
|
||||
@@ -932,13 +891,13 @@ impl ChatPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn close_reply_preview(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn close_reply_preview(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.message_editor
|
||||
.update(cx, |editor, model, _| editor.clear_reply_to_message_id());
|
||||
.update(cx, |editor, _| editor.clear_reply_to_message_id());
|
||||
}
|
||||
|
||||
fn cancel_edit_message(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
self.message_editor.update(cx, |editor, model, cx| {
|
||||
fn cancel_edit_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.message_editor.update(cx, |editor, cx| {
|
||||
// only clear the editor input if we were editing a message
|
||||
if editor.edit_message_id().is_none() {
|
||||
return;
|
||||
@@ -954,18 +913,13 @@ impl ChatPanel {
|
||||
.as_singleton()
|
||||
.expect("message editor must be singleton");
|
||||
|
||||
buffer.update(cx, |buffer, model, cx| buffer.set_text("", model, cx));
|
||||
buffer.update(cx, |buffer, cx| buffer.set_text("", cx));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ChatPanel {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let channel_id = self
|
||||
.active_chat
|
||||
.as_ref()
|
||||
@@ -1017,7 +971,6 @@ impl Render for ChatPanel {
|
||||
.full_width()
|
||||
.key_binding(KeyBinding::for_action(
|
||||
&collab_panel::ToggleFocus,
|
||||
model,
|
||||
cx,
|
||||
))
|
||||
.on_click(|_, cx| {
|
||||
@@ -1046,8 +999,8 @@ impl Render for ChatPanel {
|
||||
.child(
|
||||
IconButton::new("cancel-edit-message", IconName::Close)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(|window, cx| Tooltip::text("Cancel edit message", cx))
|
||||
.on_click(model.listener(move |this, _, cx| {
|
||||
.tooltip(|cx| Tooltip::text("Cancel edit message", cx))
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.cancel_edit_message(cx);
|
||||
})),
|
||||
),
|
||||
@@ -1108,8 +1061,8 @@ impl Render for ChatPanel {
|
||||
.child(
|
||||
IconButton::new("close-reply-preview", IconName::Close)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(|window, cx| Tooltip::text("Close reply", cx))
|
||||
.on_click(model.listener(move |this, _, cx| {
|
||||
.tooltip(|cx| Tooltip::text("Close reply", cx))
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.close_reply_preview(cx);
|
||||
})),
|
||||
),
|
||||
@@ -1143,7 +1096,7 @@ impl FocusableView for ChatPanel {
|
||||
}
|
||||
|
||||
impl Panel for ChatPanel {
|
||||
fn position(&self, window: &gpui::Window, cx: &gpui::AppContext) -> DockPosition {
|
||||
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
|
||||
ChatPanelSettings::get_global(cx).dock
|
||||
}
|
||||
|
||||
@@ -1151,7 +1104,7 @@ impl Panel for ChatPanel {
|
||||
matches!(position, DockPosition::Left | DockPosition::Right)
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position: DockPosition, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
|
||||
settings::update_settings_file::<ChatPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
@@ -1159,21 +1112,21 @@ impl Panel for ChatPanel {
|
||||
);
|
||||
}
|
||||
|
||||
fn size(&self, window: &gpui::Window, cx: &gpui::AppContext) -> Pixels {
|
||||
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
|
||||
self.width
|
||||
.unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<Pixels>, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
self.width = size;
|
||||
self.serialize(model, cx);
|
||||
model.notify(cx);
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_active(&mut self, active: bool, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
||||
self.active = active;
|
||||
if active {
|
||||
self.acknowledge_last_message(model, cx);
|
||||
self.acknowledge_last_message(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1181,11 +1134,11 @@ impl Panel for ChatPanel {
|
||||
"ChatPanel"
|
||||
}
|
||||
|
||||
fn icon(&self, window: &Window, cx: &AppContext) -> Option<ui::IconName> {
|
||||
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
|
||||
Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _window: &Window, cx: &AppContext) -> Option<&'static str> {
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
Some("Chat Panel")
|
||||
}
|
||||
|
||||
@@ -1193,7 +1146,7 @@ impl Panel for ChatPanel {
|
||||
Box::new(ToggleFocus)
|
||||
}
|
||||
|
||||
fn starts_open(&self, window: &Window, cx: &AppContext) -> bool {
|
||||
fn starts_open(&self, cx: &WindowContext) -> bool {
|
||||
ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.room()
|
||||
|
||||
@@ -5,8 +5,8 @@ use collections::HashSet;
|
||||
use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, FontStyle, FontWeight, HighlightStyle, IntoElement, Model, Render,
|
||||
Task, TextStyle, View, WeakView,
|
||||
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
|
||||
Render, Task, TextStyle, View, ViewContext, WeakView,
|
||||
};
|
||||
use language::{
|
||||
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
|
||||
@@ -37,7 +37,7 @@ static MENTIONS_SEARCH: LazyLock<SearchQuery> = LazyLock::new(|| {
|
||||
});
|
||||
|
||||
pub struct MessageEditor {
|
||||
pub editor: Model<Editor>,
|
||||
pub editor: View<Editor>,
|
||||
user_store: Model<UserStore>,
|
||||
channel_chat: Option<Model<ChannelChat>>,
|
||||
mentions: Vec<UserId>,
|
||||
@@ -46,7 +46,7 @@ pub struct MessageEditor {
|
||||
edit_message_id: Option<u64>,
|
||||
}
|
||||
|
||||
struct MessageEditorCompletionProvider(WeakModel<MessageEditor>);
|
||||
struct MessageEditorCompletionProvider(WeakView<MessageEditor>);
|
||||
|
||||
impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
fn completions(
|
||||
@@ -54,13 +54,12 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_position: language::Anchor,
|
||||
_: editor::CompletionContext,
|
||||
model: &Model<Editor>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<anyhow::Result<Vec<Completion>>> {
|
||||
let Some(handle) = self.0.upgrade() else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
handle.update(cx, |message_editor, model, cx| {
|
||||
handle.update(cx, |message_editor, cx| {
|
||||
message_editor.completions(buffer, buffer_position, cx)
|
||||
})
|
||||
}
|
||||
@@ -70,8 +69,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
_buffer: Model<Buffer>,
|
||||
_completion_indices: Vec<usize>,
|
||||
_completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
model: &Model<Editor>,
|
||||
_cx: &mut AppContext,
|
||||
_cx: &mut ViewContext<Editor>,
|
||||
) -> Task<anyhow::Result<bool>> {
|
||||
Task::ready(Ok(false))
|
||||
}
|
||||
@@ -81,8 +79,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
_buffer: Model<Buffer>,
|
||||
_completion: Completion,
|
||||
_push_to_history: bool,
|
||||
model: &Model<Editor>,
|
||||
_cx: &mut AppContext,
|
||||
_cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Option<language::Transaction>>> {
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
@@ -93,8 +90,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
_position: language::Anchor,
|
||||
text: &str,
|
||||
_trigger_in_words: bool,
|
||||
model: &Model<Editor>,
|
||||
_cx: &mut AppContext,
|
||||
_cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
text == "@"
|
||||
}
|
||||
@@ -105,17 +101,16 @@ impl MessageEditor {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
user_store: Model<UserStore>,
|
||||
channel_chat: Option<Model<ChannelChat>>,
|
||||
editor: Model<Editor>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
editor: View<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let this = model.downgrade();
|
||||
editor.update(cx, |editor, model, cx| {
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, model, cx);
|
||||
let this = cx.view().downgrade();
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_use_autoclose(false);
|
||||
editor.set_show_gutter(false, model, cx);
|
||||
editor.set_show_wrap_guides(false, model, cx);
|
||||
editor.set_show_indent_guides(false, model, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_show_wrap_guides(false, cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_completion_provider(Some(Box::new(MessageEditorCompletionProvider(this))));
|
||||
editor.set_auto_replace_emoji_shortcode(
|
||||
MessageEditorSettings::get_global(cx)
|
||||
@@ -133,7 +128,7 @@ impl MessageEditor {
|
||||
|
||||
cx.subscribe(&buffer, Self::on_buffer_event).detach();
|
||||
cx.observe_global::<settings::SettingsStore>(|view, cx| {
|
||||
view.editor.update(cx, |editor, model, cx| {
|
||||
view.editor.update(cx, |editor, cx| {
|
||||
editor.set_auto_replace_emoji_shortcode(
|
||||
MessageEditorSettings::get_global(cx)
|
||||
.auto_replace_emoji_shortcode
|
||||
@@ -187,29 +182,24 @@ impl MessageEditor {
|
||||
self.edit_message_id = None;
|
||||
}
|
||||
|
||||
pub fn set_channel_chat(
|
||||
&mut self,
|
||||
chat: Model<ChannelChat>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn set_channel_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
|
||||
let channel_id = chat.read(cx).channel_id;
|
||||
self.channel_chat = Some(chat);
|
||||
let channel_name = ChannelStore::global(cx)
|
||||
.read(cx)
|
||||
.channel_for_id(channel_id)
|
||||
.map(|channel| channel.name.clone());
|
||||
self.editor.update(cx, |editor, model, cx| {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if let Some(channel_name) = channel_name {
|
||||
editor.set_placeholder_text(format!("Message #{channel_name}"), model, cx);
|
||||
editor.set_placeholder_text(format!("Message #{channel_name}"), cx);
|
||||
} else {
|
||||
editor.set_placeholder_text("Message Channel", model, cx);
|
||||
editor.set_placeholder_text("Message Channel", cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn take_message(&mut self, model: &Model<Self>, cx: &mut AppContext) -> MessageParams {
|
||||
self.editor.update(cx, |editor, model, cx| {
|
||||
pub fn take_message(&mut self, cx: &mut ViewContext<Self>) -> MessageParams {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let highlights = editor.text_highlights::<Self>(cx);
|
||||
let text = editor.text(cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
@@ -223,7 +213,7 @@ impl MessageEditor {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
editor.clear(model, cx);
|
||||
editor.clear(cx);
|
||||
self.mentions.clear();
|
||||
let reply_to_message_id = std::mem::take(&mut self.reply_to_message_id);
|
||||
|
||||
@@ -239,16 +229,15 @@ impl MessageEditor {
|
||||
&mut self,
|
||||
buffer: Model<Buffer>,
|
||||
event: &language::BufferEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
|
||||
let buffer = buffer.read(cx).snapshot();
|
||||
self.mentions_task = Some(model.spawn(cx, |this, cx| async move {
|
||||
self.mentions_task = Some(cx.spawn(|this, cx| async move {
|
||||
cx.background_executor()
|
||||
.timer(MENTIONS_DEBOUNCE_INTERVAL)
|
||||
.await;
|
||||
Self::find_mentions(this, buffer, model, cx).await;
|
||||
Self::find_mentions(this, buffer, cx).await;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -257,11 +246,10 @@ impl MessageEditor {
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
end_anchor: Anchor,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<Vec<Completion>>> {
|
||||
if let Some((start_anchor, query, candidates)) =
|
||||
self.collect_mention_candidates(buffer, end_anchor, model, cx)
|
||||
self.collect_mention_candidates(buffer, end_anchor, cx)
|
||||
{
|
||||
if !candidates.is_empty() {
|
||||
return cx.spawn(|_, cx| async move {
|
||||
@@ -278,7 +266,7 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
if let Some((start_anchor, query, candidates)) =
|
||||
self.collect_emoji_candidates(buffer, end_anchor, model, cx)
|
||||
self.collect_emoji_candidates(buffer, end_anchor, cx)
|
||||
{
|
||||
if !candidates.is_empty() {
|
||||
return cx.spawn(|_, cx| async move {
|
||||
@@ -298,7 +286,7 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
async fn resolve_completions_for_candidates(
|
||||
cx: &AsyncAppContext,
|
||||
cx: &AsyncWindowContext,
|
||||
query: &str,
|
||||
candidates: &[StringMatchCandidate],
|
||||
range: Range<Anchor>,
|
||||
@@ -354,12 +342,11 @@ impl MessageEditor {
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
end_anchor: Anchor,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<(Anchor, String, Vec<StringMatchCandidate>)> {
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
let query = buffer.update(cx, |buffer, model, _| {
|
||||
let query = buffer.update(cx, |buffer, _| {
|
||||
let mut query = String::new();
|
||||
for ch in buffer.reversed_chars_at(end_offset).take(100) {
|
||||
if ch == '@' {
|
||||
@@ -408,8 +395,7 @@ impl MessageEditor {
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
end_anchor: Anchor,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<(Anchor, String, &'static [StringMatchCandidate])> {
|
||||
static EMOJI_FUZZY_MATCH_CANDIDATES: LazyLock<Vec<StringMatchCandidate>> =
|
||||
LazyLock::new(|| {
|
||||
@@ -426,7 +412,7 @@ impl MessageEditor {
|
||||
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
let query = buffer.update(cx, |buffer, model, _| {
|
||||
let query = buffer.update(cx, |buffer, _| {
|
||||
let mut query = String::new();
|
||||
for ch in buffer.reversed_chars_at(end_offset).take(100) {
|
||||
if ch == ':' {
|
||||
@@ -471,10 +457,9 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
async fn find_mentions(
|
||||
this: WeakModel<MessageEditor>,
|
||||
this: WeakView<MessageEditor>,
|
||||
buffer: BufferSnapshot,
|
||||
mut window: AnyWindowHandle,
|
||||
cx: AsyncAppContext,
|
||||
mut cx: AsyncWindowContext,
|
||||
) {
|
||||
let (buffer, ranges) = cx
|
||||
.background_executor()
|
||||
@@ -484,12 +469,12 @@ impl MessageEditor {
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let mut anchor_ranges = Vec::new();
|
||||
let mut mentioned_user_ids = Vec::new();
|
||||
let mut text = String::new();
|
||||
|
||||
this.editor.update(cx, |editor, model, cx| {
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
for range in ranges {
|
||||
text.clear();
|
||||
@@ -532,12 +517,7 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
impl Render for MessageEditor {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: if self.editor.read(cx).read_only(cx) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@ use client::{
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, anchored, deferred, div, AppContext, ClipboardItem, DismissEvent, EventEmitter,
|
||||
FocusableView, Model, ParentElement, Render, Styled, Subscription, Task, View, VisualContext,
|
||||
WeakView,
|
||||
FocusableView, Model, ParentElement, Render, Styled, Subscription, Task, View, ViewContext,
|
||||
VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
@@ -26,7 +26,7 @@ actions!(
|
||||
);
|
||||
|
||||
pub struct ChannelModal {
|
||||
picker: Model<Picker<ChannelModalDelegate>>,
|
||||
picker: View<Picker<ChannelModalDelegate>>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
}
|
||||
@@ -37,13 +37,11 @@ impl ChannelModal {
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
mode: Mode,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
cx.observe(&channel_store, |_, _, cx| model.notify(cx))
|
||||
.detach();
|
||||
let channel_modal = model.downgrade();
|
||||
let picker = cx.new_model(|model, cx| {
|
||||
cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
|
||||
let channel_modal = cx.view().downgrade();
|
||||
let picker = cx.new_view(|cx| {
|
||||
Picker::uniform_list(
|
||||
ChannelModalDelegate {
|
||||
channel_modal,
|
||||
@@ -59,7 +57,6 @@ impl ChannelModal {
|
||||
has_all_members: false,
|
||||
mode,
|
||||
},
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
.modal(false)
|
||||
@@ -72,33 +69,28 @@ impl ChannelModal {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_mode(&mut self, _: &ToggleMode, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
|
||||
let mode = match self.picker.read(cx).delegate.mode {
|
||||
Mode::ManageMembers => Mode::InviteMembers,
|
||||
Mode::InviteMembers => Mode::ManageMembers,
|
||||
};
|
||||
self.set_mode(mode, model, cx);
|
||||
self.set_mode(mode, cx);
|
||||
}
|
||||
|
||||
fn set_mode(&mut self, mode: Mode, model: &Model<Self>, cx: &mut AppContext) {
|
||||
self.picker.update(cx, |picker, model, cx| {
|
||||
fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
|
||||
self.picker.update(cx, |picker, cx| {
|
||||
let delegate = &mut picker.delegate;
|
||||
delegate.mode = mode;
|
||||
delegate.selected_index = 0;
|
||||
picker.set_query("", model, cx);
|
||||
picker.update_matches(picker.query(cx), model, cx);
|
||||
model.notify(cx)
|
||||
picker.set_query("", cx);
|
||||
picker.update_matches(picker.query(cx), cx);
|
||||
cx.notify()
|
||||
});
|
||||
model.notify(cx)
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn set_channel_visibility(
|
||||
&mut self,
|
||||
selection: &Selection,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.channel_store.update(cx, |channel_store, model, cx| {
|
||||
fn set_channel_visibility(&mut self, selection: &Selection, cx: &mut ViewContext<Self>) {
|
||||
self.channel_store.update(cx, |channel_store, cx| {
|
||||
channel_store
|
||||
.set_channel_visibility(
|
||||
self.channel_id,
|
||||
@@ -107,15 +99,14 @@ impl ChannelModal {
|
||||
Selection::Selected => ChannelVisibility::Public,
|
||||
Selection::Indeterminate => return,
|
||||
},
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &menu::Cancel, model: &Model<Self>, cx: &mut AppContext) {
|
||||
model.emit(DismissEvent, cx);
|
||||
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,12 +120,7 @@ impl FocusableView for ChannelModal {
|
||||
}
|
||||
|
||||
impl Render for ChannelModal {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let Some(channel) = channel_store.channel_for_id(self.channel_id) else {
|
||||
return div();
|
||||
@@ -183,7 +169,7 @@ impl Render for ChannelModal {
|
||||
Some(
|
||||
Button::new("copy-link", "Copy Link")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(model.listener(move |this, _, cx| {
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
if let Some(channel) = this
|
||||
.channel_store
|
||||
.read(cx)
|
||||
@@ -211,7 +197,7 @@ impl Render for ChannelModal {
|
||||
this.border_color(cx.theme().colors().border)
|
||||
})
|
||||
.child(Label::new("Manage Members"))
|
||||
.on_click(model.listener(|this, model, _, cx| {
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.set_mode(Mode::ManageMembers, cx);
|
||||
})),
|
||||
)
|
||||
@@ -226,7 +212,7 @@ impl Render for ChannelModal {
|
||||
this.border_color(cx.theme().colors().border)
|
||||
})
|
||||
.child(Label::new("Invite Members"))
|
||||
.on_click(model.listener(|this, model, _, cx| {
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.set_mode(Mode::InviteMembers, cx);
|
||||
})),
|
||||
),
|
||||
@@ -243,7 +229,7 @@ pub enum Mode {
|
||||
}
|
||||
|
||||
pub struct ChannelModalDelegate {
|
||||
channel_modal: WeakModel<ChannelModal>,
|
||||
channel_modal: WeakView<ChannelModal>,
|
||||
matching_users: Vec<Arc<User>>,
|
||||
matching_member_indices: Vec<usize>,
|
||||
user_store: Model<UserStore>,
|
||||
@@ -254,13 +240,13 @@ pub struct ChannelModalDelegate {
|
||||
match_candidates: Vec<StringMatchCandidate>,
|
||||
members: Vec<ChannelMembership>,
|
||||
has_all_members: bool,
|
||||
context_menu: Option<(Model<ContextMenu>, Subscription)>,
|
||||
context_menu: Option<(View<ContextMenu>, Subscription)>,
|
||||
}
|
||||
|
||||
impl PickerDelegate for ChannelModalDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self, _window: &mut gpui::Window, _cx: &mut gpui::AppContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
|
||||
@@ -275,16 +261,11 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &Model<Picker>, _: &mut AppContext) {
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<()> {
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
match self.mode {
|
||||
Mode::ManageMembers => {
|
||||
if self.has_all_members {
|
||||
@@ -315,13 +296,13 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
delegate
|
||||
.matching_member_indices
|
||||
.extend(matches.into_iter().map(|m| m.candidate_id));
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
} else {
|
||||
let search_members = self.channel_store.update(cx, |store, model, cx| {
|
||||
store.fuzzy_search_members(self.channel_id, query.clone(), 100, model, cx)
|
||||
let search_members = self.channel_store.update(cx, |store, cx| {
|
||||
store.fuzzy_search_members(self.channel_id, query.clone(), 100, cx)
|
||||
});
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
async {
|
||||
@@ -332,7 +313,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
picker.delegate.matching_member_indices =
|
||||
(0..members.len()).collect();
|
||||
picker.delegate.members = members;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
@@ -342,15 +323,15 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
}
|
||||
}
|
||||
Mode::InviteMembers => {
|
||||
let search_users = self.user_store.update(cx, |store, model, cx| {
|
||||
store.fuzzy_search_users(query, model, cx)
|
||||
});
|
||||
let search_users = self
|
||||
.user_store
|
||||
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
async {
|
||||
let users = search_users.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
picker.delegate.matching_users = users;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
@@ -361,30 +342,30 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(selected_user) = self.user_at_index(self.selected_index) {
|
||||
if Some(selected_user.id) == self.user_store.read(cx).current_user().map(|user| user.id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match self.mode {
|
||||
Mode::ManageMembers => self.show_context_menu(self.selected_index, model, cx),
|
||||
Mode::InviteMembers => match self.member_status(selected_user.id, model, cx) {
|
||||
Mode::ManageMembers => self.show_context_menu(self.selected_index, cx),
|
||||
Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
|
||||
Some(proto::channel_member::Kind::Invitee) => {
|
||||
self.remove_member(selected_user.id, model, cx);
|
||||
self.remove_member(selected_user.id, cx);
|
||||
}
|
||||
Some(proto::channel_member::Kind::Member) => {}
|
||||
None => self.invite_member(selected_user, model, cx),
|
||||
None => self.invite_member(selected_user, cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if self.context_menu.is_none() {
|
||||
self.channel_modal
|
||||
.update(cx, |_, model, cx| {
|
||||
model.emit(DismissEvent, cx);
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -394,8 +375,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let user = self.user_at_index(ix)?;
|
||||
let membership = self.member_at_index(ix);
|
||||
@@ -494,11 +474,10 @@ impl ChannelModalDelegate {
|
||||
&mut self,
|
||||
user_id: UserId,
|
||||
new_role: ChannelRole,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<()> {
|
||||
let update = self.channel_store.update(cx, |store, model, cx| {
|
||||
store.set_member_role(self.channel_id, user_id, new_role, model, cx)
|
||||
let update = self.channel_store.update(cx, |store, cx| {
|
||||
store.set_member_role(self.channel_id, user_id, new_role, cx)
|
||||
});
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
update.await?;
|
||||
@@ -508,21 +487,16 @@ impl ChannelModalDelegate {
|
||||
member.role = new_role;
|
||||
}
|
||||
cx.focus_self();
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach_and_prompt_err("Failed to update role", cx, |_, _| None);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn remove_member(
|
||||
&mut self,
|
||||
user_id: UserId,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<()> {
|
||||
let update = self.channel_store.update(cx, |store, model, cx| {
|
||||
store.remove_member(self.channel_id, user_id, model, cx)
|
||||
fn remove_member(&mut self, user_id: UserId, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
|
||||
let update = self.channel_store.update(cx, |store, cx| {
|
||||
store.remove_member(self.channel_id, user_id, cx)
|
||||
});
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
update.await?;
|
||||
@@ -544,53 +518,52 @@ impl ChannelModalDelegate {
|
||||
.selected_index
|
||||
.min(this.matching_member_indices.len().saturating_sub(1));
|
||||
|
||||
picker.focus(window);
|
||||
model.notify(cx);
|
||||
picker.focus(cx);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach_and_prompt_err("Failed to remove member", cx, |_, _| None);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn invite_member(&mut self, user: Arc<User>, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
let invite_member = self.channel_store.update(cx, |store, model, cx| {
|
||||
store.invite_member(self.channel_id, user.id, ChannelRole::Member, model, cx)
|
||||
fn invite_member(&mut self, user: Arc<User>, cx: &mut ViewContext<Picker<Self>>) {
|
||||
let invite_member = self.channel_store.update(cx, |store, cx| {
|
||||
store.invite_member(self.channel_id, user.id, ChannelRole::Member, cx)
|
||||
});
|
||||
|
||||
model
|
||||
.spawn(cx, |this, mut cx| async move {
|
||||
invite_member.await?;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
invite_member.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
let new_member = ChannelMembership {
|
||||
user,
|
||||
kind: proto::channel_member::Kind::Invitee,
|
||||
role: ChannelRole::Member,
|
||||
};
|
||||
let members = &mut this.delegate.members;
|
||||
match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
|
||||
Ok(ix) | Err(ix) => members.insert(ix, new_member),
|
||||
}
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let new_member = ChannelMembership {
|
||||
user,
|
||||
kind: proto::channel_member::Kind::Invitee,
|
||||
role: ChannelRole::Member,
|
||||
};
|
||||
let members = &mut this.delegate.members;
|
||||
match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
|
||||
Ok(ix) | Err(ix) => members.insert(ix, new_member),
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
})
|
||||
cx.notify();
|
||||
})
|
||||
.detach_and_prompt_err("Failed to invite member", cx, |_, _| None);
|
||||
})
|
||||
.detach_and_prompt_err("Failed to invite member", cx, |_, _| None);
|
||||
}
|
||||
|
||||
fn show_context_menu(&mut self, ix: usize, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn show_context_menu(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
let Some(membership) = self.member_at_index(ix) else {
|
||||
return;
|
||||
};
|
||||
let user_id = membership.user.id;
|
||||
let picker = cx.view().clone();
|
||||
let context_menu = ContextMenu::build(cx, window, |mut menu, _model, _window, _cx| {
|
||||
let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
|
||||
let role = membership.role;
|
||||
|
||||
if role == ChannelRole::Admin || role == ChannelRole::Member {
|
||||
let picker = picker.clone();
|
||||
menu = menu.entry("Demote to Guest", None, move |cx| {
|
||||
picker.update(cx, |picker, model, cx| {
|
||||
picker.update(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
.set_user_role(user_id, ChannelRole::Guest, cx);
|
||||
@@ -607,7 +580,7 @@ impl ChannelModalDelegate {
|
||||
};
|
||||
|
||||
menu = menu.entry(label, None, move |cx| {
|
||||
picker.update(cx, |picker, model, cx| {
|
||||
picker.update(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
.set_user_role(user_id, ChannelRole::Member, cx);
|
||||
@@ -618,7 +591,7 @@ impl ChannelModalDelegate {
|
||||
if role == ChannelRole::Member || role == ChannelRole::Guest {
|
||||
let picker = picker.clone();
|
||||
menu = menu.entry("Promote to Admin", None, move |cx| {
|
||||
picker.update(cx, |picker, model, cx| {
|
||||
picker.update(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
.set_user_role(user_id, ChannelRole::Admin, cx);
|
||||
@@ -630,7 +603,7 @@ impl ChannelModalDelegate {
|
||||
menu = menu.entry("Remove from Channel", None, {
|
||||
let picker = picker.clone();
|
||||
move |cx| {
|
||||
picker.update(cx, |picker, model, cx| {
|
||||
picker.update(cx, |picker, cx| {
|
||||
picker.delegate.remove_member(user_id, cx);
|
||||
})
|
||||
}
|
||||
@@ -640,8 +613,8 @@ impl ChannelModalDelegate {
|
||||
cx.focus_view(&context_menu);
|
||||
let subscription = cx.subscribe(&context_menu, |picker, _, _: &DismissEvent, cx| {
|
||||
picker.delegate.context_menu = None;
|
||||
picker.focus(window);
|
||||
model.notify(cx);
|
||||
picker.focus(cx);
|
||||
cx.notify();
|
||||
});
|
||||
self.context_menu = Some((context_menu, subscription));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use client::{ContactRequestStatus, User, UserStore};
|
||||
use gpui::{
|
||||
AppContext, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
|
||||
ParentElement as _, Render, Styled, Task, View, VisualContext, WeakView,
|
||||
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ParentElement as _,
|
||||
Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
@@ -10,37 +10,31 @@ use util::{ResultExt as _, TryFutureExt};
|
||||
use workspace::ModalView;
|
||||
|
||||
pub struct ContactFinder {
|
||||
picker: Model<Picker<ContactFinderDelegate>>,
|
||||
picker: View<Picker<ContactFinderDelegate>>,
|
||||
}
|
||||
|
||||
impl ContactFinder {
|
||||
pub fn new(user_store: Model<UserStore>, model: &Model<Self>, cx: &mut AppContext) -> Self {
|
||||
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let delegate = ContactFinderDelegate {
|
||||
parent: model.downgrade(),
|
||||
parent: cx.view().downgrade(),
|
||||
user_store,
|
||||
potential_contacts: Arc::from([]),
|
||||
selected_index: 0,
|
||||
};
|
||||
let picker =
|
||||
cx.new_model(|model, cx| Picker::uniform_list(delegate, model, cx).modal(false));
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx).modal(false));
|
||||
|
||||
Self { picker }
|
||||
}
|
||||
|
||||
pub fn set_query(&mut self, query: String, model: &Model<Self>, cx: &mut AppContext) {
|
||||
self.picker.update(cx, |picker, model, cx| {
|
||||
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
|
||||
self.picker.update(cx, |picker, cx| {
|
||||
picker.set_query(query, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContactFinder {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.elevation_3(cx)
|
||||
.child(
|
||||
@@ -59,7 +53,7 @@ impl Render for ContactFinder {
|
||||
}
|
||||
|
||||
pub struct ContactFinderDelegate {
|
||||
parent: WeakModel<ContactFinder>,
|
||||
parent: WeakView<ContactFinder>,
|
||||
potential_contacts: Arc<[Arc<User>]>,
|
||||
user_store: Model<UserStore>,
|
||||
selected_index: usize,
|
||||
@@ -85,30 +79,25 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &Model<Picker>, _: &mut AppContext) {
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut gpui::Window, _cx: &mut gpui::AppContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<()> {
|
||||
let search_users = self.user_store.update(cx, |store, model, cx| {
|
||||
store.fuzzy_search_users(query, model, cx)
|
||||
});
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let search_users = self
|
||||
.user_store
|
||||
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
|
||||
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
async {
|
||||
let potential_contacts = search_users.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
picker.delegate.potential_contacts = potential_contacts.into();
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
@@ -117,22 +106,18 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(user) = self.potential_contacts.get(self.selected_index) {
|
||||
let user_store = self.user_store.read(cx);
|
||||
match user_store.contact_request_status(user) {
|
||||
ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
|
||||
self.user_store
|
||||
.update(cx, |store, model, cx| {
|
||||
store.request_contact(user.id, model, cx)
|
||||
})
|
||||
.update(cx, |store, cx| store.request_contact(user.id, cx))
|
||||
.detach();
|
||||
}
|
||||
ContactRequestStatus::RequestSent => {
|
||||
self.user_store
|
||||
.update(cx, |store, model, cx| {
|
||||
store.remove_contact(user.id, model, cx)
|
||||
})
|
||||
.update(cx, |store, cx| store.remove_contact(user.id, cx))
|
||||
.detach();
|
||||
}
|
||||
_ => {}
|
||||
@@ -140,9 +125,9 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.parent
|
||||
.update(cx, |_, model, cx| model.emit(DismissEvent, cx))
|
||||
.update(cx, |_, cx| cx.emit(DismissEvent))
|
||||
.log_err();
|
||||
}
|
||||
|
||||
@@ -150,8 +135,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let user = &self.potential_contacts[ix];
|
||||
let request_status = self.user_store.read(cx).contact_request_status(user);
|
||||
|
||||
@@ -6,10 +6,11 @@ use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, div, img, list, px, AnyElement, AppContext, AppContext, CursorStyle, DismissEvent,
|
||||
Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement, IntoElement,
|
||||
ListAlignment, ListScrollEvent, ListState, Model, ParentElement, Render,
|
||||
StatefulInteractiveElement, Styled, Task, View, VisualContext, WeakView,
|
||||
actions, div, img, list, px, AnyElement, AppContext, AsyncWindowContext, CursorStyle,
|
||||
DismissEvent, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
||||
IntoElement, ListAlignment, ListScrollEvent, ListState, Model, ParentElement, Render,
|
||||
StatefulInteractiveElement, Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
|
||||
use project::Fs;
|
||||
@@ -44,7 +45,7 @@ pub struct NotificationPanel {
|
||||
notification_list: ListState,
|
||||
pending_serialization: Task<Option<()>>,
|
||||
subscriptions: Vec<gpui::Subscription>,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
current_notification_toast: Option<(u64, Task<()>)>,
|
||||
local_timezone: UtcOffset,
|
||||
focus_handle: FocusHandle,
|
||||
@@ -76,7 +77,7 @@ actions!(notification_panel, [ToggleFocus]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
||||
workspace.register_action(model, |workspace, _: &ToggleFocus, cx| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<NotificationPanel>(cx);
|
||||
});
|
||||
})
|
||||
@@ -84,51 +85,43 @@ pub fn init(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
impl NotificationPanel {
|
||||
pub fn new(
|
||||
workspace: &mut Workspace,
|
||||
model: &Model<Workspace>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self> {
|
||||
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let client = workspace.app_state().client.clone();
|
||||
let user_store = workspace.app_state().user_store.clone();
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
|
||||
cx.new_model(|model: &Model<Self>, cx: &mut AppContext| {
|
||||
cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
let mut status = client.status();
|
||||
model
|
||||
.spawn(cx, |this, mut cx| async move {
|
||||
while (status.next().await).is_some() {
|
||||
if this
|
||||
.update(&mut cx, |_, cx| {
|
||||
model.notify(cx);
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
while (status.next().await).is_some() {
|
||||
if this
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.notify();
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let view = model.downgrade();
|
||||
let view = cx.view().downgrade();
|
||||
let notification_list =
|
||||
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, cx| {
|
||||
view.upgrade()
|
||||
.and_then(|view| {
|
||||
view.update(cx, |this, model, cx| {
|
||||
this.render_notification(ix, model, cx)
|
||||
})
|
||||
view.update(cx, |this, cx| this.render_notification(ix, cx))
|
||||
})
|
||||
.unwrap_or_else(|| div().into_any())
|
||||
});
|
||||
notification_list.set_scroll_handler(cx.listener(
|
||||
|this, event: &ListScrollEvent, cx| {
|
||||
if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
|
||||
if let Some(task) =
|
||||
this.notification_store.update(cx, |store, model, cx| {
|
||||
store.load_more_notifications(false, cx)
|
||||
})
|
||||
if let Some(task) = this
|
||||
.notification_store
|
||||
.update(cx, |store, cx| store.load_more_notifications(false, cx))
|
||||
{
|
||||
task.detach();
|
||||
}
|
||||
@@ -147,7 +140,7 @@ impl NotificationPanel {
|
||||
notification_list,
|
||||
pending_serialization: Task::ready(None),
|
||||
workspace: workspace_handle,
|
||||
focus_handle: window.focus_handle(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
current_notification_toast: None,
|
||||
subscriptions: Vec::new(),
|
||||
active: false,
|
||||
@@ -158,15 +151,15 @@ impl NotificationPanel {
|
||||
|
||||
let mut old_dock_position = this.position(cx);
|
||||
this.subscriptions.extend([
|
||||
cx.observe(&this.notification_store, |_, _, cx| model.notify(cx)),
|
||||
cx.observe(&this.notification_store, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&this.notification_store, Self::on_notification_event),
|
||||
cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
|
||||
let new_dock_position = this.position(cx);
|
||||
if new_dock_position != old_dock_position {
|
||||
old_dock_position = new_dock_position;
|
||||
model.emit(Event::DockPositionChanged, cx);
|
||||
cx.emit(Event::DockPositionChanged);
|
||||
}
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}),
|
||||
]);
|
||||
this
|
||||
@@ -174,10 +167,9 @@ impl NotificationPanel {
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
workspace: WeakModel<Workspace>,
|
||||
window: AnyWindowHandle,
|
||||
cx: AsyncAppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let serialized_panel = if let Some(panel) = cx
|
||||
.background_executor()
|
||||
@@ -192,11 +184,11 @@ impl NotificationPanel {
|
||||
};
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let panel = Self::new(workspace, model, cx);
|
||||
let panel = Self::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, model, cx| {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
panel
|
||||
@@ -204,7 +196,7 @@ impl NotificationPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let width = self.width;
|
||||
self.pending_serialization = cx.background_executor().spawn(
|
||||
async move {
|
||||
@@ -220,12 +212,7 @@ impl NotificationPanel {
|
||||
);
|
||||
}
|
||||
|
||||
fn render_notification(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<AnyElement> {
|
||||
fn render_notification(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
let entry = self.notification_store.read(cx).notification_at(ix)?;
|
||||
let notification_id = entry.id;
|
||||
let now = OffsetDateTime::now_utc();
|
||||
@@ -242,7 +229,7 @@ impl NotificationPanel {
|
||||
let notification = entry.notification.clone();
|
||||
|
||||
if self.active && !entry.is_read {
|
||||
self.did_render_notification(notification_id, ¬ification, model, cx);
|
||||
self.did_render_notification(notification_id, ¬ification, cx);
|
||||
}
|
||||
|
||||
let relative_timestamp = time_format::format_localized_timestamp(
|
||||
@@ -272,7 +259,7 @@ impl NotificationPanel {
|
||||
.when(can_navigate, |el| {
|
||||
el.cursor(CursorStyle::PointingHand).on_click({
|
||||
let notification = notification.clone();
|
||||
model.listener(move |this, _, cx| {
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.did_click_notification(¬ification, cx)
|
||||
})
|
||||
})
|
||||
@@ -301,7 +288,7 @@ impl NotificationPanel {
|
||||
.rounded_md()
|
||||
})
|
||||
.child(Label::new(relative_timestamp).color(Color::Muted))
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(absolute_timestamp.clone(), cx)
|
||||
}),
|
||||
)
|
||||
@@ -322,7 +309,7 @@ impl NotificationPanel {
|
||||
let notification = notification.clone();
|
||||
let view = cx.view().clone();
|
||||
move |_, cx| {
|
||||
view.update(cx, |this, model, cx| {
|
||||
view.update(cx, |this, cx| {
|
||||
this.respond_to_notification(
|
||||
notification.clone(),
|
||||
false,
|
||||
@@ -335,7 +322,7 @@ impl NotificationPanel {
|
||||
let notification = notification.clone();
|
||||
let view = cx.view().clone();
|
||||
move |_, cx| {
|
||||
view.update(cx, |this, model, cx| {
|
||||
view.update(cx, |this, cx| {
|
||||
this.respond_to_notification(
|
||||
notification.clone(),
|
||||
true,
|
||||
@@ -428,8 +415,7 @@ impl NotificationPanel {
|
||||
&mut self,
|
||||
notification_id: u64,
|
||||
notification: &Notification,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let should_mark_as_read = match notification {
|
||||
Notification::ContactRequestAccepted { .. } => true,
|
||||
@@ -443,12 +429,12 @@ impl NotificationPanel {
|
||||
.entry(notification_id)
|
||||
.or_insert_with(|| {
|
||||
let client = self.client.clone();
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor().timer(MARK_AS_READ_DELAY).await;
|
||||
client
|
||||
.request(proto::MarkNotificationRead { notification_id })
|
||||
.await?;
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.mark_as_read_tasks.remove(¬ification_id);
|
||||
})?;
|
||||
Ok(())
|
||||
@@ -457,12 +443,7 @@ impl NotificationPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn did_click_notification(
|
||||
&mut self,
|
||||
notification: &Notification,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
fn did_click_notification(&mut self, notification: &Notification, cx: &mut ViewContext<Self>) {
|
||||
if let Notification::ChannelMessageMention {
|
||||
message_id,
|
||||
channel_id,
|
||||
@@ -471,9 +452,9 @@ impl NotificationPanel {
|
||||
{
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
|
||||
panel.update(cx, |panel, model, cx| {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.select_channel(ChannelId(channel_id), Some(message_id), cx)
|
||||
.detach_and_log_err(cx);
|
||||
@@ -485,12 +466,7 @@ impl NotificationPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_showing_notification(
|
||||
&self,
|
||||
notification: &Notification,
|
||||
model: &Model<Self>,
|
||||
cx: &AppContext,
|
||||
) -> bool {
|
||||
fn is_showing_notification(&self, notification: &Notification, cx: &ViewContext<Self>) -> bool {
|
||||
if !self.active {
|
||||
return false;
|
||||
}
|
||||
@@ -516,33 +492,32 @@ impl NotificationPanel {
|
||||
&mut self,
|
||||
_: Model<NotificationStore>,
|
||||
event: &NotificationEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
NotificationEvent::NewNotification { entry } => {
|
||||
if !self.is_showing_notification(&entry.notification, model, cx) {
|
||||
if !self.is_showing_notification(&entry.notification, cx) {
|
||||
self.unseen_notifications.push(entry.clone());
|
||||
}
|
||||
self.add_toast(entry, model, cx);
|
||||
self.add_toast(entry, cx);
|
||||
}
|
||||
NotificationEvent::NotificationRemoved { entry }
|
||||
| NotificationEvent::NotificationRead { entry } => {
|
||||
self.unseen_notifications.retain(|n| n.id != entry.id);
|
||||
self.remove_toast(entry.id, model, cx);
|
||||
self.remove_toast(entry.id, cx);
|
||||
}
|
||||
NotificationEvent::NotificationsUpdated {
|
||||
old_range,
|
||||
new_count,
|
||||
} => {
|
||||
self.notification_list.splice(old_range.clone(), *new_count);
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_toast(&mut self, entry: &NotificationEntry, model: &Model<Self>, cx: &mut AppContext) {
|
||||
if self.is_showing_notification(&entry.notification, model, cx) {
|
||||
fn add_toast(&mut self, entry: &NotificationEntry, cx: &mut ViewContext<Self>) {
|
||||
if self.is_showing_notification(&entry.notification, cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -554,23 +529,21 @@ impl NotificationPanel {
|
||||
let notification_id = entry.id;
|
||||
self.current_notification_toast = Some((
|
||||
notification_id,
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor().timer(TOAST_DURATION).await;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.remove_toast(notification_id, model, cx)
|
||||
})
|
||||
.ok();
|
||||
this.update(&mut cx, |this, cx| this.remove_toast(notification_id, cx))
|
||||
.ok();
|
||||
}),
|
||||
));
|
||||
|
||||
self.workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
let id = NotificationId::unique::<NotificationToast>();
|
||||
|
||||
workspace.dismiss_notification(&id, cx);
|
||||
workspace.show_notification(id, cx, |cx| {
|
||||
let workspace = model.downgrade();
|
||||
cx.new_model(|_, _| NotificationToast {
|
||||
let workspace = cx.view().downgrade();
|
||||
cx.new_view(|_| NotificationToast {
|
||||
notification_id,
|
||||
actor,
|
||||
text,
|
||||
@@ -581,12 +554,12 @@ impl NotificationPanel {
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn remove_toast(&mut self, notification_id: u64, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn remove_toast(&mut self, notification_id: u64, cx: &mut ViewContext<Self>) {
|
||||
if let Some((current_id, _)) = &self.current_notification_toast {
|
||||
if *current_id == notification_id {
|
||||
self.current_notification_toast.take();
|
||||
self.workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
let id = NotificationId::unique::<NotificationToast>();
|
||||
workspace.dismiss_notification(&id, cx)
|
||||
})
|
||||
@@ -599,22 +572,16 @@ impl NotificationPanel {
|
||||
&mut self,
|
||||
notification: Notification,
|
||||
response: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.notification_store.update(cx, |store, model, cx| {
|
||||
store.respond_to_notification(notification, response, model, cx);
|
||||
self.notification_store.update(cx, |store, cx| {
|
||||
store.respond_to_notification(notification, response, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for NotificationPanel {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.child(
|
||||
@@ -695,7 +662,7 @@ impl Panel for NotificationPanel {
|
||||
"NotificationPanel"
|
||||
}
|
||||
|
||||
fn position(&self, cx: &gpui::AppContext) -> DockPosition {
|
||||
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
|
||||
NotificationPanelSettings::get_global(cx).dock
|
||||
}
|
||||
|
||||
@@ -703,7 +670,7 @@ impl Panel for NotificationPanel {
|
||||
matches!(position, DockPosition::Left | DockPosition::Right)
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position: DockPosition, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
|
||||
settings::update_settings_file::<NotificationPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
@@ -711,31 +678,31 @@ impl Panel for NotificationPanel {
|
||||
);
|
||||
}
|
||||
|
||||
fn size(&self, cx: &gpui::AppContext) -> Pixels {
|
||||
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
|
||||
self.width
|
||||
.unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<Pixels>, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
self.width = size;
|
||||
self.serialize(model, cx);
|
||||
model.notify(cx);
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_active(&mut self, active: bool, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
||||
self.active = active;
|
||||
|
||||
if self.active {
|
||||
self.unseen_notifications = Vec::new();
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if self.notification_store.read(cx).notification_count() == 0 {
|
||||
model.emit(Event::Dismissed, cx);
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &gpui::AppContext) -> Option<IconName> {
|
||||
fn icon(&self, cx: &gpui::WindowContext) -> Option<IconName> {
|
||||
let show_button = NotificationPanelSettings::get_global(cx).button;
|
||||
if !show_button {
|
||||
return None;
|
||||
@@ -748,11 +715,11 @@ impl Panel for NotificationPanel {
|
||||
Some(IconName::BellDot)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, cx: &AppContext) -> Option<&'static str> {
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
Some("Notification Panel")
|
||||
}
|
||||
|
||||
fn icon_label(&self, cx: &AppContext) -> Option<String> {
|
||||
fn icon_label(&self, cx: &WindowContext) -> Option<String> {
|
||||
let count = self.notification_store.read(cx).unread_notification_count();
|
||||
if count == 0 {
|
||||
None
|
||||
@@ -770,18 +737,18 @@ pub struct NotificationToast {
|
||||
notification_id: u64,
|
||||
actor: Option<Arc<User>>,
|
||||
text: String,
|
||||
workspace: WeakModel<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
}
|
||||
|
||||
impl NotificationToast {
|
||||
fn focus_notification_panel(&self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn focus_notification_panel(&self, cx: &mut ViewContext<Self>) {
|
||||
let workspace = self.workspace.clone();
|
||||
let notification_id = self.notification_id;
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace
|
||||
.update(cx, |workspace, model, cx| {
|
||||
.update(cx, |workspace, cx| {
|
||||
if let Some(panel) = workspace.focus_panel::<NotificationPanel>(cx) {
|
||||
panel.update(cx, |panel, model, cx| {
|
||||
panel.update(cx, |panel, cx| {
|
||||
let store = panel.notification_store.read(cx);
|
||||
if let Some(entry) = store.notification_for_id(notification_id) {
|
||||
panel.did_click_notification(&entry.clone().notification, cx);
|
||||
@@ -795,12 +762,7 @@ impl NotificationToast {
|
||||
}
|
||||
|
||||
impl Render for NotificationToast {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let user = self.actor.clone();
|
||||
|
||||
h_flex()
|
||||
@@ -812,11 +774,11 @@ impl Render for NotificationToast {
|
||||
.child(Label::new(self.text.clone()))
|
||||
.child(
|
||||
IconButton::new("close", IconName::Close)
|
||||
.on_click(cx.listener(|_, _, cx| model.emit(DismissEvent, cx))),
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
.on_click(model.listener(|this, model, _, cx| {
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.focus_notification_panel(cx);
|
||||
model.emit(DismissEvent, cx);
|
||||
cx.emit(DismissEvent);
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ impl ParentElement for CollabNotification {
|
||||
}
|
||||
|
||||
impl RenderOnce for CollabNotification {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::AppContext) -> impl IntoElement {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex()
|
||||
.text_ui(cx)
|
||||
.justify_between()
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
{
|
||||
let window = cx
|
||||
.open_window(options, |cx| {
|
||||
cx.new_model(|_, _| {
|
||||
cx.new_view(|_| {
|
||||
IncomingCallNotification::new(
|
||||
incoming_call.clone(),
|
||||
app_state.clone(),
|
||||
@@ -70,8 +70,7 @@ impl IncomingCallNotificationState {
|
||||
fn respond(&self, accept: bool, cx: &mut AppContext) {
|
||||
let active_call = ActiveCall::global(cx);
|
||||
if accept {
|
||||
let join =
|
||||
active_call.update(cx, |active_call, model, cx| active_call.accept_incoming(cx));
|
||||
let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
|
||||
let caller_user_id = self.call.calling_user.id;
|
||||
let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
|
||||
let app_state = self.app_state.clone();
|
||||
@@ -96,7 +95,7 @@ impl IncomingCallNotificationState {
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
} else {
|
||||
active_call.update(cx, |active_call, model, cx| {
|
||||
active_call.update(cx, |active_call, cx| {
|
||||
active_call.decline_incoming(cx).log_err();
|
||||
});
|
||||
}
|
||||
@@ -112,12 +111,7 @@ impl IncomingCallNotification {
|
||||
}
|
||||
|
||||
impl Render for IncomingCallNotification {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(cx);
|
||||
|
||||
div().size_full().font(ui_font).child(
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
let options = notification_window_options(screen, window_size, cx);
|
||||
let Some(window) = cx
|
||||
.open_window(options, |cx| {
|
||||
cx.new_model(|_, _| {
|
||||
cx.new_view(|_| {
|
||||
ProjectSharedNotification::new(
|
||||
owner.clone(),
|
||||
*project_id,
|
||||
@@ -55,7 +55,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
if let Some(windows) = notification_windows.remove(project_id) {
|
||||
for window in windows {
|
||||
window
|
||||
.update(cx, |_, model, cx| {
|
||||
.update(cx, |_, cx| {
|
||||
cx.remove_window();
|
||||
})
|
||||
.ok();
|
||||
@@ -67,7 +67,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
for (_, windows) in notification_windows.drain() {
|
||||
for window in windows {
|
||||
window
|
||||
.update(cx, |_, model, cx| {
|
||||
.update(cx, |_, cx| {
|
||||
cx.remove_window();
|
||||
})
|
||||
.ok();
|
||||
@@ -101,53 +101,39 @@ impl ProjectSharedNotification {
|
||||
}
|
||||
}
|
||||
|
||||
fn join(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn join(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(app_state) = self.app_state.upgrade() {
|
||||
workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_room) =
|
||||
ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
|
||||
{
|
||||
active_room.update(cx, |_, model, cx| {
|
||||
model.emit(
|
||||
cx,
|
||||
room::Event::RemoteProjectInvitationDiscarded {
|
||||
project_id: self.project_id,
|
||||
},
|
||||
);
|
||||
active_room.update(cx, |_, cx| {
|
||||
cx.emit(room::Event::RemoteProjectInvitationDiscarded {
|
||||
project_id: self.project_id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ProjectSharedNotification {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(cx);
|
||||
|
||||
div().size_full().font(ui_font).child(
|
||||
CollabNotification::new(
|
||||
self.owner.avatar_uri.clone(),
|
||||
Button::new("open", "Open").on_click(model.listener(
|
||||
cx,
|
||||
move |this, _event, cx| {
|
||||
this.join(cx);
|
||||
},
|
||||
)),
|
||||
Button::new("dismiss", "Dismiss").on_click(model.listener(
|
||||
cx,
|
||||
move |this, _event, cx| {
|
||||
this.dismiss(cx);
|
||||
},
|
||||
)),
|
||||
Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
|
||||
this.join(cx);
|
||||
})),
|
||||
Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
|
||||
this.dismiss(cx);
|
||||
})),
|
||||
)
|
||||
.child(Label::new(self.owner.github_login.clone()))
|
||||
.child(Label::new(format!(
|
||||
|
||||
@@ -7,12 +7,7 @@ use crate::notifications::collab_notification::CollabNotification;
|
||||
pub struct CollabNotificationStory;
|
||||
|
||||
impl Render for CollabNotificationStory {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let window_container = |width, height| div().w(px(width)).h(px(height));
|
||||
|
||||
Story::container()
|
||||
|
||||
@@ -11,8 +11,8 @@ use command_palette_hooks::{
|
||||
};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
Action, AppContext, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
|
||||
ParentElement, Render, Styled, Task, UpdateGlobal, View, VisualContext, WeakView,
|
||||
Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
|
||||
ParentElement, Render, Styled, Task, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
|
||||
@@ -33,7 +33,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
impl ModalView for CommandPalette {}
|
||||
|
||||
pub struct CommandPalette {
|
||||
picker: Model<Picker<CommandPaletteDelegate>>,
|
||||
picker: View<Picker<CommandPaletteDelegate>>,
|
||||
}
|
||||
|
||||
fn trim_consecutive_whitespaces(input: &str) -> String {
|
||||
@@ -55,24 +55,17 @@ fn trim_consecutive_whitespaces(input: &str) -> String {
|
||||
}
|
||||
|
||||
impl CommandPalette {
|
||||
fn register(workspace: &mut Workspace, _: &Model<Workspace>, _: &mut AppContext) {
|
||||
workspace.register_action(model, |workspace, _: &Toggle, cx| {
|
||||
Self::toggle(workspace, "", model, cx)
|
||||
});
|
||||
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||
workspace.register_action(|workspace, _: &Toggle, cx| Self::toggle(workspace, "", cx));
|
||||
}
|
||||
|
||||
pub fn toggle(
|
||||
workspace: &mut Workspace,
|
||||
query: &str,
|
||||
model: &Model<Workspace>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn toggle(workspace: &mut Workspace, query: &str, cx: &mut ViewContext<Workspace>) {
|
||||
let Some(previous_focus_handle) = cx.focused() else {
|
||||
return;
|
||||
};
|
||||
let telemetry = workspace.client().telemetry().clone();
|
||||
workspace.toggle_modal(window, cx, move |cx| {
|
||||
CommandPalette::new(previous_focus_handle, telemetry, query, model, cx)
|
||||
workspace.toggle_modal(cx, move |cx| {
|
||||
CommandPalette::new(previous_focus_handle, telemetry, query, cx)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,8 +73,7 @@ impl CommandPalette {
|
||||
previous_focus_handle: FocusHandle,
|
||||
telemetry: Arc<Telemetry>,
|
||||
query: &str,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let filter = CommandPaletteFilter::try_global(cx);
|
||||
|
||||
@@ -101,23 +93,23 @@ impl CommandPalette {
|
||||
.collect();
|
||||
|
||||
let delegate = CommandPaletteDelegate::new(
|
||||
model.downgrade(),
|
||||
cx.view().downgrade(),
|
||||
commands,
|
||||
telemetry,
|
||||
previous_focus_handle,
|
||||
);
|
||||
|
||||
let picker = cx.new_model(|model, cx| {
|
||||
let picker = Picker::uniform_list(delegate, model, cx);
|
||||
picker.set_query(query, model, cx);
|
||||
let picker = cx.new_view(|cx| {
|
||||
let picker = Picker::uniform_list(delegate, cx);
|
||||
picker.set_query(query, cx);
|
||||
picker
|
||||
});
|
||||
Self { picker }
|
||||
}
|
||||
|
||||
pub fn set_query(&mut self, query: &str, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn set_query(&mut self, query: &str, cx: &mut ViewContext<Self>) {
|
||||
self.picker
|
||||
.update(cx, |picker, model, cx| picker.set_query(query, cx))
|
||||
.update(cx, |picker, cx| picker.set_query(query, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,13 +122,13 @@ impl FocusableView for CommandPalette {
|
||||
}
|
||||
|
||||
impl Render for CommandPalette {
|
||||
fn render(&mut self, model: &Model<Self>, _cx: &mut AppContext) -> impl IntoElement {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex().w(rems(34.)).child(self.picker.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandPaletteDelegate {
|
||||
command_palette: WeakModel<CommandPalette>,
|
||||
command_palette: WeakView<CommandPalette>,
|
||||
all_commands: Vec<Command>,
|
||||
commands: Vec<Command>,
|
||||
matches: Vec<StringMatch>,
|
||||
@@ -173,7 +165,7 @@ impl Global for HitCounts {}
|
||||
|
||||
impl CommandPaletteDelegate {
|
||||
fn new(
|
||||
command_palette: WeakModel<CommandPalette>,
|
||||
command_palette: WeakView<CommandPalette>,
|
||||
commands: Vec<Command>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
previous_focus_handle: FocusHandle,
|
||||
@@ -195,8 +187,7 @@ impl CommandPaletteDelegate {
|
||||
query: String,
|
||||
mut commands: Vec<Command>,
|
||||
mut matches: Vec<StringMatch>,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) {
|
||||
self.updating_matches.take();
|
||||
|
||||
@@ -250,7 +241,7 @@ impl CommandPaletteDelegate {
|
||||
impl PickerDelegate for CommandPaletteDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self, _window: &mut gpui::Window, _cx: &mut gpui::AppContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Execute a command...".into()
|
||||
}
|
||||
|
||||
@@ -262,15 +253,14 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
self.selected_ix
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &Model<Picker>, _: &mut AppContext) {
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_ix = ix;
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
mut query: String,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> gpui::Task<()> {
|
||||
let settings = WorkspaceSettings::get_global(cx);
|
||||
if let Some(alias) = settings.command_aliases.get(&query) {
|
||||
@@ -346,8 +336,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
&mut self,
|
||||
query: String,
|
||||
duration: Duration,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> bool {
|
||||
let Some((task, rx)) = self.updating_matches.take() else {
|
||||
return true;
|
||||
@@ -358,7 +347,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
.block_with_timeout(duration, rx.clone().recv())
|
||||
{
|
||||
Ok(Some((commands, matches))) => {
|
||||
self.matches_updated(query, commands, matches, model, cx);
|
||||
self.matches_updated(query, commands, matches, cx);
|
||||
true
|
||||
}
|
||||
_ => {
|
||||
@@ -368,15 +357,15 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.command_palette
|
||||
.update(cx, |_, model, cx| model.emit(DismissEvent, cx))
|
||||
.update(cx, |_, cx| cx.emit(DismissEvent))
|
||||
.log_err();
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, model: &Model<Picker>, cx: &mut AppContext) {
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if self.matches.is_empty() {
|
||||
self.dismissed(model, cx);
|
||||
self.dismissed(cx);
|
||||
return;
|
||||
}
|
||||
let action_ix = self.matches[self.selected_ix].candidate_id;
|
||||
@@ -392,16 +381,15 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
});
|
||||
let action = command.action;
|
||||
cx.focus(&self.previous_focus_handle);
|
||||
self.dismissed(model, cx);
|
||||
model.dispatch_action(cx, action);
|
||||
self.dismissed(cx);
|
||||
cx.dispatch_action(action);
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
model: &Model<Picker>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let r#match = self.matches.get(ix)?;
|
||||
let command = self.commands.get(r#match.candidate_id)?;
|
||||
@@ -422,7 +410,6 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
.children(KeyBinding::for_action_in(
|
||||
&*command.action,
|
||||
&self.previous_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
),
|
||||
@@ -495,23 +482,22 @@ mod tests {
|
||||
async fn test_command_palette(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, model, cx) =
|
||||
cx.add_window_view(|cx| Workspace::test_new(project.clone(), model, cx));
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
let editor = cx.new_model(|model, cx| {
|
||||
let mut editor = Editor::single_line(model, cx);
|
||||
editor.set_text("abc", model, cx);
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::single_line(cx);
|
||||
editor.set_text("abc", cx);
|
||||
editor
|
||||
});
|
||||
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
|
||||
editor.update(cx, |editor, model, cx| editor.focus(window, cx))
|
||||
editor.update(cx, |editor, cx| editor.focus(cx))
|
||||
});
|
||||
|
||||
cx.simulate_keystrokes("cmd-shift-p");
|
||||
|
||||
let palette = workspace.update(cx, |workspace, model, cx| {
|
||||
let palette = workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_modal::<CommandPalette>(cx)
|
||||
.unwrap()
|
||||
@@ -520,7 +506,7 @@ mod tests {
|
||||
.clone()
|
||||
});
|
||||
|
||||
palette.update(cx, |palette, model, _| {
|
||||
palette.update(cx, |palette, _| {
|
||||
assert!(palette.delegate.commands.len() > 5);
|
||||
let is_sorted =
|
||||
|actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
|
||||
@@ -529,13 +515,13 @@ mod tests {
|
||||
|
||||
cx.simulate_input("bcksp");
|
||||
|
||||
palette.update(cx, |palette, model, _| {
|
||||
palette.update(cx, |palette, _| {
|
||||
assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
|
||||
});
|
||||
|
||||
cx.simulate_keystrokes("enter");
|
||||
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.active_modal::<CommandPalette>(cx).is_none());
|
||||
assert_eq!(editor.read(cx).text(cx), "ab")
|
||||
});
|
||||
@@ -550,7 +536,7 @@ mod tests {
|
||||
cx.simulate_keystrokes("cmd-shift-p");
|
||||
cx.simulate_input("bcksp");
|
||||
|
||||
let palette = workspace.update(cx, |workspace, model, cx| {
|
||||
let palette = workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_modal::<CommandPalette>(cx)
|
||||
.unwrap()
|
||||
@@ -558,7 +544,7 @@ mod tests {
|
||||
.picker
|
||||
.clone()
|
||||
});
|
||||
palette.update(cx, |palette, model, _| {
|
||||
palette.update(cx, |palette, _| {
|
||||
assert!(palette.delegate.matches.is_empty())
|
||||
});
|
||||
}
|
||||
@@ -567,30 +553,27 @@ mod tests {
|
||||
async fn test_go_to_line(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|cx| Workspace::test_new(project.clone(), model, cx));
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
cx.simulate_keystrokes("cmd-n");
|
||||
|
||||
let editor = workspace.update(cx, |workspace, model, cx| {
|
||||
let editor = workspace.update(cx, |workspace, cx| {
|
||||
workspace.active_item_as::<Editor>(cx).unwrap()
|
||||
});
|
||||
editor.update(cx, |editor, model, cx| {
|
||||
editor.set_text("1\n2\n3\n4\n5\n6\n", cx)
|
||||
});
|
||||
editor.update(cx, |editor, cx| editor.set_text("1\n2\n3\n4\n5\n6\n", cx));
|
||||
|
||||
cx.simulate_keystrokes("cmd-shift-p");
|
||||
cx.simulate_input("go to line: Toggle");
|
||||
cx.simulate_keystrokes("enter");
|
||||
|
||||
workspace.update(cx, |workspace, model, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.active_modal::<GoToLine>(cx).is_some())
|
||||
});
|
||||
|
||||
cx.simulate_keystrokes("3 enter");
|
||||
|
||||
editor.update(cx, |editor, model, cx| {
|
||||
assert!(editor.focus_handle(cx).is_focused(window));
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert!(editor.focus_handle(cx).is_focused(cx));
|
||||
assert_eq!(
|
||||
editor.selections.last::<Point>(cx).range().start,
|
||||
Point::new(2, 0)
|
||||
|
||||
22
crates/component_preview/Cargo.toml
Normal file
22
crates/component_preview/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "component_preview"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/component_preview.rs"
|
||||
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
ui.workspace = true
|
||||
component_system.workspace = true
|
||||
strum.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
110
crates/component_preview/src/component_preview.rs
Normal file
110
crates/component_preview/src/component_preview.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
//! # Component Preview
|
||||
//!
|
||||
//! A view for exploring Zed components.
|
||||
|
||||
use component_system::components;
|
||||
use gpui::{prelude::*, AppContext, EventEmitter, FocusHandle, FocusableView};
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use ui::{prelude::*, TintColor};
|
||||
|
||||
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace.register_action(|workspace, _: &workspace::ComponentPreview, cx| {
|
||||
let component_preview = cx.new_view(ComponentPreview::new);
|
||||
workspace.add_item_to_active_pane(Box::new(component_preview), None, true, cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
struct ComponentPreview {
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl ComponentPreview {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_sidebar(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
h_flex().children(components().all().iter().map(|component| {
|
||||
Button::new(component.name().clone(), component.name()).on_click(cx.listener(
|
||||
move |_this, _, _cx| {
|
||||
// Handle button click
|
||||
},
|
||||
))
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_preview(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.children(components().all_previews().iter().map(|component| {
|
||||
if let Some(preview) = component.preview() {
|
||||
preview(cx)
|
||||
} else {
|
||||
div().into_any_element()
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ComponentPreview {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.id("component-preview")
|
||||
.key_context("ComponentPreview")
|
||||
.items_start()
|
||||
.overflow_hidden()
|
||||
.size_full()
|
||||
.max_h_full()
|
||||
.track_focus(&self.focus_handle)
|
||||
.px_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(self.render_sidebar(cx))
|
||||
.child(self.render_preview(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ItemEvent> for ComponentPreview {}
|
||||
|
||||
impl FocusableView for ComponentPreview {
|
||||
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ComponentPreview {
|
||||
type Event = ItemEvent;
|
||||
|
||||
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
|
||||
Some("Component Preview".into())
|
||||
}
|
||||
|
||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn show_toolbar(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<gpui::View<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Some(cx.new_view(Self::new))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||
f(*event)
|
||||
}
|
||||
}
|
||||
23
crates/component_system/Cargo.toml
Normal file
23
crates/component_system/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "component_system"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/component_system.rs"
|
||||
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
lazy_static.workspace = true
|
||||
linkme.workspace = true
|
||||
once_cell.workspace = true
|
||||
collections.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
142
crates/component_system/src/component_system.rs
Normal file
142
crates/component_system/src/component_system.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyElement, SharedString, WindowContext};
|
||||
use linkme::distributed_slice;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub trait Component {
|
||||
fn scope() -> Option<&'static str>;
|
||||
fn name() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
fn description() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComponentPreview: Component {
|
||||
fn preview(_cx: &WindowContext) -> AnyElement;
|
||||
}
|
||||
|
||||
#[distributed_slice]
|
||||
pub static __ALL_COMPONENTS: [fn()] = [..];
|
||||
|
||||
#[distributed_slice]
|
||||
pub static __ALL_PREVIEWS: [fn()] = [..];
|
||||
|
||||
pub static COMPONENT_DATA: Lazy<RwLock<ComponentData>> =
|
||||
Lazy::new(|| RwLock::new(ComponentData::new()));
|
||||
|
||||
pub struct ComponentData {
|
||||
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
|
||||
previews: HashMap<&'static str, fn(&WindowContext) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl ComponentData {
|
||||
fn new() -> Self {
|
||||
ComponentData {
|
||||
components: Vec::new(),
|
||||
previews: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
|
||||
let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
|
||||
|
||||
for f in component_fns {
|
||||
f();
|
||||
}
|
||||
for f in preview_fns {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_component<T: Component>() {
|
||||
let component_data = (T::scope(), T::name(), T::description());
|
||||
COMPONENT_DATA.write().components.push(component_data);
|
||||
}
|
||||
|
||||
pub fn register_preview<T: ComponentPreview>() {
|
||||
let preview_data = (T::name(), T::preview as fn(&WindowContext) -> AnyElement);
|
||||
COMPONENT_DATA
|
||||
.write()
|
||||
.previews
|
||||
.insert(preview_data.0, preview_data.1);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ComponentId(pub &'static str);
|
||||
|
||||
pub struct ComponentMetadata {
|
||||
name: SharedString,
|
||||
scope: Option<SharedString>,
|
||||
description: Option<SharedString>,
|
||||
preview: Option<fn(&WindowContext) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl ComponentMetadata {
|
||||
pub fn name(&self) -> SharedString {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> Option<SharedString> {
|
||||
self.scope.clone()
|
||||
}
|
||||
|
||||
pub fn description(&self) -> Option<SharedString> {
|
||||
self.description.clone()
|
||||
}
|
||||
|
||||
pub fn preview(&self) -> Option<fn(&WindowContext) -> AnyElement> {
|
||||
self.preview
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
|
||||
|
||||
impl AllComponents {
|
||||
pub fn new() -> Self {
|
||||
AllComponents(HashMap::default())
|
||||
}
|
||||
|
||||
pub fn add(&mut self, id: ComponentId, metadata: ComponentMetadata) {
|
||||
self.0.insert(id, metadata);
|
||||
}
|
||||
|
||||
pub fn get(&self, id: &ComponentId) -> Option<&ComponentMetadata> {
|
||||
self.0.get(id)
|
||||
}
|
||||
|
||||
/// Returns all components with previews
|
||||
pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().filter(|c| c.preview.is_some()).collect()
|
||||
}
|
||||
|
||||
/// Returns all components
|
||||
pub fn all(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn components() -> AllComponents {
|
||||
let data = COMPONENT_DATA.read();
|
||||
let mut all_components = AllComponents::new();
|
||||
|
||||
for &(scope, name, description) in &data.components {
|
||||
let scope = scope.map(Into::into);
|
||||
let preview = data.previews.get(name).cloned();
|
||||
all_components.add(
|
||||
ComponentId(name),
|
||||
ComponentMetadata {
|
||||
name: name.into(),
|
||||
scope,
|
||||
description: description.map(Into::into),
|
||||
preview,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
all_components
|
||||
}
|
||||
@@ -51,8 +51,8 @@ impl Tool for ContextServerTool {
|
||||
fn run(
|
||||
self: std::sync::Arc<Self>,
|
||||
input: serde_json::Value,
|
||||
_workspace: gpui::WeakModel<workspace::Workspace>,
|
||||
cx: &mut model, ui::
|
||||
_workspace: gpui::WeakView<workspace::Workspace>,
|
||||
cx: &mut ui::WindowContext,
|
||||
) -> gpui::Task<gpui::Result<String>> {
|
||||
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
|
||||
cx.foreground_executor().spawn({
|
||||
|
||||
@@ -34,7 +34,7 @@ impl ExtensionContextServerProxy for ContextServerFactoryRegistryProxy {
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.context_server_factory_registry
|
||||
.update(cx, |registry, model, _| {
|
||||
.update(cx, |registry, _| {
|
||||
registry.register_server_factory(
|
||||
id.clone(),
|
||||
Arc::new({
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::sync::Arc;
|
||||
use anyhow::{bail, Result};
|
||||
use collections::HashMap;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use gpui::{AsyncAppContext, EventEmitter, Model, Subscription, Task, WeakModel};
|
||||
use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Subscription, Task, WeakModel};
|
||||
use log;
|
||||
use parking_lot::RwLock;
|
||||
use project::Project;
|
||||
@@ -122,8 +122,7 @@ impl ContextServerManager {
|
||||
pub fn new(
|
||||
registry: Model<ContextServerFactoryRegistry>,
|
||||
project: Model<Project>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let mut this = Self {
|
||||
_subscriptions: vec![
|
||||
@@ -144,18 +143,18 @@ impl ContextServerManager {
|
||||
this
|
||||
}
|
||||
|
||||
fn available_context_servers_changed(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn available_context_servers_changed(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if self.update_servers_task.is_some() {
|
||||
self.needs_server_update = true;
|
||||
} else {
|
||||
self.update_servers_task = Some(model.spawn(cx, |this, mut cx| async move {
|
||||
this.update(&mut cx, |this, _, _| {
|
||||
self.update_servers_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.needs_server_update = false;
|
||||
})?;
|
||||
|
||||
Self::maintain_servers(this.clone(), cx.clone()).await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let has_any_context_servers = !this.servers().is_empty();
|
||||
if has_any_context_servers {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
@@ -184,30 +183,23 @@ impl ContextServerManager {
|
||||
pub fn restart_server(
|
||||
&mut self,
|
||||
id: &Arc<str>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let id = id.clone();
|
||||
model.spawn(cx, |this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some(server) = this.update(&mut cx, |this, _cx| this.servers.remove(&id))? {
|
||||
server.stop()?;
|
||||
let config = server.config();
|
||||
let new_server = Arc::new(ContextServer::new(id.clone(), config));
|
||||
new_server.clone().start(&cx).await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.servers.insert(id.clone(), new_server);
|
||||
model.emit(
|
||||
cx,
|
||||
Event::ServerStopped {
|
||||
server_id: id.clone(),
|
||||
},
|
||||
);
|
||||
model.emit(
|
||||
cx,
|
||||
Event::ServerStarted {
|
||||
server_id: id.clone(),
|
||||
},
|
||||
);
|
||||
cx.emit(Event::ServerStopped {
|
||||
server_id: id.clone(),
|
||||
});
|
||||
cx.emit(Event::ServerStarted {
|
||||
server_id: id.clone(),
|
||||
});
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -225,7 +217,7 @@ impl ContextServerManager {
|
||||
async fn maintain_servers(this: WeakModel<Self>, mut cx: AsyncAppContext) -> Result<()> {
|
||||
let mut desired_servers = HashMap::default();
|
||||
|
||||
let (registry, project) = this.update(&mut cx, |this, model, cx| {
|
||||
let (registry, project) = this.update(&mut cx, |this, cx| {
|
||||
let location = this.project.read(cx).worktrees(cx).next().map(|worktree| {
|
||||
settings::SettingsLocation {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
@@ -279,14 +271,14 @@ impl ContextServerManager {
|
||||
for (id, server) in servers_to_stop {
|
||||
server.stop().log_err();
|
||||
this.update(&mut cx, |_, cx| {
|
||||
model.emit(Event::ServerStopped { server_id: id }, cx)
|
||||
cx.emit(Event::ServerStopped { server_id: id })
|
||||
})?;
|
||||
}
|
||||
|
||||
for (id, server) in servers_to_start {
|
||||
if server.start(&cx).await.log_err().is_some() {
|
||||
this.update(&mut cx, |_, cx| {
|
||||
model.emit(Event::ServerStarted { server_id: id }, cx)
|
||||
cx.emit(Event::ServerStarted { server_id: id })
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ impl ContextServerFactoryRegistry {
|
||||
/// Inserts a default [`ContextServerFactoryRegistry`] if one does not yet exist.
|
||||
pub fn default_global(cx: &mut AppContext) -> Model<Self> {
|
||||
if !cx.has_global::<GlobalContextServerFactoryRegistry>() {
|
||||
let registry = cx.new_model(|_, _| Self::new());
|
||||
let registry = cx.new_model(|_| Self::new());
|
||||
cx.set_global(GlobalContextServerFactoryRegistry(registry));
|
||||
}
|
||||
cx.global::<GlobalContextServerFactoryRegistry>().0.clone()
|
||||
|
||||
@@ -12,7 +12,7 @@ use command_palette_hooks::CommandPaletteFilter;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
|
||||
Task, WeakModel,
|
||||
ModelContext, Task, WeakModel,
|
||||
};
|
||||
use http_client::github::get_release_by_tag_name;
|
||||
use http_client::HttpClient;
|
||||
@@ -64,7 +64,7 @@ pub fn init(
|
||||
|
||||
let copilot = cx.new_model({
|
||||
let node_runtime = node_runtime.clone();
|
||||
move |cx| Copilot::start(new_server_id, http, node_runtime, model, cx)
|
||||
move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
|
||||
});
|
||||
Copilot::set_global(copilot.clone(), cx);
|
||||
cx.observe(&copilot, |handle, cx| {
|
||||
@@ -105,21 +105,21 @@ pub fn init(
|
||||
cx.on_action(|_: &SignIn, cx| {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
copilot
|
||||
.update(cx, |copilot, model, cx| copilot.sign_in(model, cx))
|
||||
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
});
|
||||
cx.on_action(|_: &SignOut, cx| {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
copilot
|
||||
.update(cx, |copilot, model, cx| copilot.sign_out(model, cx))
|
||||
.update(cx, |copilot, cx| copilot.sign_out(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
});
|
||||
cx.on_action(|_: &Reinstall, cx| {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
copilot
|
||||
.update(cx, |copilot, model, cx| copilot.reinstall(model, cx))
|
||||
.update(cx, |copilot, cx| copilot.reinstall(cx))
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
@@ -210,8 +210,7 @@ impl RegisteredBuffer {
|
||||
fn report_changes(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
model: &Model<Copilot>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Copilot>,
|
||||
) -> oneshot::Receiver<(i32, BufferSnapshot)> {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
|
||||
@@ -336,8 +335,7 @@ impl Copilot {
|
||||
new_server_id: LanguageServerId,
|
||||
http: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let mut this = Self {
|
||||
server_id: new_server_id,
|
||||
@@ -345,9 +343,9 @@ impl Copilot {
|
||||
node_runtime,
|
||||
server: CopilotServer::Disabled,
|
||||
buffers: Default::default(),
|
||||
_subscription: model.on_app_quit(cx, Self::shutdown_language_server),
|
||||
_subscription: cx.on_app_quit(Self::shutdown_language_server),
|
||||
};
|
||||
this.enable_or_disable_copilot(model, cx);
|
||||
this.enable_or_disable_copilot(cx);
|
||||
cx.observe_global::<SettingsStore>(move |this, cx| this.enable_or_disable_copilot(cx))
|
||||
.detach();
|
||||
this
|
||||
@@ -355,8 +353,7 @@ impl Copilot {
|
||||
|
||||
fn shutdown_language_server(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
_cx: &mut AppContext,
|
||||
_cx: &mut ModelContext<Self>,
|
||||
) -> impl Future<Output = ()> {
|
||||
let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) {
|
||||
CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })),
|
||||
@@ -370,7 +367,7 @@ impl Copilot {
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_or_disable_copilot(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let server_id = self.server_id;
|
||||
let http = self.http.clone();
|
||||
let node_runtime = self.node_runtime.clone();
|
||||
@@ -384,11 +381,11 @@ impl Copilot {
|
||||
})
|
||||
.shared();
|
||||
self.server = CopilotServer::Starting { task: start_task };
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
} else {
|
||||
self.server = CopilotServer::Disabled;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +407,7 @@ impl Copilot {
|
||||
);
|
||||
let http = http_client::FakeHttpClient::create(|_| async { unreachable!() });
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
let this = cx.new_model(|model, cx| Self {
|
||||
let this = cx.new_model(|cx| Self {
|
||||
server_id: LanguageServerId(0),
|
||||
http: http.clone(),
|
||||
node_runtime,
|
||||
@@ -419,7 +416,7 @@ impl Copilot {
|
||||
sign_in_status: SignInStatus::Authorized,
|
||||
registered_buffers: Default::default(),
|
||||
}),
|
||||
_subscription: model.on_app_quit(cx, Self::shutdown_language_server),
|
||||
_subscription: cx.on_app_quit(Self::shutdown_language_server),
|
||||
buffers: Default::default(),
|
||||
});
|
||||
(this, fake_server)
|
||||
@@ -488,8 +485,8 @@ impl Copilot {
|
||||
};
|
||||
|
||||
let server = start_language_server.await;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
model.notify(cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.notify();
|
||||
match server {
|
||||
Ok((server, status)) => {
|
||||
this.server = CopilotServer::Running(RunningCopilotServer {
|
||||
@@ -497,24 +494,24 @@ impl Copilot {
|
||||
sign_in_status: SignInStatus::SignedOut,
|
||||
registered_buffers: Default::default(),
|
||||
});
|
||||
model.emit(Event::CopilotLanguageServerStarted, cx);
|
||||
this.update_sign_in_status(status, model, cx);
|
||||
cx.emit(Event::CopilotLanguageServerStarted);
|
||||
this.update_sign_in_status(status, cx);
|
||||
}
|
||||
Err(error) => {
|
||||
this.server = CopilotServer::Error(error.to_string().into());
|
||||
model.notify(cx)
|
||||
cx.notify()
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn sign_in(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
pub fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if let CopilotServer::Running(server) = &mut self.server {
|
||||
let task = match &server.sign_in_status {
|
||||
SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(),
|
||||
SignInStatus::SigningIn { task, .. } => {
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
task.clone()
|
||||
}
|
||||
SignInStatus::SignedOut | SignInStatus::Unauthorized { .. } => {
|
||||
@@ -532,7 +529,7 @@ impl Copilot {
|
||||
Ok(request::SignInStatus::Ok { user: Some(user) })
|
||||
}
|
||||
request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let CopilotServer::Running(RunningCopilotServer {
|
||||
sign_in_status: status,
|
||||
..
|
||||
@@ -544,7 +541,7 @@ impl Copilot {
|
||||
} = status
|
||||
{
|
||||
*prompt_flow = Some(flow.clone());
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
@@ -561,7 +558,7 @@ impl Copilot {
|
||||
};
|
||||
|
||||
let sign_in = sign_in.await;
|
||||
this.update(&mut cx, |this, model, cx| match sign_in {
|
||||
this.update(&mut cx, |this, cx| match sign_in {
|
||||
Ok(status) => {
|
||||
this.update_sign_in_status(status, cx);
|
||||
Ok(())
|
||||
@@ -580,7 +577,7 @@ impl Copilot {
|
||||
prompt: None,
|
||||
task: task.clone(),
|
||||
};
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
task
|
||||
}
|
||||
};
|
||||
@@ -594,8 +591,8 @@ impl Copilot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign_out(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
self.update_sign_in_status(request::SignInStatus::NotSignedIn, model, cx);
|
||||
pub fn sign_out(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
|
||||
if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
|
||||
let server = server.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
@@ -609,7 +606,7 @@ impl Copilot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reinstall(&mut self, model: &Model<Self>, cx: &mut AppContext) -> Task<()> {
|
||||
pub fn reinstall(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
|
||||
let start_task = cx
|
||||
.spawn({
|
||||
let http = self.http.clone();
|
||||
@@ -626,7 +623,7 @@ impl Copilot {
|
||||
task: start_task.clone(),
|
||||
};
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
|
||||
cx.background_executor().spawn(start_task)
|
||||
}
|
||||
@@ -639,12 +636,7 @@ impl Copilot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_buffer(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn register_buffer(&mut self, buffer: &Model<Buffer>, cx: &mut ModelContext<Self>) {
|
||||
let weak_buffer = buffer.downgrade();
|
||||
self.buffers.insert(weak_buffer.clone());
|
||||
|
||||
@@ -702,15 +694,14 @@ impl Copilot {
|
||||
&mut self,
|
||||
buffer: Model<Buffer>,
|
||||
event: &language::BufferEvent,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
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 {
|
||||
language::BufferEvent::Edited => {
|
||||
drop(registered_buffer.report_changes(&buffer, model, cx));
|
||||
drop(registered_buffer.report_changes(&buffer, cx));
|
||||
}
|
||||
language::BufferEvent::Saved => {
|
||||
server
|
||||
@@ -781,33 +772,30 @@ impl Copilot {
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
position: T,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Completion>>>
|
||||
where
|
||||
T: ToPointUtf16,
|
||||
{
|
||||
self.request_completions::<request::GetCompletions, _>(buffer, position, model, cx)
|
||||
self.request_completions::<request::GetCompletions, _>(buffer, position, cx)
|
||||
}
|
||||
|
||||
pub fn completions_cycling<T>(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
position: T,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Completion>>>
|
||||
where
|
||||
T: ToPointUtf16,
|
||||
{
|
||||
self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, model, cx)
|
||||
self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
|
||||
}
|
||||
|
||||
pub fn accept_completion(
|
||||
&mut self,
|
||||
completion: &Completion,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let server = match self.server.as_authenticated() {
|
||||
Ok(server) => server,
|
||||
@@ -828,8 +816,7 @@ impl Copilot {
|
||||
pub fn discard_completions(
|
||||
&mut self,
|
||||
completions: &[Completion],
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let server = match self.server.as_authenticated() {
|
||||
Ok(server) => server,
|
||||
@@ -854,8 +841,7 @@ impl Copilot {
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
position: T,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Completion>>>
|
||||
where
|
||||
R: 'static
|
||||
@@ -865,7 +851,7 @@ impl Copilot {
|
||||
>,
|
||||
T: ToPointUtf16,
|
||||
{
|
||||
self.register_buffer(buffer, model, cx);
|
||||
self.register_buffer(buffer, cx);
|
||||
|
||||
let server = match self.server.as_authenticated() {
|
||||
Ok(server) => server,
|
||||
@@ -876,7 +862,7 @@ impl Copilot {
|
||||
.registered_buffers
|
||||
.get_mut(&buffer.entity_id())
|
||||
.unwrap();
|
||||
let snapshot = registered_buffer.report_changes(buffer, model, cx);
|
||||
let snapshot = registered_buffer.report_changes(buffer, cx);
|
||||
let buffer = buffer.read(cx);
|
||||
let uri = registered_buffer.uri.clone();
|
||||
let position = position.to_point_utf16(buffer);
|
||||
@@ -947,8 +933,7 @@ impl Copilot {
|
||||
fn update_sign_in_status(
|
||||
&mut self,
|
||||
lsp_status: request::SignInStatus,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.buffers.retain(|buffer| buffer.is_upgradable());
|
||||
|
||||
@@ -958,10 +943,10 @@ impl Copilot {
|
||||
| request::SignInStatus::MaybeOk { .. }
|
||||
| request::SignInStatus::AlreadySignedIn { .. } => {
|
||||
server.sign_in_status = SignInStatus::Authorized;
|
||||
model.emit(Event::CopilotAuthSignedIn, cx);
|
||||
cx.emit(Event::CopilotAuthSignedIn);
|
||||
for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
|
||||
if let Some(buffer) = buffer.upgrade() {
|
||||
self.register_buffer(&buffer, model, cx);
|
||||
self.register_buffer(&buffer, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -973,14 +958,14 @@ impl Copilot {
|
||||
}
|
||||
request::SignInStatus::Ok { user: None } | request::SignInStatus::NotSignedIn => {
|
||||
server.sign_in_status = SignInStatus::SignedOut;
|
||||
model.emit(Event::CopilotAuthSignedOut, cx);
|
||||
cx.emit(Event::CopilotAuthSignedOut);
|
||||
for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
|
||||
self.unregister_buffer(&buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1081,13 +1066,11 @@ mod tests {
|
||||
async fn test_buffer_management(cx: &mut TestAppContext) {
|
||||
let (copilot, mut lsp) = Copilot::fake(cx);
|
||||
|
||||
let buffer_1 = cx.new_model(|model, cx| Buffer::local("Hello", model, cx));
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local("Hello", cx));
|
||||
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
copilot.update(cx, |copilot, model, cx| {
|
||||
copilot.register_buffer(&buffer_1, model, cx)
|
||||
});
|
||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
@@ -1101,13 +1084,11 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let buffer_2 = cx.new_model(|model, cx| Buffer::local("Goodbye", model, cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local("Goodbye", cx));
|
||||
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
copilot.update(cx, |copilot, model, cx| {
|
||||
copilot.register_buffer(&buffer_2, model, cx)
|
||||
});
|
||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
@@ -1121,9 +1102,7 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
buffer_1.update(cx, |buffer, model, cx| {
|
||||
buffer.edit([(5..5, " world")], None, model, cx)
|
||||
});
|
||||
buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
|
||||
.await,
|
||||
@@ -1141,13 +1120,12 @@ mod tests {
|
||||
);
|
||||
|
||||
// Ensure updates to the file are reflected in the LSP.
|
||||
buffer_1.update(cx, |buffer, model, cx| {
|
||||
buffer_1.update(cx, |buffer, cx| {
|
||||
buffer.file_updated(
|
||||
Arc::new(File {
|
||||
abs_path: "/root/child/buffer-1".into(),
|
||||
path: Path::new("child/buffer-1").into(),
|
||||
}),
|
||||
model,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1177,7 +1155,7 @@ mod tests {
|
||||
Ok(request::SignOutResult {})
|
||||
});
|
||||
copilot
|
||||
.update(cx, |copilot, model, cx| copilot.sign_out(model, cx))
|
||||
.update(cx, |copilot, cx| copilot.sign_out(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1202,7 +1180,7 @@ mod tests {
|
||||
})
|
||||
});
|
||||
copilot
|
||||
.update(cx, |copilot, model, cx| copilot.sign_in(model, cx))
|
||||
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ pub struct CopilotChat {
|
||||
}
|
||||
|
||||
pub fn init(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut AppContext) {
|
||||
let copilot_chat = cx.new_model(|model, cx| CopilotChat::new(fs, client, cx));
|
||||
let copilot_chat = cx.new_model(|cx| CopilotChat::new(fs, client, cx));
|
||||
cx.set_global(GlobalCopilotChat(copilot_chat));
|
||||
}
|
||||
|
||||
@@ -245,9 +245,9 @@ impl CopilotChat {
|
||||
|
||||
cx.update(|cx| {
|
||||
if let Some(this) = Self::global(cx).as_ref() {
|
||||
this.update(cx, |this, model, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.oauth_token = oauth_token;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
})?;
|
||||
@@ -289,9 +289,9 @@ impl CopilotChat {
|
||||
Some(api_token) if api_token.remaining_seconds() > 5 * 60 => api_token.clone(),
|
||||
_ => {
|
||||
let token = request_api_token(&oauth_token, client.clone()).await?;
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.api_token = Some(token.clone());
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
})?;
|
||||
token
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{Completion, Copilot};
|
||||
use anyhow::Result;
|
||||
use client::telemetry::Telemetry;
|
||||
use gpui::{AppContext, EntityId, Model, Task};
|
||||
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
|
||||
use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
|
||||
use language::{
|
||||
language_settings::{all_language_settings, AllLanguageSettings},
|
||||
@@ -85,11 +85,10 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
buffer: Model<Buffer>,
|
||||
cursor_position: language::Anchor,
|
||||
debounce: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let copilot = self.copilot.clone();
|
||||
self.pending_refresh = model.spawn(cx, |this, mut cx| async move {
|
||||
self.pending_refresh = cx.spawn(|this, mut cx| async move {
|
||||
if debounce {
|
||||
cx.background_executor()
|
||||
.timer(COPILOT_DEBOUNCE_TIMEOUT)
|
||||
@@ -102,7 +101,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if !completions.is_empty() {
|
||||
this.cycled = false;
|
||||
this.pending_cycling_refresh = Task::ready(Ok(()));
|
||||
@@ -121,7 +120,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
for completion in completions {
|
||||
this.push_completion(completion);
|
||||
}
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -134,8 +133,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
buffer: Model<Buffer>,
|
||||
cursor_position: language::Anchor,
|
||||
direction: Direction,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if self.cycled {
|
||||
match direction {
|
||||
@@ -156,17 +154,17 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
} else {
|
||||
let copilot = self.copilot.clone();
|
||||
self.pending_cycling_refresh = model.spawn(cx, |this, mut cx| async move {
|
||||
self.pending_cycling_refresh = cx.spawn(|this, mut cx| async move {
|
||||
let completions = copilot
|
||||
.update(&mut cx, |copilot, cx| {
|
||||
copilot.completions_cycling(&buffer, cursor_position, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, model, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.cycled = true;
|
||||
this.file_extension = buffer.read(cx).file().and_then(|file| {
|
||||
Some(
|
||||
@@ -179,7 +177,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
for completion in completions {
|
||||
this.push_completion(completion);
|
||||
}
|
||||
this.cycle(buffer, cursor_position, direction, model, cx);
|
||||
this.cycle(buffer, cursor_position, direction, cx);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
@@ -187,12 +185,10 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
fn accept(&mut self, model: &Model<Self>, cx: &mut AppContext) {
|
||||
fn accept(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(completion) = self.active_completion() {
|
||||
self.copilot
|
||||
.update(cx, |copilot, model, cx| {
|
||||
copilot.accept_completion(completion, model, cx)
|
||||
})
|
||||
.update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
|
||||
.detach_and_log_err(cx);
|
||||
if self.active_completion().is_some() {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
@@ -209,8 +205,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
fn discard(
|
||||
&mut self,
|
||||
should_report_inline_completion_event: bool,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let settings = AllLanguageSettings::get_global(cx);
|
||||
|
||||
@@ -221,8 +216,8 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
}
|
||||
|
||||
self.copilot
|
||||
.update(cx, |copilot, model, cx| {
|
||||
copilot.discard_completions(&self.completions, model, cx)
|
||||
.update(cx, |copilot, cx| {
|
||||
copilot.discard_completions(&self.completions, cx)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
@@ -331,7 +326,7 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
let copilot_provider = cx.new_model(|_, _| CopilotCompletionProvider::new(copilot));
|
||||
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), cx)
|
||||
});
|
||||
@@ -533,7 +528,7 @@ mod tests {
|
||||
// Reset the editor to verify how suggestions behave when tabbing on leading indentation.
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.set_text("fn foo() {\n \n}", cx);
|
||||
editor.change_selections(None, model, cx, |s| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
|
||||
});
|
||||
});
|
||||
@@ -588,7 +583,7 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
let copilot_provider = cx.new_model(|_, _| CopilotCompletionProvider::new(copilot));
|
||||
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), cx)
|
||||
});
|
||||
@@ -712,7 +707,7 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
let copilot_provider = cx.new_model(|_, _| CopilotCompletionProvider::new(copilot));
|
||||
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), cx)
|
||||
});
|
||||
@@ -769,9 +764,9 @@ mod tests {
|
||||
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
|
||||
let buffer_1 = cx.new_model(|model, cx| Buffer::local("a = 1\nb = 2\n", model, cx));
|
||||
let buffer_2 = cx.new_model(|model, cx| Buffer::local("c = 3\nd = 4\n", model, cx));
|
||||
let multibuffer = cx.new_model(|model, cx| {
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local("c = 3\nd = 4\n", cx));
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(language::Capability::ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
@@ -779,7 +774,6 @@ mod tests {
|
||||
context: Point::new(0, 0)..Point::new(2, 0),
|
||||
primary: None,
|
||||
}],
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
@@ -788,19 +782,15 @@ mod tests {
|
||||
context: Point::new(0, 0)..Point::new(2, 0),
|
||||
primary: None,
|
||||
}],
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
let editor =
|
||||
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, true, model, cx));
|
||||
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, true, cx));
|
||||
editor.update(cx, |editor, cx| editor.focus(cx)).unwrap();
|
||||
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
|
||||
editor
|
||||
.update(cx, |editor, model, cx| editor.focus(window, cx))
|
||||
.unwrap();
|
||||
let copilot_provider = cx.new_model(|_, _| CopilotCompletionProvider::new(copilot));
|
||||
editor
|
||||
.update(cx, |editor, model, cx| {
|
||||
.update(cx, |editor, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), cx)
|
||||
})
|
||||
.unwrap();
|
||||
@@ -814,15 +804,15 @@ mod tests {
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
_ = editor.update(cx, |editor, model, cx| {
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
// Ensure copilot suggestions are shown for the first excerpt.
|
||||
editor.change_selections(None, model, cx, |s| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
|
||||
});
|
||||
editor.next_inline_completion(&Default::default(), cx);
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
_ = editor.update(cx, |editor, model, cx| {
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
assert!(editor.has_active_inline_completion(cx));
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
@@ -840,9 +830,9 @@ mod tests {
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
_ = editor.update(cx, |editor, model, cx| {
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
// Move to another excerpt, ensuring the suggestion gets cleared.
|
||||
editor.change_selections(None, model, cx, |s| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
|
||||
});
|
||||
assert!(!editor.has_active_inline_completion(cx));
|
||||
@@ -864,7 +854,7 @@ mod tests {
|
||||
|
||||
// Ensure the new suggestion is displayed when the debounce timeout expires.
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
_ = editor.update(cx, |editor, model, cx| {
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
assert!(editor.has_active_inline_completion(cx));
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
@@ -893,7 +883,7 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
let copilot_provider = cx.new_model(|_, _| CopilotCompletionProvider::new(copilot));
|
||||
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), cx)
|
||||
});
|
||||
@@ -1015,19 +1005,19 @@ mod tests {
|
||||
let project = Project::test(fs, ["/test".as_ref()], cx).await;
|
||||
|
||||
let private_buffer = project
|
||||
.update(cx, |project, model, cx| {
|
||||
project.open_local_buffer("/test/.env", model, cx)
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/test/.env", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let public_buffer = project
|
||||
.update(cx, |project, model, cx| {
|
||||
project.open_local_buffer("/test/README.md", model, cx)
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/test/README.md", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let multibuffer = cx.new_model(|model, cx| {
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(language::Capability::ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
private_buffer.clone(),
|
||||
@@ -1035,7 +1025,6 @@ mod tests {
|
||||
context: Point::new(0, 0)..Point::new(1, 0),
|
||||
primary: None,
|
||||
}],
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
@@ -1044,16 +1033,14 @@ mod tests {
|
||||
context: Point::new(0, 0)..Point::new(1, 0),
|
||||
primary: None,
|
||||
}],
|
||||
model,
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
let editor =
|
||||
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, true, model, cx));
|
||||
let copilot_provider = cx.new_model(|_, _| CopilotCompletionProvider::new(copilot));
|
||||
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, true, cx));
|
||||
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
|
||||
editor
|
||||
.update(cx, |editor, model, cx| {
|
||||
.update(cx, |editor, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), cx)
|
||||
})
|
||||
.unwrap();
|
||||
@@ -1074,8 +1061,8 @@ mod tests {
|
||||
},
|
||||
);
|
||||
|
||||
_ = editor.update(cx, |editor, model, cx| {
|
||||
editor.change_selections(None, model, cx, |selections| {
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
|
||||
});
|
||||
editor.refresh_inline_completion(true, false, cx);
|
||||
@@ -1084,8 +1071,8 @@ mod tests {
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
assert!(copilot_requests.try_next().is_err());
|
||||
|
||||
_ = editor.update(cx, |editor, model, cx| {
|
||||
editor.change_selections(None, model, cx, |s| {
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
|
||||
});
|
||||
editor.refresh_inline_completion(true, false, cx);
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{request::PromptUserDeviceFlow, Copilot, Status};
|
||||
use gpui::{
|
||||
div, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle,
|
||||
FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, ParentElement, Render,
|
||||
Styled, Subscription,
|
||||
Styled, Subscription, ViewContext,
|
||||
};
|
||||
use ui::{prelude::*, Button, Label, Vector, VectorName};
|
||||
use util::ResultExt as _;
|
||||
@@ -13,7 +13,7 @@ const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
|
||||
|
||||
struct CopilotStartingToast;
|
||||
|
||||
pub fn initiate_sign_in(window: &mut gpui::Window, cx: &mut gpui::AppContext) {
|
||||
pub fn initiate_sign_in(cx: &mut WindowContext) {
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return;
|
||||
};
|
||||
@@ -27,7 +27,7 @@ pub fn initiate_sign_in(window: &mut gpui::Window, cx: &mut gpui::AppContext) {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(workspace) = workspace.update(cx, |workspace, model, cx| {
|
||||
let Ok(workspace) = workspace.update(cx, |workspace, cx| {
|
||||
workspace.show_toast(
|
||||
Toast::new(
|
||||
NotificationId::unique::<CopilotStartingToast>(),
|
||||
@@ -58,7 +58,7 @@ pub fn initiate_sign_in(window: &mut gpui::Window, cx: &mut gpui::AppContext) {
|
||||
cx,
|
||||
);
|
||||
copilot
|
||||
.update(cx, |copilot, model, cx| copilot.sign_in(cx))
|
||||
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
@@ -68,12 +68,10 @@ pub fn initiate_sign_in(window: &mut gpui::Window, cx: &mut gpui::AppContext) {
|
||||
.detach();
|
||||
}
|
||||
_ => {
|
||||
copilot
|
||||
.update(cx, |this, model, cx| this.sign_in(cx))
|
||||
.detach();
|
||||
copilot.update(cx, |this, cx| this.sign_in(cx)).detach();
|
||||
workspace
|
||||
.update(cx, |this, model, cx| {
|
||||
this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, model, cx));
|
||||
.update(cx, |this, cx| {
|
||||
this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx));
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -97,33 +95,32 @@ impl EventEmitter<DismissEvent> for CopilotCodeVerification {}
|
||||
impl ModalView for CopilotCodeVerification {}
|
||||
|
||||
impl CopilotCodeVerification {
|
||||
pub fn new(copilot: &Model<Copilot>, model: &Model<Self>, cx: &mut AppContext) -> Self {
|
||||
pub fn new(copilot: &Model<Copilot>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let status = copilot.read(cx).status();
|
||||
Self {
|
||||
status,
|
||||
connect_clicked: false,
|
||||
focus_handle: window.focus_handle(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
_subscription: cx.observe(copilot, |this, copilot, cx| {
|
||||
let status = copilot.read(cx).status();
|
||||
match status {
|
||||
Status::Authorized | Status::Unauthorized | Status::SigningIn { .. } => {
|
||||
this.set_status(status, cx)
|
||||
}
|
||||
_ => model.emit(DismissEvent, cx),
|
||||
_ => cx.emit(DismissEvent),
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: Status, model: &Model<Self>, cx: &mut AppContext) {
|
||||
pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
|
||||
self.status = status;
|
||||
model.notify(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_device_code(
|
||||
data: &PromptUserDeviceFlow,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let copied = cx
|
||||
.read_from_clipboard()
|
||||
@@ -155,8 +152,7 @@ impl CopilotCodeVerification {
|
||||
fn render_prompting_modal(
|
||||
connect_clicked: bool,
|
||||
data: &PromptUserDeviceFlow,
|
||||
model: &Model<Self>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl Element {
|
||||
let connect_button_label = if connect_clicked {
|
||||
"Waiting for connection..."
|
||||
@@ -172,7 +168,7 @@ impl CopilotCodeVerification {
|
||||
Label::new("Using Copilot requires an active subscription on GitHub.")
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Self::render_device_code(data, model, cx))
|
||||
.child(Self::render_device_code(data, cx))
|
||||
.child(
|
||||
Label::new("Paste this code into GitHub after clicking the button below.")
|
||||
.size(ui::LabelSize::Small),
|
||||
@@ -181,7 +177,7 @@ impl CopilotCodeVerification {
|
||||
Button::new("connect-button", connect_button_label)
|
||||
.on_click({
|
||||
let verification_uri = data.verification_uri.clone();
|
||||
model.listener(move |this, _, cx| {
|
||||
cx.listener(move |this, _, cx| {
|
||||
cx.open_url(&verification_uri);
|
||||
this.connect_clicked = true;
|
||||
})
|
||||
@@ -192,10 +188,10 @@ impl CopilotCodeVerification {
|
||||
.child(
|
||||
Button::new("copilot-enable-cancel-button", "Cancel")
|
||||
.full_width()
|
||||
.on_click(cx.listener(|_, _, cx| model.emit(DismissEvent, cx))),
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
}
|
||||
fn render_enabled_modal(model: &Model<Self>, cx: &mut AppContext) -> impl Element {
|
||||
fn render_enabled_modal(cx: &mut ViewContext<Self>) -> impl Element {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Headline::new("Copilot Enabled!").size(HeadlineSize::Large))
|
||||
@@ -205,11 +201,11 @@ impl CopilotCodeVerification {
|
||||
.child(
|
||||
Button::new("copilot-enabled-done-button", "Done")
|
||||
.full_width()
|
||||
.on_click(cx.listener(|_, _, cx| model.emit(DismissEvent, cx))),
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_unauthorized_modal(model: &Model<Self>, cx: &mut AppContext) -> impl Element {
|
||||
fn render_unauthorized_modal(cx: &mut ViewContext<Self>) -> impl Element {
|
||||
v_flex()
|
||||
.child(Headline::new("You must have an active GitHub Copilot subscription.").size(HeadlineSize::Large))
|
||||
|
||||
@@ -224,7 +220,7 @@ impl CopilotCodeVerification {
|
||||
.child(
|
||||
Button::new("copilot-subscribe-cancel-button", "Cancel")
|
||||
.full_width()
|
||||
.on_click(cx.listener(|_, _, cx| model.emit(DismissEvent, cx))),
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -236,24 +232,18 @@ impl CopilotCodeVerification {
|
||||
}
|
||||
|
||||
impl Render for CopilotCodeVerification {
|
||||
fn render(
|
||||
&mut self,
|
||||
model: &Model<Self>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut AppContext,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let prompt = match &self.status {
|
||||
Status::SigningIn {
|
||||
prompt: Some(prompt),
|
||||
} => Self::render_prompting_modal(self.connect_clicked, prompt, model, cx)
|
||||
.into_any_element(),
|
||||
} => Self::render_prompting_modal(self.connect_clicked, prompt, cx).into_any_element(),
|
||||
Status::Unauthorized => {
|
||||
self.connect_clicked = false;
|
||||
Self::render_unauthorized_modal(model, cx).into_any_element()
|
||||
Self::render_unauthorized_modal(cx).into_any_element()
|
||||
}
|
||||
Status::Authorized => {
|
||||
self.connect_clicked = false;
|
||||
Self::render_enabled_modal(model, cx).into_any_element()
|
||||
Self::render_enabled_modal(cx).into_any_element()
|
||||
}
|
||||
Status::Disabled => {
|
||||
self.connect_clicked = false;
|
||||
@@ -271,7 +261,7 @@ impl Render for CopilotCodeVerification {
|
||||
.p_4()
|
||||
.gap_2()
|
||||
.on_action(cx.listener(|_, _: &menu::Cancel, cx| {
|
||||
model.emit(DismissEvent, cx);
|
||||
cx.emit(DismissEvent);
|
||||
}))
|
||||
.on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, cx| {
|
||||
cx.focus(&this.focus_handle);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user