Merge branch 'main' into collab-panel

This commit is contained in:
Mikayla 2023-08-09 10:37:22 -07:00
commit 99daa73325
No known key found for this signature in database
93 changed files with 1908 additions and 1442 deletions

43
Cargo.lock generated
View file

@ -1652,6 +1652,12 @@ dependencies = [
"theme", "theme",
] ]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.6.0" version = "0.6.0"
@ -2145,6 +2151,19 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case 0.4.0",
"proc-macro2",
"quote",
"rustc_version 0.4.0",
"syn 1.0.109",
]
[[package]] [[package]]
name = "dhat" name = "dhat"
version = "0.3.2" version = "0.3.2"
@ -2326,7 +2345,7 @@ dependencies = [
"clock", "clock",
"collections", "collections",
"context_menu", "context_menu",
"convert_case", "convert_case 0.6.0",
"copilot", "copilot",
"ctor", "ctor",
"db", "db",
@ -3109,6 +3128,7 @@ dependencies = [
"core-graphics", "core-graphics",
"core-text", "core-text",
"ctor", "ctor",
"derive_more",
"dhat", "dhat",
"env_logger 0.9.3", "env_logger 0.9.3",
"etagere", "etagere",
@ -5119,7 +5139,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff"
dependencies = [ dependencies = [
"rustc_version", "rustc_version 0.3.3",
] ]
[[package]] [[package]]
@ -6289,7 +6309,16 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [ dependencies = [
"semver", "semver 0.11.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.18",
] ]
[[package]] [[package]]
@ -6729,6 +6758,12 @@ dependencies = [
"semver-parser", "semver-parser",
] ]
[[package]]
name = "semver"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]] [[package]]
name = "semver-parser" name = "semver-parser"
version = "0.10.2" version = "0.10.2"
@ -9828,7 +9863,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.99.0" version = "0.100.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"ai", "ai",

View file

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

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

View file

@ -541,45 +541,50 @@ impl TestClient {
&self, &self,
project: &ModelHandle<Project>, project: &ModelHandle<Project>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> ViewHandle<Workspace> { // <<<<<<< HEAD
struct WorkspaceContainer { // ) -> ViewHandle<Workspace> {
workspace: Option<WeakViewHandle<Workspace>>, // struct WorkspaceContainer {
} // workspace: Option<WeakViewHandle<Workspace>>,
// }
impl Entity for WorkspaceContainer { // impl Entity for WorkspaceContainer {
type Event = (); // type Event = ();
} // }
impl View for WorkspaceContainer { // impl View for WorkspaceContainer {
fn ui_name() -> &'static str { // fn ui_name() -> &'static str {
"WorkspaceContainer" // "WorkspaceContainer"
} // }
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { // fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
if let Some(workspace) = self // if let Some(workspace) = self
.workspace // .workspace
.as_ref() // .as_ref()
.and_then(|workspace| workspace.upgrade(cx)) // .and_then(|workspace| workspace.upgrade(cx))
{ // {
ChildView::new(&workspace, cx).into_any() // ChildView::new(&workspace, cx).into_any()
} else { // } else {
Empty::new().into_any() // Empty::new().into_any()
} // }
} // }
} // }
// We use a workspace container so that we don't need to remove the window in order to // // We use a workspace container so that we don't need to remove the window in order to
// drop the workspace and we can use a ViewHandle instead. // // drop the workspace and we can use a ViewHandle instead.
let window = cx.add_window(|_| WorkspaceContainer { workspace: None }); // let window = cx.add_window(|_| WorkspaceContainer { workspace: None });
let container = window.root(cx); // let container = window.root(cx);
let workspace = window.add_view(cx, |cx| { // let workspace = window.add_view(cx, |cx| {
Workspace::new(0, project.clone(), self.app_state.clone(), cx) // Workspace::new(0, project.clone(), self.app_state.clone(), cx)
}); // });
container.update(cx, |container, cx| { // container.update(cx, |container, cx| {
container.workspace = Some(workspace.downgrade()); // container.workspace = Some(workspace.downgrade());
cx.notify(); // cx.notify();
}); // });
workspace // workspace
// =======
) -> WindowHandle<Workspace> {
cx.add_window(|cx| Workspace::test_new(project.clone(), cx))
// >>>>>>> main
} }
} }

View file

@ -1511,7 +1511,7 @@ async fn test_host_disconnect(
.unwrap(); .unwrap();
assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx))); assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.is_window_edited(workspace_b.window_id())); assert!(window_b.is_edited(cx_b));
// Drop client A's connection. Collaborators should disappear and the project should not be shown as shared. // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
server.forbid_connections(); server.forbid_connections();
@ -1526,7 +1526,7 @@ async fn test_host_disconnect(
window_b.read_with(cx_b, |cx| { window_b.read_with(cx_b, |cx| {
assert_eq!(cx.focused_view_id(), None); assert_eq!(cx.focused_view_id(), None);
}); });
assert!(!cx_b.is_window_edited(workspace_b.window_id())); assert!(!window_b.is_edited(cx_b));
// Ensure client B is not prompted to save edits when closing window after disconnecting. // Ensure client B is not prompted to save edits when closing window after disconnecting.
let can_close = workspace_b let can_close = workspace_b
@ -3442,7 +3442,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
let mut editor_cx_a = EditorTestContext { let mut editor_cx_a = EditorTestContext {
cx: cx_a, cx: cx_a,
window_id: window_a.window_id(), window: window_a.into(),
editor: editor_a, editor: editor_a,
}; };
@ -3455,7 +3455,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx)); let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
let mut editor_cx_b = EditorTestContext { let mut editor_cx_b = EditorTestContext {
cx: cx_b, cx: cx_b,
window_id: window_b.window_id(), window: window_b.into(),
editor: editor_b, editor: editor_b,
}; };
@ -6440,8 +6440,10 @@ async fn test_basic_following(
.await .await
.unwrap(); .unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a); let window_a = client_a.build_workspace(&project_a, cx_a);
let workspace_b = client_b.build_workspace(&project_b, cx_b); let workspace_a = window_a.root(cx_a);
let window_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = window_b.root(cx_b);
// Client A opens some editors. // Client A opens some editors.
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone()); let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
@ -6524,7 +6526,8 @@ async fn test_basic_following(
cx_c.foreground().run_until_parked(); cx_c.foreground().run_until_parked();
let active_call_c = cx_c.read(ActiveCall::global); let active_call_c = cx_c.read(ActiveCall::global);
let project_c = client_c.build_remote_project(project_id, cx_c).await; let project_c = client_c.build_remote_project(project_id, cx_c).await;
let workspace_c = client_c.build_workspace(&project_c, cx_c); let window_c = client_c.build_workspace(&project_c, cx_c);
let workspace_c = window_c.root(cx_c);
active_call_c active_call_c
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
.await .await
@ -6542,7 +6545,7 @@ async fn test_basic_following(
cx_d.foreground().run_until_parked(); cx_d.foreground().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global); let active_call_d = cx_d.read(ActiveCall::global);
let project_d = client_d.build_remote_project(project_id, cx_d).await; let project_d = client_d.build_remote_project(project_id, cx_d).await;
let workspace_d = client_d.build_workspace(&project_d, cx_d); let workspace_d = client_d.build_workspace(&project_d, cx_d).root(cx_d);
active_call_d active_call_d
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx)) .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
.await .await
@ -6640,6 +6643,7 @@ async fn test_basic_following(
} }
// Client C closes the project. // Client C closes the project.
window_c.remove(cx_c);
cx_c.drop_last(workspace_c); cx_c.drop_last(workspace_c);
// Clients A and B see that client B is following A, and client C is not present in the followers. // Clients A and B see that client B is following A, and client C is not present in the followers.
@ -6869,9 +6873,7 @@ async fn test_basic_following(
}); });
// Client B activates a panel, and the previously-opened screen-sharing item gets activated. // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
let panel = cx_b.add_view(workspace_b.window_id(), |_| { let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
TestPanel::new(DockPosition::Left)
});
workspace_b.update(cx_b, |workspace, cx| { workspace_b.update(cx_b, |workspace, cx| {
workspace.add_panel(panel, cx); workspace.add_panel(panel, cx);
workspace.toggle_panel_focus::<TestPanel>(cx); workspace.toggle_panel_focus::<TestPanel>(cx);
@ -6899,7 +6901,7 @@ async fn test_basic_following(
// Client B activates an item that doesn't implement following, // Client B activates an item that doesn't implement following,
// so the previously-opened screen-sharing item gets activated. // so the previously-opened screen-sharing item gets activated.
let unfollowable_item = cx_b.add_view(workspace_b.window_id(), |_| TestItem::new()); let unfollowable_item = window_b.add_view(cx_b, |_| TestItem::new());
workspace_b.update(cx_b, |workspace, cx| { workspace_b.update(cx_b, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| { workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(Box::new(unfollowable_item), true, true, None, cx) pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
@ -7061,10 +7063,10 @@ async fn test_following_tab_order(
.await .await
.unwrap(); .unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a); let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone()); let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
let workspace_b = client_b.build_workspace(&project_b, cx_b); let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone()); let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let client_b_id = project_a.read_with(cx_a, |project, _| { let client_b_id = project_a.read_with(cx_a, |project, _| {
@ -7187,7 +7189,7 @@ async fn test_peers_following_each_other(
.unwrap(); .unwrap();
// Client A opens some editors. // Client A opens some editors.
let workspace_a = client_a.build_workspace(&project_a, cx_a); let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone()); let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
let _editor_a1 = workspace_a let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| { .update(cx_a, |workspace, cx| {
@ -7199,7 +7201,7 @@ async fn test_peers_following_each_other(
.unwrap(); .unwrap();
// Client B opens an editor. // Client B opens an editor.
let workspace_b = client_b.build_workspace(&project_b, cx_b); let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone()); let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let _editor_b1 = workspace_b let _editor_b1 = workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {
@ -7358,7 +7360,7 @@ async fn test_auto_unfollowing(
.unwrap(); .unwrap();
// Client A opens some editors. // Client A opens some editors.
let workspace_a = client_a.build_workspace(&project_a, cx_a); let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let _editor_a1 = workspace_a let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| { .update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx) workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@ -7369,7 +7371,7 @@ async fn test_auto_unfollowing(
.unwrap(); .unwrap();
// Client B starts following client A. // Client B starts following client A.
let workspace_b = client_b.build_workspace(&project_b, cx_b); let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone()); let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let leader_id = project_b.read_with(cx_b, |project, _| { let leader_id = project_b.read_with(cx_b, |project, _| {
project.collaborators().values().next().unwrap().peer_id project.collaborators().values().next().unwrap().peer_id
@ -7497,14 +7499,14 @@ async fn test_peers_simultaneously_following_each_other(
client_a.fs().insert_tree("/a", json!({})).await; client_a.fs().insert_tree("/a", json!({})).await;
let (project_a, _) = client_a.build_local_project("/a", cx_a).await; let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
let workspace_a = client_a.build_workspace(&project_a, cx_a); let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let project_id = active_call_a let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await; let project_b = client_b.build_remote_project(project_id, cx_b).await;
let workspace_b = client_b.build_workspace(&project_b, cx_b); let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
deterministic.run_until_parked(); deterministic.run_until_parked();
let client_a_id = project_b.read_with(cx_b, |project, _| { let client_a_id = project_b.read_with(cx_b, |project, _| {
@ -7886,7 +7888,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.await .await
.unwrap(); .unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a); let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
cx_a.foreground().start_waiting(); cx_a.foreground().start_waiting();
let _buffer_a = project_a let _buffer_a = project_a
@ -7954,7 +7956,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"Host editor update the cache version after every cache/view change", "Host editor update the cache version after every cache/view change",
); );
}); });
let workspace_b = client_b.build_workspace(&project_b, cx_b); let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let editor_b = workspace_b let editor_b = workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx) workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@ -8193,8 +8195,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
.await .await
.unwrap(); .unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a); let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let workspace_b = client_b.build_workspace(&project_b, cx_b); let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
cx_a.foreground().start_waiting(); cx_a.foreground().start_waiting();
cx_b.foreground().start_waiting(); cx_b.foreground().start_waiting();

View file

@ -1891,18 +1891,18 @@ impl CollabPanel {
); );
let mut answer = let mut answer =
cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]); cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
let window_id = cx.window_id(); let window = cx.window();
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
if answer.next().await == Some(0) { if answer.next().await == Some(0) {
if let Err(e) = channel_store if let Err(e) = channel_store
.update(&mut cx, |channels, _| channels.remove_channel(channel_id)) .update(&mut cx, |channels, _| channels.remove_channel(channel_id))
.await .await
{ {
cx.prompt( window.prompt(
window_id,
PromptLevel::Info, PromptLevel::Info,
&format!("Failed to remove channel: {}", e), &format!("Failed to remove channel: {}", e),
&["Ok"], &["Ok"],
&mut cx,
); );
} }
} }
@ -1921,18 +1921,18 @@ impl CollabPanel {
github_login github_login
); );
let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]); let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
let window_id = cx.window_id(); let window = cx.window();
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
if answer.next().await == Some(0) { if answer.next().await == Some(0) {
if let Err(e) = user_store if let Err(e) = user_store
.update(&mut cx, |store, cx| store.remove_contact(user_id, cx)) .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
.await .await
{ {
cx.prompt( window.prompt(
window_id,
PromptLevel::Info, PromptLevel::Info,
&format!("Failed to remove contact: {}", e), &format!("Failed to remove contact: {}", e),
&["Ok"], &["Ok"],
&mut cx,
); );
} }
} }

View file

@ -13,8 +13,8 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f, PathBuilder}, geometry::{rect::RectF, vector::vec2f, PathBuilder},
json::{self, ToJson}, json::{self, ToJson},
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View, AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
ViewContext, ViewHandle, WeakViewHandle, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use picker::PickerEvent; use picker::PickerEvent;
use project::{Project, RepositoryEntry}; use project::{Project, RepositoryEntry};
@ -1190,7 +1190,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
_: RectF, _: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
_: &mut CollabTitlebarItem, _: &mut CollabTitlebarItem,
_: &mut ViewContext<CollabTitlebarItem>, _: &mut PaintContext<CollabTitlebarItem>,
) -> Self::PaintState { ) -> Self::PaintState {
let mut path = PathBuilder::new(); let mut path = PathBuilder::new();
path.reset(bounds.lower_left()); path.reset(bounds.lower_left());

View file

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

View file

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

View file

@ -52,20 +52,20 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
notification_windows notification_windows
.entry(*project_id) .entry(*project_id)
.or_insert(Vec::new()) .or_insert(Vec::new())
.push(window.window_id()); .push(window);
} }
} }
room::Event::RemoteProjectUnshared { project_id } => { room::Event::RemoteProjectUnshared { project_id } => {
if let Some(window_ids) = notification_windows.remove(&project_id) { if let Some(windows) = notification_windows.remove(&project_id) {
for window_id in window_ids { for window in windows {
cx.update_window(window_id, |cx| cx.remove_window()); window.remove(cx);
} }
} }
} }
room::Event::Left => { room::Event::Left => {
for (_, window_ids) in notification_windows.drain() { for (_, windows) in notification_windows.drain() {
for window_id in window_ids { for window in windows {
cx.update_window(window_id, |cx| cx.remove_window()); window.remove(cx);
} }
} }
} }

View file

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

View file

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

View file

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

View file

@ -857,7 +857,6 @@ mod tests {
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
// Create some diagnostics // Create some diagnostics
project.update(cx, |project, cx| { project.update(cx, |project, cx| {
@ -944,7 +943,7 @@ mod tests {
}); });
// Open the project diagnostics view while there are already diagnostics. // Open the project diagnostics view while there are already diagnostics.
let view = cx.add_view(window_id, |cx| { let view = window.add_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx) ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
}); });
@ -1252,9 +1251,8 @@ mod tests {
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let view = cx.add_view(window_id, |cx| { let view = window.add_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx) ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
}); });

View file

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

View file

@ -90,7 +90,7 @@ use std::{
cmp::{self, Ordering, Reverse}, cmp::{self, Ordering, Reverse},
mem, mem,
num::NonZeroU32, num::NonZeroU32,
ops::{ControlFlow, Deref, DerefMut, Range}, ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
path::Path, path::Path,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
@ -7549,6 +7549,78 @@ impl Editor {
results results
} }
pub fn background_highlight_row_ranges<T: 'static>(
&self,
search_range: Range<Anchor>,
display_snapshot: &DisplaySnapshot,
count: usize,
) -> Vec<RangeInclusive<DisplayPoint>> {
let mut results = Vec::new();
let buffer = &display_snapshot.buffer_snapshot;
let Some((_, ranges)) = self.background_highlights
.get(&TypeId::of::<T>()) else {
return vec![];
};
let start_ix = match ranges.binary_search_by(|probe| {
let cmp = probe.end.cmp(&search_range.start, buffer);
if cmp.is_gt() {
Ordering::Greater
} else {
Ordering::Less
}
}) {
Ok(i) | Err(i) => i,
};
let mut push_region = |start: Option<Point>, end: Option<Point>| {
if let (Some(start_display), Some(end_display)) = (start, end) {
results.push(
start_display.to_display_point(display_snapshot)
..=end_display.to_display_point(display_snapshot),
);
}
};
let mut start_row: Option<Point> = None;
let mut end_row: Option<Point> = None;
if ranges.len() > count {
return vec![];
}
for range in &ranges[start_ix..] {
if range.start.cmp(&search_range.end, buffer).is_ge() {
break;
}
let end = range.end.to_point(buffer);
if let Some(current_row) = &end_row {
if end.row == current_row.row {
continue;
}
}
let start = range.start.to_point(buffer);
if start_row.is_none() {
assert_eq!(end_row, None);
start_row = Some(start);
end_row = Some(end);
continue;
}
if let Some(current_end) = end_row.as_mut() {
if start.row > current_end.row + 1 {
push_region(start_row, end_row);
start_row = Some(start);
end_row = Some(end);
} else {
// Merge two hunks.
*current_end = end;
}
} else {
unreachable!();
}
}
// We might still have a hunk that was not rendered (if there was a search hit on the last line)
push_region(start_row, end_row);
results
}
pub fn highlight_text<T: 'static>( pub fn highlight_text<T: 'static>(
&mut self, &mut self,
ranges: Vec<Range<Anchor>>, ranges: Vec<Range<Anchor>>,

View file

@ -525,9 +525,8 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
let project = Project::test(fs, [], cx).await; let project = Project::test(fs, [], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
cx.add_view(window_id, |cx| { window.add_view(cx, |cx| {
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
let mut editor = build_editor(buffer.clone(), cx); let mut editor = build_editor(buffer.clone(), cx);
let handle = cx.handle(); let handle = cx.handle();
@ -1290,7 +1289,8 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon
let mut cx = EditorTestContext::new(cx).await; let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height)); let window = cx.window;
window.simulate_resize(vec2f(100., 4. * line_height), &mut cx);
cx.set_state( cx.set_state(
&r#"ˇone &r#"ˇone
@ -1401,7 +1401,8 @@ async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await; let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
cx.simulate_window_resize(cx.window_id, vec2f(1000., 4. * line_height + 0.5)); let window = cx.window;
window.simulate_resize(vec2f(1000., 4. * line_height + 0.5), &mut cx);
cx.set_state( cx.set_state(
&r#"ˇone &r#"ˇone
@ -1439,7 +1440,8 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await; let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height)); let window = cx.window;
window.simulate_resize(vec2f(100., 4. * line_height), &mut cx);
cx.set_state( cx.set_state(
&r#" &r#"

View file

@ -32,7 +32,7 @@ use gpui::{
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent}, platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache}, text_layout::{self, Line, RunStyle, TextLayoutCache},
AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext, AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext,
MouseRegion, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext, MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
}; };
use itertools::Itertools; use itertools::Itertools;
use json::json; use json::json;
@ -1107,8 +1107,6 @@ impl EditorElement {
if layout.is_singleton && scrollbar_settings.selections { if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min(); let start_anchor = Anchor::min();
let end_anchor = Anchor::max(); let end_anchor = Anchor::max();
let mut start_row = None;
let mut end_row = None;
let color = scrollbar_theme.selections; let color = scrollbar_theme.selections;
let border = Border { let border = Border {
width: 1., width: 1.,
@ -1119,54 +1117,32 @@ impl EditorElement {
bottom: false, bottom: false,
left: true, left: true,
}; };
let mut push_region = |start, end| { let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
if let (Some(start_display), Some(end_display)) = (start, end) { let start_y = y_for_row(start.row() as f32);
let start_y = y_for_row(start_display as f32); let mut end_y = y_for_row(end.row() as f32);
let mut end_y = y_for_row(end_display as f32); if end_y - start_y < 1. {
if end_y - start_y < 1. { end_y = start_y + 1.;
end_y = start_y + 1.;
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
} }
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
}; };
for (row, _) in &editor let background_ranges = editor
.background_highlights_in_range_for::<crate::items::BufferSearchHighlights>( .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
start_anchor..end_anchor, start_anchor..end_anchor,
&layout.position_map.snapshot, &layout.position_map.snapshot,
&theme, 50000,
) );
{ for row in background_ranges {
let start_display = row.start; let start = row.start();
let end_display = row.end; let end = row.end();
push_region(*start, *end);
if start_row.is_none() {
assert_eq!(end_row, None);
start_row = Some(start_display.row());
end_row = Some(end_display.row());
continue;
}
if let Some(current_end) = end_row.as_mut() {
if start_display.row() > *current_end + 1 {
push_region(start_row, end_row);
start_row = Some(start_display.row());
end_row = Some(end_display.row());
} else {
// Merge two hunks.
*current_end = end_display.row();
}
} else {
unreachable!();
}
} }
// We might still have a hunk that was not rendered (if there was a search hit on the last line)
push_region(start_row, end_row);
} }
if layout.is_singleton && scrollbar_settings.git_diff { if layout.is_singleton && scrollbar_settings.git_diff {
@ -2479,7 +2455,7 @@ impl Element<Editor> for EditorElement {
visible_bounds: RectF, visible_bounds: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
editor: &mut Editor, editor: &mut Editor,
cx: &mut ViewContext<Editor>, cx: &mut PaintContext<Editor>,
) -> Self::PaintState { ) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds)); scene.push_layer(Some(visible_bounds));
@ -3079,7 +3055,14 @@ mod tests {
let mut scene = SceneBuilder::new(1.0); let mut scene = SceneBuilder::new(1.0);
let bounds = RectF::new(Default::default(), size); let bounds = RectF::new(Default::default(), size);
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
element.paint(&mut scene, bounds, bounds, &mut state, editor, cx); element.paint(
&mut scene,
bounds,
bounds,
&mut state,
editor,
&mut PaintContext::new(cx),
);
}); });
} }

View file

@ -99,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> {
Self { Self {
cx: EditorTestContext { cx: EditorTestContext {
cx, cx,
window_id: window.window_id(), window: window.into(),
editor, editor,
}, },
lsp, lsp,

View file

@ -3,7 +3,8 @@ use crate::{
}; };
use futures::Future; use futures::Future;
use gpui::{ use gpui::{
keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle, keymap_matcher::Keystroke, AnyWindowHandle, AppContext, ContextHandle, ModelContext,
ViewContext, ViewHandle,
}; };
use indoc::indoc; use indoc::indoc;
use language::{Buffer, BufferSnapshot}; use language::{Buffer, BufferSnapshot};
@ -21,7 +22,7 @@ use super::build_editor;
pub struct EditorTestContext<'a> { pub struct EditorTestContext<'a> {
pub cx: &'a mut gpui::TestAppContext, pub cx: &'a mut gpui::TestAppContext,
pub window_id: usize, pub window: AnyWindowHandle,
pub editor: ViewHandle<Editor>, pub editor: ViewHandle<Editor>,
} }
@ -39,7 +40,7 @@ impl<'a> EditorTestContext<'a> {
let editor = window.root(cx); let editor = window.root(cx);
Self { Self {
cx, cx,
window_id: window.window_id(), window: window.into(),
editor, editor,
} }
} }
@ -111,7 +112,8 @@ impl<'a> EditorTestContext<'a> {
let keystroke_under_test_handle = let keystroke_under_test_handle =
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text)); self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
let keystroke = Keystroke::parse(keystroke_text).unwrap(); let keystroke = Keystroke::parse(keystroke_text).unwrap();
self.cx.dispatch_keystroke(self.window_id, keystroke, false);
self.cx.dispatch_keystroke(self.window, keystroke, false);
keystroke_under_test_handle keystroke_under_test_handle
} }

View file

@ -619,7 +619,7 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle); cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap()); let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder finder
@ -632,8 +632,8 @@ mod tests {
}); });
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window.window_id(), SelectNext); cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.window_id(), Confirm); cx.dispatch_action(window.into(), Confirm);
active_pane active_pane
.condition(cx, |pane, _| pane.active_item().is_some()) .condition(cx, |pane, _| pane.active_item().is_some())
.await; .await;
@ -674,7 +674,7 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle); cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap()); let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3]; let file_query = &first_file_name[..3];
@ -706,8 +706,8 @@ mod tests {
}); });
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window.window_id(), SelectNext); cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.window_id(), Confirm); cx.dispatch_action(window.into(), Confirm);
active_pane active_pane
.condition(cx, |pane, _| pane.active_item().is_some()) .condition(cx, |pane, _| pane.active_item().is_some())
.await; .await;
@ -758,7 +758,7 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle); cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap()); let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3]; let file_query = &first_file_name[..3];
@ -790,8 +790,8 @@ mod tests {
}); });
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window.window_id(), SelectNext); cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.window_id(), Confirm); cx.dispatch_action(window.into(), Confirm);
active_pane active_pane
.condition(cx, |pane, _| pane.active_item().is_some()) .condition(cx, |pane, _| pane.active_item().is_some())
.await; .await;
@ -1168,7 +1168,6 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let worktree_id = cx.read(|cx| { let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1); assert_eq!(worktrees.len(), 1);
@ -1186,7 +1185,7 @@ mod tests {
"fir", "fir",
1, 1,
"first.rs", "first.rs",
window_id, window.into(),
&workspace, &workspace,
&deterministic, &deterministic,
cx, cx,
@ -1201,7 +1200,7 @@ mod tests {
"sec", "sec",
1, 1,
"second.rs", "second.rs",
window_id, window.into(),
&workspace, &workspace,
&deterministic, &deterministic,
cx, cx,
@ -1223,7 +1222,7 @@ mod tests {
"thi", "thi",
1, 1,
"third.rs", "third.rs",
window_id, window.into(),
&workspace, &workspace,
&deterministic, &deterministic,
cx, cx,
@ -1255,7 +1254,7 @@ mod tests {
"sec", "sec",
1, 1,
"second.rs", "second.rs",
window_id, window.into(),
&workspace, &workspace,
&deterministic, &deterministic,
cx, cx,
@ -1294,7 +1293,7 @@ mod tests {
"thi", "thi",
1, 1,
"third.rs", "third.rs",
window_id, window.into(),
&workspace, &workspace,
&deterministic, &deterministic,
cx, cx,
@ -1376,7 +1375,6 @@ mod tests {
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let worktree_id = cx.read(|cx| { let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1,); assert_eq!(worktrees.len(), 1,);
@ -1411,7 +1409,7 @@ mod tests {
"sec", "sec",
1, 1,
"second.rs", "second.rs",
window_id, window.into(),
&workspace, &workspace,
&deterministic, &deterministic,
cx, cx,
@ -1433,7 +1431,7 @@ mod tests {
"fir", "fir",
1, 1,
"first.rs", "first.rs",
window_id, window.into(),
&workspace, &workspace,
&deterministic, &deterministic,
cx, cx,
@ -1465,12 +1463,12 @@ mod tests {
input: &str, input: &str,
expected_matches: usize, expected_matches: usize,
expected_editor_title: &str, expected_editor_title: &str,
window_id: usize, window: gpui::AnyWindowHandle,
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
deterministic: &gpui::executor::Deterministic, deterministic: &gpui::executor::Deterministic,
cx: &mut gpui::TestAppContext, cx: &mut gpui::TestAppContext,
) -> Vec<FoundPath> { ) -> Vec<FoundPath> {
cx.dispatch_action(window_id, Toggle); cx.dispatch_action(window, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap()); let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder finder
.update(cx, |finder, cx| { .update(cx, |finder, cx| {
@ -1487,8 +1485,8 @@ mod tests {
}); });
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext); cx.dispatch_action(window, SelectNext);
cx.dispatch_action(window_id, Confirm); cx.dispatch_action(window, Confirm);
deterministic.run_until_parked(); deterministic.run_until_parked();
active_pane active_pane
.condition(cx, |pane, _| pane.active_item().is_some()) .condition(cx, |pane, _| pane.active_item().is_some())

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -33,8 +33,8 @@ use crate::{
rect::RectF, rect::RectF,
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
WindowContext, WeakViewHandle, WindowContext,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use collections::HashMap; use collections::HashMap;
@ -65,7 +65,7 @@ pub trait Element<V: View>: 'static {
visible_bounds: RectF, visible_bounds: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState; ) -> Self::PaintState;
fn rect_for_text_range( fn rect_for_text_range(
@ -310,7 +310,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
mut layout, mut layout,
} => { } => {
let bounds = RectF::new(origin, size); let bounds = RectF::new(origin, size);
let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx); let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
ElementState::PostPaint { ElementState::PostPaint {
element, element,
constraint, constraint,
@ -328,7 +335,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
.. ..
} => { } => {
let bounds = RectF::new(origin, bounds.size()); let bounds = RectF::new(origin, bounds.size());
let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx); let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
ElementState::PostPaint { ElementState::PostPaint {
element, element,
constraint, constraint,
@ -525,7 +539,7 @@ impl<V: View> Element<V> for AnyElement<V> {
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
self.paint(scene, bounds.origin(), visible_bounds, view, cx); self.paint(scene, bounds.origin(), visible_bounds, view, cx);
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,8 @@ use crate::{
json::ToJson, json::ToJson,
platform::CursorStyle, platform::CursorStyle,
scene::{self, Border, CursorRegion, Quad}, scene::{self, Border, CursorRegion, Quad},
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
}; };
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
@ -214,7 +215,7 @@ impl<V: View> Element<V> for Container<V> {
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let quad_bounds = RectF::from_points( let quad_bounds = RectF::from_points(
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top), bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,8 +6,8 @@ use crate::{
fonts::TextStyle, fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::json, json::json,
Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View, Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext, Task, View, ViewContext,
}; };
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
@ -194,7 +194,7 @@ impl<V: View> Element<V> for Tooltip<V> {
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) { ) {
self.child self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx); .paint(scene, bounds.origin(), visible_bounds, view, cx);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1415,7 +1415,7 @@ impl ProjectPanel {
if cx if cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window())
.is_some() .is_some()
&& dragged_entry_destination && dragged_entry_destination
.as_ref() .as_ref()
@ -1459,7 +1459,7 @@ impl ProjectPanel {
.on_up(MouseButton::Left, move |_, this, cx| { .on_up(MouseButton::Left, move |_, this, cx| {
if let Some((_, dragged_entry)) = cx if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window())
{ {
this.move_entry( this.move_entry(
*dragged_entry, *dragged_entry,
@ -1472,7 +1472,7 @@ impl ProjectPanel {
.on_move(move |_, this, cx| { .on_move(move |_, this, cx| {
if cx if cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window())
.is_some() .is_some()
{ {
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) { this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
@ -1702,7 +1702,7 @@ impl ClipboardEntry {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use gpui::{TestAppContext, ViewHandle}; use gpui::{AnyWindowHandle, TestAppContext, ViewHandle, WindowHandle};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use project::FakeFs; use project::FakeFs;
use serde_json::json; use serde_json::json;
@ -1848,7 +1848,6 @@ mod tests {
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx); select_path(&panel, "root1", cx);
@ -1870,7 +1869,7 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed // Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder. // before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx)); panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
let panel = panel.read(cx); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2201,7 +2200,6 @@ mod tests {
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx); select_path(&panel, "root1", cx);
@ -2223,7 +2221,7 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed // Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder. // before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx)); panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
let panel = panel.read(cx); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2378,7 +2376,6 @@ mod tests {
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
toggle_expand_dir(&panel, "src/test", cx); toggle_expand_dir(&panel, "src/test", cx);
@ -2395,9 +2392,9 @@ mod tests {
" third.rs" " third.rs"
] ]
); );
ensure_single_file_is_opened(window_id, &workspace, "test/first.rs", cx); ensure_single_file_is_opened(window, "test/first.rs", cx);
submit_deletion(window_id, &panel, cx); submit_deletion(window.into(), &panel, cx);
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), visible_entries_as_strings(&panel, 0..10, cx),
&[ &[
@ -2408,7 +2405,7 @@ mod tests {
], ],
"Project panel should have no deleted file, no other file is selected in it" "Project panel should have no deleted file, no other file is selected in it"
); );
ensure_no_open_items_and_panes(window_id, &workspace, cx); ensure_no_open_items_and_panes(window.into(), &workspace, cx);
select_path(&panel, "src/test/second.rs", cx); select_path(&panel, "src/test/second.rs", cx);
panel.update(cx, |panel, cx| panel.open_file(&Open, cx)); panel.update(cx, |panel, cx| panel.open_file(&Open, cx));
@ -2422,9 +2419,9 @@ mod tests {
" third.rs" " third.rs"
] ]
); );
ensure_single_file_is_opened(window_id, &workspace, "test/second.rs", cx); ensure_single_file_is_opened(window, "test/second.rs", cx);
cx.update_window(window_id, |cx| { window.update(cx, |cx| {
let active_items = workspace let active_items = workspace
.read(cx) .read(cx)
.panes() .panes()
@ -2440,13 +2437,13 @@ mod tests {
.expect("Open item should be an editor"); .expect("Open item should be an editor");
open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx)); open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx));
}); });
submit_deletion(window_id, &panel, cx); submit_deletion(window.into(), &panel, cx);
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), visible_entries_as_strings(&panel, 0..10, cx),
&["v src", " v test", " third.rs"], &["v src", " v test", " third.rs"],
"Project panel should have no deleted file, with one last file remaining" "Project panel should have no deleted file, with one last file remaining"
); );
ensure_no_open_items_and_panes(window_id, &workspace, cx); ensure_no_open_items_and_panes(window.into(), &workspace, cx);
} }
#[gpui::test] #[gpui::test]
@ -2469,7 +2466,6 @@ mod tests {
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "src/", cx); select_path(&panel, "src/", cx);
@ -2480,7 +2476,7 @@ mod tests {
&["v src <== selected", " > test"] &["v src <== selected", " > test"]
); );
panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx)); panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx));
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
let panel = panel.read(cx); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2511,7 +2507,7 @@ mod tests {
&["v src", " > test <== selected"] &["v src", " > test <== selected"]
); );
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx)); panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
let panel = panel.read(cx); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2561,7 +2557,7 @@ mod tests {
], ],
); );
panel.update(cx, |panel, cx| panel.rename(&Rename, cx)); panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
let panel = panel.read(cx); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2858,13 +2854,11 @@ mod tests {
} }
fn ensure_single_file_is_opened( fn ensure_single_file_is_opened(
window_id: usize, window: WindowHandle<Workspace>,
workspace: &ViewHandle<Workspace>,
expected_path: &str, expected_path: &str,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) { ) {
cx.read_window(window_id, |cx| { window.update_root(cx, |workspace, cx| {
let workspace = workspace.read(cx);
let worktrees = workspace.worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1); assert_eq!(worktrees.len(), 1);
let worktree_id = WorktreeId::from_usize(worktrees[0].id()); let worktree_id = WorktreeId::from_usize(worktrees[0].id());
@ -2886,12 +2880,12 @@ mod tests {
} }
fn submit_deletion( fn submit_deletion(
window_id: usize, window: AnyWindowHandle,
panel: &ViewHandle<ProjectPanel>, panel: &ViewHandle<ProjectPanel>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) { ) {
assert!( assert!(
!cx.has_pending_prompt(window_id), !window.has_pending_prompt(cx),
"Should have no prompts before the deletion" "Should have no prompts before the deletion"
); );
panel.update(cx, |panel, cx| { panel.update(cx, |panel, cx| {
@ -2901,27 +2895,27 @@ mod tests {
.detach_and_log_err(cx); .detach_and_log_err(cx);
}); });
assert!( assert!(
cx.has_pending_prompt(window_id), window.has_pending_prompt(cx),
"Should have a prompt after the deletion" "Should have a prompt after the deletion"
); );
cx.simulate_prompt_answer(window_id, 0); window.simulate_prompt_answer(0, cx);
assert!( assert!(
!cx.has_pending_prompt(window_id), !window.has_pending_prompt(cx),
"Should have no prompts after prompt was replied to" "Should have no prompts after prompt was replied to"
); );
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
} }
fn ensure_no_open_items_and_panes( fn ensure_no_open_items_and_panes(
window_id: usize, window: AnyWindowHandle,
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) { ) {
assert!( assert!(
!cx.has_pending_prompt(window_id), !window.has_pending_prompt(cx),
"Should have no prompts after deletion operation closes the file" "Should have no prompts after deletion operation closes the file"
); );
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
let open_project_paths = workspace let open_project_paths = workspace
.read(cx) .read(cx)
.panes() .panes()

View file

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

View file

@ -850,12 +850,9 @@ mod tests {
) )
}); });
let window = cx.add_window(|_| EmptyView); let window = cx.add_window(|_| EmptyView);
let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let editor = cx.add_view(window.window_id(), |cx| { let search_bar = window.add_view(cx, |cx| {
Editor::for_buffer(buffer.clone(), None, cx)
});
let search_bar = cx.add_view(window.window_id(), |cx| {
let mut search_bar = BufferSearchBar::new(cx); let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx); search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx); search_bar.show(cx);
@ -1232,11 +1229,9 @@ mod tests {
); );
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
let window = cx.add_window(|_| EmptyView); let window = cx.add_window(|_| EmptyView);
let window_id = window.window_id(); let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx)); let search_bar = window.add_view(cx, |cx| {
let search_bar = cx.add_view(window_id, |cx| {
let mut search_bar = BufferSearchBar::new(cx); let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx); search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx); search_bar.show(cx);
@ -1252,12 +1247,13 @@ mod tests {
search_bar.activate_current_match(cx); search_bar.activate_current_match(cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
!editor.is_focused(cx), !editor.is_focused(cx),
"Initially, the editor should not be focused" "Initially, the editor should not be focused"
); );
}); });
let initial_selections = editor.update(cx, |editor, cx| { let initial_selections = editor.update(cx, |editor, cx| {
let initial_selections = editor.selections.display_ranges(cx); let initial_selections = editor.selections.display_ranges(cx);
assert_eq!( assert_eq!(
@ -1274,7 +1270,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any()); cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx); search_bar.select_all_matches(&SelectAllMatches, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
editor.is_focused(cx), editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches" "Should focus editor after successful SelectAllMatches"
@ -1298,7 +1294,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
search_bar.select_next_match(&SelectNextMatch, cx); search_bar.select_next_match(&SelectNextMatch, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
editor.is_focused(cx), editor.is_focused(cx),
"Should still have editor focused after SelectNextMatch" "Should still have editor focused after SelectNextMatch"
@ -1327,7 +1323,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any()); cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx); search_bar.select_all_matches(&SelectAllMatches, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
editor.is_focused(cx), editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches" "Should focus editor after successful SelectAllMatches"
@ -1351,7 +1347,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
search_bar.select_prev_match(&SelectPrevMatch, cx); search_bar.select_prev_match(&SelectPrevMatch, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
editor.is_focused(cx), editor.is_focused(cx),
"Should still have editor focused after SelectPrevMatch" "Should still have editor focused after SelectPrevMatch"
@ -1387,7 +1383,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
search_bar.select_all_matches(&SelectAllMatches, cx); search_bar.select_all_matches(&SelectAllMatches, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
!editor.is_focused(cx), !editor.is_focused(cx),
"Should not switch focus to editor if SelectAllMatches does not find any matches" "Should not switch focus to editor if SelectAllMatches does not find any matches"
@ -1421,11 +1417,9 @@ mod tests {
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
let window = cx.add_window(|_| EmptyView); let window = cx.add_window(|_| EmptyView);
let editor = cx.add_view(window.window_id(), |cx| { let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
Editor::for_buffer(buffer.clone(), None, cx)
});
let search_bar = cx.add_view(window.window_id(), |cx| { let search_bar = window.add_view(cx, |cx| {
let mut search_bar = BufferSearchBar::new(cx); let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx); search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx); search_bar.show(cx);

View file

@ -1568,7 +1568,6 @@ pub mod tests {
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let active_item = cx.read(|cx| { let active_item = cx.read(|cx| {
workspace workspace
@ -1599,9 +1598,9 @@ pub mod tests {
}; };
let search_view_id = search_view.id(); let search_view_id = search_view.id();
cx.spawn( cx.spawn(|mut cx| async move {
|mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) }, window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
) })
.detach(); .detach();
deterministic.run_until_parked(); deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| { search_view.update(cx, |search_view, cx| {
@ -1652,7 +1651,7 @@ pub mod tests {
); );
}); });
cx.spawn( cx.spawn(
|mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) }, |mut cx| async move { window.dispatch_action(search_view_id, &ToggleFocus, &mut cx) },
) )
.detach(); .detach();
deterministic.run_until_parked(); deterministic.run_until_parked();
@ -1683,9 +1682,9 @@ pub mod tests {
"Search view with mismatching query should be focused after search results are available", "Search view with mismatching query should be focused after search results are available",
); );
}); });
cx.spawn( cx.spawn(|mut cx| async move {
|mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) }, window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
) })
.detach(); .detach();
deterministic.run_until_parked(); deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| { search_view.update(cx, |search_view, cx| {
@ -1713,9 +1712,9 @@ pub mod tests {
); );
}); });
cx.spawn( cx.spawn(|mut cx| async move {
|mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) }, window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
) })
.detach(); .detach();
deterministic.run_until_parked(); deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| { search_view.update(cx, |search_view, cx| {
@ -1874,7 +1873,6 @@ pub mod tests {
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
}); });
@ -1889,7 +1887,7 @@ pub mod tests {
.expect("Search view expected to appear after new search event trigger") .expect("Search view expected to appear after new search event trigger")
}); });
let search_bar = cx.add_view(window_id, |cx| { let search_bar = window.add_view(cx, |cx| {
let mut search_bar = ProjectSearchBar::new(); let mut search_bar = ProjectSearchBar::new();
search_bar.set_active_pane_item(Some(&search_view), cx); search_bar.set_active_pane_item(Some(&search_view), cx);
// search_bar.show(cx); // search_bar.show(cx);

View file

@ -53,7 +53,7 @@ use gpui::{
keymap_matcher::Keystroke, keymap_matcher::Keystroke,
platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase}, platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase},
scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp}, scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp},
AppContext, ClipboardItem, Entity, ModelContext, Task, AnyWindowHandle, AppContext, ClipboardItem, Entity, ModelContext, Task,
}; };
use crate::mappings::{ use crate::mappings::{
@ -404,7 +404,7 @@ impl TerminalBuilder {
mut env: HashMap<String, String>, mut env: HashMap<String, String>,
blink_settings: Option<TerminalBlink>, blink_settings: Option<TerminalBlink>,
alternate_scroll: AlternateScroll, alternate_scroll: AlternateScroll,
window_id: usize, window: AnyWindowHandle,
) -> Result<TerminalBuilder> { ) -> Result<TerminalBuilder> {
let pty_config = { let pty_config = {
let alac_shell = match shell.clone() { let alac_shell = match shell.clone() {
@ -462,7 +462,7 @@ impl TerminalBuilder {
let pty = match tty::new( let pty = match tty::new(
&pty_config, &pty_config,
TerminalSize::default().into(), TerminalSize::default().into(),
window_id as u64, window.id() as u64,
) { ) {
Ok(pty) => pty, Ok(pty) => pty,
Err(error) => { Err(error) => {

View file

@ -10,8 +10,9 @@ use gpui::{
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
serde_json::json, serde_json::json,
text_layout::{Line, RunStyle}, text_layout::{Line, RunStyle},
AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, Quad, AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion,
SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle, PaintContext, Quad, SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext,
WeakModelHandle,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::CursorShape; use language::CursorShape;
@ -734,7 +735,7 @@ impl Element<TerminalView> for TerminalElement {
visible_bounds: RectF, visible_bounds: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
view: &mut TerminalView, view: &mut TerminalView,
cx: &mut ViewContext<TerminalView>, cx: &mut PaintContext<TerminalView>,
) -> Self::PaintState { ) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

View file

@ -48,7 +48,7 @@ impl TerminalPanel {
fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self { fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
let weak_self = cx.weak_handle(); let weak_self = cx.weak_handle();
let pane = cx.add_view(|cx| { let pane = cx.add_view(|cx| {
let window_id = cx.window_id(); let window = cx.window();
let mut pane = Pane::new( let mut pane = Pane::new(
workspace.weak_handle(), workspace.weak_handle(),
workspace.project().clone(), workspace.project().clone(),
@ -60,7 +60,7 @@ impl TerminalPanel {
pane.set_can_navigate(false, cx); pane.set_can_navigate(false, cx);
pane.on_can_drop(move |drag_and_drop, cx| { pane.on_can_drop(move |drag_and_drop, cx| {
drag_and_drop drag_and_drop
.currently_dragged::<DraggedItem>(window_id) .currently_dragged::<DraggedItem>(window)
.map_or(false, |(_, item)| { .map_or(false, |(_, item)| {
item.handle.act_as::<TerminalView>(cx).is_some() item.handle.act_as::<TerminalView>(cx).is_some()
}) })
@ -255,10 +255,10 @@ impl TerminalPanel {
.clone(); .clone();
let working_directory = let working_directory =
crate::get_working_directory(workspace, cx, working_directory_strategy); crate::get_working_directory(workspace, cx, working_directory_strategy);
let window_id = cx.window_id(); let window = cx.window();
if let Some(terminal) = workspace.project().update(cx, |project, cx| { if let Some(terminal) = workspace.project().update(cx, |project, cx| {
project project
.create_terminal(working_directory, window_id, cx) .create_terminal(working_directory, window, cx)
.log_err() .log_err()
}) { }) {
let terminal = Box::new(cx.add_view(|cx| { let terminal = Box::new(cx.add_view(|cx| {

View file

@ -112,11 +112,11 @@ impl TerminalView {
let working_directory = let working_directory =
get_working_directory(workspace, cx, strategy.working_directory.clone()); get_working_directory(workspace, cx, strategy.working_directory.clone());
let window_id = cx.window_id(); let window = cx.window();
let terminal = workspace let terminal = workspace
.project() .project()
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.create_terminal(working_directory, window_id, cx) project.create_terminal(working_directory, window, cx)
}) })
.notify_err(workspace, cx); .notify_err(workspace, cx);
@ -741,7 +741,7 @@ impl Item for TerminalView {
item_id: workspace::ItemId, item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>, cx: &mut ViewContext<Pane>,
) -> Task<anyhow::Result<ViewHandle<Self>>> { ) -> Task<anyhow::Result<ViewHandle<Self>>> {
let window_id = cx.window_id(); let window = cx.window();
cx.spawn(|pane, mut cx| async move { cx.spawn(|pane, mut cx| async move {
let cwd = TERMINAL_DB let cwd = TERMINAL_DB
.get_working_directory(item_id, workspace_id) .get_working_directory(item_id, workspace_id)
@ -762,7 +762,7 @@ impl Item for TerminalView {
}); });
let terminal = project.update(&mut cx, |project, cx| { let terminal = project.update(&mut cx, |project, cx| {
project.create_terminal(cwd, window_id, cx) project.create_terminal(cwd, window, cx)
})?; })?;
Ok(pane.update(&mut cx, |_, cx| { Ok(pane.update(&mut cx, |_, cx| {
cx.add_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx)) cx.add_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))

View file

@ -202,7 +202,6 @@ where
F: FnOnce(&mut gpui::ViewContext<V>) -> D, F: FnOnce(&mut gpui::ViewContext<V>) -> D,
{ {
const TITLEBAR_HEIGHT: f32 = 28.; const TITLEBAR_HEIGHT: f32 = 28.;
// let active = cx.window_is_active(cx.window_id());
Flex::column() Flex::column()
.with_child( .with_child(

View file

@ -33,6 +33,7 @@ pub mod legacy {
pub trait PathExt { pub trait PathExt {
fn compact(&self) -> PathBuf; fn compact(&self) -> PathBuf;
fn icon_suffix(&self) -> Option<&str>; fn icon_suffix(&self) -> Option<&str>;
fn extension_or_hidden_file_name(&self) -> Option<&str>;
} }
impl<T: AsRef<Path>> PathExt for T { impl<T: AsRef<Path>> PathExt for T {
@ -60,6 +61,7 @@ impl<T: AsRef<Path>> PathExt for T {
} }
} }
/// Returns a suffix of the path that is used to determine which file icon to use
fn icon_suffix(&self) -> Option<&str> { fn icon_suffix(&self) -> Option<&str> {
let file_name = self.as_ref().file_name()?.to_str()?; let file_name = self.as_ref().file_name()?.to_str()?;
@ -69,8 +71,16 @@ impl<T: AsRef<Path>> PathExt for T {
self.as_ref() self.as_ref()
.extension() .extension()
.map(|extension| extension.to_str()) .and_then(|extension| extension.to_str())
.flatten() }
/// Returns a file's extension or, if the file is hidden, its name without the leading dot
fn extension_or_hidden_file_name(&self) -> Option<&str> {
if let Some(extension) = self.as_ref().extension() {
return extension.to_str();
}
self.as_ref().file_name()?.to_str()?.split('.').last()
} }
} }
@ -294,7 +304,7 @@ mod tests {
} }
#[test] #[test]
fn test_path_suffix() { fn test_icon_suffix() {
// No dots in name // No dots in name
let path = Path::new("/a/b/c/file_name.rs"); let path = Path::new("/a/b/c/file_name.rs");
assert_eq!(path.icon_suffix(), Some("rs")); assert_eq!(path.icon_suffix(), Some("rs"));
@ -315,4 +325,27 @@ mod tests {
let path = Path::new("/a/b/c/.eslintrc.js"); let path = Path::new("/a/b/c/.eslintrc.js");
assert_eq!(path.icon_suffix(), Some("eslintrc.js")); assert_eq!(path.icon_suffix(), Some("eslintrc.js"));
} }
#[test]
fn test_extension_or_hidden_file_name() {
// No dots in name
let path = Path::new("/a/b/c/file_name.rs");
assert_eq!(path.extension_or_hidden_file_name(), Some("rs"));
// Single dot in name
let path = Path::new("/a/b/c/file.name.rs");
assert_eq!(path.extension_or_hidden_file_name(), Some("rs"));
// Multiple dots in name
let path = Path::new("/a/b/c/long.file.name.rs");
assert_eq!(path.extension_or_hidden_file_name(), Some("rs"));
// Hidden file, no extension
let path = Path::new("/a/b/c/.gitignore");
assert_eq!(path.extension_or_hidden_file_name(), Some("gitignore"));
// Hidden file, with extension
let path = Path::new("/a/b/c/.eslintrc.js");
assert_eq!(path.extension_or_hidden_file_name(), Some("js"));
}
} }

View file

@ -10,7 +10,7 @@ pub fn init(cx: &mut AppContext) {
fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) { fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
if let Some(previously_active_editor) = Vim::read(cx).active_editor.clone() { if let Some(previously_active_editor) = Vim::read(cx).active_editor.clone() {
cx.update_window(previously_active_editor.window_id(), |cx| { previously_active_editor.window().update(cx, |cx| {
Vim::update(cx, |vim, cx| { Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |previously_active_editor, cx| { vim.update_active_editor(cx, |previously_active_editor, cx| {
vim.unhook_vim_settings(previously_active_editor, cx) vim.unhook_vim_settings(previously_active_editor, cx)
@ -19,7 +19,7 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
}); });
} }
cx.update_window(editor.window_id(), |cx| { editor.window().update(cx, |cx| {
Vim::update(cx, |vim, cx| { Vim::update(cx, |vim, cx| {
vim.set_active_editor(editor.clone(), cx); vim.set_active_editor(editor.clone(), cx);
}); });
@ -27,7 +27,7 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
} }
fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) { fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
cx.update_window(editor.window_id(), |cx| { editor.window().update(cx, |cx| {
Vim::update(cx, |vim, cx| { Vim::update(cx, |vim, cx| {
if let Some(previous_editor) = vim.active_editor.clone() { if let Some(previous_editor) = vim.active_editor.clone() {
if previous_editor == editor.clone() { if previous_editor == editor.clone() {
@ -41,7 +41,7 @@ fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
} }
fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) { fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
cx.update_window(editor.window_id(), |cx| { editor.window().update(cx, |cx| {
cx.update_default_global(|vim: &mut Vim, _| { cx.update_default_global(|vim: &mut Vim, _| {
if let Some(previous_editor) = vim.active_editor.clone() { if let Some(previous_editor) = vim.active_editor.clone() {
if previous_editor == editor.clone() { if previous_editor == editor.clone() {

View file

@ -20,7 +20,7 @@ impl ModeIndicator {
if let Some(mode_indicator) = handle.upgrade(cx) { if let Some(mode_indicator) = handle.upgrade(cx) {
match event { match event {
VimEvent::ModeChanged { mode } => { VimEvent::ModeChanged { mode } => {
cx.update_window(mode_indicator.window_id(), |cx| { mode_indicator.window().update(cx, |cx| {
mode_indicator.update(cx, move |mode_indicator, cx| { mode_indicator.update(cx, move |mode_indicator, cx| {
mode_indicator.set_mode(mode, cx); mode_indicator.set_mode(mode, cx);
}) })

View file

@ -85,8 +85,8 @@ impl<'a> VimTestContext<'a> {
} }
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle { pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
let window_id = self.window_id; let window = self.window;
self.update_window(window_id, |cx| { window.update(self.cx.cx.cx, |cx| {
Vim::update(cx, |vim, cx| { Vim::update(cx, |vim, cx| {
vim.switch_mode(mode, false, cx); vim.switch_mode(mode, false, cx);
}) })

View file

@ -213,7 +213,7 @@ impl Dock {
pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option<usize> { pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option<usize> {
self.panel_entries.iter().position(|entry| { self.panel_entries.iter().position(|entry| {
let panel = entry.panel.as_any(); let panel = entry.panel.as_any();
cx.view_ui_name(panel.window_id(), panel.id()) == Some(ui_name) cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name)
}) })
} }
@ -542,16 +542,15 @@ impl View for PanelButtons {
tooltip_action.as_ref().map(|action| action.boxed_clone()); tooltip_action.as_ref().map(|action| action.boxed_clone());
move |_, this, cx| { move |_, this, cx| {
if let Some(tooltip_action) = &tooltip_action { if let Some(tooltip_action) = &tooltip_action {
let window_id = cx.window_id(); let window = cx.window();
let view_id = this.workspace.id(); let view_id = this.workspace.id();
let tooltip_action = tooltip_action.boxed_clone(); let tooltip_action = tooltip_action.boxed_clone();
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
cx.dispatch_action( window.dispatch_action(
window_id,
view_id, view_id,
&*tooltip_action, &*tooltip_action,
) &mut cx,
.ok(); );
}) })
.detach(); .detach();
} }

View file

@ -6,6 +6,7 @@ use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
use anyhow::Result; use anyhow::Result;
use client::{proto, Client}; use client::{proto, Client};
use gpui::geometry::vector::Vector2F; use gpui::geometry::vector::Vector2F;
use gpui::AnyWindowHandle;
use gpui::{ use gpui::{
fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View, fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
ViewContext, ViewHandle, WeakViewHandle, WindowContext, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
@ -250,7 +251,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
fn workspace_deactivated(&self, cx: &mut WindowContext); fn workspace_deactivated(&self, cx: &mut WindowContext);
fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool; fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
fn id(&self) -> usize; fn id(&self) -> usize;
fn window_id(&self) -> usize; fn window(&self) -> AnyWindowHandle;
fn as_any(&self) -> &AnyViewHandle; fn as_any(&self) -> &AnyViewHandle;
fn is_dirty(&self, cx: &AppContext) -> bool; fn is_dirty(&self, cx: &AppContext) -> bool;
fn has_conflict(&self, cx: &AppContext) -> bool; fn has_conflict(&self, cx: &AppContext) -> bool;
@ -280,7 +281,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
pub trait WeakItemHandle { pub trait WeakItemHandle {
fn id(&self) -> usize; fn id(&self) -> usize;
fn window_id(&self) -> usize; fn window(&self) -> AnyWindowHandle;
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>; fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
} }
@ -542,8 +543,8 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
self.id() self.id()
} }
fn window_id(&self) -> usize { fn window(&self) -> AnyWindowHandle {
self.window_id() AnyViewHandle::window(self)
} }
fn as_any(&self) -> &AnyViewHandle { fn as_any(&self) -> &AnyViewHandle {
@ -649,8 +650,8 @@ impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
self.id() self.id()
} }
fn window_id(&self) -> usize { fn window(&self) -> AnyWindowHandle {
self.window_id() self.window()
} }
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> { fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {

View file

@ -25,8 +25,8 @@ use gpui::{
keymap_matcher::KeymapContext, keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel}, platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
LayoutContext, ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, LayoutContext, ModelHandle, MouseRegion, PaintContext, Quad, Task, View, ViewContext,
WeakViewHandle, WindowContext, ViewHandle, WeakViewHandle, WindowContext,
}; };
use project::{Project, ProjectEntryId, ProjectPath}; use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize; use serde::Deserialize;
@ -1900,7 +1900,7 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let background = theme::current(cx).editor.background; let background = theme::current(cx).editor.background;
@ -1917,8 +1917,8 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down( MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
gpui::platform::MouseButton::Left, gpui::platform::MouseButton::Left,
move |_, _: &mut V, cx| { move |_, _: &mut V, cx| {
let window_id = cx.window_id(); let window = cx.window();
cx.app_context().focus(window_id, Some(child_view_id)) cx.app_context().focus(window, Some(child_view_id))
}, },
), ),
); );

View file

@ -28,11 +28,11 @@ where
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>(); let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
let drag_position = if (pane.can_drop)(drag_and_drop, cx) { let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
drag_and_drop drag_and_drop
.currently_dragged::<DraggedItem>(cx.window_id()) .currently_dragged::<DraggedItem>(cx.window())
.map(|(drag_position, _)| drag_position) .map(|(drag_position, _)| drag_position)
.or_else(|| { .or_else(|| {
drag_and_drop drag_and_drop
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window())
.map(|(drag_position, _)| drag_position) .map(|(drag_position, _)| drag_position)
}) })
} else { } else {
@ -91,10 +91,10 @@ where
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>(); let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
if drag_and_drop if drag_and_drop
.currently_dragged::<DraggedItem>(cx.window_id()) .currently_dragged::<DraggedItem>(cx.window())
.is_some() .is_some()
|| drag_and_drop || drag_and_drop
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window())
.is_some() .is_some()
{ {
cx.notify(); cx.notify();
@ -122,11 +122,11 @@ pub fn handle_dropped_item<V: View>(
} }
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>(); let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
let action = if let Some((_, dragged_item)) = let action = if let Some((_, dragged_item)) =
drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id()) drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
{ {
Action::Move(dragged_item.pane.clone(), dragged_item.handle.id()) Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
} else if let Some((_, project_entry)) = } else if let Some((_, project_entry)) =
drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id()) drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
{ {
Action::Open(*project_entry) Action::Open(*project_entry)
} else { } else {

View file

@ -595,7 +595,7 @@ mod element {
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
scene::MouseDrag, scene::MouseDrag,
AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion, AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion,
RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, PaintContext, RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
}; };
use crate::{ use crate::{
@ -856,7 +856,7 @@ mod element {
visible_bounds: RectF, visible_bounds: RectF,
remaining_space: &mut Self::LayoutState, remaining_space: &mut Self::LayoutState,
view: &mut Workspace, view: &mut Workspace,
cx: &mut ViewContext<Workspace>, cx: &mut PaintContext<Workspace>,
) -> Self::PaintState { ) -> Self::PaintState {
let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.; let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

View file

@ -235,7 +235,7 @@ impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
impl PartialEq for Box<dyn SearchableItemHandle> { impl PartialEq for Box<dyn SearchableItemHandle> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id() == other.id() && self.window_id() == other.window_id() self.id() == other.id() && self.window() == other.window()
} }
} }
@ -259,7 +259,7 @@ impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
impl PartialEq for Box<dyn WeakSearchableItemHandle> { impl PartialEq for Box<dyn WeakSearchableItemHandle> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id() == other.id() && self.window_id() == other.window_id() self.id() == other.id() && self.window() == other.window()
} }
} }
@ -267,6 +267,6 @@ impl Eq for Box<dyn WeakSearchableItemHandle> {}
impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> { impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
(self.id(), self.window_id()).hash(state) (self.id(), self.window().id()).hash(state)
} }
} }

View file

@ -8,8 +8,8 @@ use gpui::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::{json, ToJson}, json::{json, ToJson},
AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription, AnyElement, AnyViewHandle, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
View, ViewContext, ViewHandle, WindowContext, Subscription, View, ViewContext, ViewHandle, WindowContext,
}; };
pub trait StatusItemView: View { pub trait StatusItemView: View {
@ -231,7 +231,7 @@ impl Element<StatusBar> for StatusBarElement {
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut StatusBar, view: &mut StatusBar,
cx: &mut ViewContext<StatusBar>, cx: &mut PaintContext<StatusBar>,
) -> Self::PaintState { ) -> Self::PaintState {
let origin_y = bounds.upper_right().y(); let origin_y = bounds.upper_right().y();
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

View file

@ -37,7 +37,7 @@ use gpui::{
}, },
AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
WeakViewHandle, WindowContext, WeakViewHandle, WindowContext, WindowHandle,
}; };
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
use itertools::Itertools; use itertools::Itertools;
@ -753,7 +753,7 @@ impl Workspace {
fn new_local( fn new_local(
abs_paths: Vec<PathBuf>, abs_paths: Vec<PathBuf>,
app_state: Arc<AppState>, app_state: Arc<AppState>,
requesting_window_id: Option<usize>, requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut AppContext, cx: &mut AppContext,
) -> Task<( ) -> Task<(
WeakViewHandle<Workspace>, WeakViewHandle<Workspace>,
@ -797,55 +797,60 @@ impl Workspace {
DB.next_id().await.unwrap_or(0) DB.next_id().await.unwrap_or(0)
}; };
let window = requesting_window_id.and_then(|window_id| { let window = if let Some(window) = requesting_window {
cx.update(|cx| { window.replace_root(&mut cx, |cx| {
cx.replace_root_view(window_id, |cx| { Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) });
}) window
}) } else {
}); {
let window = window.unwrap_or_else(|| { let window_bounds_override = window_bounds_env_override(&cx);
let window_bounds_override = window_bounds_env_override(&cx); let (bounds, display) = if let Some(bounds) = window_bounds_override {
let (bounds, display) = if let Some(bounds) = window_bounds_override { (Some(bounds), None)
(Some(bounds), None) } else {
} else { serialized_workspace
serialized_workspace .as_ref()
.as_ref() .and_then(|serialized_workspace| {
.and_then(|serialized_workspace| { let display = serialized_workspace.display?;
let display = serialized_workspace.display?; let mut bounds = serialized_workspace.bounds?;
let mut bounds = serialized_workspace.bounds?;
// Stored bounds are relative to the containing display. // Stored bounds are relative to the containing display.
// So convert back to global coordinates if that screen still exists // So convert back to global coordinates if that screen still exists
if let WindowBounds::Fixed(mut window_bounds) = bounds { if let WindowBounds::Fixed(mut window_bounds) = bounds {
if let Some(screen) = cx.platform().screen_by_id(display) { if let Some(screen) = cx.platform().screen_by_id(display) {
let screen_bounds = screen.bounds(); let screen_bounds = screen.bounds();
window_bounds.set_origin_x( window_bounds.set_origin_x(
window_bounds.origin_x() + screen_bounds.origin_x(), window_bounds.origin_x() + screen_bounds.origin_x(),
); );
window_bounds.set_origin_y( window_bounds.set_origin_y(
window_bounds.origin_y() + screen_bounds.origin_y(), window_bounds.origin_y() + screen_bounds.origin_y(),
); );
bounds = WindowBounds::Fixed(window_bounds); bounds = WindowBounds::Fixed(window_bounds);
} else { } else {
// Screen no longer exists. Return none here. // Screen no longer exists. Return none here.
return None; return None;
}
} }
}
Some((bounds, display)) Some((bounds, display))
}) })
.unzip() .unzip()
}; };
// Use the serialized workspace to construct the new window // Use the serialized workspace to construct the new window
cx.add_window( cx.add_window(
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()), (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
|cx| { |cx| {
Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) Workspace::new(
}, workspace_id,
) project_handle.clone(),
}); app_state.clone(),
cx,
)
},
)
}
};
// We haven't yielded the main thread since obtaining the window handle, // We haven't yielded the main thread since obtaining the window handle,
// so the window exists. // so the window exists.
@ -1231,14 +1236,14 @@ impl Workspace {
pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
let id = cx let window = cx
.window_ids() .windows()
.into_iter() .into_iter()
.find(|&id| cx.window_is_active(id)); .find(|window| window.is_active(&cx).unwrap_or(false));
if let Some(id) = id { if let Some(window) = window {
//This can only get called when the window's project connection has been lost //This can only get called when the window's project connection has been lost
//so we don't need to prompt the user for anything and instead just close the window //so we don't need to prompt the user for anything and instead just close the window
cx.remove_window(id); window.remove(&mut cx);
} }
}) })
.detach(); .detach();
@ -1249,11 +1254,11 @@ impl Workspace {
_: &CloseWindow, _: &CloseWindow,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> { ) -> Option<Task<Result<()>>> {
let window_id = cx.window_id(); let window = cx.window();
let prepare = self.prepare_to_close(false, cx); let prepare = self.prepare_to_close(false, cx);
Some(cx.spawn(|_, mut cx| async move { Some(cx.spawn(|_, mut cx| async move {
if prepare.await? { if prepare.await? {
cx.remove_window(window_id); window.remove(&mut cx);
} }
Ok(()) Ok(())
})) }))
@ -1265,13 +1270,13 @@ impl Workspace {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> { ) -> Task<Result<bool>> {
let active_call = self.active_call().cloned(); let active_call = self.active_call().cloned();
let window_id = cx.window_id(); let window = cx.window();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let workspace_count = cx let workspace_count = cx
.window_ids() .windows()
.into_iter() .into_iter()
.filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>()) .filter(|window| window.root_is::<Workspace>())
.count(); .count();
if let Some(active_call) = active_call { if let Some(active_call) = active_call {
@ -1279,11 +1284,11 @@ impl Workspace {
&& workspace_count == 1 && workspace_count == 1
&& active_call.read_with(&cx, |call, _| call.room().is_some()) && active_call.read_with(&cx, |call, _| call.room().is_some())
{ {
let answer = cx.prompt( let answer = window.prompt(
window_id,
PromptLevel::Warning, PromptLevel::Warning,
"Do you want to leave the current call?", "Do you want to leave the current call?",
&["Close window and hang up", "Cancel"], &["Close window and hang up", "Cancel"],
&mut cx,
); );
if let Some(mut answer) = answer { if let Some(mut answer) = answer {
@ -1389,7 +1394,7 @@ impl Workspace {
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let window_id = cx.window_id(); let window = cx.window().downcast::<Self>();
let is_remote = self.project.read(cx).is_remote(); let is_remote = self.project.read(cx).is_remote();
let has_worktree = self.project.read(cx).worktrees(cx).next().is_some(); let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx)); let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
@ -1401,15 +1406,15 @@ impl Workspace {
let app_state = self.app_state.clone(); let app_state = self.app_state.clone();
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
let window_id_to_replace = if let Some(close_task) = close_task { let window_to_replace = if let Some(close_task) = close_task {
if !close_task.await? { if !close_task.await? {
return Ok(()); return Ok(());
} }
Some(window_id) window
} else { } else {
None None
}; };
cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx)) cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
.await?; .await?;
Ok(()) Ok(())
}) })
@ -3180,7 +3185,7 @@ impl Workspace {
let left_visible = left_dock.is_open(); let left_visible = left_dock.is_open();
let left_active_panel = left_dock.visible_panel().and_then(|panel| { let left_active_panel = left_dock.visible_panel().and_then(|panel| {
Some( Some(
cx.view_ui_name(panel.as_any().window_id(), panel.id())? cx.view_ui_name(panel.as_any().window(), panel.id())?
.to_string(), .to_string(),
) )
}); });
@ -3193,7 +3198,7 @@ impl Workspace {
let right_visible = right_dock.is_open(); let right_visible = right_dock.is_open();
let right_active_panel = right_dock.visible_panel().and_then(|panel| { let right_active_panel = right_dock.visible_panel().and_then(|panel| {
Some( Some(
cx.view_ui_name(panel.as_any().window_id(), panel.id())? cx.view_ui_name(panel.as_any().window(), panel.id())?
.to_string(), .to_string(),
) )
}); });
@ -3206,7 +3211,7 @@ impl Workspace {
let bottom_visible = bottom_dock.is_open(); let bottom_visible = bottom_dock.is_open();
let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| { let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
Some( Some(
cx.view_ui_name(panel.as_any().window_id(), panel.id())? cx.view_ui_name(panel.as_any().window(), panel.id())?
.to_string(), .to_string(),
) )
}); });
@ -3841,9 +3846,9 @@ pub fn activate_workspace_for_project(
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool, predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
) -> Option<WeakViewHandle<Workspace>> { ) -> Option<WeakViewHandle<Workspace>> {
for window_id in cx.window_ids() { for window in cx.windows() {
let handle = cx let handle = window
.update_window(window_id, |cx| { .update(cx, |cx| {
if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() { if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
let project = workspace_handle.read(cx).project.clone(); let project = workspace_handle.read(cx).project.clone();
if project.update(cx, &predicate) { if project.update(cx, &predicate) {
@ -3870,7 +3875,7 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
pub fn open_paths( pub fn open_paths(
abs_paths: &[PathBuf], abs_paths: &[PathBuf],
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
requesting_window_id: Option<usize>, requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut AppContext, cx: &mut AppContext,
) -> Task< ) -> Task<
Result<( Result<(
@ -3898,7 +3903,7 @@ pub fn open_paths(
} else { } else {
Ok(cx Ok(cx
.update(|cx| { .update(|cx| {
Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx) Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
}) })
.await) .await)
} }
@ -3959,18 +3964,23 @@ pub fn join_remote_project(
) -> Task<Result<()>> { ) -> Task<Result<()>> {
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
let existing_workspace = cx let existing_workspace = cx
.window_ids() .windows()
.into_iter() .into_iter()
.filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>()) .find_map(|window| {
.find(|workspace| { window.downcast::<Workspace>().and_then(|window| {
cx.read_window(workspace.window_id(), |cx| { window.read_root_with(&cx, |workspace, cx| {
workspace.read(cx).project().read(cx).remote_id() == Some(project_id) if workspace.project().read(cx).remote_id() == Some(project_id) {
Some(cx.handle().downgrade())
} else {
None
}
})
}) })
.unwrap_or(false) })
}); .flatten();
let workspace = if let Some(existing_workspace) = existing_workspace { let workspace = if let Some(existing_workspace) = existing_workspace {
existing_workspace.downgrade() existing_workspace
} else { } else {
let active_call = cx.read(ActiveCall::global); let active_call = cx.read(ActiveCall::global);
let room = active_call let room = active_call
@ -4009,7 +4019,7 @@ pub fn join_remote_project(
workspace.downgrade() workspace.downgrade()
}; };
cx.activate_window(workspace.window_id()); workspace.window().activate(&mut cx);
cx.platform().activate(true); cx.platform().activate(true);
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {
@ -4048,29 +4058,22 @@ pub fn join_remote_project(
pub fn restart(_: &Restart, cx: &mut AppContext) { pub fn restart(_: &Restart, cx: &mut AppContext) {
let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit; let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
let mut workspaces = cx let mut workspace_windows = cx
.window_ids() .windows()
.into_iter() .into_iter()
.filter_map(|window_id| { .filter_map(|window| window.downcast::<Workspace>())
Some(
cx.root_view(window_id)?
.clone()
.downcast::<Workspace>()?
.downgrade(),
)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// If multiple windows have unsaved changes, and need a save prompt, // If multiple windows have unsaved changes, and need a save prompt,
// prompt in the active window before switching to a different window. // prompt in the active window before switching to a different window.
workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
let answer = cx.prompt( let answer = window.prompt(
workspace.window_id(),
PromptLevel::Info, PromptLevel::Info,
"Are you sure you want to restart?", "Are you sure you want to restart?",
&["Restart", "Cancel"], &["Restart", "Cancel"],
&mut cx,
); );
if let Some(mut answer) = answer { if let Some(mut answer) = answer {
@ -4082,14 +4085,13 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
} }
// If the user cancels any save prompt, then keep the app open. // If the user cancels any save prompt, then keep the app open.
for workspace in workspaces { for window in workspace_windows {
if !workspace if let Some(close) = window.update_root(&mut cx, |workspace, cx| {
.update(&mut cx, |workspace, cx| { workspace.prepare_to_close(true, cx)
workspace.prepare_to_close(true, cx) }) {
})? if !close.await? {
.await? return Ok(());
{ }
return Ok(());
} }
} }
cx.platform().restart(); cx.platform().restart();
@ -4214,17 +4216,11 @@ mod tests {
.map(|e| e.id) .map(|e| e.id)
); );
}); });
assert_eq!( assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1"));
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root1")
);
// Add a second item to a non-empty pane // Add a second item to a non-empty pane
workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx)); workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
assert_eq!( assert_eq!(window.current_title(cx).as_deref(), Some("two.txt — root1"));
cx.current_window_title(window.window_id()).as_deref(),
Some("two.txt — root1")
);
project.read_with(cx, |project, cx| { project.read_with(cx, |project, cx| {
assert_eq!( assert_eq!(
project.active_entry(), project.active_entry(),
@ -4240,10 +4236,7 @@ mod tests {
}) })
.await .await
.unwrap(); .unwrap();
assert_eq!( assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1"));
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root1")
);
project.read_with(cx, |project, cx| { project.read_with(cx, |project, cx| {
assert_eq!( assert_eq!(
project.active_entry(), project.active_entry(),
@ -4261,16 +4254,13 @@ mod tests {
.await .await
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
cx.current_window_title(window.window_id()).as_deref(), window.current_title(cx).as_deref(),
Some("one.txt — root1, root2") Some("one.txt — root1, root2")
); );
// Remove a project folder // Remove a project folder
project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx)); project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
assert_eq!( assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root2"));
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root2")
);
} }
#[gpui::test] #[gpui::test]
@ -4304,9 +4294,9 @@ mod tests {
}); });
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.simulate_prompt_answer(window.window_id(), 2 /* cancel */); window.simulate_prompt_answer(2, cx); // cancel
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
assert!(!cx.has_pending_prompt(window.window_id())); assert!(!window.has_pending_prompt(cx));
assert!(!task.await.unwrap()); assert!(!task.await.unwrap());
} }
@ -4365,10 +4355,10 @@ mod tests {
assert_eq!(pane.items_len(), 4); assert_eq!(pane.items_len(), 4);
assert_eq!(pane.active_item().unwrap().id(), item1.id()); assert_eq!(pane.active_item().unwrap().id(), item1.id());
}); });
assert!(cx.has_pending_prompt(window.window_id())); assert!(window.has_pending_prompt(cx));
// Confirm saving item 1. // Confirm saving item 1.
cx.simulate_prompt_answer(window.window_id(), 0); window.simulate_prompt_answer(0, cx);
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
// Item 1 is saved. There's a prompt to save item 3. // Item 1 is saved. There's a prompt to save item 3.
@ -4379,10 +4369,10 @@ mod tests {
assert_eq!(pane.items_len(), 3); assert_eq!(pane.items_len(), 3);
assert_eq!(pane.active_item().unwrap().id(), item3.id()); assert_eq!(pane.active_item().unwrap().id(), item3.id());
}); });
assert!(cx.has_pending_prompt(window.window_id())); assert!(window.has_pending_prompt(cx));
// Cancel saving item 3. // Cancel saving item 3.
cx.simulate_prompt_answer(window.window_id(), 1); window.simulate_prompt_answer(1, cx);
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
// Item 3 is reloaded. There's a prompt to save item 4. // Item 3 is reloaded. There's a prompt to save item 4.
@ -4393,10 +4383,10 @@ mod tests {
assert_eq!(pane.items_len(), 2); assert_eq!(pane.items_len(), 2);
assert_eq!(pane.active_item().unwrap().id(), item4.id()); assert_eq!(pane.active_item().unwrap().id(), item4.id());
}); });
assert!(cx.has_pending_prompt(window.window_id())); assert!(window.has_pending_prompt(cx));
// Confirm saving item 4. // Confirm saving item 4.
cx.simulate_prompt_answer(window.window_id(), 0); window.simulate_prompt_answer(0, cx);
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
// There's a prompt for a path for item 4. // There's a prompt for a path for item 4.
@ -4499,7 +4489,7 @@ mod tests {
&[ProjectEntryId::from_proto(0)] &[ProjectEntryId::from_proto(0)]
); );
}); });
cx.simulate_prompt_answer(window.window_id(), 0); window.simulate_prompt_answer(0, cx);
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
left_pane.read_with(cx, |pane, cx| { left_pane.read_with(cx, |pane, cx| {
@ -4508,7 +4498,7 @@ mod tests {
&[ProjectEntryId::from_proto(2)] &[ProjectEntryId::from_proto(2)]
); );
}); });
cx.simulate_prompt_answer(window.window_id(), 0); window.simulate_prompt_answer(0, cx);
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
close.await.unwrap(); close.await.unwrap();
@ -4547,7 +4537,7 @@ mod tests {
}); });
// Deactivating the window saves the file. // Deactivating the window saves the file.
cx.simulate_window_activation(None); window.simulate_deactivation(cx);
deterministic.run_until_parked(); deterministic.run_until_parked();
item.read_with(cx, |item, _| assert_eq!(item.save_count, 1)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
@ -4568,12 +4558,12 @@ mod tests {
item.read_with(cx, |item, _| assert_eq!(item.save_count, 2)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
// Deactivating the window still saves the file. // Deactivating the window still saves the file.
cx.simulate_window_activation(Some(window.window_id())); window.simulate_activation(cx);
item.update(cx, |item, cx| { item.update(cx, |item, cx| {
cx.focus_self(); cx.focus_self();
item.is_dirty = true; item.is_dirty = true;
}); });
cx.simulate_window_activation(None); window.simulate_deactivation(cx);
deterministic.run_until_parked(); deterministic.run_until_parked();
item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
@ -4610,7 +4600,7 @@ mod tests {
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)) pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
.await .await
.unwrap(); .unwrap();
assert!(!cx.has_pending_prompt(window.window_id())); assert!(!window.has_pending_prompt(cx));
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
// Add the item again, ensuring autosave is prevented if the underlying file has been deleted. // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
@ -4631,7 +4621,7 @@ mod tests {
let _close_items = let _close_items =
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)); pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
deterministic.run_until_parked(); deterministic.run_until_parked();
assert!(cx.has_pending_prompt(window.window_id())); assert!(window.has_pending_prompt(cx));
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
} }

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor." description = "The fast, collaborative code editor."
edition = "2021" edition = "2021"
name = "zed" name = "zed"
version = "0.99.0" version = "0.100.0"
publish = false publish = false
[lib] [lib]

View file

@ -1,5 +1,5 @@
name = "Shell Script" name = "Shell Script"
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin"] path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile"]
line_comment = "# " line_comment = "# "
first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b" first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
brackets = [ brackets = [

View file

@ -102,7 +102,7 @@ impl LspAdapter for RustLspAdapter {
Some("rust-analyzer/flycheck".into()) Some("rust-analyzer/flycheck".into())
} }
async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
lazy_static! { lazy_static! {
static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap(); static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap();
} }
@ -310,7 +310,7 @@ mod tests {
}, },
], ],
}; };
RustLspAdapter.process_diagnostics(&mut params).await; RustLspAdapter.process_diagnostics(&mut params);
assert_eq!(params.diagnostics[0].message, "use of moved value `a`"); assert_eq!(params.diagnostics[0].message, "use of moved value `a`");

View file

@ -1,5 +1,5 @@
name = "TOML" name = "TOML"
path_suffixes = ["toml"] path_suffixes = ["Cargo.lock", "toml"]
line_comment = "# " line_comment = "# "
autoclose_before = ",]}" autoclose_before = ",]}"
brackets = [ brackets = [

View file

@ -16,7 +16,7 @@ use futures::{
channel::{mpsc, oneshot}, channel::{mpsc, oneshot},
FutureExt, SinkExt, StreamExt, FutureExt, SinkExt, StreamExt,
}; };
use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task, ViewContext}; use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task};
use isahc::{config::Configurable, Request}; use isahc::{config::Configurable, Request};
use language::{LanguageRegistry, Point}; use language::{LanguageRegistry, Point};
use log::LevelFilter; use log::LevelFilter;
@ -45,7 +45,6 @@ use std::{
time::{Duration, SystemTime, UNIX_EPOCH}, time::{Duration, SystemTime, UNIX_EPOCH},
}; };
use sum_tree::Bias; use sum_tree::Bias;
use terminal_view::{get_working_directory, TerminalSettings, TerminalView};
use util::{ use util::{
channel::ReleaseChannel, channel::ReleaseChannel,
http::{self, HttpClient}, http::{self, HttpClient},
@ -58,7 +57,7 @@ use fs::RealFs;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
use staff_mode::StaffMode; use staff_mode::StaffMode;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace}; use workspace::AppState;
use zed::{ use zed::{
assets::Assets, assets::Assets,
build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
@ -660,6 +659,10 @@ fn load_embedded_fonts(app: &App) {
let embedded_fonts = Mutex::new(Vec::new()); let embedded_fonts = Mutex::new(Vec::new());
smol::block_on(app.background().scoped(|scope| { smol::block_on(app.background().scoped(|scope| {
for font_path in &font_paths { for font_path in &font_paths {
if !font_path.ends_with(".ttf") {
continue;
}
scope.spawn(async { scope.spawn(async {
let font_path = &*font_path; let font_path = &*font_path;
let font_bytes = Assets.load(font_path).unwrap().to_vec(); let font_bytes = Assets.load(font_path).unwrap().to_vec();
@ -927,35 +930,6 @@ async fn handle_cli_connection(
} }
} }
pub fn dock_default_item_factory(
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) -> Option<Box<dyn ItemHandle>> {
let strategy = settings::get::<TerminalSettings>(cx)
.working_directory
.clone();
let working_directory = get_working_directory(workspace, cx, strategy);
let window_id = cx.window_id();
let terminal = workspace
.project()
.update(cx, |project, cx| {
project.create_terminal(working_directory, window_id, cx)
})
.notify_err(workspace, cx)?;
let terminal_view = cx.add_view(|cx| {
TerminalView::new(
terminal,
workspace.weak_handle(),
workspace.database_id(),
cx,
)
});
Some(Box::new(terminal_view))
}
pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
&[ &[
("Go to file", &file_finder::Toggle), ("Go to file", &file_finder::Toggle),

View file

@ -165,13 +165,12 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| { move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
let app_state = workspace.app_state().clone(); let app_state = workspace.app_state().clone();
let markdown = app_state.languages.language_for_name("JSON"); let markdown = app_state.languages.language_for_name("JSON");
let window_id = cx.window_id(); let window = cx.window();
cx.spawn(|workspace, mut cx| async move { cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err(); let markdown = markdown.await.log_err();
let content = to_string_pretty( let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| {
&cx.debug_elements(window_id) anyhow!("could not debug elements for window {}", window.id())
.ok_or_else(|| anyhow!("could not debug elements for {window_id}"))?, })?)
)
.unwrap(); .unwrap();
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
@ -405,29 +404,22 @@ pub fn build_window_options(
fn quit(_: &Quit, cx: &mut gpui::AppContext) { fn quit(_: &Quit, cx: &mut gpui::AppContext) {
let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit; let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
let mut workspaces = cx let mut workspace_windows = cx
.window_ids() .windows()
.into_iter() .into_iter()
.filter_map(|window_id| { .filter_map(|window| window.downcast::<Workspace>())
Some(
cx.root_view(window_id)?
.clone()
.downcast::<Workspace>()?
.downgrade(),
)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// If multiple windows have unsaved changes, and need a save prompt, // If multiple windows have unsaved changes, and need a save prompt,
// prompt in the active window before switching to a different window. // prompt in the active window before switching to a different window.
workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) {
let answer = cx.prompt( let answer = window.prompt(
workspace.window_id(),
PromptLevel::Info, PromptLevel::Info,
"Are you sure you want to quit?", "Are you sure you want to quit?",
&["Quit", "Cancel"], &["Quit", "Cancel"],
&mut cx,
); );
if let Some(mut answer) = answer { if let Some(mut answer) = answer {
@ -439,14 +431,13 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
} }
// If the user cancels any save prompt, then keep the app open. // If the user cancels any save prompt, then keep the app open.
for workspace in workspaces { for window in workspace_windows {
if !workspace if let Some(close) = window.update_root(&mut cx, |workspace, cx| {
.update(&mut cx, |workspace, cx| { workspace.prepare_to_close(false, cx)
workspace.prepare_to_close(true, cx) }) {
})? if close.await? {
.await? return Ok(());
{ }
return Ok(());
} }
} }
cx.platform().quit(); cx.platform().quit();
@ -723,8 +714,8 @@ mod tests {
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
use fs::{FakeFs, Fs}; use fs::{FakeFs, Fs};
use gpui::{ use gpui::{
actions, elements::Empty, executor::Deterministic, Action, AnyElement, AppContext, actions, elements::Empty, executor::Deterministic, Action, AnyElement, AnyWindowHandle,
AssetSource, Element, Entity, TestAppContext, View, ViewHandle, AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
@ -781,17 +772,13 @@ mod tests {
}) })
.await .await
.unwrap(); .unwrap();
assert_eq!(cx.window_ids().len(), 1); assert_eq!(cx.windows().len(), 1);
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
.await .await
.unwrap(); .unwrap();
assert_eq!(cx.window_ids().len(), 1); assert_eq!(cx.windows().len(), 1);
let workspace_1 = cx let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
.read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
.unwrap()
.downcast::<Workspace>()
.unwrap();
workspace_1.update(cx, |workspace, cx| { workspace_1.update(cx, |workspace, cx| {
assert_eq!(workspace.worktrees(cx).count(), 2); assert_eq!(workspace.worktrees(cx).count(), 2);
assert!(workspace.left_dock().read(cx).is_open()); assert!(workspace.left_dock().read(cx).is_open());
@ -808,27 +795,22 @@ mod tests {
}) })
.await .await
.unwrap(); .unwrap();
assert_eq!(cx.window_ids().len(), 2); assert_eq!(cx.windows().len(), 2);
// Replace existing windows // Replace existing windows
let window_id = cx.window_ids()[0]; let window = cx.windows()[0].downcast::<Workspace>().unwrap();
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/c"), PathBuf::from("/root/d")], &[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
&app_state, &app_state,
Some(window_id), Some(window),
cx, cx,
) )
}) })
.await .await
.unwrap(); .unwrap();
assert_eq!(cx.window_ids().len(), 2); assert_eq!(cx.windows().len(), 2);
let workspace_1 = cx let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
.read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
.unwrap()
.clone()
.downcast::<Workspace>()
.unwrap();
workspace_1.update(cx, |workspace, cx| { workspace_1.update(cx, |workspace, cx| {
assert_eq!( assert_eq!(
workspace workspace
@ -854,14 +836,11 @@ mod tests {
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
.await .await
.unwrap(); .unwrap();
assert_eq!(cx.window_ids().len(), 1); assert_eq!(cx.windows().len(), 1);
// When opening the workspace, the window is not in a edited state. // When opening the workspace, the window is not in a edited state.
let workspace = cx let window = cx.windows()[0].downcast::<Workspace>().unwrap();
.read_window(cx.window_ids()[0], |cx| cx.root_view().clone()) let workspace = window.root(cx);
.unwrap()
.downcast::<Workspace>()
.unwrap();
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let editor = workspace.read_with(cx, |workspace, cx| { let editor = workspace.read_with(cx, |workspace, cx| {
workspace workspace
@ -870,19 +849,19 @@ mod tests {
.downcast::<Editor>() .downcast::<Editor>()
.unwrap() .unwrap()
}); });
assert!(!cx.is_window_edited(workspace.window_id())); assert!(!window.is_edited(cx));
// Editing a buffer marks the window as edited. // Editing a buffer marks the window as edited.
editor.update(cx, |editor, cx| editor.insert("EDIT", cx)); editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
assert!(cx.is_window_edited(workspace.window_id())); assert!(window.is_edited(cx));
// Undoing the edit restores the window's edited state. // Undoing the edit restores the window's edited state.
editor.update(cx, |editor, cx| editor.undo(&Default::default(), cx)); editor.update(cx, |editor, cx| editor.undo(&Default::default(), cx));
assert!(!cx.is_window_edited(workspace.window_id())); assert!(!window.is_edited(cx));
// Redoing the edit marks the window as edited again. // Redoing the edit marks the window as edited again.
editor.update(cx, |editor, cx| editor.redo(&Default::default(), cx)); editor.update(cx, |editor, cx| editor.redo(&Default::default(), cx));
assert!(cx.is_window_edited(workspace.window_id())); assert!(window.is_edited(cx));
// Closing the item restores the window's edited state. // Closing the item restores the window's edited state.
let close = pane.update(cx, |pane, cx| { let close = pane.update(cx, |pane, cx| {
@ -890,9 +869,10 @@ mod tests {
pane.close_active_item(&Default::default(), cx).unwrap() pane.close_active_item(&Default::default(), cx).unwrap()
}); });
executor.run_until_parked(); executor.run_until_parked();
cx.simulate_prompt_answer(workspace.window_id(), 1);
window.simulate_prompt_answer(1, cx);
close.await.unwrap(); close.await.unwrap();
assert!(!cx.is_window_edited(workspace.window_id())); assert!(!window.is_edited(cx));
// Opening the buffer again doesn't impact the window's edited state. // Opening the buffer again doesn't impact the window's edited state.
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
@ -905,22 +885,22 @@ mod tests {
.downcast::<Editor>() .downcast::<Editor>()
.unwrap() .unwrap()
}); });
assert!(!cx.is_window_edited(workspace.window_id())); assert!(!window.is_edited(cx));
// Editing the buffer marks the window as edited. // Editing the buffer marks the window as edited.
editor.update(cx, |editor, cx| editor.insert("EDIT", cx)); editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
assert!(cx.is_window_edited(workspace.window_id())); assert!(window.is_edited(cx));
// Ensure closing the window via the mouse gets preempted due to the // Ensure closing the window via the mouse gets preempted due to the
// buffer having unsaved changes. // buffer having unsaved changes.
assert!(!cx.simulate_window_close(workspace.window_id())); assert!(!window.simulate_close(cx));
executor.run_until_parked(); executor.run_until_parked();
assert_eq!(cx.window_ids().len(), 1); assert_eq!(cx.windows().len(), 1);
// The window is successfully closed after the user dismisses the prompt. // The window is successfully closed after the user dismisses the prompt.
cx.simulate_prompt_answer(workspace.window_id(), 1); window.simulate_prompt_answer(1, cx);
executor.run_until_parked(); executor.run_until_parked();
assert_eq!(cx.window_ids().len(), 0); assert_eq!(cx.windows().len(), 0);
} }
#[gpui::test] #[gpui::test]
@ -933,12 +913,13 @@ mod tests {
}) })
.await; .await;
let window_id = *cx.window_ids().first().unwrap(); let window = cx
let workspace = cx .windows()
.read_window(window_id, |cx| cx.root_view().clone()) .first()
.unwrap() .unwrap()
.downcast::<Workspace>() .downcast::<Workspace>()
.unwrap(); .unwrap();
let workspace = window.root(cx);
let editor = workspace.update(cx, |workspace, cx| { let editor = workspace.update(cx, |workspace, cx| {
workspace workspace
@ -981,9 +962,8 @@ mod tests {
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let workspace = cx let window = cx.add_window(|cx| Workspace::test_new(project, cx));
.add_window(|cx| Workspace::test_new(project, cx)) let workspace = window.root(cx);
.root(cx);
let entries = cx.read(|cx| workspace.file_project_paths(cx)); let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone(); let file1 = entries[0].clone();
@ -1104,12 +1084,8 @@ mod tests {
cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx)) cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
.await .await
.unwrap(); .unwrap();
assert_eq!(cx.window_ids().len(), 1); assert_eq!(cx.windows().len(), 1);
let workspace = cx let workspace = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
.read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
.unwrap()
.downcast::<Workspace>()
.unwrap();
#[track_caller] #[track_caller]
fn assert_project_panel_selection( fn assert_project_panel_selection(
@ -1297,7 +1273,6 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
// Open a file within an existing worktree. // Open a file within an existing worktree.
workspace workspace
@ -1323,7 +1298,7 @@ mod tests {
cx.read(|cx| assert!(editor.is_dirty(cx))); cx.read(|cx| assert!(editor.is_dirty(cx)));
let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx)); let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx));
cx.simulate_prompt_answer(window_id, 0); window.simulate_prompt_answer(0, cx);
save_task.await.unwrap(); save_task.await.unwrap();
editor.read_with(cx, |editor, cx| { editor.read_with(cx, |editor, cx| {
assert!(!editor.is_dirty(cx)); assert!(!editor.is_dirty(cx));
@ -1340,11 +1315,10 @@ mod tests {
project.update(cx, |project, _| project.languages().add(rust_lang())); project.update(cx, |project, _| project.languages().add(rust_lang()));
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap()); let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
// Create a new untitled buffer // Create a new untitled buffer
cx.dispatch_action(window_id, NewFile); cx.dispatch_action(window.into(), NewFile);
let editor = workspace.read_with(cx, |workspace, cx| { let editor = workspace.read_with(cx, |workspace, cx| {
workspace workspace
.active_item(cx) .active_item(cx)
@ -1399,7 +1373,7 @@ mod tests {
// Open the same newly-created file in another pane item. The new editor should reuse // Open the same newly-created file in another pane item. The new editor should reuse
// the same buffer. // the same buffer.
cx.dispatch_action(window_id, NewFile); cx.dispatch_action(window.into(), NewFile);
workspace workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
workspace.split_and_clone( workspace.split_and_clone(
@ -1435,10 +1409,9 @@ mod tests {
project.update(cx, |project, _| project.languages().add(rust_lang())); project.update(cx, |project, _| project.languages().add(rust_lang()));
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
// Create a new untitled buffer // Create a new untitled buffer
cx.dispatch_action(window_id, NewFile); cx.dispatch_action(window.into(), NewFile);
let editor = workspace.read_with(cx, |workspace, cx| { let editor = workspace.read_with(cx, |workspace, cx| {
workspace workspace
.active_item(cx) .active_item(cx)
@ -1488,7 +1461,6 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx); let workspace = window.root(cx);
let window_id = window.window_id();
let entries = cx.read(|cx| workspace.file_project_paths(cx)); let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone(); let file1 = entries[0].clone();
@ -1510,7 +1482,7 @@ mod tests {
(editor.downgrade(), buffer) (editor.downgrade(), buffer)
}); });
cx.dispatch_action(window_id, pane::SplitRight); cx.dispatch_action(window.into(), pane::SplitRight);
let editor_2 = cx.update(|cx| { let editor_2 = cx.update(|cx| {
let pane_2 = workspace.read(cx).active_pane().clone(); let pane_2 = workspace.read(cx).active_pane().clone();
assert_ne!(pane_1, pane_2); assert_ne!(pane_1, pane_2);
@ -1520,7 +1492,7 @@ mod tests {
pane2_item.downcast::<Editor>().unwrap().downgrade() pane2_item.downcast::<Editor>().unwrap().downgrade()
}); });
cx.dispatch_action(window_id, workspace::CloseActiveItem); cx.dispatch_action(window.into(), workspace::CloseActiveItem);
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
workspace.read_with(cx, |workspace, _| { workspace.read_with(cx, |workspace, _| {
@ -1528,9 +1500,9 @@ mod tests {
assert_eq!(workspace.active_pane(), &pane_1); assert_eq!(workspace.active_pane(), &pane_1);
}); });
cx.dispatch_action(window_id, workspace::CloseActiveItem); cx.dispatch_action(window.into(), workspace::CloseActiveItem);
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.simulate_prompt_answer(window_id, 1); window.simulate_prompt_answer(1, cx);
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
workspace.read_with(cx, |workspace, cx| { workspace.read_with(cx, |workspace, cx| {
@ -2086,11 +2058,10 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
let window = cx.add_window(|_| TestView); let window = cx.add_window(|_| TestView);
let window_id = window.window_id();
// Test loading the keymap base at all // Test loading the keymap base at all
assert_key_bindings_for( assert_key_bindings_for(
window_id, window.into(),
cx, cx,
vec![("backspace", &A), ("k", &ActivatePreviousPane)], vec![("backspace", &A), ("k", &ActivatePreviousPane)],
line!(), line!(),
@ -2117,7 +2088,7 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
assert_key_bindings_for( assert_key_bindings_for(
window_id, window.into(),
cx, cx,
vec![("backspace", &B), ("k", &ActivatePreviousPane)], vec![("backspace", &B), ("k", &ActivatePreviousPane)],
line!(), line!(),
@ -2140,7 +2111,7 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
assert_key_bindings_for( assert_key_bindings_for(
window_id, window.into(),
cx, cx,
vec![("backspace", &B), ("[", &ActivatePrevItem)], vec![("backspace", &B), ("[", &ActivatePrevItem)],
line!(), line!(),
@ -2148,7 +2119,7 @@ mod tests {
#[track_caller] #[track_caller]
fn assert_key_bindings_for<'a>( fn assert_key_bindings_for<'a>(
window_id: usize, window: AnyWindowHandle,
cx: &TestAppContext, cx: &TestAppContext,
actions: Vec<(&'static str, &'a dyn Action)>, actions: Vec<(&'static str, &'a dyn Action)>,
line: u32, line: u32,
@ -2156,7 +2127,7 @@ mod tests {
for (key, action) in actions { for (key, action) in actions {
// assert that... // assert that...
assert!( assert!(
cx.available_actions(window_id, 0) cx.available_actions(window, 0)
.into_iter() .into_iter()
.any(|(_, bound_action, b)| { .any(|(_, bound_action, b)| {
// action names match... // action names match...
@ -2257,11 +2228,10 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
let window = cx.add_window(|_| TestView); let window = cx.add_window(|_| TestView);
let window_id = window.window_id();
// Test loading the keymap base at all // Test loading the keymap base at all
assert_key_bindings_for( assert_key_bindings_for(
window_id, window.into(),
cx, cx,
vec![("backspace", &A), ("k", &ActivatePreviousPane)], vec![("backspace", &A), ("k", &ActivatePreviousPane)],
line!(), line!(),
@ -2287,7 +2257,12 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
assert_key_bindings_for(window_id, cx, vec![("k", &ActivatePreviousPane)], line!()); assert_key_bindings_for(
window.into(),
cx,
vec![("k", &ActivatePreviousPane)],
line!(),
);
// Test modifying the base, while retaining the users keymap // Test modifying the base, while retaining the users keymap
fs.save( fs.save(
@ -2305,11 +2280,11 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
assert_key_bindings_for(window_id, cx, vec![("[", &ActivatePrevItem)], line!()); assert_key_bindings_for(window.into(), cx, vec![("[", &ActivatePrevItem)], line!());
#[track_caller] #[track_caller]
fn assert_key_bindings_for<'a>( fn assert_key_bindings_for<'a>(
window_id: usize, window: AnyWindowHandle,
cx: &TestAppContext, cx: &TestAppContext,
actions: Vec<(&'static str, &'a dyn Action)>, actions: Vec<(&'static str, &'a dyn Action)>,
line: u32, line: u32,
@ -2317,7 +2292,7 @@ mod tests {
for (key, action) in actions { for (key, action) in actions {
// assert that... // assert that...
assert!( assert!(
cx.available_actions(window_id, 0) cx.available_actions(window, 0)
.into_iter() .into_iter()
.any(|(_, bound_action, b)| { .any(|(_, bound_action, b)| {
// action names match... // action names match...
@ -2351,6 +2326,11 @@ mod tests {
.unwrap() .unwrap()
.to_vec() .to_vec()
.into(), .into(),
Assets
.load("fonts/plex/IBMPlexSans-Regular.ttf")
.unwrap()
.to_vec()
.into(),
]) ])
.unwrap(); .unwrap();
let themes = ThemeRegistry::new(Assets, cx.font_cache().clone()); let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());

View file

@ -4,6 +4,7 @@ export * from "./theme/theme_config"
export { chroma } export { chroma }
export const font_families = { export const font_families = {
ui_sans: "IBM Plex Sans",
sans: "Zed Sans", sans: "Zed Sans",
mono: "Zed Mono", mono: "Zed Mono",
} }

View file

@ -1,4 +1,4 @@
import { Scale, Color } from "chroma-js" import chroma, { Scale, Color } from "chroma-js"
import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax" import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax"
export { Syntax, ThemeSyntax, SyntaxHighlightStyle } export { Syntax, ThemeSyntax, SyntaxHighlightStyle }
import { import {
@ -32,6 +32,7 @@ export interface Theme {
players: Players players: Players
syntax?: Partial<ThemeSyntax> syntax?: Partial<ThemeSyntax>
color_family: ColorFamily
} }
export interface Meta { export interface Meta {
@ -69,6 +70,15 @@ export interface Players {
"7": Player "7": Player
} }
export type ColorFamily = Partial<{ [K in keyof RampSet]: ColorFamilyRange }>
export interface ColorFamilyRange {
low: number
high: number
range: number
scaling_value: number
}
export interface Shadow { export interface Shadow {
blur: number blur: number
color: string color: string
@ -162,6 +172,8 @@ export function create_theme(theme: ThemeConfig): Theme {
"7": player(ramps.yellow), "7": player(ramps.yellow),
} }
const color_family = build_color_family(ramps)
return { return {
name, name,
is_light, is_light,
@ -177,6 +189,7 @@ export function create_theme(theme: ThemeConfig): Theme {
players, players,
syntax, syntax,
color_family,
} }
} }
@ -187,6 +200,28 @@ function player(ramp: Scale): Player {
} }
} }
function build_color_family(ramps: RampSet): ColorFamily {
const color_family: ColorFamily = {}
for (const ramp in ramps) {
const ramp_value = ramps[ramp as keyof RampSet]
const lightnessValues = [ramp_value(0).get('hsl.l') * 100, ramp_value(1).get('hsl.l') * 100]
const low = Math.min(...lightnessValues)
const high = Math.max(...lightnessValues)
const range = high - low
color_family[ramp as keyof RampSet] = {
low,
high,
range,
scaling_value: 100 / range,
}
}
return color_family
}
function lowest_layer(ramps: RampSet): Layer { function lowest_layer(ramps: RampSet): Layer {
return { return {
base: build_style_set(ramps.neutral, 0.2, 1), base: build_style_set(ramps.neutral, 0.2, 1),