Detailed changes
@@ -1649,6 +1649,21 @@ dependencies = [
"theme",
]
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "convert_case"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
+dependencies = [
+ "unicode-segmentation",
+]
+
[[package]]
name = "copilot"
version = "0.1.0"
@@ -2133,6 +2148,19 @@ dependencies = [
"serde",
]
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case 0.4.0",
+ "proc-macro2",
+ "quote",
+ "rustc_version 0.4.0",
+ "syn 1.0.109",
+]
+
[[package]]
name = "dhat"
version = "0.3.2"
@@ -2314,6 +2342,7 @@ dependencies = [
"clock",
"collections",
"context_menu",
+ "convert_case 0.6.0",
"copilot",
"ctor",
"db",
@@ -3096,6 +3125,7 @@ dependencies = [
"core-graphics",
"core-text",
"ctor",
+ "derive_more",
"dhat",
"env_logger 0.9.3",
"etagere",
@@ -5106,7 +5136,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff"
dependencies = [
- "rustc_version",
+ "rustc_version 0.3.3",
]
[[package]]
@@ -6276,7 +6306,16 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
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]]
@@ -6716,6 +6755,12 @@ dependencies = [
"semver-parser",
]
+[[package]]
+name = "semver"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
+
[[package]]
name = "semver-parser"
version = "0.10.2"
@@ -79,6 +79,7 @@ resolver = "2"
anyhow = { version = "1.0.57" }
async-trait = { version = "0.1" }
ctor = { version = "0.1" }
+derive_more = { version = "0.99.17" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
globset = { version = "0.4" }
@@ -12,10 +12,7 @@ use client::{
use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
-use gpui::{
- elements::*, executor::Deterministic, AnyElement, Entity, ModelHandle, TestAppContext, View,
- ViewContext, ViewHandle, WeakViewHandle,
-};
+use gpui::{executor::Deterministic, ModelHandle, TestAppContext, WindowHandle};
use language::LanguageRegistry;
use parking_lot::Mutex;
use project::{Project, WorktreeId};
@@ -466,42 +463,8 @@ impl TestClient {
&self,
project: &ModelHandle<Project>,
cx: &mut TestAppContext,
- ) -> ViewHandle<Workspace> {
- struct WorkspaceContainer {
- workspace: Option<WeakViewHandle<Workspace>>,
- }
-
- impl Entity for WorkspaceContainer {
- type Event = ();
- }
-
- impl View for WorkspaceContainer {
- fn ui_name() -> &'static str {
- "WorkspaceContainer"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- if let Some(workspace) = self
- .workspace
- .as_ref()
- .and_then(|workspace| workspace.upgrade(cx))
- {
- ChildView::new(&workspace, cx).into_any()
- } else {
- Empty::new().into_any()
- }
- }
- }
-
- // We use a workspace container so that we don't need to remove the window in order to
- // drop the workspace and we can use a ViewHandle instead.
- let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None });
- let workspace = cx.add_view(window_id, |cx| Workspace::test_new(project.clone(), cx));
- container.update(cx, |container, cx| {
- container.workspace = Some(workspace.downgrade());
- cx.notify();
- });
- workspace
+ ) -> WindowHandle<Workspace> {
+ cx.add_window(|cx| Workspace::test_new(project.clone(), cx))
}
}
@@ -7,8 +7,7 @@ use client::{User, RECEIVE_TIMEOUT};
use collections::HashSet;
use editor::{
test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
- ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions,
- Undo,
+ ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToggleCodeActions, Undo,
};
use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions};
use futures::StreamExt as _;
@@ -1208,7 +1207,7 @@ async fn test_share_project(
cx_c: &mut TestAppContext,
) {
deterministic.forbid_parking();
- let (window_b, _) = cx_b.add_window(|_| EmptyView);
+ let window_b = cx_b.add_window(|_| EmptyView);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -1316,7 +1315,7 @@ async fn test_share_project(
.await
.unwrap();
- let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
+ let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
// Client A sees client B's selection
deterministic.run_until_parked();
@@ -1499,8 +1498,8 @@ async fn test_host_disconnect(
deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
- let (window_id_b, workspace_b) =
- cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+ let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+ let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@@ -1509,11 +1508,9 @@ async fn test_host_disconnect(
.unwrap()
.downcast::<Editor>()
.unwrap();
- assert!(cx_b
- .read_window(window_id_b, |cx| editor_b.is_focused(cx))
- .unwrap());
+ assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(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.
server.forbid_connections();
@@ -1525,10 +1522,10 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
// Ensure client B's edited state is reset and that the whole window is blurred.
- cx_b.read_window(window_id_b, |cx| {
+ window_b.read_with(cx_b, |cx| {
assert_eq!(cx.focused_view_id(), None);
});
- assert!(!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.
let can_close = workspace_b
@@ -3445,13 +3442,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
- let (window_a, _) = cx_a.add_window(|_| EmptyView);
- let editor_a = cx_a.add_view(window_a, |cx| {
- Editor::for_buffer(buffer_a, Some(project_a), cx)
- });
+ let window_a = cx_a.add_window(|_| EmptyView);
+ 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 {
cx: cx_a,
- window_id: window_a,
+ window: window_a.into(),
editor: editor_a,
};
@@ -3460,13 +3455,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
- let (window_b, _) = cx_b.add_window(|_| EmptyView);
- let editor_b = cx_b.add_view(window_b, |cx| {
- Editor::for_buffer(buffer_b, Some(project_b), cx)
- });
+ let window_b = cx_b.add_window(|_| EmptyView);
+ 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 {
cx: cx_b,
- window_id: window_b,
+ window: window_b.into(),
editor: editor_b,
};
@@ -4205,8 +4198,8 @@ async fn test_collaborating_with_completion(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
- let (window_b, _) = cx_b.add_window(|_| EmptyView);
- let editor_b = cx_b.add_view(window_b, |cx| {
+ let window_b = cx_b.add_window(|_| EmptyView);
+ let editor_b = window_b.add_view(cx_b, |cx| {
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
});
@@ -5316,7 +5309,8 @@ async fn test_collaborating_with_code_actions(
// Join the project as client B.
let project_b = client_b.build_remote_project(project_id, cx_b).await;
- let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+ let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+ let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@@ -5540,7 +5534,8 @@ async fn test_collaborating_with_renames(
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
- let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+ let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+ let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
@@ -5571,6 +5566,7 @@ async fn test_collaborating_with_renames(
.unwrap();
prepare_rename.await.unwrap();
editor_b.update(cx_b, |editor, cx| {
+ use editor::ToOffset;
let rename = editor.pending_rename().unwrap();
let buffer = editor.buffer().read(cx).snapshot(cx);
assert_eq!(
@@ -6445,8 +6441,10 @@ async fn test_basic_following(
.await
.unwrap();
- let workspace_a = client_a.build_workspace(&project_a, cx_a);
- let workspace_b = client_b.build_workspace(&project_b, cx_b);
+ let window_a = client_a.build_workspace(&project_a, cx_a);
+ 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.
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
@@ -6529,7 +6527,8 @@ async fn test_basic_following(
cx_c.foreground().run_until_parked();
let active_call_c = cx_c.read(ActiveCall::global);
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
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
.await
@@ -6547,7 +6546,7 @@ async fn test_basic_following(
cx_d.foreground().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
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
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
.await
@@ -6645,6 +6644,7 @@ async fn test_basic_following(
}
// Client C closes the project.
+ window_c.remove(cx_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.
@@ -6874,9 +6874,7 @@ async fn test_basic_following(
});
// Client B activates a panel, and the previously-opened screen-sharing item gets activated.
- let panel = cx_b.add_view(workspace_b.window_id(), |_| {
- TestPanel::new(DockPosition::Left)
- });
+ let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
workspace_b.update(cx_b, |workspace, cx| {
workspace.add_panel(panel, cx);
workspace.toggle_panel_focus::<TestPanel>(cx);
@@ -6904,7 +6902,7 @@ async fn test_basic_following(
// Client B activates an item that doesn't implement following,
// 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.active_pane().update(cx, |pane, cx| {
pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
@@ -7066,10 +7064,10 @@ async fn test_following_tab_order(
.await
.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 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 client_b_id = project_a.read_with(cx_a, |project, _| {
@@ -7192,7 +7190,7 @@ async fn test_peers_following_each_other(
.unwrap();
// 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 _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
@@ -7204,7 +7202,7 @@ async fn test_peers_following_each_other(
.unwrap();
// 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 _editor_b1 = workspace_b
.update(cx_b, |workspace, cx| {
@@ -7363,7 +7361,7 @@ async fn test_auto_unfollowing(
.unwrap();
// 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
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@@ -7374,7 +7372,7 @@ async fn test_auto_unfollowing(
.unwrap();
// 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 leader_id = project_b.read_with(cx_b, |project, _| {
project.collaborators().values().next().unwrap().peer_id
@@ -7502,14 +7500,14 @@ async fn test_peers_simultaneously_following_each_other(
client_a.fs.insert_tree("/a", json!({})).await;
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
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
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();
let client_a_id = project_b.read_with(cx_b, |project, _| {
@@ -7601,8 +7599,8 @@ async fn test_on_input_format_from_host_to_guest(
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
- let (window_a, _) = cx_a.add_window(|_| EmptyView);
- let editor_a = cx_a.add_view(window_a, |cx| {
+ let window_a = cx_a.add_window(|_| EmptyView);
+ let editor_a = window_a.add_view(cx_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
});
@@ -7730,8 +7728,8 @@ async fn test_on_input_format_from_guest_to_host(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
- let (window_b, _) = cx_b.add_window(|_| EmptyView);
- let editor_b = cx_b.add_view(window_b, |cx| {
+ let window_b = cx_b.add_window(|_| EmptyView);
+ let editor_b = window_b.add_view(cx_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
});
@@ -7891,7 +7889,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.await
.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();
let _buffer_a = project_a
@@ -7959,7 +7957,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"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
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@@ -8198,8 +8196,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
.await
.unwrap();
- let workspace_a = client_a.build_workspace(&project_a, cx_a);
- let workspace_b = client_b.build_workspace(&project_b, cx_b);
+ 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).root(cx_b);
cx_a.foreground().start_waiting();
cx_b.foreground().start_waiting();
@@ -15,8 +15,8 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f, PathBuilder},
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
- AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
- ViewContext, ViewHandle, WeakViewHandle,
+ AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
+ Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
};
use picker::PickerEvent;
use project::{Project, RepositoryEntry};
@@ -1312,7 +1312,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
_: RectF,
_: &mut Self::LayoutState,
_: &mut CollabTitlebarItem,
- _: &mut ViewContext<CollabTitlebarItem>,
+ _: &mut PaintContext<CollabTitlebarItem>,
) -> Self::PaintState {
let mut path = PathBuilder::new();
path.reset(bounds.lower_left());
@@ -305,18 +305,18 @@ impl ContactList {
github_login
);
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 {
if answer.next().await == Some(0) {
if let Err(e) = user_store
.update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
.await
{
- cx.prompt(
- window_id,
+ window.prompt(
PromptLevel::Info,
&format!("Failed to remove contact: {}", e),
&["Ok"],
+ &mut cx,
);
}
}
@@ -7,7 +7,7 @@ use gpui::{
},
json::ToJson,
serde_json::{self, json},
- AnyElement, Axis, Element, LayoutContext, SceneBuilder, ViewContext,
+ AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, ViewContext,
};
use crate::CollabTitlebarItem;
@@ -54,7 +54,7 @@ impl Element<CollabTitlebarItem> for FacePile {
visible_bounds: RectF,
_layout: &mut Self::LayoutState,
view: &mut CollabTitlebarItem,
- cx: &mut ViewContext<CollabTitlebarItem>,
+ cx: &mut PaintContext<CollabTitlebarItem>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@@ -7,7 +7,7 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
- AnyElement, AppContext, Entity, View, ViewContext,
+ AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
};
use util::ResultExt;
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 mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
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 {
- for window_id in notification_windows.drain(..) {
- cx.remove_window(window_id);
+ for window in notification_windows.drain(..) {
+ window.remove(&mut cx);
}
if let Some(incoming_call) = incoming_call {
@@ -31,7 +31,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.platform().screens() {
let screen_bounds = screen.bounds();
- let (window_id, _) = cx.add_window(
+ let window = cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right()
@@ -49,7 +49,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()),
);
- notification_windows.push(window_id);
+ notification_windows.push(window);
}
}
}
@@ -26,7 +26,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.platform().screens() {
let screen_bounds = screen.bounds();
- let (window_id, _) = cx.add_window(
+ let window = cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING),
@@ -52,20 +52,20 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
notification_windows
.entry(*project_id)
.or_insert(Vec::new())
- .push(window_id);
+ .push(window);
}
}
room::Event::RemoteProjectUnshared { project_id } => {
- if let Some(window_ids) = notification_windows.remove(&project_id) {
- for window_id in window_ids {
- cx.update_window(window_id, |cx| cx.remove_window());
+ if let Some(windows) = notification_windows.remove(&project_id) {
+ for window in windows {
+ window.remove(cx);
}
}
}
room::Event::Left => {
- for (_, window_ids) in notification_windows.drain() {
- for window_id in window_ids {
- cx.update_window(window_id, |cx| cx.remove_window());
+ for (_, windows) in notification_windows.drain() {
+ for window in windows {
+ window.remove(cx);
}
}
}
@@ -20,11 +20,11 @@ pub fn init(cx: &mut AppContext) {
{
status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator));
}
- } else if let Some((window_id, _)) = status_indicator.take() {
- cx.update_window(window_id, |cx| cx.remove_window());
+ } else if let Some(window) = status_indicator.take() {
+ window.update(cx, |cx| cx.remove_window());
}
- } else if let Some((window_id, _)) = status_indicator.take() {
- cx.update_window(window_id, |cx| cx.remove_window());
+ } else if let Some(window) = status_indicator.take() {
+ window.update(cx, |cx| cx.remove_window());
}
})
.detach();
@@ -1,8 +1,8 @@
use collections::CommandPaletteFilter;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Element, MouseState,
- ViewContext,
+ actions, anyhow::anyhow, elements::*, keymap_matcher::Keystroke, Action, AnyWindowHandle,
+ AppContext, Element, MouseState, ViewContext,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use std::cmp;
@@ -28,7 +28,7 @@ pub struct CommandPaletteDelegate {
pub enum Event {
Dismissed,
Confirmed {
- window_id: usize,
+ window: AnyWindowHandle,
focused_view_id: usize,
action: Box<dyn Action>,
},
@@ -80,12 +80,13 @@ impl PickerDelegate for CommandPaletteDelegate {
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
- let window_id = cx.window_id();
let view_id = self.focused_view_id;
+ let window = cx.window();
cx.spawn(move |picker, mut cx| async move {
- let actions = cx
- .available_actions(window_id, view_id)
+ let actions = window
+ .available_actions(view_id, &cx)
.into_iter()
+ .flatten()
.filter_map(|(name, action, bindings)| {
let filtered = cx.read(|cx| {
if cx.has_global::<CommandPaletteFilter>() {
@@ -162,13 +163,15 @@ impl PickerDelegate for CommandPaletteDelegate {
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if !self.matches.is_empty() {
- let window_id = cx.window_id();
+ let window = cx.window();
let focused_view_id = self.focused_view_id;
let action_ix = self.matches[self.selected_ix].candidate_id;
let action = self.actions.remove(action_ix).action;
cx.app_context()
.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);
}
@@ -295,8 +298,9 @@ mod tests {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let editor = cx.add_view(window_id, |cx| {
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
+ let editor = window.add_view(cx, |cx| {
let mut editor = Editor::single_line(None, cx);
editor.set_text("abc", cx);
editor
@@ -1,5 +1,5 @@
use gpui::{
- anyhow,
+ anyhow::{self, anyhow},
elements::*,
geometry::vector::Vector2F,
keymap_matcher::KeymapContext,
@@ -218,12 +218,14 @@ impl ContextMenu {
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
match action {
ContextMenuItemAction::Action(action) => {
- let window_id = cx.window_id();
+ let window = cx.window();
let view_id = self.parent_view_id;
let action = action.boxed_clone();
cx.app_context()
.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);
}
@@ -480,17 +482,19 @@ impl ContextMenu {
.on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
.on_click(MouseButton::Left, move |_, menu, cx| {
menu.cancel(&Default::default(), cx);
- let window_id = cx.window_id();
+ let window = cx.window();
match &action {
ContextMenuItemAction::Action(action) => {
let action = action.boxed_clone();
cx.app_context()
.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);
}
@@ -4,7 +4,7 @@ use gpui::{
geometry::rect::RectF,
platform::{WindowBounds, WindowKind, WindowOptions},
AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
- ViewHandle,
+ WindowHandle,
};
use theme::ui::modal;
@@ -18,43 +18,43 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
pub fn init(cx: &mut AppContext) {
if let Some(copilot) = Copilot::global(cx) {
- let mut code_verification: Option<ViewHandle<CopilotCodeVerification>> = None;
+ let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
cx.observe(&copilot, move |copilot, cx| {
let status = copilot.read(cx).status();
match &status {
crate::Status::SigningIn { prompt } => {
- if let Some(code_verification_handle) = code_verification.as_mut() {
- let window_id = code_verification_handle.window_id();
- let updated = cx.update_window(window_id, |cx| {
- code_verification_handle.update(cx, |code_verification, cx| {
- code_verification.set_status(status.clone(), cx)
- });
- cx.activate_window();
- });
- if updated.is_none() {
- code_verification = Some(create_copilot_auth_window(cx, &status));
+ if let Some(window) = verification_window.as_mut() {
+ let updated = window
+ .root(cx)
+ .map(|root| {
+ root.update(cx, |verification, cx| {
+ verification.set_status(status.clone(), cx);
+ cx.activate_window();
+ })
+ })
+ .is_some();
+ if !updated {
+ verification_window = Some(create_copilot_auth_window(cx, &status));
}
} else if let Some(_prompt) = prompt {
- code_verification = Some(create_copilot_auth_window(cx, &status));
+ verification_window = Some(create_copilot_auth_window(cx, &status));
}
}
Status::Authorized | Status::Unauthorized => {
- if let Some(code_verification) = code_verification.as_ref() {
- let window_id = code_verification.window_id();
- cx.update_window(window_id, |cx| {
- code_verification.update(cx, |code_verification, cx| {
- code_verification.set_status(status, cx)
+ if let Some(window) = verification_window.as_ref() {
+ if let Some(verification) = window.root(cx) {
+ verification.update(cx, |verification, cx| {
+ verification.set_status(status, cx);
+ cx.platform().activate(true);
+ cx.activate_window();
});
-
- cx.platform().activate(true);
- cx.activate_window();
- });
+ }
}
}
_ => {
- if let Some(code_verification) = code_verification.take() {
- cx.update_window(code_verification.window_id(), |cx| cx.remove_window());
+ if let Some(code_verification) = verification_window.take() {
+ code_verification.update(cx, |cx| cx.remove_window());
}
}
}
@@ -66,7 +66,7 @@ pub fn init(cx: &mut AppContext) {
fn create_copilot_auth_window(
cx: &mut AppContext,
status: &Status,
-) -> ViewHandle<CopilotCodeVerification> {
+) -> WindowHandle<CopilotCodeVerification> {
let window_size = theme::current(cx).copilot.modal.dimensions();
let window_options = WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
@@ -78,10 +78,9 @@ fn create_copilot_auth_window(
is_movable: true,
screen: None,
};
- let (_, view) = cx.add_window(window_options, |_cx| {
+ cx.add_window(window_options, |_cx| {
CopilotCodeVerification::new(status.clone())
- });
- view
+ })
}
pub struct CopilotCodeVerification {
@@ -855,7 +855,8 @@ mod tests {
let language_server_id = LanguageServerId(0);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
// Create some diagnostics
project.update(cx, |project, cx| {
@@ -942,7 +943,7 @@ mod tests {
});
// 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)
});
@@ -1248,9 +1249,10 @@ mod tests {
let server_id_1 = LanguageServerId(100);
let server_id_2 = LanguageServerId(101);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
- let view = cx.add_view(window_id, |cx| {
+ let view = window.add_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});
@@ -6,7 +6,7 @@ use gpui::{
geometry::{rect::RectF, vector::Vector2F},
platform::{CursorStyle, MouseButton},
scene::{MouseDown, MouseDrag},
- AnyElement, Element, View, ViewContext, WeakViewHandle, WindowContext,
+ AnyElement, AnyWindowHandle, Element, View, ViewContext, WeakViewHandle, WindowContext,
};
const DEAD_ZONE: f32 = 4.;
@@ -21,7 +21,7 @@ enum State<V: View> {
region: RectF,
},
Dragging {
- window_id: usize,
+ window: AnyWindowHandle,
position: Vector2F,
region_offset: Vector2F,
region: RectF,
@@ -49,14 +49,14 @@ impl<V: View> Clone for State<V> {
region,
},
State::Dragging {
- window_id,
+ window,
position,
region_offset,
region,
payload,
render,
} => Self::Dragging {
- window_id: window_id.clone(),
+ window: window.clone(),
position: position.clone(),
region_offset: region_offset.clone(),
region: region.clone(),
@@ -87,16 +87,16 @@ impl<V: View> DragAndDrop<V> {
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| {
if let State::Dragging {
position,
payload,
- window_id: window_dragged_from,
+ window: window_dragged_from,
..
} = state
{
- if &window_id != window_dragged_from {
+ if &window != window_dragged_from {
return None;
}
@@ -126,9 +126,9 @@ impl<V: View> DragAndDrop<V> {
cx: &mut WindowContext,
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| {
- this.notify_containers_for_window(window_id, cx);
+ this.notify_containers_for_window(window, cx);
match this.currently_dragged.as_ref() {
Some(&State::Down {
@@ -141,7 +141,7 @@ impl<V: View> DragAndDrop<V> {
}) => {
if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE {
this.currently_dragged = Some(State::Dragging {
- window_id,
+ window,
region_offset,
region,
position: event.position,
@@ -163,7 +163,7 @@ impl<V: View> DragAndDrop<V> {
..
}) => {
this.currently_dragged = Some(State::Dragging {
- window_id,
+ window,
region_offset,
region,
position: event.position,
@@ -188,14 +188,14 @@ impl<V: View> DragAndDrop<V> {
State::Down { .. } => None,
State::DeadZone { .. } => None,
State::Dragging {
- window_id,
+ window,
region_offset,
position,
region,
payload,
render,
} => {
- if cx.window_id() != window_id {
+ if cx.window() != window {
return None;
}
@@ -260,27 +260,27 @@ impl<V: View> DragAndDrop<V> {
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
if let Some(State::Dragging {
- payload, window_id, ..
+ payload, window, ..
}) = &self.currently_dragged
{
if payload.is::<P>() {
- let window_id = *window_id;
+ let window = *window;
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) {
- if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
- self.notify_containers_for_window(window_id, cx);
+ if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() {
+ 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| {
if let Some(container) = container.upgrade(cx) {
- if container.window_id() == window_id {
+ if container.window() == window {
container.update(cx, |_, cx| cx.notify());
}
true
@@ -47,6 +47,7 @@ workspace = { path = "../workspace" }
aho-corasick = "0.7"
anyhow.workspace = true
+convert_case = "0.6.0"
futures.workspace = true
indoc = "1.0.4"
itertools = "0.10"
@@ -56,12 +57,12 @@ ordered-float.workspace = true
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
+rand.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
smol.workspace = true
-rand.workspace = true
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-html = { workspace = true, optional = true }
@@ -28,6 +28,7 @@ use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings};
use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
+use convert_case::{Case, Casing};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
@@ -89,7 +90,7 @@ use std::{
cmp::{self, Ordering, Reverse},
mem,
num::NonZeroU32,
- ops::{ControlFlow, Deref, DerefMut, Range},
+ ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
path::Path,
sync::Arc,
time::{Duration, Instant},
@@ -231,6 +232,13 @@ actions!(
SortLinesCaseInsensitive,
ReverseLines,
ShuffleLines,
+ ConvertToUpperCase,
+ ConvertToLowerCase,
+ ConvertToTitleCase,
+ ConvertToSnakeCase,
+ ConvertToKebabCase,
+ ConvertToUpperCamelCase,
+ ConvertToLowerCamelCase,
Transpose,
Cut,
Copy,
@@ -353,6 +361,13 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::sort_lines_case_insensitive);
cx.add_action(Editor::reverse_lines);
cx.add_action(Editor::shuffle_lines);
+ cx.add_action(Editor::convert_to_upper_case);
+ cx.add_action(Editor::convert_to_lower_case);
+ cx.add_action(Editor::convert_to_title_case);
+ cx.add_action(Editor::convert_to_snake_case);
+ cx.add_action(Editor::convert_to_kebab_case);
+ cx.add_action(Editor::convert_to_upper_camel_case);
+ cx.add_action(Editor::convert_to_lower_camel_case);
cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end);
@@ -4306,6 +4321,97 @@ impl Editor {
});
}
+ pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
+ self.manipulate_text(cx, |text| text.to_uppercase())
+ }
+
+ pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
+ self.manipulate_text(cx, |text| text.to_lowercase())
+ }
+
+ pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
+ self.manipulate_text(cx, |text| text.to_case(Case::Title))
+ }
+
+ pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
+ self.manipulate_text(cx, |text| text.to_case(Case::Snake))
+ }
+
+ pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
+ self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
+ }
+
+ pub fn convert_to_upper_camel_case(
+ &mut self,
+ _: &ConvertToUpperCamelCase,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.manipulate_text(cx, |text| text.to_case(Case::UpperCamel))
+ }
+
+ pub fn convert_to_lower_camel_case(
+ &mut self,
+ _: &ConvertToLowerCamelCase,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.manipulate_text(cx, |text| text.to_case(Case::Camel))
+ }
+
+ fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
+ where
+ Fn: FnMut(&str) -> String,
+ {
+ let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ let buffer = self.buffer.read(cx).snapshot(cx);
+
+ let mut new_selections = Vec::new();
+ let mut edits = Vec::new();
+ let mut selection_adjustment = 0i32;
+
+ for selection in self.selections.all::<usize>(cx) {
+ let selection_is_empty = selection.is_empty();
+
+ let (start, end) = if selection_is_empty {
+ let word_range = movement::surrounding_word(
+ &display_map,
+ selection.start.to_display_point(&display_map),
+ );
+ let start = word_range.start.to_offset(&display_map, Bias::Left);
+ let end = word_range.end.to_offset(&display_map, Bias::Left);
+ (start, end)
+ } else {
+ (selection.start, selection.end)
+ };
+
+ let text = buffer.text_for_range(start..end).collect::<String>();
+ let old_length = text.len() as i32;
+ let text = callback(&text);
+
+ new_selections.push(Selection {
+ start: (start as i32 - selection_adjustment) as usize,
+ end: ((start + text.len()) as i32 - selection_adjustment) as usize,
+ goal: SelectionGoal::None,
+ ..selection
+ });
+
+ selection_adjustment += old_length - text.len() as i32;
+
+ edits.push((start..end, text));
+ }
+
+ self.transact(cx, |this, cx| {
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.edit(edits, None, cx);
+ });
+
+ this.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select(new_selections);
+ });
+
+ this.request_autoscroll(Autoscroll::fit(), cx);
+ });
+ }
+
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
@@ -7443,6 +7549,78 @@ impl Editor {
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>(
&mut self,
ranges: Vec<Range<Anchor>>,
@@ -48,36 +48,40 @@ fn test_edit_events(cx: &mut TestAppContext) {
});
let events = Rc::new(RefCell::new(Vec::new()));
- let (_, editor1) = cx.add_window({
- let events = events.clone();
- |cx| {
- cx.subscribe(&cx.handle(), move |_, _, event, _| {
- if matches!(
- event,
- Event::Edited | Event::BufferEdited | Event::DirtyChanged
- ) {
- events.borrow_mut().push(("editor1", event.clone()));
- }
- })
- .detach();
- Editor::for_buffer(buffer.clone(), None, cx)
- }
- });
- let (_, editor2) = cx.add_window({
- let events = events.clone();
- |cx| {
- cx.subscribe(&cx.handle(), move |_, _, event, _| {
- if matches!(
- event,
- Event::Edited | Event::BufferEdited | Event::DirtyChanged
- ) {
- events.borrow_mut().push(("editor2", event.clone()));
- }
- })
- .detach();
- Editor::for_buffer(buffer.clone(), None, cx)
- }
- });
+ let editor1 = cx
+ .add_window({
+ let events = events.clone();
+ |cx| {
+ cx.subscribe(&cx.handle(), move |_, _, event, _| {
+ if matches!(
+ event,
+ Event::Edited | Event::BufferEdited | Event::DirtyChanged
+ ) {
+ events.borrow_mut().push(("editor1", event.clone()));
+ }
+ })
+ .detach();
+ Editor::for_buffer(buffer.clone(), None, cx)
+ }
+ })
+ .root(cx);
+ let editor2 = cx
+ .add_window({
+ let events = events.clone();
+ |cx| {
+ cx.subscribe(&cx.handle(), move |_, _, event, _| {
+ if matches!(
+ event,
+ Event::Edited | Event::BufferEdited | Event::DirtyChanged
+ ) {
+ events.borrow_mut().push(("editor2", event.clone()));
+ }
+ })
+ .detach();
+ Editor::for_buffer(buffer.clone(), None, cx)
+ }
+ })
+ .root(cx);
assert_eq!(mem::take(&mut *events.borrow_mut()), []);
// Mutating editor 1 will emit an `Edited` event only for that editor.
@@ -173,7 +177,9 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
+ let editor = cx
+ .add_window(|cx| build_editor(buffer.clone(), cx))
+ .root(cx);
editor.update(cx, |editor, cx| {
editor.start_transaction_at(now, cx);
@@ -343,10 +349,12 @@ fn test_ime_composition(cx: &mut TestAppContext) {
fn test_selection_with_mouse(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
- build_editor(buffer, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
editor.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
});
@@ -410,10 +418,12 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
fn test_canceling_pending_selection(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
@@ -456,10 +466,12 @@ fn test_clone(cx: &mut TestAppContext) {
true,
);
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&text, cx);
- build_editor(buffer, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&text, cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
@@ -473,9 +485,11 @@ fn test_clone(cx: &mut TestAppContext) {
);
});
- let (_, cloned_editor) = editor.update(cx, |editor, cx| {
- cx.add_window(Default::default(), |cx| editor.clone(cx))
- });
+ let cloned_editor = editor
+ .update(cx, |editor, cx| {
+ cx.add_window(Default::default(), |cx| editor.clone(cx))
+ })
+ .root(cx);
let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
@@ -509,9 +523,10 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let 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 mut editor = build_editor(buffer.clone(), cx);
let handle = cx.handle();
@@ -618,10 +633,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
fn test_cancel(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
@@ -661,9 +678,10 @@ fn test_cancel(cx: &mut TestAppContext) {
fn test_fold_action(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(
- &"
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(
+ &"
impl Foo {
// Hello!
@@ -680,11 +698,12 @@ fn test_fold_action(cx: &mut TestAppContext) {
}
}
"
- .unindent(),
- cx,
- );
- build_editor(buffer.clone(), cx)
- });
+ .unindent(),
+ cx,
+ );
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -752,7 +771,9 @@ fn test_move_cursor(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
- let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
+ let view = cx
+ .add_window(|cx| build_editor(buffer.clone(), cx))
+ .root(cx);
buffer.update(cx, |buffer, cx| {
buffer.edit(
@@ -827,10 +848,12 @@ fn test_move_cursor(cx: &mut TestAppContext) {
fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
- build_editor(buffer.clone(), cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
assert_eq!('ⓐ'.len_utf8(), 3);
assert_eq!('α'.len_utf8(), 2);
@@ -932,10 +955,12 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
- build_editor(buffer.clone(), cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
@@ -982,10 +1007,12 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
fn test_beginning_end_of_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\n def", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\n def", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -1145,10 +1172,12 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -1197,10 +1226,13 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer =
+ MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.set_wrap_width(Some(140.), cx);
@@ -1257,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 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(
&r#"ˇone
@@ -1368,7 +1401,8 @@ async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
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(
&r#"ˇone
@@ -1406,7 +1440,8 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
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(
&r#"
@@ -1530,10 +1565,12 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("one two three four", cx);
- build_editor(buffer.clone(), cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("one two three four", cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -1566,10 +1603,12 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
fn test_newline(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
- build_editor(buffer.clone(), cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -1589,9 +1628,10 @@ fn test_newline(cx: &mut TestAppContext) {
fn test_newline_with_old_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(
- "
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(
+ "
a
b(
X
@@ -1600,19 +1640,20 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) {
X
)
"
- .unindent()
- .as_str(),
- cx,
- );
- let mut editor = build_editor(buffer.clone(), cx);
- editor.change_selections(None, cx, |s| {
- s.select_ranges([
- Point::new(2, 4)..Point::new(2, 5),
- Point::new(5, 4)..Point::new(5, 5),
- ])
- });
- editor
- });
+ .unindent()
+ .as_str(),
+ cx,
+ );
+ let mut editor = build_editor(buffer.clone(), cx);
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([
+ Point::new(2, 4)..Point::new(2, 5),
+ Point::new(5, 4)..Point::new(5, 5),
+ ])
+ });
+ editor
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
// Edit the buffer directly, deleting ranges surrounding the editor's selections
@@ -1817,12 +1858,14 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
fn test_insert_with_old_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
- let mut editor = build_editor(buffer.clone(), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
- editor
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
+ let mut editor = build_editor(buffer.clone(), cx);
+ editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
+ editor
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
// Edit the buffer directly, deleting ranges surrounding the editor's selections
@@ -2329,10 +2372,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
fn test_delete_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2352,10 +2397,12 @@ fn test_delete_line(cx: &mut TestAppContext) {
);
});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
@@ -2650,14 +2697,94 @@ async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
"});
}
+#[gpui::test]
+async fn test_manipulate_text(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ // Test convert_to_upper_case()
+ cx.set_state(indoc! {"
+ «hello worldˇ»
+ "});
+ cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «HELLO WORLDˇ»
+ "});
+
+ // Test convert_to_lower_case()
+ cx.set_state(indoc! {"
+ «HELLO WORLDˇ»
+ "});
+ cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «hello worldˇ»
+ "});
+
+ // From here on out, test more complex cases of manipulate_text()
+
+ // Test no selection case - should affect words cursors are in
+ // Cursor at beginning, middle, and end of word
+ cx.set_state(indoc! {"
+ ˇhello big beauˇtiful worldˇ
+ "});
+ cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
+ "});
+
+ // Test multiple selections on a single line and across multiple lines
+ cx.set_state(indoc! {"
+ «Theˇ» quick «brown
+ foxˇ» jumps «overˇ»
+ the «lazyˇ» dog
+ "});
+ cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «THEˇ» quick «BROWN
+ FOXˇ» jumps «OVERˇ»
+ the «LAZYˇ» dog
+ "});
+
+ // Test case where text length grows
+ cx.set_state(indoc! {"
+ «tschüߡ»
+ "});
+ cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «TSCHÜSSˇ»
+ "});
+
+ // Test to make sure we don't crash when text shrinks
+ cx.set_state(indoc! {"
+ aaa_bbbˇ
+ "});
+ cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «aaaBbbˇ»
+ "});
+
+ // Test to make sure we all aware of the fact that each word can grow and shrink
+ // Final selections should be aware of this fact
+ cx.set_state(indoc! {"
+ aaa_bˇbb bbˇb_ccc ˇccc_ddd
+ "});
+ cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
+ "});
+}
+
#[gpui::test]
fn test_duplicate_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2680,10 +2807,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
);
});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2707,10 +2836,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
fn test_move_line_up_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.fold_ranges(
vec![
@@ -2806,10 +2937,12 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
- build_editor(buffer, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer.read(cx).snapshot(cx);
editor.insert_blocks(
@@ -2834,102 +2967,94 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
fn test_transpose(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- _ = cx
- .add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
+ _ = cx.add_window(|cx| {
+ let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bac");
- assert_eq!(editor.selections.ranges(cx), [2..2]);
+ editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bac");
+ assert_eq!(editor.selections.ranges(cx), [2..2]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bca");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bca");
+ assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bac");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bac");
+ assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor
- })
- .1;
+ editor
+ });
- _ = cx
- .add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+ _ = cx.add_window(|cx| {
+ let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acb\nde");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+ editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "acb\nde");
+ assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbd\ne");
- assert_eq!(editor.selections.ranges(cx), [5..5]);
+ editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "acbd\ne");
+ assert_eq!(editor.selections.ranges(cx), [5..5]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbde\n");
- assert_eq!(editor.selections.ranges(cx), [6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "acbde\n");
+ assert_eq!(editor.selections.ranges(cx), [6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbd\ne");
- assert_eq!(editor.selections.ranges(cx), [6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "acbd\ne");
+ assert_eq!(editor.selections.ranges(cx), [6..6]);
- editor
- })
- .1;
+ editor
+ });
- _ = cx
- .add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+ _ = cx.add_window(|cx| {
+ let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bacd\ne");
- assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
+ editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bacd\ne");
+ assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcade\n");
- assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bcade\n");
+ assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcda\ne");
- assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bcda\ne");
+ assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcade\n");
- assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bcade\n");
+ assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcaed\n");
- assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bcaed\n");
+ assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
- editor
- })
- .1;
+ editor
+ });
- _ = cx
- .add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
+ _ = cx.add_window(|cx| {
+ let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "🏀🍐✋");
- assert_eq!(editor.selections.ranges(cx), [8..8]);
+ editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "🏀🍐✋");
+ assert_eq!(editor.selections.ranges(cx), [8..8]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "🏀✋🍐");
- assert_eq!(editor.selections.ranges(cx), [11..11]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "🏀✋🍐");
+ assert_eq!(editor.selections.ranges(cx), [11..11]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "🏀🍐✋");
- assert_eq!(editor.selections.ranges(cx), [11..11]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "🏀🍐✋");
+ assert_eq!(editor.selections.ranges(cx), [11..11]);
- editor
- })
- .1;
+ editor
+ });
}
#[gpui::test]
@@ -3132,10 +3257,12 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
fn test_select_all(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.select_all(&SelectAll, cx);
assert_eq!(
@@ -3149,10 +3276,12 @@ fn test_select_all(cx: &mut TestAppContext) {
fn test_select_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -3196,10 +3325,12 @@ fn test_select_line(cx: &mut TestAppContext) {
fn test_split_selection_into_lines(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.fold_ranges(
vec![
@@ -3267,10 +3398,12 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
fn test_add_selection_above_below(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -3555,7 +3688,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+ let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@@ -3718,7 +3851,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor
.condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
.await;
@@ -4281,7 +4414,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+ let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@@ -4429,7 +4562,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor
.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@@ -4519,7 +4652,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) {
);
let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| {
let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
@@ -4649,7 +4782,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
@@ -4761,7 +4894,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
@@ -4875,7 +5008,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
let format = editor.update(cx, |editor, cx| {
@@ -5653,7 +5786,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
multibuffer
});
- let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
+ let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
view.update(cx, |view, cx| {
assert_eq!(view.text(cx), "aaaa\nbbbb");
view.change_selections(None, cx, |s| {
@@ -5723,7 +5856,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
multibuffer
});
- let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
+ let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
view.update(cx, |view, cx| {
let (expected_text, selection_ranges) = marked_text_ranges(
indoc! {"
@@ -5799,22 +5932,24 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
multibuffer
});
- let (_, editor) = cx.add_window(|cx| {
- let mut editor = build_editor(multibuffer.clone(), cx);
- let snapshot = editor.snapshot(cx);
- editor.change_selections(None, cx, |s| {
- s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
- });
- editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
- assert_eq!(
- editor.selections.ranges(cx),
- [
- Point::new(1, 3)..Point::new(1, 3),
- Point::new(2, 1)..Point::new(2, 1),
- ]
- );
- editor
- });
+ let editor = cx
+ .add_window(|cx| {
+ let mut editor = build_editor(multibuffer.clone(), cx);
+ let snapshot = editor.snapshot(cx);
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
+ });
+ editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
+ assert_eq!(
+ editor.selections.ranges(cx),
+ [
+ Point::new(1, 3)..Point::new(1, 3),
+ Point::new(2, 1)..Point::new(2, 1),
+ ]
+ );
+ editor
+ })
+ .root(cx);
// Refreshing selections is a no-op when excerpts haven't changed.
editor.update(cx, |editor, cx| {
@@ -5884,16 +6019,18 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
multibuffer
});
- let (_, editor) = cx.add_window(|cx| {
- let mut editor = build_editor(multibuffer.clone(), cx);
- let snapshot = editor.snapshot(cx);
- editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
- assert_eq!(
- editor.selections.ranges(cx),
- [Point::new(1, 3)..Point::new(1, 3)]
- );
- editor
- });
+ let editor = cx
+ .add_window(|cx| {
+ let mut editor = build_editor(multibuffer.clone(), cx);
+ let snapshot = editor.snapshot(cx);
+ editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
+ assert_eq!(
+ editor.selections.ranges(cx),
+ [Point::new(1, 3)..Point::new(1, 3)]
+ );
+ editor
+ })
+ .root(cx);
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
@@ -32,7 +32,7 @@ use gpui::{
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache},
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 json::json;
@@ -1107,8 +1107,6 @@ impl EditorElement {
if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min();
let end_anchor = Anchor::max();
- let mut start_row = None;
- let mut end_row = None;
let color = scrollbar_theme.selections;
let border = Border {
width: 1.,
@@ -1119,54 +1117,32 @@ impl EditorElement {
bottom: false,
left: true,
};
- let mut push_region = |start, end| {
- if let (Some(start_display), Some(end_display)) = (start, end) {
- let start_y = y_for_row(start_display as f32);
- let mut end_y = y_for_row(end_display as f32);
- if 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 mut push_region = |start: DisplayPoint, end: DisplayPoint| {
+ let start_y = y_for_row(start.row() as f32);
+ let mut end_y = y_for_row(end.row() as f32);
+ if 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,
+ })
};
- for (row, _) in &editor
- .background_highlights_in_range_for::<crate::items::BufferSearchHighlights>(
+ let background_ranges = editor
+ .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
start_anchor..end_anchor,
&layout.position_map.snapshot,
- &theme,
- )
- {
- let start_display = row.start;
- let end_display = row.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!();
- }
+ 50000,
+ );
+ for row in background_ranges {
+ let start = row.start();
+ let end = row.end();
+ push_region(*start, *end);
}
- // 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 {
@@ -2479,7 +2455,7 @@ impl Element<Editor> for EditorElement {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
editor: &mut Editor,
- cx: &mut ViewContext<Editor>,
+ cx: &mut PaintContext<Editor>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
@@ -3002,10 +2978,12 @@ mod tests {
fn test_layout_line_numbers(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
- Editor::new(EditorMode::Full, buffer, None, None, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
+ Editor::new(EditorMode::Full, buffer, None, None, cx)
+ })
+ .root(cx);
let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let layouts = editor.update(cx, |editor, cx| {
@@ -3021,10 +2999,12 @@ mod tests {
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("", cx);
- Editor::new(EditorMode::Full, buffer, None, None, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("", cx);
+ Editor::new(EditorMode::Full, buffer, None, None, cx)
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
editor.set_placeholder_text("hello", cx);
@@ -3075,7 +3055,14 @@ mod tests {
let mut scene = SceneBuilder::new(1.0);
let bounds = RectF::new(Default::default(), size);
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),
+ );
});
}
@@ -3231,10 +3218,12 @@ mod tests {
info!(
"Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
);
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&input_text, cx);
- Editor::new(editor_mode, buffer, None, None, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&input_text, cx);
+ Editor::new(editor_mode, buffer, None, None, cx)
+ })
+ .root(cx);
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, layout_state) = editor.update(cx, |editor, cx| {
@@ -1135,7 +1135,9 @@ mod tests {
)
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -1835,7 +1837,9 @@ mod tests {
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -1988,7 +1992,9 @@ mod tests {
project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language))
});
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -2074,8 +2080,9 @@ mod tests {
deterministic.run_until_parked();
cx.foreground().run_until_parked();
- let (_, editor) =
- cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+ let editor = cx
+ .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
+ .root(cx);
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2327,7 +2334,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language))
});
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -2372,8 +2381,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
deterministic.run_until_parked();
cx.foreground().run_until_parked();
- let (_, editor) =
- cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+ let editor = cx
+ .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
+ .root(cx);
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2561,7 +2571,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -28,7 +28,10 @@ use std::{
path::{Path, PathBuf},
};
use text::Selection;
-use util::{paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
+use util::{
+ paths::{PathExt, FILE_ROW_COLUMN_DELIMITER},
+ ResultExt, TryFutureExt,
+};
use workspace::item::{BreadcrumbText, FollowableItemHandle};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
@@ -546,9 +549,7 @@ impl Item for Editor {
.and_then(|f| f.as_local())?
.abs_path(cx);
- let file_path = util::paths::compact(&file_path)
- .to_string_lossy()
- .to_string();
+ let file_path = file_path.compact().to_string_lossy().to_string();
Some(file_path.into())
}
@@ -69,7 +69,8 @@ impl<'a> EditorLspTestContext<'a> {
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
.await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root", true, cx)
@@ -98,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> {
Self {
cx: EditorTestContext {
cx,
- window_id,
+ window: window.into(),
editor,
},
lsp,
@@ -3,7 +3,8 @@ use crate::{
};
use futures::Future;
use gpui::{
- keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
+ keymap_matcher::Keystroke, AnyWindowHandle, AppContext, ContextHandle, ModelContext,
+ ViewContext, ViewHandle,
};
use indoc::indoc;
use language::{Buffer, BufferSnapshot};
@@ -21,7 +22,7 @@ use super::build_editor;
pub struct EditorTestContext<'a> {
pub cx: &'a mut gpui::TestAppContext,
- pub window_id: usize,
+ pub window: AnyWindowHandle,
pub editor: ViewHandle<Editor>,
}
@@ -32,16 +33,14 @@ impl<'a> EditorTestContext<'a> {
let buffer = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.unwrap();
- let (window_id, editor) = cx.update(|cx| {
- cx.add_window(Default::default(), |cx| {
- cx.focus_self();
- build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
- })
+ let window = cx.add_window(|cx| {
+ cx.focus_self();
+ build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
});
-
+ let editor = window.root(cx);
Self {
cx,
- window_id,
+ window: window.into(),
editor,
}
}
@@ -113,7 +112,8 @@ impl<'a> EditorTestContext<'a> {
let keystroke_under_test_handle =
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
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
}
@@ -617,8 +617,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- cx.dispatch_action(window_id, Toggle);
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
+ cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
@@ -631,8 +632,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window_id, SelectNext);
- cx.dispatch_action(window_id, Confirm);
+ cx.dispatch_action(window.into(), SelectNext);
+ cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -671,8 +672,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- cx.dispatch_action(window_id, Toggle);
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
+ cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3];
@@ -704,8 +706,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window_id, SelectNext);
- cx.dispatch_action(window_id, Confirm);
+ cx.dispatch_action(window.into(), SelectNext);
+ cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -754,8 +756,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- cx.dispatch_action(window_id, Toggle);
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
+ cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3];
@@ -787,8 +790,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window_id, SelectNext);
- cx.dispatch_action(window_id, Confirm);
+ cx.dispatch_action(window.into(), SelectNext);
+ cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -837,19 +840,23 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
let query = test_path_like("hi");
finder
@@ -931,19 +938,23 @@ mod tests {
cx,
)
.await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("hi"), cx)
@@ -967,19 +978,23 @@ mod tests {
cx,
)
.await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@@ -1015,61 +1030,6 @@ mod tests {
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
}
- #[gpui::test]
- async fn test_multiple_matches_with_same_relative_path(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/root",
- json!({
- "dir1": { "a.txt": "" },
- "dir2": { "a.txt": "" }
- }),
- )
- .await;
-
- let project = Project::test(
- app_state.fs.clone(),
- ["/root/dir1".as_ref(), "/root/dir2".as_ref()],
- cx,
- )
- .await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
- cx,
- ),
- cx,
- )
- });
-
- // Run a search that matches two files with the same relative path.
- finder
- .update(cx, |f, cx| {
- f.delegate_mut().spawn_search(test_path_like("a.t"), cx)
- })
- .await;
-
- // Can switch between different matches with the same relative path.
- finder.update(cx, |finder, cx| {
- let delegate = finder.delegate_mut();
- assert_eq!(delegate.matches.len(), 2);
- assert_eq!(delegate.selected_index(), 0);
- delegate.set_selected_index(1, cx);
- assert_eq!(delegate.selected_index(), 1);
- delegate.set_selected_index(0, cx);
- assert_eq!(delegate.selected_index(), 0);
- });
- }
-
#[gpui::test]
async fn test_path_distance_ordering(cx: &mut TestAppContext) {
let app_state = init_test(cx);
@@ -1089,7 +1049,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@@ -1103,18 +1065,20 @@ mod tests {
worktree_id,
path: Arc::from(Path::new("/root/dir2/b.txt")),
}));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- b_path,
- Vec::new(),
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ b_path,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
finder
.update(cx, |f, cx| {
@@ -1151,19 +1115,23 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("dir"), cx)
@@ -1198,7 +1166,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@@ -1216,7 +1185,7 @@ mod tests {
"fir",
1,
"first.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1231,7 +1200,7 @@ mod tests {
"sec",
1,
"second.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1253,7 +1222,7 @@ mod tests {
"thi",
1,
"third.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1285,7 +1254,7 @@ mod tests {
"sec",
1,
"second.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1324,7 +1293,7 @@ mod tests {
"thi",
1,
"third.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1404,7 +1373,8 @@ mod tests {
.detach();
deterministic.run_until_parked();
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1,);
@@ -1439,7 +1409,7 @@ mod tests {
"sec",
1,
"second.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1461,7 +1431,7 @@ mod tests {
"fir",
1,
"first.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1493,12 +1463,12 @@ mod tests {
input: &str,
expected_matches: usize,
expected_editor_title: &str,
- window_id: usize,
+ window: gpui::AnyWindowHandle,
workspace: &ViewHandle<Workspace>,
deterministic: &gpui::executor::Deterministic,
cx: &mut gpui::TestAppContext,
) -> Vec<FoundPath> {
- cx.dispatch_action(window_id, Toggle);
+ cx.dispatch_action(window, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
.update(cx, |finder, cx| {
@@ -1515,8 +1485,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window_id, SelectNext);
- cx.dispatch_action(window_id, Confirm);
+ cx.dispatch_action(window, SelectNext);
+ cx.dispatch_action(window, Confirm);
deterministic.run_until_parked();
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
@@ -135,7 +135,7 @@ impl Entity for GoToLine {
fn release(&mut self, cx: &mut AppContext) {
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| {
editor.highlight_rows(None);
if let Some(scroll_position) = scroll_position {
@@ -22,6 +22,7 @@ sqlez = { path = "../sqlez" }
async-task = "4.0.3"
backtrace = { version = "0.3", optional = true }
ctor.workspace = true
+derive_more.workspace = true
dhat = { version = "0.3", optional = true }
env_logger = { version = "0.9", optional = true }
etagere = "0.2"
@@ -23,6 +23,8 @@ use std::{
};
use anyhow::{anyhow, Context, Result};
+
+use derive_more::Deref;
use parking_lot::Mutex;
use postage::oneshot;
use smallvec::SmallVec;
@@ -44,6 +46,7 @@ use window_input_handler::WindowInputHandler;
use crate::{
elements::{AnyElement, AnyRootElement, RootElement},
executor::{self, Task},
+ fonts::TextStyle,
json,
keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
platform::{
@@ -130,8 +133,20 @@ pub trait BorrowAppContext {
}
pub trait BorrowWindowContext {
- fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T;
- fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T;
+ type Result<T>;
+
+ fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
+ where
+ F: FnOnce(&WindowContext) -> T;
+ fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+ where
+ F: FnOnce(&WindowContext) -> Option<T>;
+ fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
+ where
+ F: FnOnce(&mut WindowContext) -> T;
+ fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
+ where
+ F: FnOnce(&mut WindowContext) -> Option<T>;
}
#[derive(Clone)]
@@ -294,13 +309,12 @@ impl App {
result
}
- fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- window_id: usize,
- callback: F,
- ) -> Option<T> {
+ fn update_window<T, F>(&mut self, window: AnyWindowHandle, callback: F) -> Option<T>
+ where
+ F: FnOnce(&mut WindowContext) -> T,
+ {
let mut state = self.0.borrow_mut();
- let result = state.update_window(window_id, callback);
+ let result = state.update_window(window, callback);
state.pending_notifications.clear();
result
}
@@ -327,67 +341,8 @@ impl AsyncAppContext {
self.0.borrow_mut().update(callback)
}
- pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
- &self,
- window_id: usize,
- callback: F,
- ) -> Option<T> {
- self.0.borrow_mut().read_window(window_id, callback)
- }
-
- pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- window_id: usize,
- callback: F,
- ) -> Option<T> {
- self.0.borrow_mut().update_window(window_id, callback)
- }
-
- pub fn debug_elements(&self, window_id: usize) -> Option<json::Value> {
- self.0.borrow().read_window(window_id, |cx| {
- let root_view = cx.window.root_view();
- let root_element = cx.window.rendered_views.get(&root_view.id())?;
- root_element.debug(cx).log_err()
- })?
- }
-
- pub fn dispatch_action(
- &mut self,
- window_id: usize,
- view_id: usize,
- action: &dyn Action,
- ) -> Result<()> {
- self.0
- .borrow_mut()
- .update_window(window_id, |window| {
- window.dispatch_action(Some(view_id), action);
- })
- .ok_or_else(|| anyhow!("window not found"))
- }
-
- pub fn available_actions(
- &self,
- window_id: usize,
- view_id: usize,
- ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
- self.read_window(window_id, |cx| cx.available_actions(view_id))
- .unwrap_or_default()
- }
-
- pub fn has_window(&self, window_id: usize) -> bool {
- self.read(|cx| cx.windows.contains_key(&window_id))
- }
-
- pub fn window_is_active(&self, window_id: usize) -> bool {
- self.read(|cx| cx.windows.get(&window_id).map_or(false, |w| w.is_active))
- }
-
- pub fn root_view(&self, window_id: usize) -> Option<AnyViewHandle> {
- self.read(|cx| cx.windows.get(&window_id).map(|w| w.root_view().clone()))
- }
-
- pub fn window_ids(&self) -> Vec<usize> {
- self.read(|cx| cx.windows.keys().copied().collect())
+ pub fn windows(&self) -> Vec<AnyWindowHandle> {
+ self.0.borrow().windows().collect()
}
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
@@ -402,7 +357,7 @@ impl AsyncAppContext {
&mut self,
window_options: WindowOptions,
build_root_view: F,
- ) -> (usize, ViewHandle<T>)
+ ) -> WindowHandle<T>
where
T: View,
F: FnOnce(&mut ViewContext<T>) -> T,
@@ -410,25 +365,6 @@ impl AsyncAppContext {
self.update(|cx| cx.add_window(window_options, build_root_view))
}
- pub fn remove_window(&mut self, window_id: usize) {
- self.update_window(window_id, |cx| cx.remove_window());
- }
-
- pub fn activate_window(&mut self, window_id: usize) {
- self.update_window(window_id, |cx| cx.activate_window());
- }
-
- // TODO: Can we eliminate this method and move it to WindowContext then call it with update_window?s
- pub fn prompt(
- &mut self,
- window_id: usize,
- level: PromptLevel,
- msg: &str,
- answers: &[&str],
- ) -> Option<oneshot::Receiver<usize>> {
- self.update_window(window_id, |cx| cx.prompt(level, msg, answers))
- }
-
pub fn platform(&self) -> Arc<dyn Platform> {
self.0.borrow().platform().clone()
}
@@ -452,6 +388,42 @@ impl BorrowAppContext for AsyncAppContext {
}
}
+impl BorrowWindowContext for AsyncAppContext {
+ type Result<T> = Option<T>;
+
+ fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
+ where
+ F: FnOnce(&WindowContext) -> T,
+ {
+ self.0.borrow().read_with(|cx| cx.read_window(window, f))
+ }
+
+ fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+ where
+ F: FnOnce(&WindowContext) -> Option<T>,
+ {
+ self.0
+ .borrow_mut()
+ .update(|cx| cx.read_window_optional(window, f))
+ }
+
+ fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
+ where
+ F: FnOnce(&mut WindowContext) -> T,
+ {
+ self.0.borrow_mut().update(|cx| cx.update_window(window, f))
+ }
+
+ fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
+ where
+ F: FnOnce(&mut WindowContext) -> Option<T>,
+ {
+ self.0
+ .borrow_mut()
+ .update(|cx| cx.update_window_optional(window, f))
+ }
+}
+
type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
@@ -473,9 +445,9 @@ type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut AppContext) -> b
pub struct AppContext {
models: HashMap<usize, Box<dyn AnyModel>>,
- views: HashMap<(usize, usize), Box<dyn AnyView>>,
- views_metadata: HashMap<(usize, usize), ViewMetadata>,
- windows: HashMap<usize, Window>,
+ views: HashMap<(AnyWindowHandle, usize), Box<dyn AnyView>>,
+ views_metadata: HashMap<(AnyWindowHandle, usize), ViewMetadata>,
+ windows: HashMap<AnyWindowHandle, Window>,
globals: HashMap<TypeId, Box<dyn Any>>,
element_states: HashMap<ElementStateId, Box<dyn Any>>,
background: Arc<executor::Background>,
@@ -494,8 +466,8 @@ pub struct AppContext {
// Action Types -> Action Handlers
global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
keystroke_matcher: KeymapMatcher,
- next_entity_id: usize,
- next_window_id: usize,
+ next_id: usize,
+ // next_window: AnyWindowHandle,
next_subscription_id: usize,
frame_count: usize,
@@ -506,10 +478,10 @@ pub struct AppContext {
focus_observations: CallbackCollection<usize, FocusObservationCallback>,
release_observations: CallbackCollection<usize, ReleaseObservationCallback>,
action_dispatch_observations: CallbackCollection<(), ActionObservationCallback>,
- window_activation_observations: CallbackCollection<usize, WindowActivationCallback>,
- window_fullscreen_observations: CallbackCollection<usize, WindowFullscreenCallback>,
- window_bounds_observations: CallbackCollection<usize, WindowBoundsCallback>,
- keystroke_observations: CallbackCollection<usize, KeystrokeCallback>,
+ window_activation_observations: CallbackCollection<AnyWindowHandle, WindowActivationCallback>,
+ window_fullscreen_observations: CallbackCollection<AnyWindowHandle, WindowFullscreenCallback>,
+ window_bounds_observations: CallbackCollection<AnyWindowHandle, WindowBoundsCallback>,
+ keystroke_observations: CallbackCollection<AnyWindowHandle, KeystrokeCallback>,
active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>,
foreground: Rc<executor::Foreground>,
@@ -554,8 +526,7 @@ impl AppContext {
actions: Default::default(),
global_actions: Default::default(),
keystroke_matcher: KeymapMatcher::default(),
- next_entity_id: 0,
- next_window_id: 0,
+ next_id: 0,
next_subscription_id: 0,
frame_count: 0,
subscriptions: Default::default(),
@@ -756,13 +727,13 @@ impl AppContext {
}
}
- pub fn view_ui_name(&self, window_id: usize, view_id: usize) -> Option<&'static str> {
- Some(self.views.get(&(window_id, view_id))?.ui_name())
+ pub fn view_ui_name(&self, window: AnyWindowHandle, view_id: usize) -> Option<&'static str> {
+ Some(self.views.get(&(window, view_id))?.ui_name())
}
- pub fn view_type_id(&self, window_id: usize, view_id: usize) -> Option<TypeId> {
+ pub fn view_type_id(&self, window: AnyWindowHandle, view_id: usize) -> Option<TypeId> {
self.views_metadata
- .get(&(window_id, view_id))
+ .get(&(window, view_id))
.map(|metadata| metadata.type_id)
}
@@ -783,39 +754,22 @@ impl AppContext {
result
}
- pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
+ fn read_window<T, F: FnOnce(&WindowContext) -> T>(
&self,
- window_id: usize,
+ handle: AnyWindowHandle,
callback: F,
) -> Option<T> {
- let window = self.windows.get(&window_id)?;
- let window_context = WindowContext::immutable(self, &window, window_id);
+ let window = self.windows.get(&handle)?;
+ let window_context = WindowContext::immutable(self, &window, handle);
Some(callback(&window_context))
}
- pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- window_id: usize,
- callback: F,
- ) -> Option<T> {
- self.update(|app_context| {
- let mut window = app_context.windows.remove(&window_id)?;
- let mut window_context = WindowContext::mutable(app_context, &mut window, window_id);
- let result = callback(&mut window_context);
- if !window_context.removed {
- app_context.windows.insert(window_id, window);
- }
- Some(result)
- })
- }
-
pub fn update_active_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
callback: F,
) -> Option<T> {
- self.platform
- .main_window_id()
- .and_then(|id| self.update_window(id, callback))
+ self.active_window()
+ .and_then(|window| window.update(self, callback))
}
pub fn prompt_for_paths(
@@ -1053,10 +1007,10 @@ impl AppContext {
}
}
- fn notify_view(&mut self, window_id: usize, view_id: usize) {
+ fn notify_view(&mut self, window: AnyWindowHandle, view_id: usize) {
if self.pending_notifications.insert(view_id) {
self.pending_effects
- .push_back(Effect::ViewNotification { window_id, view_id });
+ .push_back(Effect::ViewNotification { window, view_id });
}
}
@@ -1074,13 +1028,13 @@ impl AppContext {
pub fn is_action_available(&self, action: &dyn Action) -> bool {
let mut available_in_window = false;
let action_id = action.id();
- if let Some(window_id) = self.platform.main_window_id() {
+ if let Some(window) = self.active_window() {
available_in_window = self
- .read_window(window_id, |cx| {
+ .read_window(window, |cx| {
if let Some(focused_view_id) = cx.focused_view_id() {
for view_id in cx.ancestors(focused_view_id) {
if let Some(view_metadata) =
- cx.views_metadata.get(&(window_id, view_id))
+ cx.views_metadata.get(&(cx.window_handle, view_id))
{
if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
if actions.contains_key(&action_id) {
@@ -1226,7 +1180,7 @@ impl AppContext {
F: FnOnce(&mut ModelContext<T>) -> T,
{
self.update(|this| {
- let model_id = post_inc(&mut this.next_entity_id);
+ let model_id = post_inc(&mut this.next_id);
let handle = ModelHandle::new(model_id, &this.ref_counts);
let mut cx = ModelContext::new(this, model_id);
let model = build_model(&mut cx);
@@ -1300,46 +1254,40 @@ impl AppContext {
&mut self,
window_options: WindowOptions,
build_root_view: F,
- ) -> (usize, ViewHandle<V>)
+ ) -> WindowHandle<V>
where
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
self.update(|this| {
- let window_id = post_inc(&mut this.next_window_id);
+ let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
let platform_window =
this.platform
- .open_window(window_id, window_options, this.foreground.clone());
- let window = this.build_window(window_id, platform_window, build_root_view);
- let root_view = window.root_view().clone().downcast::<V>().unwrap();
- this.windows.insert(window_id, window);
- (window_id, root_view)
+ .open_window(handle.into(), window_options, this.foreground.clone());
+ let window = this.build_window(handle.into(), platform_window, build_root_view);
+ this.windows.insert(handle.into(), window);
+ handle
})
}
- pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<V>)
+ pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
where
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
self.update(|this| {
- let window_id = post_inc(&mut this.next_window_id);
- let platform_window = this.platform.add_status_item(window_id);
- let window = this.build_window(window_id, platform_window, build_root_view);
- let root_view = window.root_view().clone().downcast::<V>().unwrap();
-
- this.windows.insert(window_id, window);
- this.update_window(window_id, |cx| {
- root_view.update(cx, |view, cx| view.focus_in(cx.handle().into_any(), cx))
- });
-
- (window_id, root_view)
+ let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
+ let platform_window = this.platform.add_status_item(handle.into());
+ let window = this.build_window(handle.into(), platform_window, build_root_view);
+ this.windows.insert(handle.into(), window);
+ handle.update_root(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
+ handle
})
}
pub fn build_window<V, F>(
&mut self,
- window_id: usize,
+ handle: AnyWindowHandle,
mut platform_window: Box<dyn platform::Window>,
build_root_view: F,
) -> Window
@@ -1351,7 +1299,7 @@ impl AppContext {
let mut app = self.upgrade();
platform_window.on_event(Box::new(move |event| {
- app.update_window(window_id, |cx| {
+ app.update_window(handle, |cx| {
if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
if cx.dispatch_keystroke(keystroke) {
return true;
@@ -1367,35 +1315,35 @@ impl AppContext {
{
let mut app = self.upgrade();
platform_window.on_active_status_change(Box::new(move |is_active| {
- app.update(|cx| cx.window_changed_active_status(window_id, is_active))
+ app.update(|cx| cx.window_changed_active_status(handle, is_active))
}));
}
{
let mut app = self.upgrade();
platform_window.on_resize(Box::new(move || {
- app.update(|cx| cx.window_was_resized(window_id))
+ app.update(|cx| cx.window_was_resized(handle))
}));
}
{
let mut app = self.upgrade();
platform_window.on_moved(Box::new(move || {
- app.update(|cx| cx.window_was_moved(window_id))
+ app.update(|cx| cx.window_was_moved(handle))
}));
}
{
let mut app = self.upgrade();
platform_window.on_fullscreen(Box::new(move |is_fullscreen| {
- app.update(|cx| cx.window_was_fullscreen_changed(window_id, is_fullscreen))
+ app.update(|cx| cx.window_was_fullscreen_changed(handle, is_fullscreen))
}));
}
{
let mut app = self.upgrade();
platform_window.on_close(Box::new(move || {
- app.update(|cx| cx.update_window(window_id, |cx| cx.remove_window()));
+ app.update(|cx| cx.update_window(handle, |cx| cx.remove_window()));
}));
}
@@ -1407,31 +1355,27 @@ impl AppContext {
platform_window.set_input_handler(Box::new(WindowInputHandler {
app: self.upgrade().0,
- window_id,
+ window: handle,
}));
- let mut window = Window::new(window_id, platform_window, self, build_root_view);
- let mut cx = WindowContext::mutable(self, &mut window, window_id);
+ let mut window = Window::new(handle, platform_window, self, build_root_view);
+ let mut cx = WindowContext::mutable(self, &mut window, handle);
cx.layout(false).expect("initial layout should not error");
let scene = cx.paint().expect("initial paint should not error");
window.platform_window.present_scene(scene);
window
}
- pub fn replace_root_view<V, F>(
- &mut self,
- window_id: usize,
- build_root_view: F,
- ) -> Option<ViewHandle<V>>
- where
- V: View,
- F: FnOnce(&mut ViewContext<V>) -> V,
- {
- self.update_window(window_id, |cx| cx.replace_root_view(build_root_view))
+ pub fn active_window(&self) -> Option<AnyWindowHandle> {
+ self.platform.main_window()
+ }
+
+ pub fn windows(&self) -> impl '_ + Iterator<Item = AnyWindowHandle> {
+ self.windows.keys().copied()
}
pub fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
- if let Some(view) = self.views.get(&(handle.window_id, handle.view_id)) {
+ if let Some(view) = self.views.get(&(handle.window, handle.view_id)) {
view.as_any().downcast_ref().expect("downcast is type safe")
} else {
panic!("circular view reference for type {}", type_name::<T>());
@@ -1441,7 +1385,7 @@ impl AppContext {
fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
if self.ref_counts.lock().is_entity_alive(handle.view_id) {
Some(ViewHandle::new(
- handle.window_id,
+ handle.window,
handle.view_id,
&self.ref_counts,
))
@@ -1453,7 +1397,7 @@ impl AppContext {
fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option<AnyViewHandle> {
if self.ref_counts.lock().is_entity_alive(handle.view_id) {
Some(AnyViewHandle::new(
- handle.window_id,
+ handle.window,
handle.view_id,
handle.view_type,
self.ref_counts.clone(),
@@ -1483,13 +1427,13 @@ impl AppContext {
.push_back(Effect::ModelRelease { model_id, model });
}
- for (window_id, view_id) in dropped_views {
+ for (window, view_id) in dropped_views {
self.subscriptions.remove(view_id);
self.observations.remove(view_id);
- self.views_metadata.remove(&(window_id, view_id));
- let mut view = self.views.remove(&(window_id, view_id)).unwrap();
+ self.views_metadata.remove(&(window, view_id));
+ let mut view = self.views.remove(&(window, view_id)).unwrap();
view.release(self);
- if let Some(window) = self.windows.get_mut(&window_id) {
+ if let Some(window) = self.windows.get_mut(&window) {
window.parents.remove(&view_id);
window
.invalidation
@@ -1517,7 +1461,7 @@ impl AppContext {
let mut refreshing = false;
let mut updated_windows = HashSet::default();
- let mut focus_effects = HashMap::<usize, FocusEffect>::default();
+ let mut focus_effects = HashMap::<AnyWindowHandle, FocusEffect>::default();
loop {
self.remove_dropped_entities();
if let Some(effect) = self.pending_effects.pop_front() {
@@ -1561,9 +1505,10 @@ impl AppContext {
observations.emit(model_id, |callback| callback(self));
}
- Effect::ViewNotification { window_id, view_id } => {
- self.handle_view_notification_effect(window_id, view_id)
- }
+ Effect::ViewNotification {
+ window: window_id,
+ view_id,
+ } => self.handle_view_notification_effect(window_id, view_id),
Effect::GlobalNotification { type_id } => {
let mut subscriptions = self.global_observations.clone();
@@ -1594,13 +1539,13 @@ impl AppContext {
Effect::Focus(mut effect) => {
if focus_effects
- .get(&effect.window_id())
+ .get(&effect.window())
.map_or(false, |prev_effect| prev_effect.is_forced())
{
effect.force();
}
- focus_effects.insert(effect.window_id(), effect);
+ focus_effects.insert(effect.window(), effect);
}
Effect::FocusObservation {
@@ -1615,42 +1560,38 @@ impl AppContext {
);
}
- Effect::ResizeWindow { window_id } => {
- if let Some(window) = self.windows.get_mut(&window_id) {
+ Effect::ResizeWindow { window } => {
+ if let Some(window) = self.windows.get_mut(&window) {
window
.invalidation
.get_or_insert(WindowInvalidation::default());
}
- self.handle_window_moved(window_id);
+ self.handle_window_moved(window);
}
- Effect::MoveWindow { window_id } => {
- self.handle_window_moved(window_id);
+ Effect::MoveWindow { window } => {
+ self.handle_window_moved(window);
}
Effect::WindowActivationObservation {
- window_id,
+ window,
subscription_id,
callback,
} => self.window_activation_observations.add_callback(
- window_id,
+ window,
subscription_id,
callback,
),
- Effect::ActivateWindow {
- window_id,
- is_active,
- } => {
- if self.handle_window_activation_effect(window_id, is_active)
- && is_active
+ Effect::ActivateWindow { window, is_active } => {
+ if self.handle_window_activation_effect(window, is_active) && is_active
{
focus_effects
- .entry(window_id)
+ .entry(window)
.or_insert_with(|| FocusEffect::View {
- window_id,
+ window,
view_id: self
- .read_window(window_id, |cx| cx.focused_view_id())
+ .read_window(window, |cx| cx.focused_view_id())
.flatten(),
is_forced: true,
})
@@ -1659,26 +1600,26 @@ impl AppContext {
}
Effect::WindowFullscreenObservation {
- window_id,
+ window,
subscription_id,
callback,
} => self.window_fullscreen_observations.add_callback(
- window_id,
+ window,
subscription_id,
callback,
),
Effect::FullscreenWindow {
- window_id,
+ window,
is_fullscreen,
- } => self.handle_fullscreen_effect(window_id, is_fullscreen),
+ } => self.handle_fullscreen_effect(window, is_fullscreen),
Effect::WindowBoundsObservation {
- window_id,
+ window,
subscription_id,
callback,
} => self.window_bounds_observations.add_callback(
- window_id,
+ window,
subscription_id,
callback,
),
@@ -1690,18 +1631,15 @@ impl AppContext {
Effect::ActionDispatchNotification { action_id } => {
self.handle_action_dispatch_notification_effect(action_id)
}
- Effect::WindowShouldCloseSubscription {
- window_id,
- callback,
- } => {
- self.handle_window_should_close_subscription_effect(window_id, callback)
+ Effect::WindowShouldCloseSubscription { window, callback } => {
+ self.handle_window_should_close_subscription_effect(window, callback)
}
Effect::Keystroke {
- window_id,
+ window,
keystroke,
handled_by,
result,
- } => self.handle_keystroke_effect(window_id, keystroke, handled_by, result),
+ } => self.handle_keystroke_effect(window, keystroke, handled_by, result),
Effect::ActiveLabeledTasksChanged => {
self.handle_active_labeled_tasks_changed_effect()
}
@@ -1716,8 +1654,8 @@ impl AppContext {
}
self.pending_notifications.clear();
} else {
- for window_id in self.windows.keys().cloned().collect::<Vec<_>>() {
- self.update_window(window_id, |cx| {
+ for window in self.windows().collect::<Vec<_>>() {
+ self.update_window(window, |cx| {
let invalidation = if refreshing {
let mut invalidation =
cx.window.invalidation.take().unwrap_or_default();
@@ -1733,7 +1671,7 @@ impl AppContext {
let appearance = cx.window.platform_window.appearance();
cx.invalidate(invalidation, appearance);
if let Some(old_parents) = cx.layout(refreshing).log_err() {
- updated_windows.insert(window_id);
+ updated_windows.insert(window);
if let Some(focused_view_id) = cx.focused_view_id() {
let old_ancestors = std::iter::successors(
@@ -1748,15 +1686,14 @@ impl AppContext {
for old_ancestor in old_ancestors.iter().copied() {
if !new_ancestors.contains(&old_ancestor) {
if let Some(mut view) =
- cx.views.remove(&(window_id, old_ancestor))
+ cx.views.remove(&(window, old_ancestor))
{
view.focus_out(
focused_view_id,
cx,
old_ancestor,
);
- cx.views
- .insert((window_id, old_ancestor), view);
+ cx.views.insert((window, old_ancestor), view);
}
}
}
@@ -1765,15 +1702,14 @@ impl AppContext {
for new_ancestor in new_ancestors.iter().copied() {
if !old_ancestors.contains(&new_ancestor) {
if let Some(mut view) =
- cx.views.remove(&(window_id, new_ancestor))
+ cx.views.remove(&(window, new_ancestor))
{
view.focus_in(
focused_view_id,
cx,
new_ancestor,
);
- cx.views
- .insert((window_id, new_ancestor), view);
+ cx.views.insert((window, new_ancestor), view);
}
}
}
@@ -1782,13 +1718,13 @@ impl AppContext {
// there isn't any pending focus, focus the root view.
let root_view_id = cx.window.root_view().id();
if focused_view_id != root_view_id
- && !cx.views.contains_key(&(window_id, focused_view_id))
- && !focus_effects.contains_key(&window_id)
+ && !cx.views.contains_key(&(window, focused_view_id))
+ && !focus_effects.contains_key(&window)
{
focus_effects.insert(
- window_id,
+ window,
FocusEffect::View {
- window_id,
+ window,
view_id: Some(root_view_id),
is_forced: false,
},
@@ -1809,8 +1745,8 @@ impl AppContext {
callback(self);
}
- for window_id in updated_windows.drain() {
- self.update_window(window_id, |cx| {
+ for window in updated_windows.drain() {
+ self.update_window(window, |cx| {
if let Some(scene) = cx.paint().log_err() {
cx.window.platform_window.present_scene(scene);
}
@@ -1831,39 +1767,37 @@ impl AppContext {
}
}
- fn window_was_resized(&mut self, window_id: usize) {
+ fn window_was_resized(&mut self, window: AnyWindowHandle) {
self.pending_effects
- .push_back(Effect::ResizeWindow { window_id });
+ .push_back(Effect::ResizeWindow { window });
}
- fn window_was_moved(&mut self, window_id: usize) {
+ fn window_was_moved(&mut self, window: AnyWindowHandle) {
self.pending_effects
- .push_back(Effect::MoveWindow { window_id });
+ .push_back(Effect::MoveWindow { window });
}
- fn window_was_fullscreen_changed(&mut self, window_id: usize, is_fullscreen: bool) {
+ fn window_was_fullscreen_changed(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
self.pending_effects.push_back(Effect::FullscreenWindow {
- window_id,
+ window,
is_fullscreen,
});
}
- fn window_changed_active_status(&mut self, window_id: usize, is_active: bool) {
- self.pending_effects.push_back(Effect::ActivateWindow {
- window_id,
- is_active,
- });
+ fn window_changed_active_status(&mut self, window: AnyWindowHandle, is_active: bool) {
+ self.pending_effects
+ .push_back(Effect::ActivateWindow { window, is_active });
}
fn keystroke(
&mut self,
- window_id: usize,
+ window: AnyWindowHandle,
keystroke: Keystroke,
handled_by: Option<Box<dyn Action>>,
result: MatchResult,
) {
self.pending_effects.push_back(Effect::Keystroke {
- window_id,
+ window,
keystroke,
handled_by,
result,
@@ -1886,16 +1820,16 @@ impl AppContext {
fn handle_view_notification_effect(
&mut self,
- observed_window_id: usize,
+ observed_window: AnyWindowHandle,
observed_view_id: usize,
) {
- let view_key = (observed_window_id, observed_view_id);
+ let view_key = (observed_window, observed_view_id);
if let Some((view, mut view_metadata)) = self
.views
.remove(&view_key)
.zip(self.views_metadata.remove(&view_key))
{
- if let Some(window) = self.windows.get_mut(&observed_window_id) {
+ if let Some(window) = self.windows.get_mut(&observed_window) {
window
.invalidation
.get_or_insert_with(Default::default)
@@ -1922,17 +1856,17 @@ impl AppContext {
})
}
- fn handle_fullscreen_effect(&mut self, window_id: usize, is_fullscreen: bool) {
- self.update_window(window_id, |cx| {
+ fn handle_fullscreen_effect(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
+ self.update_window(window, |cx| {
cx.window.is_fullscreen = is_fullscreen;
let mut fullscreen_observations = cx.window_fullscreen_observations.clone();
- fullscreen_observations.emit(window_id, |callback| callback(is_fullscreen, cx));
+ fullscreen_observations.emit(window, |callback| callback(is_fullscreen, cx));
if let Some(uuid) = cx.window_display_uuid() {
let bounds = cx.window_bounds();
let mut bounds_observations = cx.window_bounds_observations.clone();
- bounds_observations.emit(window_id, |callback| callback(bounds, uuid, cx));
+ bounds_observations.emit(window, |callback| callback(bounds, uuid, cx));
}
Some(())
@@ -1941,42 +1875,42 @@ impl AppContext {
fn handle_keystroke_effect(
&mut self,
- window_id: usize,
+ window: AnyWindowHandle,
keystroke: Keystroke,
handled_by: Option<Box<dyn Action>>,
result: MatchResult,
) {
- self.update_window(window_id, |cx| {
+ self.update_window(window, |cx| {
let mut observations = cx.keystroke_observations.clone();
- observations.emit(window_id, move |callback| {
+ observations.emit(window, move |callback| {
callback(&keystroke, &result, handled_by.as_ref(), cx)
});
});
}
- fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) -> bool {
- self.update_window(window_id, |cx| {
+ fn handle_window_activation_effect(&mut self, window: AnyWindowHandle, active: bool) -> bool {
+ self.update_window(window, |cx| {
if cx.window.is_active == active {
return false;
}
cx.window.is_active = active;
let mut observations = cx.window_activation_observations.clone();
- observations.emit(window_id, |callback| callback(active, cx));
+ observations.emit(window, |callback| callback(active, cx));
true
})
.unwrap_or(false)
}
fn handle_focus_effect(&mut self, effect: FocusEffect) {
- let window_id = effect.window_id();
- self.update_window(window_id, |cx| {
+ let window = effect.window();
+ self.update_window(window, |cx| {
// Ensure the newly-focused view still exists, otherwise focus
// the root view instead.
let focused_id = match effect {
FocusEffect::View { view_id, .. } => {
if let Some(view_id) = view_id {
- if cx.views.contains_key(&(window_id, view_id)) {
+ if cx.views.contains_key(&(window, view_id)) {
Some(view_id)
} else {
Some(cx.root_view().id())
@@ -2001,9 +1935,9 @@ impl AppContext {
if focus_changed {
if let Some(blurred_id) = blurred_id {
for view_id in cx.ancestors(blurred_id).collect::<Vec<_>>() {
- if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+ if let Some(mut view) = cx.views.remove(&(window, view_id)) {
view.focus_out(blurred_id, cx, view_id);
- cx.views.insert((window_id, view_id), view);
+ cx.views.insert((window, view_id), view);
}
}
@@ -2015,9 +1949,9 @@ impl AppContext {
if focus_changed || effect.is_forced() {
if let Some(focused_id) = focused_id {
for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
- if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+ if let Some(mut view) = cx.views.remove(&(window, view_id)) {
view.focus_in(focused_id, cx, view_id);
- cx.views.insert((window_id, view_id), view);
+ cx.views.insert((window, view_id), view);
}
}
@@ -2039,24 +1973,24 @@ impl AppContext {
fn handle_window_should_close_subscription_effect(
&mut self,
- window_id: usize,
+ window: AnyWindowHandle,
mut callback: WindowShouldCloseSubscriptionCallback,
) {
let mut app = self.upgrade();
- if let Some(window) = self.windows.get_mut(&window_id) {
+ if let Some(window) = self.windows.get_mut(&window) {
window
.platform_window
.on_should_close(Box::new(move || app.update(|cx| callback(cx))))
}
}
- fn handle_window_moved(&mut self, window_id: usize) {
- self.update_window(window_id, |cx| {
+ fn handle_window_moved(&mut self, window: AnyWindowHandle) {
+ self.update_window(window, |cx| {
if let Some(display) = cx.window_display_uuid() {
let bounds = cx.window_bounds();
cx.window_bounds_observations
.clone()
- .emit(window_id, move |callback| {
+ .emit(window, move |callback| {
callback(bounds, display, cx);
true
});
@@ -2073,10 +2007,10 @@ impl AppContext {
});
}
- pub fn focus(&mut self, window_id: usize, view_id: Option<usize>) {
+ pub fn focus(&mut self, window: AnyWindowHandle, view_id: Option<usize>) {
self.pending_effects
.push_back(Effect::Focus(FocusEffect::View {
- window_id,
+ window,
view_id,
is_forced: false,
}));
@@ -2158,6 +2092,46 @@ impl BorrowAppContext for AppContext {
}
}
+impl BorrowWindowContext for AppContext {
+ type Result<T> = Option<T>;
+
+ fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
+ where
+ F: FnOnce(&WindowContext) -> T,
+ {
+ AppContext::read_window(self, window, f)
+ }
+
+ fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+ where
+ F: FnOnce(&WindowContext) -> Option<T>,
+ {
+ AppContext::read_window(self, window, f).flatten()
+ }
+
+ fn update_window<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Self::Result<T>
+ where
+ F: FnOnce(&mut WindowContext) -> T,
+ {
+ self.update(|cx| {
+ let mut window = cx.windows.remove(&handle)?;
+ let mut window_context = WindowContext::mutable(cx, &mut window, handle);
+ let result = f(&mut window_context);
+ if !window_context.removed {
+ cx.windows.insert(handle, window);
+ }
+ Some(result)
+ })
+ }
+
+ fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
+ where
+ F: FnOnce(&mut WindowContext) -> Option<T>,
+ {
+ AppContext::update_window(self, handle, f).flatten()
+ }
+}
+
#[derive(Debug)]
pub enum ParentId {
View(usize),
@@ -77,9 +77,9 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
let cx = app.0.clone();
move |action| {
let mut cx = cx.borrow_mut();
- if let Some(main_window_id) = cx.platform.main_window_id() {
- let dispatched = cx
- .update_window(main_window_id, |cx| {
+ if let Some(main_window) = cx.active_window() {
+ let dispatched = main_window
+ .update(&mut *cx, |cx| {
if let Some(view_id) = cx.focused_view_id() {
cx.dispatch_action(Some(view_id), action);
true
@@ -9,7 +9,7 @@ use collections::{hash_map::Entry, HashMap, HashSet};
#[cfg(any(test, feature = "test-support"))]
use crate::util::post_inc;
-use crate::ElementStateId;
+use crate::{AnyWindowHandle, ElementStateId};
lazy_static! {
static ref LEAK_BACKTRACE: bool =
@@ -26,7 +26,7 @@ pub struct RefCounts {
entity_counts: HashMap<usize, usize>,
element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
dropped_models: HashSet<usize>,
- dropped_views: HashSet<(usize, usize)>,
+ dropped_views: HashSet<(AnyWindowHandle, usize)>,
dropped_element_states: HashSet<ElementStateId>,
#[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) {
Entry::Occupied(mut entry) => *entry.get_mut() += 1,
Entry::Vacant(entry) => {
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();
*count -= 1;
if *count == 0 {
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,
) -> (
HashSet<usize>,
- HashSet<(usize, usize)>,
+ HashSet<(AnyWindowHandle, usize)>,
HashSet<ElementStateId>,
) {
(
@@ -4,9 +4,9 @@ use crate::{
keymap_matcher::{Binding, Keystroke},
platform,
platform::{Event, InputHandler, KeyDownEvent, Platform},
- Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
- ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
- WindowContext,
+ Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
+ Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
+ WeakHandle, WindowContext, WindowHandle,
};
use collections::BTreeMap;
use futures::Future;
@@ -60,7 +60,7 @@ impl TestAppContext {
RefCounts::new(leak_detector),
(),
);
- cx.next_entity_id = first_entity_id;
+ cx.next_id = first_entity_id;
let cx = TestAppContext {
cx: Rc::new(RefCell::new(cx)),
foreground_platform,
@@ -72,8 +72,8 @@ impl TestAppContext {
cx
}
- pub fn dispatch_action<A: Action>(&mut self, window_id: usize, action: A) {
- self.update_window(window_id, |window| {
+ pub fn dispatch_action<A: Action>(&mut self, window: AnyWindowHandle, action: A) {
+ self.update_window(window, |window| {
window.dispatch_action(window.focused_view_id(), &action);
})
.expect("window not found");
@@ -81,10 +81,10 @@ impl TestAppContext {
pub fn available_actions(
&self,
- window_id: usize,
+ window: AnyWindowHandle,
view_id: usize,
) -> 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()
}
@@ -92,33 +92,34 @@ impl TestAppContext {
self.update(|cx| cx.dispatch_global_action_any(&action));
}
- pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
- let handled = self
- .cx
- .borrow_mut()
- .update_window(window_id, |cx| {
- if cx.dispatch_keystroke(&keystroke) {
- return true;
- }
+ pub fn dispatch_keystroke(
+ &mut self,
+ window: AnyWindowHandle,
+ keystroke: Keystroke,
+ is_held: bool,
+ ) {
+ let handled = window.update(self, |cx| {
+ if cx.dispatch_keystroke(&keystroke) {
+ return true;
+ }
- if cx.dispatch_event(
- Event::KeyDown(KeyDownEvent {
- keystroke: keystroke.clone(),
- is_held,
- }),
- false,
- ) {
- return true;
- }
+ if cx.dispatch_event(
+ Event::KeyDown(KeyDownEvent {
+ keystroke: keystroke.clone(),
+ is_held,
+ }),
+ false,
+ ) {
+ return true;
+ }
- false
- })
- .unwrap_or(false);
+ false
+ });
if !handled && !keystroke.cmd && !keystroke.ctrl {
WindowInputHandler {
app: self.cx.clone(),
- window_id,
+ window,
}
.replace_text_in_range(None, &keystroke.key)
}
@@ -126,18 +127,18 @@ impl TestAppContext {
pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
&self,
- window_id: usize,
+ window: AnyWindowHandle,
callback: F,
) -> 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>(
&mut self,
- window_id: usize,
+ window: AnyWindowHandle,
callback: F,
) -> 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>
@@ -148,26 +149,17 @@ impl TestAppContext {
self.cx.borrow_mut().add_model(build_model)
}
- pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
+ pub fn add_window<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
where
- T: View,
- F: FnOnce(&mut ViewContext<T>) -> T,
+ V: View,
+ F: FnOnce(&mut ViewContext<V>) -> V,
{
- let (window_id, view) = self
+ let window = self
.cx
.borrow_mut()
.add_window(Default::default(), build_root_view);
- self.simulate_window_activation(Some(window_id));
- (window_id, view)
- }
-
- 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")
+ window.simulate_activation(self);
+ window
}
pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
@@ -190,8 +182,8 @@ impl TestAppContext {
self.cx.borrow_mut().subscribe_global(callback)
}
- pub fn window_ids(&self) -> Vec<usize> {
- self.cx.borrow().windows.keys().copied().collect()
+ pub fn windows(&self) -> Vec<AnyWindowHandle> {
+ self.cx.borrow().windows().collect()
}
pub fn remove_all_windows(&mut self) {
@@ -261,76 +253,6 @@ impl TestAppContext {
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>> {
self.cx.borrow().leak_detector()
}
@@ -351,18 +273,6 @@ impl TestAppContext {
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>) {
self.condition_duration = duration;
}
@@ -405,19 +315,39 @@ impl BorrowAppContext for TestAppContext {
}
impl BorrowWindowContext for TestAppContext {
- fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+ type Result<T> = T;
+
+ fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
self.cx
.borrow()
- .read_window(window_id, f)
+ .read_window(window, f)
.expect("window was closed")
}
- fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
+ fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+ where
+ F: FnOnce(&WindowContext) -> Option<T>,
+ {
+ BorrowWindowContext::read_window(self, window, f)
+ }
+
+ fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+ &mut self,
+ window: AnyWindowHandle,
+ f: F,
+ ) -> T {
self.cx
.borrow_mut()
- .update_window(window_id, f)
+ .update_window(window, f)
.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> {
@@ -532,6 +462,71 @@ impl<T: Entity> ModelHandle<T> {
}
}
+impl AnyWindowHandle {
+ pub fn has_pending_prompt(&self, cx: &mut TestAppContext) -> bool {
+ let window = self.platform_window_mut(cx);
+ let prompts = window.pending_prompts.borrow_mut();
+ !prompts.is_empty()
+ }
+
+ pub fn current_title(&self, cx: &mut TestAppContext) -> Option<String> {
+ self.platform_window_mut(cx).title.clone()
+ }
+
+ pub fn simulate_close(&self, cx: &mut TestAppContext) -> bool {
+ let handler = self.platform_window_mut(cx).should_close_handler.take();
+ if let Some(mut handler) = handler {
+ let should_close = handler();
+ self.platform_window_mut(cx).should_close_handler = Some(handler);
+ should_close
+ } else {
+ false
+ }
+ }
+
+ pub fn simulate_resize(&self, size: Vector2F, cx: &mut TestAppContext) {
+ let mut window = self.platform_window_mut(cx);
+ window.size = size;
+ let mut handlers = mem::take(&mut window.resize_handlers);
+ drop(window);
+ for handler in &mut handlers {
+ handler();
+ }
+ self.platform_window_mut(cx).resize_handlers = handlers;
+ }
+
+ pub fn is_edited(&self, cx: &mut TestAppContext) -> bool {
+ self.platform_window_mut(cx).edited
+ }
+
+ pub fn simulate_prompt_answer(&self, answer: usize, cx: &mut TestAppContext) {
+ use postage::prelude::Sink as _;
+
+ let mut done_tx = self
+ .platform_window_mut(cx)
+ .pending_prompts
+ .borrow_mut()
+ .pop_front()
+ .expect("prompt was not called");
+ done_tx.try_send(answer).ok();
+ }
+
+ fn platform_window_mut<'a>(
+ &self,
+ cx: &'a mut TestAppContext,
+ ) -> std::cell::RefMut<'a, platform::test::Window> {
+ std::cell::RefMut::map(cx.cx.borrow_mut(), |state| {
+ let window = state.windows.get_mut(&self).unwrap();
+ let test_window = window
+ .platform_window
+ .as_any_mut()
+ .downcast_mut::<platform::test::Window>()
+ .unwrap();
+ test_window
+ })
+ }
+}
+
impl<T: View> ViewHandle<T> {
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
use postage::prelude::{Sink as _, Stream as _};
@@ -13,9 +13,10 @@ use crate::{
},
text_layout::TextLayoutCache,
util::post_inc,
- Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
- Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
- View, ViewContext, ViewHandle, WindowInvalidation,
+ Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
+ BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion,
+ MouseRegionId, PaintContext, SceneBuilder, Subscription, View, ViewContext, ViewHandle,
+ WindowInvalidation,
};
use anyhow::{anyhow, bail, Result};
use collections::{HashMap, HashSet};
@@ -60,7 +61,7 @@ pub struct Window {
impl Window {
pub fn new<V, F>(
- window_id: usize,
+ handle: AnyWindowHandle,
platform_window: Box<dyn platform::Window>,
cx: &mut AppContext,
build_view: F,
@@ -92,7 +93,7 @@ impl Window {
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));
if let Some(invalidation) = window_context.window.invalidation.take() {
window_context.invalidate(invalidation, appearance);
@@ -113,7 +114,7 @@ impl Window {
pub struct WindowContext<'a> {
pub(crate) app_context: Reference<'a, AppContext>,
pub(crate) window: Reference<'a, Window>,
- pub(crate) window_id: usize,
+ pub(crate) window_handle: AnyWindowHandle,
pub(crate) removed: bool,
}
@@ -142,42 +143,66 @@ impl BorrowAppContext for WindowContext<'_> {
}
impl BorrowWindowContext for WindowContext<'_> {
- fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
- if self.window_id == window_id {
+ type Result<T> = T;
+
+ fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, handle: AnyWindowHandle, f: F) -> T {
+ if self.window_handle == handle {
f(self)
} else {
panic!("read_with called with id of window that does not belong to this context")
}
}
- fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
- if self.window_id == window_id {
+ fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+ where
+ F: FnOnce(&WindowContext) -> Option<T>,
+ {
+ BorrowWindowContext::read_window(self, window, f)
+ }
+
+ fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+ &mut self,
+ handle: AnyWindowHandle,
+ f: F,
+ ) -> T {
+ if self.window_handle == handle {
f(self)
} else {
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> {
pub fn mutable(
app_context: &'a mut AppContext,
window: &'a mut Window,
- window_id: usize,
+ handle: AnyWindowHandle,
) -> Self {
Self {
app_context: Reference::Mutable(app_context),
window: Reference::Mutable(window),
- window_id,
+ window_handle: handle,
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 {
app_context: Reference::Immutable(app_context),
window: Reference::Immutable(window),
- window_id,
+ window_handle: handle,
removed: false,
}
}
@@ -186,8 +211,8 @@ impl<'a> WindowContext<'a> {
self.removed = true;
}
- pub fn window_id(&self) -> usize {
- self.window_id
+ pub fn window(&self) -> AnyWindowHandle {
+ self.window_handle
}
pub fn app_context(&mut self) -> &mut AppContext {
@@ -210,10 +235,10 @@ impl<'a> WindowContext<'a> {
where
F: FnOnce(&mut dyn AnyView, &mut Self) -> T,
{
- let window_id = self.window_id;
- let mut view = self.views.remove(&(window_id, view_id))?;
+ let handle = self.window_handle;
+ let mut view = self.views.remove(&(handle, view_id))?;
let result = f(view.as_mut(), self);
- self.views.insert((window_id, view_id), view);
+ self.views.insert((handle, view_id), view);
Some(result)
}
@@ -238,9 +263,9 @@ impl<'a> WindowContext<'a> {
}
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| {
- cx.update_window(window_id, |cx| callback(cx));
+ cx.update_window(handle, |cx| callback(cx));
})
}
@@ -280,10 +305,10 @@ impl<'a> WindowContext<'a> {
H: Handle<E>,
F: 'static + FnMut(H, &E::Event, &mut WindowContext) -> bool,
{
- let window_id = self.window_id;
+ let window_handle = self.window_handle;
self.app_context
.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)
})
}
@@ -292,17 +317,17 @@ impl<'a> WindowContext<'a> {
where
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);
self.pending_effects
.push_back(Effect::WindowActivationObservation {
- window_id,
+ window: handle,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowActivationObservation(
self.window_activation_observations
- .subscribe(window_id, subscription_id),
+ .subscribe(handle, subscription_id),
)
}
@@ -310,17 +335,17 @@ impl<'a> WindowContext<'a> {
where
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);
self.pending_effects
.push_back(Effect::WindowFullscreenObservation {
- window_id,
+ window,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowActivationObservation(
self.window_activation_observations
- .subscribe(window_id, subscription_id),
+ .subscribe(window, subscription_id),
)
}
@@ -328,17 +353,17 @@ impl<'a> WindowContext<'a> {
where
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);
self.pending_effects
.push_back(Effect::WindowBoundsObservation {
- window_id,
+ window,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowBoundsObservation(
self.window_bounds_observations
- .subscribe(window_id, subscription_id),
+ .subscribe(window, subscription_id),
)
}
@@ -347,13 +372,13 @@ impl<'a> WindowContext<'a> {
F: 'static
+ 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);
self.keystroke_observations
- .add_callback(window_id, subscription_id, Box::new(callback));
+ .add_callback(window, subscription_id, Box::new(callback));
Subscription::KeystrokeObservation(
self.keystroke_observations
- .subscribe(window_id, subscription_id),
+ .subscribe(window, subscription_id),
)
}
@@ -361,11 +386,11 @@ impl<'a> WindowContext<'a> {
&self,
view_id: usize,
) -> 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 handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
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());
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
handler_depths_by_action_id
@@ -410,13 +435,13 @@ impl<'a> WindowContext<'a> {
}
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() {
let dispatch_path = self
.ancestors(focused_view_id)
.filter_map(|view_id| {
self.views_metadata
- .get(&(window_id, view_id))
+ .get(&(handle, view_id))
.map(|view| (view_id, view.keymap_context.clone()))
})
.collect();
@@ -441,15 +466,10 @@ impl<'a> WindowContext<'a> {
}
};
- self.keystroke(
- window_id,
- keystroke.clone(),
- handled_by,
- match_result.clone(),
- );
+ self.keystroke(handle, keystroke.clone(), handled_by, match_result.clone());
keystroke_handled
} else {
- self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
+ self.keystroke(handle, keystroke.clone(), None, MatchResult::None);
false
}
}
@@ -457,7 +477,7 @@ impl<'a> WindowContext<'a> {
pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
let mut mouse_events = SmallVec::<[_; 2]>::new();
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
// get mapped into the mouse-specific MouseEvent type.
@@ -821,19 +841,19 @@ impl<'a> WindowContext<'a> {
}
for view_id in notified_views {
- self.notify_view(window_id, view_id);
+ self.notify_view(handle, view_id);
}
any_event_handled
}
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 {
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);
- self.views.insert((window_id, view_id), view);
+ self.views.insert((handle, view_id), view);
if handled {
return true;
}
@@ -847,12 +867,12 @@ impl<'a> WindowContext<'a> {
}
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 {
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);
- self.views.insert((window_id, view_id), view);
+ self.views.insert((handle, view_id), view);
if handled {
return true;
}
@@ -866,12 +886,12 @@ impl<'a> WindowContext<'a> {
}
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 {
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);
- self.views.insert((window_id, view_id), view);
+ self.views.insert((handle, view_id), view);
if handled {
return true;
}
@@ -906,14 +926,14 @@ impl<'a> WindowContext<'a> {
}
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 mut view = self
.views
- .remove(&(window_id, view_id))
+ .remove(&(handle, view_id))
.ok_or_else(|| anyhow!("view not found"))?;
let element = view.render(self, view_id);
- self.views.insert((window_id, view_id), view);
+ self.views.insert((handle, view_id), view);
Ok(element)
}
@@ -941,9 +961,9 @@ impl<'a> WindowContext<'a> {
} else if old_parent_id == new_parent_id {
current_view_id = *old_parent_id.unwrap();
} else {
- let window_id = self.window_id;
+ let handle = self.window_handle;
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;
}
@@ -1111,7 +1131,7 @@ impl<'a> WindowContext<'a> {
}
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 {
@@ -1151,17 +1171,6 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
- pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
- where
- V: View,
- F: FnOnce(&mut ViewContext<V>) -> V,
- {
- let root_view = self.add_view(|cx| build_root_view(cx));
- self.window.root_view = Some(root_view.clone().into_any());
- self.window.focused_view_id = Some(root_view.id());
- root_view
- }
-
pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
where
T: View,
@@ -1175,26 +1184,26 @@ impl<'a> WindowContext<'a> {
T: View,
F: FnOnce(&mut ViewContext<T>) -> Option<T>,
{
- let window_id = self.window_id;
- let view_id = post_inc(&mut self.next_entity_id);
+ let handle = self.window_handle;
+ let view_id = post_inc(&mut self.next_id);
let mut cx = ViewContext::mutable(self, view_id);
let handle = if let Some(view) = build_view(&mut cx) {
let mut keymap_context = KeymapContext::default();
view.update_keymap_context(&mut keymap_context, cx.app_context());
self.views_metadata.insert(
- (window_id, view_id),
+ (handle, view_id),
ViewMetadata {
type_id: TypeId::of::<T>(),
keymap_context,
},
);
- self.views.insert((window_id, view_id), Box::new(view));
+ self.views.insert((handle, view_id), Box::new(view));
self.window
.invalidation
.get_or_insert_with(Default::default)
.updated
.insert(view_id);
- Some(ViewHandle::new(window_id, view_id, &self.ref_counts))
+ Some(ViewHandle::new(handle, view_id, &self.ref_counts))
} else {
None
};
@@ -1371,7 +1380,7 @@ pub struct ChildView {
impl ChildView {
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 {
view_id: view.id(),
view_name,
@@ -1420,7 +1429,7 @@ impl<V: View> Element<V> for ChildView {
visible_bounds: RectF,
_: &mut Self::LayoutState,
_: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) {
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
rendered_view
@@ -2,11 +2,11 @@ use std::{cell::RefCell, ops::Range, rc::Rc};
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 app: Rc<RefCell<AppContext>>,
- pub window_id: usize,
+ pub window: AnyWindowHandle,
}
impl WindowInputHandler {
@@ -21,13 +21,12 @@ impl WindowInputHandler {
//
// See https://github.com/zed-industries/community/issues/444
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 = cx.views.get(&(self.window_id, view_id))?;
+ let view = cx.views.get(&(self.window, view_id))?;
let result = f(view.as_ref(), &cx);
Some(result)
})
- .flatten()
}
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,
{
let mut app = self.app.try_borrow_mut().ok()?;
- app.update_window(self.window_id, |cx| {
- let view_id = cx.window.focused_view_id?;
- cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
- })
- .flatten()
+ self.window
+ .update(&mut *app, |cx| {
+ let view_id = cx.window.focused_view_id?;
+ cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
+ })
+ .flatten()
}
}
@@ -83,9 +83,8 @@ impl InputHandler for WindowInputHandler {
}
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
- self.app
- .borrow()
- .read_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
- .flatten()
+ self.window.read_optional_with(&*self.app.borrow(), |cx| {
+ cx.rect_for_text_range(range_utf16)
+ })
}
}
@@ -33,8 +33,8 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
- json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle,
- WindowContext,
+ json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
+ WeakViewHandle, WindowContext,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@@ -61,7 +61,7 @@ pub trait Element<V: View>: 'static {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState;
fn rect_for_text_range(
@@ -298,7 +298,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
mut layout,
} => {
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 {
element,
constraint,
@@ -316,7 +323,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
..
} => {
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 {
element,
constraint,
@@ -513,7 +527,7 @@ impl<V: View> Element<V> for AnyElement<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
self.paint(scene, bounds.origin(), visible_bounds, view, cx);
}
@@ -1,6 +1,7 @@
use crate::{
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;
@@ -69,7 +70,7 @@ impl<V: View> Element<V> for Align<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
let my_center = bounds.size() / 2.;
let my_target = my_center + my_center * self.alignment;
@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use super::Element;
use crate::{
json::{self, json},
- SceneBuilder, View, ViewContext,
+ PaintContext, SceneBuilder, View, ViewContext,
};
use json::ToJson;
use pathfinder_geometry::{
@@ -56,7 +56,7 @@ where
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
self.0(scene, bounds, visible_bounds, view, cx)
}
@@ -4,7 +4,8 @@ use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json;
use crate::{
- json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+ json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+ ViewContext,
};
pub struct Clipped<V: View> {
@@ -37,7 +38,7 @@ impl<V: View> Element<V> for Clipped<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(bounds), |scene| {
self.child
@@ -5,7 +5,8 @@ use serde_json::json;
use crate::{
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> {
@@ -156,7 +157,7 @@ impl<V: View> Element<V> for ConstrainedBox<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(visible_bounds), |scene| {
self.child
@@ -10,7 +10,8 @@ use crate::{
json::ToJson,
platform::CursorStyle,
scene::{self, Border, CursorRegion, Quad},
- AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+ AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+ ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -214,7 +215,7 @@ impl<V: View> Element<V> for Container<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
let quad_bounds = RectF::from_points(
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
@@ -6,7 +6,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- LayoutContext, SceneBuilder, View, ViewContext,
+ LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
};
use crate::{Element, SizeConstraint};
@@ -57,7 +57,7 @@ impl<V: View> Element<V> for Empty {
_: RectF,
_: &mut Self::LayoutState,
_: &mut V,
- _: &mut ViewContext<V>,
+ _: &mut PaintContext<V>,
) -> Self::PaintState {
}
@@ -2,7 +2,8 @@ use std::ops::Range;
use crate::{
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;
@@ -61,7 +62,7 @@ impl<V: View> Element<V> for Expanded<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
@@ -2,8 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
use crate::{
json::{self, ToJson, Value},
- AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint,
- Vector2FExt, View, ViewContext,
+ AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
+ SizeConstraint, Vector2FExt, View, ViewContext,
};
use pathfinder_geometry::{
rect::RectF,
@@ -258,7 +258,7 @@ impl<V: View> Element<V> for Flex<V> {
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
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,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)
@@ -3,7 +3,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
- AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+ AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+ ViewContext,
};
pub struct Hook<V: View> {
@@ -52,7 +53,7 @@ impl<V: View> Element<V> for Hook<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
@@ -5,8 +5,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
- ViewContext,
+ scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
+ View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -97,7 +97,7 @@ impl<V: View> Element<V> for Image {
_: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
- _: &mut ViewContext<V>,
+ _: &mut PaintContext<V>,
) -> Self::PaintState {
if let Some(data) = layout {
scene.push_image(scene::Image {
@@ -66,7 +66,7 @@ impl<V: View> Element<V> for KeystrokeLabel {
visible_bounds: RectF,
element: &mut AnyElement<V>,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
}
@@ -8,7 +8,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle},
- Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+ Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -163,7 +163,7 @@ impl<V: View> Element<V> for Label {
visible_bounds: RectF,
line: &mut Self::LayoutState,
_: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
line.paint(
@@ -4,8 +4,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::json,
- AnyElement, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
- ViewContext,
+ AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
+ View, ViewContext,
};
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
@@ -255,7 +255,7 @@ impl<V: View> Element<V> for List<V> {
visible_bounds: RectF,
scroll_top: &mut ListOffset,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
@@ -647,7 +647,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
#[cfg(test)]
mod tests {
use super::*;
- use crate::{elements::Empty, geometry::vector::vec2f, Entity};
+ use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
use rand::prelude::*;
use std::env;
@@ -988,7 +988,7 @@ mod tests {
_: RectF,
_: &mut (),
_: &mut V,
- _: &mut ViewContext<V>,
+ _: &mut PaintContext<V>,
) {
unimplemented!()
}
@@ -10,8 +10,8 @@ use crate::{
CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
- AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
- SizeConstraint, View, ViewContext,
+ AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
+ SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde_json::json;
use std::{marker::PhantomData, ops::Range};
@@ -256,7 +256,7 @@ impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
if self.above {
self.child
@@ -3,8 +3,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
- AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
- ViewContext,
+ AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
+ SizeConstraint, View, ViewContext,
};
use serde_json::json;
@@ -143,7 +143,7 @@ impl<V: View> Element<V> for Overlay<V> {
_: RectF,
size: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) {
let (anchor_position, mut bounds) = match self.position_mode {
OverlayPositionMode::Window => {
@@ -7,8 +7,8 @@ use crate::{
geometry::rect::RectF,
platform::{CursorStyle, MouseButton},
scene::MouseDrag,
- AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
- ViewContext,
+ AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
+ SizeConstraint, View, ViewContext,
};
#[derive(Copy, Clone, Debug)]
@@ -125,7 +125,7 @@ impl<V: View> Element<V> for Resizable<V> {
visible_bounds: pathfinder_geometry::rect::RectF,
constraint: &mut SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
scene.push_stacking_context(None, None);
@@ -3,7 +3,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
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.
@@ -57,7 +58,7 @@ impl<V: View> Element<V> for Stack<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
for child in &mut self.children {
scene.paint_layer(None, |scene| {
@@ -1,5 +1,6 @@
use super::constrain_size_preserving_aspect_ratio;
use crate::json::ToJson;
+use crate::PaintContext;
use crate::{
color::Color,
geometry::{
@@ -73,7 +74,7 @@ impl<V: View> Element<V> for Svg {
_visible_bounds: RectF,
svg: &mut Self::LayoutState,
_: &mut V,
- _: &mut ViewContext<V>,
+ _: &mut PaintContext<V>,
) {
if let Some(svg) = svg.clone() {
scene.push_icon(scene::Icon {
@@ -7,8 +7,8 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle, ShapedBoundary},
- AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache,
- View, ViewContext,
+ AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
+ TextLayoutCache, View, ViewContext,
};
use log::warn;
use serde_json::json;
@@ -171,7 +171,7 @@ impl<V: View> Element<V> for Text {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
let mut origin = bounds.origin();
let empty = Vec::new();
@@ -6,8 +6,8 @@ use crate::{
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
json::json,
- Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
- ViewContext,
+ Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
+ Task, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -194,7 +194,7 @@ impl<V: View> Element<V> for Tooltip<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
@@ -6,7 +6,7 @@ use crate::{
},
json::{self, json},
platform::ScrollWheelEvent,
- AnyElement, LayoutContext, MouseRegion, SceneBuilder, View, ViewContext,
+ AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, View, ViewContext,
};
use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -278,7 +278,7 @@ impl<V: View> Element<V> for UniformList<V> {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
@@ -71,6 +71,32 @@ pub struct TextStyle {
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)]
#[serde(remote = "Properties")]
pub struct PropertiesDef {
@@ -19,7 +19,7 @@ use crate::{
},
keymap_matcher::KeymapMatcher,
text_layout::{LineLayout, RunStyle},
- Action, ClipboardItem, Menu, Scene,
+ Action, AnyWindowHandle, ClipboardItem, Menu, Scene,
};
use anyhow::{anyhow, bail, Result};
use async_task::Runnable;
@@ -58,13 +58,13 @@ pub trait Platform: Send + Sync {
fn open_window(
&self,
- id: usize,
+ handle: AnyWindowHandle,
options: WindowOptions,
executor: Rc<executor::Foreground>,
) -> 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 read_from_clipboard(&self) -> Option<ClipboardItem>;
@@ -21,7 +21,7 @@ pub use fonts::FontSystem;
use platform::{MacForegroundPlatform, MacPlatform};
pub use renderer::Surface;
use std::{ops::Range, rc::Rc, sync::Arc};
-use window::Window;
+use window::MacWindow;
use crate::executor;
@@ -1,12 +1,12 @@
use super::{
event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
- FontSystem, Window,
+ FontSystem, MacWindow,
};
use crate::{
executor,
keymap_matcher::KeymapMatcher,
platform::{self, AppVersion, CursorStyle, Event},
- Action, ClipboardItem, Menu, MenuItem,
+ Action, AnyWindowHandle, ClipboardItem, Menu, MenuItem,
};
use anyhow::{anyhow, Result};
use block::ConcreteBlock;
@@ -590,18 +590,18 @@ impl platform::Platform for MacPlatform {
fn open_window(
&self,
- id: usize,
+ handle: AnyWindowHandle,
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
) -> 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> {
- Window::main_window_id()
+ fn main_window(&self) -> Option<AnyWindowHandle> {
+ 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()))
}
@@ -13,6 +13,7 @@ use crate::{
Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind,
},
+ AnyWindowHandle,
};
use block::ConcreteBlock;
use cocoa::{
@@ -282,7 +283,7 @@ struct InsertText {
}
struct WindowState {
- id: usize,
+ handle: AnyWindowHandle,
native_window: id,
kind: WindowKind,
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(
- id: usize,
+ handle: AnyWindowHandle,
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
fonts: Arc<dyn platform::FontSystem>,
@@ -504,7 +505,7 @@ impl Window {
assert!(!native_view.is_null());
let window = Self(Rc::new(RefCell::new(WindowState {
- id,
+ handle,
native_window,
kind: options.kind,
event_callback: None,
@@ -621,13 +622,13 @@ impl Window {
}
}
- pub fn main_window_id() -> Option<usize> {
+ pub fn main_window() -> Option<AnyWindowHandle> {
unsafe {
let app = NSApplication::sharedApplication(nil);
let main_window: id = msg_send![app, mainWindow];
if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
- let id = get_window_state(&*main_window).borrow().id;
- Some(id)
+ let handle = get_window_state(&*main_window).borrow().handle;
+ Some(handle)
} else {
None
}
@@ -635,7 +636,7 @@ impl Window {
}
}
-impl Drop for Window {
+impl Drop for MacWindow {
fn drop(&mut self) {
let this = self.0.borrow();
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 {
self.0.as_ref().borrow().bounds()
}
@@ -881,7 +882,7 @@ impl platform::Window for Window {
fn is_topmost_for_position(&self, position: Vector2F) -> bool {
let self_borrow = self.0.borrow();
- let self_id = self_borrow.id;
+ let self_handle = self_borrow.handle;
unsafe {
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_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
if is_panel == YES || is_window == YES {
- let topmost_window_id = get_window_state(&*top_most_window).borrow().id;
- topmost_window_id == self_id
+ let topmost_window = get_window_state(&*top_most_window).borrow().handle;
+ topmost_window == self_handle
} else {
// Someone else's window is on top
false
@@ -5,7 +5,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
keymap_matcher::KeymapMatcher,
- Action, ClipboardItem, Menu,
+ Action, AnyWindowHandle, ClipboardItem, Menu,
};
use anyhow::{anyhow, Result};
use collections::VecDeque;
@@ -102,7 +102,7 @@ pub struct Platform {
fonts: Arc<dyn super::FontSystem>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
cursor: Mutex<CursorStyle>,
- active_window_id: Arc<Mutex<Option<usize>>>,
+ active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
}
impl Platform {
@@ -112,7 +112,7 @@ impl Platform {
fonts: Arc::new(super::current::FontSystem::new()),
current_clipboard_item: Default::default(),
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(
&self,
- id: usize,
+ handle: AnyWindowHandle,
options: super::WindowOptions,
_executor: Rc<super::executor::Foreground>,
) -> Box<dyn super::Window> {
- *self.active_window_id.lock() = Some(id);
+ *self.active_window.lock() = Some(handle);
Box::new(Window::new(
- id,
+ handle,
match options.bounds {
WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
WindowBounds::Fixed(rect) => rect.size(),
},
- self.active_window_id.clone(),
+ self.active_window.clone(),
))
}
- fn main_window_id(&self) -> Option<usize> {
- self.active_window_id.lock().clone()
+ fn main_window(&self) -> Option<AnyWindowHandle> {
+ 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(
- id,
+ handle,
vec2f(24., 24.),
- self.active_window_id.clone(),
+ self.active_window.clone(),
))
}
@@ -256,7 +256,7 @@ impl super::Screen for Screen {
}
pub struct Window {
- id: usize,
+ handle: AnyWindowHandle,
pub(crate) size: Vector2F,
scale_factor: f32,
current_scene: Option<crate::Scene>,
@@ -270,13 +270,17 @@ pub struct Window {
pub(crate) title: Option<String>,
pub(crate) edited: bool,
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
- active_window_id: Arc<Mutex<Option<usize>>>,
+ active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
}
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 {
- id,
+ handle,
size,
event_handlers: Default::default(),
resize_handlers: Default::default(),
@@ -290,7 +294,7 @@ impl Window {
title: None,
edited: false,
pending_prompts: Default::default(),
- active_window_id,
+ active_window,
}
}
@@ -342,7 +346,7 @@ impl super::Window for Window {
}
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) {
@@ -45,7 +45,7 @@ use syntax_map::SyntaxSnapshot;
use theme::{SyntaxTheme, Theme};
use tree_sitter::{self, Query};
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};
#[cfg(any(test, feature = "test-support"))]
@@ -182,8 +182,8 @@ impl CachedLspAdapter {
self.adapter.workspace_configuration(cx)
}
- pub async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
- self.adapter.process_diagnostics(params).await
+ pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
+ self.adapter.process_diagnostics(params)
}
pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) {
@@ -262,7 +262,7 @@ pub trait LspAdapter: 'static + Send + Sync {
container_dir: PathBuf,
) -> 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) {}
@@ -777,7 +777,7 @@ impl LanguageRegistry {
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let path = path.as_ref();
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];
self.get_or_load_language(|config| {
let path_matches = config
@@ -1487,12 +1487,6 @@ impl Language {
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) {
for adapter in &self.adapters {
adapter.process_completion(completion).await;
@@ -1756,7 +1750,7 @@ impl LspAdapter for Arc<FakeLspAdapter> {
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> {
self.disk_based_diagnostics_sources.clone()
@@ -61,7 +61,9 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await;
- let (_, log_view) = cx.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx));
+ let log_view = cx
+ .add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx))
+ .root(cx);
language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
message: "hello from the server".into(),
@@ -2769,24 +2769,21 @@ impl Project {
language_server
.on_notification::<lsp::notification::PublishDiagnostics, _>({
let adapter = adapter.clone();
- move |mut params, cx| {
+ move |mut params, mut cx| {
let this = this;
let adapter = adapter.clone();
- cx.spawn(|mut cx| async move {
- adapter.process_diagnostics(&mut params).await;
- if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, cx| {
- this.update_diagnostics(
- server_id,
- params,
- &adapter.disk_based_diagnostic_sources,
- cx,
- )
- .log_err();
- });
- }
- })
- .detach();
+ adapter.process_diagnostics(&mut params);
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, cx| {
+ this.update_diagnostics(
+ server_id,
+ params,
+ &adapter.disk_based_diagnostic_sources,
+ cx,
+ )
+ .log_err();
+ });
+ }
}
})
.detach();
@@ -1,5 +1,5 @@
use crate::Project;
-use gpui::{ModelContext, ModelHandle, WeakModelHandle};
+use gpui::{AnyWindowHandle, ModelContext, ModelHandle, WeakModelHandle};
use std::path::PathBuf;
use terminal::{Terminal, TerminalBuilder, TerminalSettings};
@@ -11,7 +11,7 @@ impl Project {
pub fn create_terminal(
&mut self,
working_directory: Option<PathBuf>,
- window_id: usize,
+ window: AnyWindowHandle,
cx: &mut ModelContext<Self>,
) -> anyhow::Result<ModelHandle<Terminal>> {
if self.is_remote() {
@@ -27,7 +27,7 @@ impl Project {
settings.env.clone(),
Some(settings.blinking.clone()),
settings.alternate_scroll,
- window_id,
+ window,
)
.map(|builder| {
let terminal_handle = cx.add_model(|cx| builder.subscribe(cx));
@@ -4,7 +4,7 @@ use collections::HashMap;
use gpui::{AppContext, AssetSource};
use serde_derive::Deserialize;
-use util::iife;
+use util::{iife, paths::PathExt};
#[derive(Deserialize, Debug)]
struct TypeConfig {
@@ -48,14 +48,7 @@ impl FileAssociations {
// FIXME: Associate a type with the languages and have the file's langauge
// override these associations
iife!({
- let suffix = path
- .file_name()
- .and_then(|os_str| os_str.to_str())
- .and_then(|file_name| {
- file_name
- .find('.')
- .and_then(|dot_index| file_name.get(dot_index + 1..))
- })?;
+ let suffix = path.icon_suffix()?;
this.suffixes
.get(suffix)
@@ -1415,7 +1415,7 @@ impl ProjectPanel {
if cx
.global::<DragAndDrop<Workspace>>()
- .currently_dragged::<ProjectEntryId>(cx.window_id())
+ .currently_dragged::<ProjectEntryId>(cx.window())
.is_some()
&& dragged_entry_destination
.as_ref()
@@ -1459,7 +1459,7 @@ impl ProjectPanel {
.on_up(MouseButton::Left, move |_, this, cx| {
if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>()
- .currently_dragged::<ProjectEntryId>(cx.window_id())
+ .currently_dragged::<ProjectEntryId>(cx.window())
{
this.move_entry(
*dragged_entry,
@@ -1472,7 +1472,7 @@ impl ProjectPanel {
.on_move(move |_, this, cx| {
if cx
.global::<DragAndDrop<Workspace>>()
- .currently_dragged::<ProjectEntryId>(cx.window_id())
+ .currently_dragged::<ProjectEntryId>(cx.window())
.is_some()
{
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
@@ -1726,7 +1726,7 @@ impl ClipboardEntry {
#[cfg(test)]
mod tests {
use super::*;
- use gpui::{TestAppContext, ViewHandle};
+ use gpui::{AnyWindowHandle, TestAppContext, ViewHandle, WindowHandle};
use pretty_assertions::assert_eq;
use project::FakeFs;
use serde_json::json;
@@ -1780,7 +1780,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx),
@@ -1868,7 +1870,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@@ -1890,7 +1893,7 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder.
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);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2219,7 +2222,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@@ -2241,7 +2245,7 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder.
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);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2319,7 +2323,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {
@@ -2392,7 +2398,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
toggle_expand_dir(&panel, "src/test", cx);
@@ -2409,9 +2416,9 @@ mod tests {
" 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!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
@@ -2422,7 +2429,7 @@ mod tests {
],
"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);
panel.update(cx, |panel, cx| panel.open_file(&Open, cx));
@@ -2436,9 +2443,9 @@ mod tests {
" 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
.read(cx)
.panes()
@@ -2454,13 +2461,13 @@ mod tests {
.expect("Open item should be an editor");
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!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v src", " v test", " third.rs"],
"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]
@@ -2481,7 +2488,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "src/", cx);
@@ -2492,7 +2500,7 @@ mod tests {
&["v src <== selected", " > test"]
);
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);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2523,7 +2531,7 @@ mod tests {
&["v src", " > test <== selected"]
);
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);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2573,7 +2581,7 @@ mod tests {
],
);
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);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2627,7 +2635,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
let new_search_events_count = Arc::new(AtomicUsize::new(0));
@@ -2714,7 +2724,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {
@@ -2866,13 +2878,11 @@ mod tests {
}
fn ensure_single_file_is_opened(
- window_id: usize,
- workspace: &ViewHandle<Workspace>,
+ window: WindowHandle<Workspace>,
expected_path: &str,
cx: &mut TestAppContext,
) {
- cx.read_window(window_id, |cx| {
- let workspace = workspace.read(cx);
+ window.update_root(cx, |workspace, cx| {
let worktrees = workspace.worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
let worktree_id = WorktreeId::from_usize(worktrees[0].id());
@@ -2894,12 +2904,12 @@ mod tests {
}
fn submit_deletion(
- window_id: usize,
+ window: AnyWindowHandle,
panel: &ViewHandle<ProjectPanel>,
cx: &mut TestAppContext,
) {
assert!(
- !cx.has_pending_prompt(window_id),
+ !window.has_pending_prompt(cx),
"Should have no prompts before the deletion"
);
panel.update(cx, |panel, cx| {
@@ -2909,27 +2919,27 @@ mod tests {
.detach_and_log_err(cx);
});
assert!(
- cx.has_pending_prompt(window_id),
+ window.has_pending_prompt(cx),
"Should have a prompt after the deletion"
);
- cx.simulate_prompt_answer(window_id, 0);
+ window.simulate_prompt_answer(0, cx);
assert!(
- !cx.has_pending_prompt(window_id),
+ !window.has_pending_prompt(cx),
"Should have no prompts after prompt was replied to"
);
cx.foreground().run_until_parked();
}
fn ensure_no_open_items_and_panes(
- window_id: usize,
+ window: AnyWindowHandle,
workspace: &ViewHandle<Workspace>,
cx: &mut TestAppContext,
) {
assert!(
- !cx.has_pending_prompt(window_id),
+ !window.has_pending_prompt(cx),
"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
.read(cx)
.panes()
@@ -326,10 +326,11 @@ mod tests {
},
);
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
// Create the project symbols view.
- let symbols = cx.add_view(window_id, |cx| {
+ let symbols = window.add_view(cx, |cx| {
ProjectSymbols::new(
ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
cx,
@@ -5,6 +5,7 @@ use gpui::{
elements::{Label, LabelStyle},
AnyElement, Element, View,
};
+use util::paths::PathExt;
use workspace::WorkspaceLocation;
pub struct HighlightedText {
@@ -61,7 +62,7 @@ impl HighlightedWorkspaceLocation {
.paths()
.iter()
.map(|path| {
- let path = util::paths::compact(&path);
+ let path = path.compact();
let highlighted_text = Self::highlights_for_path(
path.as_ref(),
&string_match.positions,
@@ -11,6 +11,7 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate, PickerEvent};
use std::sync::Arc;
+use util::paths::PathExt;
use workspace::{
notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
WORKSPACE_DB,
@@ -134,7 +135,7 @@ impl PickerDelegate for RecentProjectsDelegate {
let combined_string = location
.paths()
.iter()
- .map(|path| util::paths::compact(&path).to_string_lossy().into_owned())
+ .map(|path| path.compact().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("");
StringMatchCandidate::new(id, combined_string)
@@ -822,11 +822,10 @@ mod tests {
cx,
)
});
- let (window_id, _root_view) = cx.add_window(|_| EmptyView);
+ let window = cx.add_window(|_| EmptyView);
+ let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
- let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
-
- let search_bar = cx.add_view(window_id, |cx| {
+ let search_bar = window.add_view(cx, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
@@ -1202,11 +1201,10 @@ mod tests {
"Should pick a query with multiple results"
);
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
- let (window_id, _root_view) = cx.add_window(|_| EmptyView);
-
- let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
+ let window = cx.add_window(|_| EmptyView);
+ let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
- let search_bar = cx.add_view(window_id, |cx| {
+ let search_bar = window.add_view(cx, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
@@ -1222,12 +1220,13 @@ mod tests {
search_bar.activate_current_match(cx);
});
- cx.read_window(window_id, |cx| {
+ window.read_with(cx, |cx| {
assert!(
!editor.is_focused(cx),
"Initially, the editor should not be focused"
);
});
+
let initial_selections = editor.update(cx, |editor, cx| {
let initial_selections = editor.selections.display_ranges(cx);
assert_eq!(
@@ -1244,7 +1243,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx);
});
- cx.read_window(window_id, |cx| {
+ window.read_with(cx, |cx| {
assert!(
editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches"
@@ -1268,7 +1267,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_next_match(&SelectNextMatch, cx);
});
- cx.read_window(window_id, |cx| {
+ window.read_with(cx, |cx| {
assert!(
editor.is_focused(cx),
"Should still have editor focused after SelectNextMatch"
@@ -1297,7 +1296,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx);
});
- cx.read_window(window_id, |cx| {
+ window.read_with(cx, |cx| {
assert!(
editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches"
@@ -1321,7 +1320,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_prev_match(&SelectPrevMatch, cx);
});
- cx.read_window(window_id, |cx| {
+ window.read_with(cx, |cx| {
assert!(
editor.is_focused(cx),
"Should still have editor focused after SelectPrevMatch"
@@ -1357,7 +1356,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_all_matches(&SelectAllMatches, cx);
});
- cx.read_window(window_id, |cx| {
+ window.read_with(cx, |cx| {
assert!(
!editor.is_focused(cx),
"Should not switch focus to editor if SelectAllMatches does not find any matches"
@@ -1389,11 +1388,11 @@ mod tests {
"#
.unindent();
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
- let (window_id, _root_view) = cx.add_window(|_| EmptyView);
+ let window = cx.add_window(|_| EmptyView);
- let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
+ let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
- let search_bar = cx.add_view(window_id, |cx| {
+ let search_bar = window.add_view(cx, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
@@ -1729,7 +1729,9 @@ pub mod tests {
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
- let (_, search_view) = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx));
+ let search_view = cx
+ .add_window(|cx| ProjectSearchView::new(search.clone(), cx))
+ .root(cx);
search_view.update(cx, |search_view, cx| {
search_view
@@ -1846,7 +1848,8 @@ pub mod tests {
)
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let active_item = cx.read(|cx| {
workspace
@@ -1877,9 +1880,9 @@ pub mod tests {
};
let search_view_id = search_view.id();
- cx.spawn(
- |mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
- )
+ cx.spawn(|mut cx| async move {
+ window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+ })
.detach();
deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| {
@@ -1930,7 +1933,7 @@ pub mod tests {
);
});
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();
deterministic.run_until_parked();
@@ -1961,9 +1964,9 @@ pub mod tests {
"Search view with mismatching query should be focused after search results are available",
);
});
- cx.spawn(
- |mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
- )
+ cx.spawn(|mut cx| async move {
+ window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+ })
.detach();
deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| {
@@ -1991,9 +1994,9 @@ pub mod tests {
);
});
- cx.spawn(
- |mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
- )
+ cx.spawn(|mut cx| async move {
+ window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+ })
.detach();
deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| {
@@ -2030,7 +2033,9 @@ pub mod tests {
let worktree_id = project.read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
let active_item = cx.read(|cx| {
workspace
@@ -2148,7 +2153,8 @@ pub mod tests {
)
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
workspace.update(cx, |workspace, cx| {
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
});
@@ -2163,7 +2169,7 @@ pub mod tests {
.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();
search_bar.set_active_pane_item(Some(&search_view), cx);
// search_bar.show(cx);
@@ -53,7 +53,7 @@ use gpui::{
keymap_matcher::Keystroke,
platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase},
scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp},
- AppContext, ClipboardItem, Entity, ModelContext, Task,
+ AnyWindowHandle, AppContext, ClipboardItem, Entity, ModelContext, Task,
};
use crate::mappings::{
@@ -404,7 +404,7 @@ impl TerminalBuilder {
mut env: HashMap<String, String>,
blink_settings: Option<TerminalBlink>,
alternate_scroll: AlternateScroll,
- window_id: usize,
+ window: AnyWindowHandle,
) -> Result<TerminalBuilder> {
let pty_config = {
let alac_shell = match shell.clone() {
@@ -462,7 +462,7 @@ impl TerminalBuilder {
let pty = match tty::new(
&pty_config,
TerminalSize::default().into(),
- window_id as u64,
+ window.id() as u64,
) {
Ok(pty) => pty,
Err(error) => {
@@ -10,8 +10,9 @@ use gpui::{
platform::{CursorStyle, MouseButton},
serde_json::json,
text_layout::{Line, RunStyle},
- AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, Quad,
- SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
+ AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion,
+ PaintContext, Quad, SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext,
+ WeakModelHandle,
};
use itertools::Itertools;
use language::CursorShape;
@@ -734,7 +735,7 @@ impl Element<TerminalView> for TerminalElement {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut TerminalView,
- cx: &mut ViewContext<TerminalView>,
+ cx: &mut PaintContext<TerminalView>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@@ -48,7 +48,7 @@ impl TerminalPanel {
fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
let weak_self = cx.weak_handle();
let pane = cx.add_view(|cx| {
- let window_id = cx.window_id();
+ let window = cx.window();
let mut pane = Pane::new(
workspace.weak_handle(),
workspace.project().clone(),
@@ -60,7 +60,7 @@ impl TerminalPanel {
pane.set_can_navigate(false, cx);
pane.on_can_drop(move |drag_and_drop, cx| {
drag_and_drop
- .currently_dragged::<DraggedItem>(window_id)
+ .currently_dragged::<DraggedItem>(window)
.map_or(false, |(_, item)| {
item.handle.act_as::<TerminalView>(cx).is_some()
})
@@ -255,10 +255,10 @@ impl TerminalPanel {
.clone();
let working_directory =
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| {
project
- .create_terminal(working_directory, window_id, cx)
+ .create_terminal(working_directory, window, cx)
.log_err()
}) {
let terminal = Box::new(cx.add_view(|cx| {
@@ -112,11 +112,11 @@ impl TerminalView {
let working_directory =
get_working_directory(workspace, cx, strategy.working_directory.clone());
- let window_id = cx.window_id();
+ let window = cx.window();
let terminal = workspace
.project()
.update(cx, |project, cx| {
- project.create_terminal(working_directory, window_id, cx)
+ project.create_terminal(working_directory, window, cx)
})
.notify_err(workspace, cx);
@@ -741,7 +741,7 @@ impl Item for TerminalView {
item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>,
) -> Task<anyhow::Result<ViewHandle<Self>>> {
- let window_id = cx.window_id();
+ let window = cx.window();
cx.spawn(|pane, mut cx| async move {
let cwd = TERMINAL_DB
.get_working_directory(item_id, workspace_id)
@@ -762,7 +762,7 @@ impl Item for TerminalView {
});
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| {
cx.add_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))
@@ -1070,7 +1070,9 @@ mod tests {
});
let project = Project::test(params.fs.clone(), [], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
(project, workspace)
}
@@ -192,7 +192,6 @@ where
F: FnOnce(&mut gpui::ViewContext<V>) -> D,
{
const TITLEBAR_HEIGHT: f32 = 28.;
- // let active = cx.window_is_active(cx.window_id());
Flex::column()
.with_child(
@@ -30,49 +30,57 @@ pub mod legacy {
}
}
-/// Compacts a given file path by replacing the user's home directory
-/// prefix with a tilde (`~`).
-///
-/// # Arguments
-///
-/// * `path` - A reference to a `Path` representing the file path to compact.
-///
-/// # Examples
-///
-/// ```
-/// use std::path::{Path, PathBuf};
-/// use util::paths::compact;
-/// let path: PathBuf = [
-/// util::paths::HOME.to_string_lossy().to_string(),
-/// "some_file.txt".to_string(),
-/// ]
-/// .iter()
-/// .collect();
-/// if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
-/// assert_eq!(compact(&path).to_str(), Some("~/some_file.txt"));
-/// } else {
-/// assert_eq!(compact(&path).to_str(), path.to_str());
-/// }
-/// ```
-///
-/// # Returns
-///
-/// * A `PathBuf` containing the compacted file path. If the input path
-/// does not have the user's home directory prefix, or if we are not on
-/// Linux or macOS, the original path is returned unchanged.
-pub fn compact(path: &Path) -> PathBuf {
- if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
- match path.strip_prefix(HOME.as_path()) {
- Ok(relative_path) => {
- let mut shortened_path = PathBuf::new();
- shortened_path.push("~");
- shortened_path.push(relative_path);
- shortened_path
+pub trait PathExt {
+ fn compact(&self) -> PathBuf;
+ fn icon_suffix(&self) -> Option<&str>;
+ fn extension_or_hidden_file_name(&self) -> Option<&str>;
+}
+
+impl<T: AsRef<Path>> PathExt for T {
+ /// Compacts a given file path by replacing the user's home directory
+ /// prefix with a tilde (`~`).
+ ///
+ /// # Returns
+ ///
+ /// * A `PathBuf` containing the compacted file path. If the input path
+ /// does not have the user's home directory prefix, or if we are not on
+ /// Linux or macOS, the original path is returned unchanged.
+ fn compact(&self) -> PathBuf {
+ if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
+ match self.as_ref().strip_prefix(HOME.as_path()) {
+ Ok(relative_path) => {
+ let mut shortened_path = PathBuf::new();
+ shortened_path.push("~");
+ shortened_path.push(relative_path);
+ shortened_path
+ }
+ Err(_) => self.as_ref().to_path_buf(),
}
- Err(_) => path.to_path_buf(),
+ } else {
+ self.as_ref().to_path_buf()
+ }
+ }
+
+ /// Returns a suffix of the path that is used to determine which file icon to use
+ fn icon_suffix(&self) -> Option<&str> {
+ let file_name = self.as_ref().file_name()?.to_str()?;
+
+ if file_name.starts_with(".") {
+ return file_name.strip_prefix(".");
}
- } else {
- path.to_path_buf()
+
+ self.as_ref()
+ .extension()
+ .and_then(|extension| extension.to_str())
+ }
+
+ /// 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()
}
}
@@ -279,4 +287,65 @@ mod tests {
);
}
}
+
+ #[test]
+ fn test_path_compact() {
+ let path: PathBuf = [
+ HOME.to_string_lossy().to_string(),
+ "some_file.txt".to_string(),
+ ]
+ .iter()
+ .collect();
+ if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
+ assert_eq!(path.compact().to_str(), Some("~/some_file.txt"));
+ } else {
+ assert_eq!(path.compact().to_str(), path.to_str());
+ }
+ }
+
+ #[test]
+ fn test_icon_suffix() {
+ // No dots in name
+ let path = Path::new("/a/b/c/file_name.rs");
+ assert_eq!(path.icon_suffix(), Some("rs"));
+
+ // Single dot in name
+ let path = Path::new("/a/b/c/file.name.rs");
+ assert_eq!(path.icon_suffix(), Some("rs"));
+
+ // Multiple dots in name
+ let path = Path::new("/a/b/c/long.file.name.rs");
+ assert_eq!(path.icon_suffix(), Some("rs"));
+
+ // Hidden file, no extension
+ let path = Path::new("/a/b/c/.gitignore");
+ assert_eq!(path.icon_suffix(), Some("gitignore"));
+
+ // Hidden file, with extension
+ let path = Path::new("/a/b/c/.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"));
+ }
}
@@ -10,7 +10,7 @@ pub fn init(cx: &mut AppContext) {
fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
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_active_editor(cx, |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.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) {
- cx.update_window(editor.window_id(), |cx| {
+ editor.window().update(cx, |cx| {
Vim::update(cx, |vim, cx| {
if let Some(previous_editor) = vim.active_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) {
- cx.update_window(editor.window_id(), |cx| {
+ editor.window().update(cx, |cx| {
cx.update_default_global(|vim: &mut Vim, _| {
if let Some(previous_editor) = vim.active_editor.clone() {
if previous_editor == editor.clone() {
@@ -20,7 +20,7 @@ impl ModeIndicator {
if let Some(mode_indicator) = handle.upgrade(cx) {
match event {
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.set_mode(mode, cx);
})
@@ -85,8 +85,8 @@ impl<'a> VimTestContext<'a> {
}
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
- let window_id = self.window_id;
- self.update_window(window_id, |cx| {
+ let window = self.window;
+ window.update(self.cx.cx.cx, |cx| {
Vim::update(cx, |vim, cx| {
vim.switch_mode(mode, false, cx);
})
@@ -203,7 +203,7 @@ impl Dock {
pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option<usize> {
self.panel_entries.iter().position(|entry| {
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)
})
}
@@ -530,16 +530,15 @@ impl View for PanelButtons {
tooltip_action.as_ref().map(|action| action.boxed_clone());
move |_, this, cx| {
if let Some(tooltip_action) = &tooltip_action {
- let window_id = cx.window_id();
+ let window = cx.window();
let view_id = this.workspace.id();
let tooltip_action = tooltip_action.boxed_clone();
cx.spawn(|_, mut cx| async move {
- cx.dispatch_action(
- window_id,
+ window.dispatch_action(
view_id,
&*tooltip_action,
- )
- .ok();
+ &mut cx,
+ );
})
.detach();
}
@@ -6,6 +6,7 @@ use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
use anyhow::Result;
use client::{proto, Client};
use gpui::geometry::vector::Vector2F;
+use gpui::AnyWindowHandle;
use gpui::{
fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
@@ -250,7 +251,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
fn workspace_deactivated(&self, cx: &mut WindowContext);
fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
fn id(&self) -> usize;
- fn window_id(&self) -> usize;
+ fn window(&self) -> AnyWindowHandle;
fn as_any(&self) -> &AnyViewHandle;
fn is_dirty(&self, cx: &AppContext) -> bool;
fn has_conflict(&self, cx: &AppContext) -> bool;
@@ -280,7 +281,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
pub trait WeakItemHandle {
fn id(&self) -> usize;
- fn window_id(&self) -> usize;
+ fn window(&self) -> AnyWindowHandle;
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
}
@@ -542,8 +543,8 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
self.id()
}
- fn window_id(&self) -> usize {
- self.window_id()
+ fn window(&self) -> AnyWindowHandle {
+ AnyViewHandle::window(self)
}
fn as_any(&self) -> &AnyViewHandle {
@@ -649,8 +650,8 @@ impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
self.id()
}
- fn window_id(&self) -> usize {
- self.window_id()
+ fn window(&self) -> AnyWindowHandle {
+ self.window()
}
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
@@ -25,8 +25,8 @@ use gpui::{
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
- LayoutContext, ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle,
- WeakViewHandle, WindowContext,
+ LayoutContext, ModelHandle, MouseRegion, PaintContext, Quad, Task, View, ViewContext,
+ ViewHandle, WeakViewHandle, WindowContext,
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
@@ -2006,7 +2006,7 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut PaintContext<V>,
) -> Self::PaintState {
let background = theme::current(cx).editor.background;
@@ -2023,8 +2023,8 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
gpui::platform::MouseButton::Left,
move |_, _: &mut V, cx| {
- let window_id = cx.window_id();
- cx.app_context().focus(window_id, Some(child_view_id))
+ let window = cx.window();
+ cx.app_context().focus(window, Some(child_view_id))
},
),
);
@@ -2078,7 +2078,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
pane.update(cx, |pane, cx| {
@@ -2093,7 +2094,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@@ -2171,7 +2173,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@@ -2247,7 +2250,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// singleton view
@@ -2315,7 +2319,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
add_labeled_item(&pane, "A", false, cx);
@@ -2362,7 +2367,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@@ -2382,7 +2388,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
add_labeled_item(&pane, "A", true, cx);
@@ -2405,7 +2412,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@@ -2425,7 +2433,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@@ -2445,7 +2454,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
add_labeled_item(&pane, "A", false, cx);
@@ -28,11 +28,11 @@ where
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
drag_and_drop
- .currently_dragged::<DraggedItem>(cx.window_id())
+ .currently_dragged::<DraggedItem>(cx.window())
.map(|(drag_position, _)| drag_position)
.or_else(|| {
drag_and_drop
- .currently_dragged::<ProjectEntryId>(cx.window_id())
+ .currently_dragged::<ProjectEntryId>(cx.window())
.map(|(drag_position, _)| drag_position)
})
} else {
@@ -91,10 +91,10 @@ where
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
if drag_and_drop
- .currently_dragged::<DraggedItem>(cx.window_id())
+ .currently_dragged::<DraggedItem>(cx.window())
.is_some()
|| drag_and_drop
- .currently_dragged::<ProjectEntryId>(cx.window_id())
+ .currently_dragged::<ProjectEntryId>(cx.window())
.is_some()
{
cx.notify();
@@ -122,11 +122,11 @@ pub fn handle_dropped_item<V: View>(
}
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
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())
} 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)
} else {
@@ -595,7 +595,7 @@ mod element {
platform::{CursorStyle, MouseButton},
scene::MouseDrag,
AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion,
- RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
+ PaintContext, RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
};
use crate::{
@@ -856,7 +856,7 @@ mod element {
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
view: &mut Workspace,
- cx: &mut ViewContext<Workspace>,
+ cx: &mut PaintContext<Workspace>,
) -> Self::PaintState {
let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@@ -235,7 +235,7 @@ impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
impl PartialEq for Box<dyn SearchableItemHandle> {
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> {
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> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- (self.id(), self.window_id()).hash(state)
+ (self.id(), self.window().id()).hash(state)
}
}
@@ -8,8 +8,8 @@ use gpui::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription,
- View, ViewContext, ViewHandle, WindowContext,
+ AnyElement, AnyViewHandle, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
+ Subscription, View, ViewContext, ViewHandle, WindowContext,
};
pub trait StatusItemView: View {
@@ -231,7 +231,7 @@ impl Element<StatusBar> for StatusBarElement {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut StatusBar,
- cx: &mut ViewContext<StatusBar>,
+ cx: &mut PaintContext<StatusBar>,
) -> Self::PaintState {
let origin_y = bounds.upper_right().y();
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@@ -37,7 +37,7 @@ use gpui::{
},
AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
- WeakViewHandle, WindowContext,
+ WeakViewHandle, WindowContext, WindowHandle,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
use itertools::Itertools;
@@ -749,7 +749,7 @@ impl Workspace {
fn new_local(
abs_paths: Vec<PathBuf>,
app_state: Arc<AppState>,
- requesting_window_id: Option<usize>,
+ requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut AppContext,
) -> Task<(
WeakViewHandle<Workspace>,
@@ -793,20 +793,13 @@ impl Workspace {
DB.next_id().await.unwrap_or(0)
};
- let workspace = requesting_window_id
- .and_then(|window_id| {
- cx.update(|cx| {
- cx.replace_root_view(window_id, |cx| {
- Workspace::new(
- workspace_id,
- project_handle.clone(),
- app_state.clone(),
- cx,
- )
- })
- })
- })
- .unwrap_or_else(|| {
+ let window = if let Some(window) = requesting_window {
+ window.replace_root(&mut cx, |cx| {
+ Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+ });
+ window
+ } else {
+ {
let window_bounds_override = window_bounds_env_override(&cx);
let (bounds, display) = if let Some(bounds) = window_bounds_override {
(Some(bounds), None)
@@ -852,8 +845,12 @@ impl Workspace {
)
},
)
- .1
- });
+ }
+ };
+
+ // We haven't yielded the main thread since obtaining the window handle,
+ // so the window exists.
+ let workspace = window.root(&cx).unwrap();
(app_state.initialize_workspace)(
workspace.downgrade(),
@@ -864,7 +861,7 @@ impl Workspace {
.await
.log_err();
- cx.update_window(workspace.window_id(), |cx| cx.activate_window());
+ window.update(&mut cx, |cx| cx.activate_window());
let workspace = workspace.downgrade();
notify_if_database_failed(&workspace, &mut cx);
@@ -1235,14 +1232,14 @@ impl Workspace {
pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
cx.spawn(|mut cx| async move {
- let id = cx
- .window_ids()
+ let window = cx
+ .windows()
.into_iter()
- .find(|&id| cx.window_is_active(id));
- if let Some(id) = id {
+ .find(|window| window.is_active(&cx).unwrap_or(false));
+ if let Some(window) = window {
//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
- cx.remove_window(id);
+ window.remove(&mut cx);
}
})
.detach();
@@ -1253,11 +1250,11 @@ impl Workspace {
_: &CloseWindow,
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
- let window_id = cx.window_id();
+ let window = cx.window();
let prepare = self.prepare_to_close(false, cx);
Some(cx.spawn(|_, mut cx| async move {
if prepare.await? {
- cx.remove_window(window_id);
+ window.remove(&mut cx);
}
Ok(())
}))
@@ -1269,13 +1266,13 @@ impl Workspace {
cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> {
let active_call = self.active_call().cloned();
- let window_id = cx.window_id();
+ let window = cx.window();
cx.spawn(|this, mut cx| async move {
let workspace_count = cx
- .window_ids()
+ .windows()
.into_iter()
- .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
+ .filter(|window| window.root_is::<Workspace>())
.count();
if let Some(active_call) = active_call {
@@ -1283,11 +1280,11 @@ impl Workspace {
&& workspace_count == 1
&& active_call.read_with(&cx, |call, _| call.room().is_some())
{
- let answer = cx.prompt(
- window_id,
+ let answer = window.prompt(
PromptLevel::Warning,
"Do you want to leave the current call?",
&["Close window and hang up", "Cancel"],
+ &mut cx,
);
if let Some(mut answer) = answer {
@@ -1393,7 +1390,7 @@ impl Workspace {
paths: Vec<PathBuf>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
- let window_id = cx.window_id();
+ let window = cx.window().downcast::<Self>();
let is_remote = self.project.read(cx).is_remote();
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));
@@ -1405,15 +1402,15 @@ impl Workspace {
let app_state = self.app_state.clone();
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? {
return Ok(());
}
- Some(window_id)
+ window
} else {
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?;
Ok(())
})
@@ -3184,7 +3181,7 @@ impl Workspace {
let left_visible = left_dock.is_open();
let left_active_panel = left_dock.visible_panel().and_then(|panel| {
Some(
- cx.view_ui_name(panel.as_any().window_id(), panel.id())?
+ cx.view_ui_name(panel.as_any().window(), panel.id())?
.to_string(),
)
});
@@ -3197,7 +3194,7 @@ impl Workspace {
let right_visible = right_dock.is_open();
let right_active_panel = right_dock.visible_panel().and_then(|panel| {
Some(
- cx.view_ui_name(panel.as_any().window_id(), panel.id())?
+ cx.view_ui_name(panel.as_any().window(), panel.id())?
.to_string(),
)
});
@@ -3210,7 +3207,7 @@ impl Workspace {
let bottom_visible = bottom_dock.is_open();
let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
Some(
- cx.view_ui_name(panel.as_any().window_id(), panel.id())?
+ cx.view_ui_name(panel.as_any().window(), panel.id())?
.to_string(),
)
});
@@ -3830,9 +3827,9 @@ pub fn activate_workspace_for_project(
cx: &mut AsyncAppContext,
predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
) -> Option<WeakViewHandle<Workspace>> {
- for window_id in cx.window_ids() {
- let handle = cx
- .update_window(window_id, |cx| {
+ for window in cx.windows() {
+ let handle = window
+ .update(cx, |cx| {
if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
let project = workspace_handle.read(cx).project.clone();
if project.update(cx, &predicate) {
@@ -3859,7 +3856,7 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
pub fn open_paths(
abs_paths: &[PathBuf],
app_state: &Arc<AppState>,
- requesting_window_id: Option<usize>,
+ requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut AppContext,
) -> Task<
Result<(
@@ -3887,7 +3884,7 @@ pub fn open_paths(
} else {
Ok(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)
}
@@ -3948,18 +3945,23 @@ pub fn join_remote_project(
) -> Task<Result<()>> {
cx.spawn(|mut cx| async move {
let existing_workspace = cx
- .window_ids()
+ .windows()
.into_iter()
- .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
- .find(|workspace| {
- cx.read_window(workspace.window_id(), |cx| {
- workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
+ .find_map(|window| {
+ window.downcast::<Workspace>().and_then(|window| {
+ window.read_root_with(&cx, |workspace, cx| {
+ 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 {
- existing_workspace.downgrade()
+ existing_workspace
} else {
let active_call = cx.read(ActiveCall::global);
let room = active_call
@@ -3977,7 +3979,7 @@ pub fn join_remote_project(
.await?;
let window_bounds_override = window_bounds_env_override(&cx);
- let (_, workspace) = cx.add_window(
+ let window = cx.add_window(
(app_state.build_window_options)(
window_bounds_override,
None,
@@ -3985,6 +3987,7 @@ pub fn join_remote_project(
),
|cx| Workspace::new(0, project, app_state.clone(), cx),
);
+ let workspace = window.root(&cx).unwrap();
(app_state.initialize_workspace)(
workspace.downgrade(),
false,
@@ -3997,7 +4000,7 @@ pub fn join_remote_project(
workspace.downgrade()
};
- cx.activate_window(workspace.window_id());
+ workspace.window().activate(&mut cx);
cx.platform().activate(true);
workspace.update(&mut cx, |workspace, cx| {
@@ -4036,29 +4039,22 @@ pub fn join_remote_project(
pub fn restart(_: &Restart, cx: &mut AppContext) {
let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
cx.spawn(|mut cx| async move {
- let mut workspaces = cx
- .window_ids()
+ let mut workspace_windows = cx
+ .windows()
.into_iter()
- .filter_map(|window_id| {
- Some(
- cx.root_view(window_id)?
- .clone()
- .downcast::<Workspace>()?
- .downgrade(),
- )
- })
+ .filter_map(|window| window.downcast::<Workspace>())
.collect::<Vec<_>>();
// If multiple windows have unsaved changes, and need a save prompt,
// 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()) {
- let answer = cx.prompt(
- workspace.window_id(),
+ if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
+ let answer = window.prompt(
PromptLevel::Info,
"Are you sure you want to restart?",
&["Restart", "Cancel"],
+ &mut cx,
);
if let Some(mut answer) = answer {
@@ -4070,14 +4066,13 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
}
// If the user cancels any save prompt, then keep the app open.
- for workspace in workspaces {
- if !workspace
- .update(&mut cx, |workspace, cx| {
- workspace.prepare_to_close(true, cx)
- })?
- .await?
- {
- return Ok(());
+ for window in workspace_windows {
+ if let Some(close) = window.update_root(&mut cx, |workspace, cx| {
+ workspace.prepare_to_close(true, cx)
+ }) {
+ if !close.await? {
+ return Ok(());
+ }
}
}
cx.platform().restart();
@@ -4113,10 +4108,11 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
// Adding an item with no ambiguity renders the tab without detail.
- let item1 = cx.add_view(window_id, |_| {
+ let item1 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
item
@@ -4128,7 +4124,7 @@ mod tests {
// Adding an item that creates ambiguity increases the level of detail on
// both tabs.
- let item2 = cx.add_view(window_id, |_| {
+ let item2 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
@@ -4142,7 +4138,7 @@ mod tests {
// Adding an item that creates ambiguity increases the level of detail only
// on the ambiguous tabs. In this case, the ambiguity can't be resolved so
// we stop at the highest detail available.
- let item3 = cx.add_view(window_id, |_| {
+ let item3 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
@@ -4177,16 +4173,17 @@ mod tests {
.await;
let project = Project::test(fs, ["root1".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let worktree_id = project.read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
- let item1 = cx.add_view(window_id, |cx| {
+ let item1 = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
});
- let item2 = cx.add_view(window_id, |cx| {
+ let item2 = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
});
@@ -4200,17 +4197,11 @@ mod tests {
.map(|e| e.id)
);
});
- assert_eq!(
- cx.current_window_title(window_id).as_deref(),
- Some("one.txt — root1")
- );
+ assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1"));
// Add a second item to a non-empty pane
workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
- assert_eq!(
- cx.current_window_title(window_id).as_deref(),
- Some("two.txt — root1")
- );
+ assert_eq!(window.current_title(cx).as_deref(), Some("two.txt — root1"));
project.read_with(cx, |project, cx| {
assert_eq!(
project.active_entry(),
@@ -4226,10 +4217,7 @@ mod tests {
})
.await
.unwrap();
- assert_eq!(
- cx.current_window_title(window_id).as_deref(),
- Some("one.txt — root1")
- );
+ assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1"));
project.read_with(cx, |project, cx| {
assert_eq!(
project.active_entry(),
@@ -4247,16 +4235,13 @@ mod tests {
.await
.unwrap();
assert_eq!(
- cx.current_window_title(window_id).as_deref(),
+ window.current_title(cx).as_deref(),
Some("one.txt — root1, root2")
);
// Remove a project folder
project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
- assert_eq!(
- cx.current_window_title(window_id).as_deref(),
- Some("one.txt — root2")
- );
+ assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root2"));
}
#[gpui::test]
@@ -4267,18 +4252,19 @@ mod tests {
fs.insert_tree("/root", json!({ "one": "" })).await;
let project = Project::test(fs, ["root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
// When there are no dirty items, there's nothing to do.
- let item1 = cx.add_view(window_id, |_| TestItem::new());
+ let item1 = window.add_view(cx, |_| TestItem::new());
workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
assert!(task.await.unwrap());
// When there are dirty untitled items, prompt to save each one. If the user
// cancels any prompt, then abort.
- let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
- let item3 = cx.add_view(window_id, |cx| {
+ let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
+ let item3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@@ -4289,9 +4275,9 @@ mod tests {
});
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
cx.foreground().run_until_parked();
- cx.simulate_prompt_answer(window_id, 2 /* cancel */);
+ window.simulate_prompt_answer(2, cx); // cancel
cx.foreground().run_until_parked();
- assert!(!cx.has_pending_prompt(window_id));
+ assert!(!window.has_pending_prompt(cx));
assert!(!task.await.unwrap());
}
@@ -4302,26 +4288,27 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
- let item1 = cx.add_view(window_id, |cx| {
+ let item1 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
- let item2 = cx.add_view(window_id, |cx| {
+ let item2 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_conflict(true)
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
});
- let item3 = cx.add_view(window_id, |cx| {
+ let item3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_conflict(true)
.with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
});
- let item4 = cx.add_view(window_id, |cx| {
+ let item4 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new_untitled(cx)])
@@ -4349,10 +4336,10 @@ mod tests {
assert_eq!(pane.items_len(), 4);
assert_eq!(pane.active_item().unwrap().id(), item1.id());
});
- assert!(cx.has_pending_prompt(window_id));
+ assert!(window.has_pending_prompt(cx));
// Confirm saving item 1.
- cx.simulate_prompt_answer(window_id, 0);
+ window.simulate_prompt_answer(0, cx);
cx.foreground().run_until_parked();
// Item 1 is saved. There's a prompt to save item 3.
@@ -4363,10 +4350,10 @@ mod tests {
assert_eq!(pane.items_len(), 3);
assert_eq!(pane.active_item().unwrap().id(), item3.id());
});
- assert!(cx.has_pending_prompt(window_id));
+ assert!(window.has_pending_prompt(cx));
// Cancel saving item 3.
- cx.simulate_prompt_answer(window_id, 1);
+ window.simulate_prompt_answer(1, cx);
cx.foreground().run_until_parked();
// Item 3 is reloaded. There's a prompt to save item 4.
@@ -4377,10 +4364,10 @@ mod tests {
assert_eq!(pane.items_len(), 2);
assert_eq!(pane.active_item().unwrap().id(), item4.id());
});
- assert!(cx.has_pending_prompt(window_id));
+ assert!(window.has_pending_prompt(cx));
// Confirm saving item 4.
- cx.simulate_prompt_answer(window_id, 0);
+ window.simulate_prompt_answer(0, cx);
cx.foreground().run_until_parked();
// There's a prompt for a path for item 4.
@@ -4404,13 +4391,14 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
// Create several workspace items with single project entries, and two
// workspace items with multiple project entries.
let single_entry_items = (0..=4)
.map(|project_entry_id| {
- cx.add_view(window_id, |cx| {
+ window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(
@@ -4421,7 +4409,7 @@ mod tests {
})
})
.collect::<Vec<_>>();
- let item_2_3 = cx.add_view(window_id, |cx| {
+ let item_2_3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_singleton(false)
@@ -4430,7 +4418,7 @@ mod tests {
single_entry_items[3].read(cx).project_items[0].clone(),
])
});
- let item_3_4 = cx.add_view(window_id, |cx| {
+ let item_3_4 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_singleton(false)
@@ -4482,7 +4470,7 @@ mod tests {
&[ProjectEntryId::from_proto(0)]
);
});
- cx.simulate_prompt_answer(window_id, 0);
+ window.simulate_prompt_answer(0, cx);
cx.foreground().run_until_parked();
left_pane.read_with(cx, |pane, cx| {
@@ -4491,7 +4479,7 @@ mod tests {
&[ProjectEntryId::from_proto(2)]
);
});
- cx.simulate_prompt_answer(window_id, 0);
+ window.simulate_prompt_answer(0, cx);
cx.foreground().run_until_parked();
close.await.unwrap();
@@ -4507,10 +4495,11 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
- let item = cx.add_view(window_id, |cx| {
+ let item = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let item_id = item.id();
@@ -4529,7 +4518,7 @@ mod tests {
});
// Deactivating the window saves the file.
- cx.simulate_window_activation(None);
+ window.simulate_deactivation(cx);
deterministic.run_until_parked();
item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
@@ -4550,12 +4539,12 @@ mod tests {
item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
// Deactivating the window still saves the file.
- cx.simulate_window_activation(Some(window_id));
+ window.simulate_activation(cx);
item.update(cx, |item, cx| {
cx.focus_self();
item.is_dirty = true;
});
- cx.simulate_window_activation(None);
+ window.simulate_deactivation(cx);
deterministic.run_until_parked();
item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
@@ -4592,7 +4581,7 @@ mod tests {
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
.await
.unwrap();
- assert!(!cx.has_pending_prompt(window_id));
+ assert!(!window.has_pending_prompt(cx));
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.
@@ -4613,7 +4602,7 @@ mod tests {
let _close_items =
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
deterministic.run_until_parked();
- assert!(cx.has_pending_prompt(window_id));
+ assert!(window.has_pending_prompt(cx));
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
}
@@ -4624,9 +4613,10 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
- let item = cx.add_view(window_id, |cx| {
+ let item = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
@@ -4677,7 +4667,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| {
let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
@@ -4824,7 +4815,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
// Add panel_1 on the left, panel_2 on the right.
@@ -4979,7 +4971,7 @@ mod tests {
// If focus is transferred to another view that's not a panel or another pane, we still show
// the panel as zoomed.
- let focus_receiver = cx.add_view(window_id, |_| EmptyView);
+ let focus_receiver = window.add_view(cx, |_| EmptyView);
focus_receiver.update(cx, |_, cx| cx.focus_self());
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
@@ -1,5 +1,6 @@
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 = "# "
first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
brackets = [
{ start = "[", end = "]", close = true, newline = false },
@@ -102,7 +102,7 @@ impl LspAdapter for RustLspAdapter {
Some("rust-analyzer/flycheck".into())
}
- async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
+ fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
lazy_static! {
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`");
@@ -1,5 +1,5 @@
name = "TOML"
-path_suffixes = ["toml"]
+path_suffixes = ["Cargo.lock", "toml"]
line_comment = "# "
autoclose_before = ",]}"
brackets = [
@@ -14,7 +14,7 @@ use futures::{
channel::{mpsc, oneshot},
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 language::{LanguageRegistry, Point};
use log::LevelFilter;
@@ -43,7 +43,6 @@ use std::{
time::{Duration, SystemTime, UNIX_EPOCH},
};
use sum_tree::Bias;
-use terminal_view::{get_working_directory, TerminalSettings, TerminalView};
use util::{
channel::ReleaseChannel,
http::{self, HttpClient},
@@ -56,7 +55,7 @@ use fs::RealFs;
#[cfg(debug_assertions)]
use staff_mode::StaffMode;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
-use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace};
+use workspace::AppState;
use zed::{
assets::Assets,
build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
@@ -922,35 +921,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)] {
&[
("Go to file", &file_finder::Toggle),
@@ -179,13 +179,12 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
let app_state = workspace.app_state().clone();
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 {
let markdown = markdown.await.log_err();
- let content = to_string_pretty(
- &cx.debug_elements(window_id)
- .ok_or_else(|| anyhow!("could not debug elements for {window_id}"))?,
- )
+ let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| {
+ anyhow!("could not debug elements for window {}", window.id())
+ })?)
.unwrap();
workspace
.update(&mut cx, |workspace, cx| {
@@ -406,29 +405,22 @@ pub fn build_window_options(
fn quit(_: &Quit, cx: &mut gpui::AppContext) {
let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
cx.spawn(|mut cx| async move {
- let mut workspaces = cx
- .window_ids()
+ let mut workspace_windows = cx
+ .windows()
.into_iter()
- .filter_map(|window_id| {
- Some(
- cx.root_view(window_id)?
- .clone()
- .downcast::<Workspace>()?
- .downgrade(),
- )
- })
+ .filter_map(|window| window.downcast::<Workspace>())
.collect::<Vec<_>>();
// If multiple windows have unsaved changes, and need a save prompt,
// 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()) {
- let answer = cx.prompt(
- workspace.window_id(),
+ if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) {
+ let answer = window.prompt(
PromptLevel::Info,
"Are you sure you want to quit?",
&["Quit", "Cancel"],
+ &mut cx,
);
if let Some(mut answer) = answer {
@@ -440,14 +432,13 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
}
// If the user cancels any save prompt, then keep the app open.
- for workspace in workspaces {
- if !workspace
- .update(&mut cx, |workspace, cx| {
- workspace.prepare_to_close(true, cx)
- })?
- .await?
- {
- return Ok(());
+ for window in workspace_windows {
+ if let Some(close) = window.update_root(&mut cx, |workspace, cx| {
+ workspace.prepare_to_close(false, cx)
+ }) {
+ if close.await? {
+ return Ok(());
+ }
}
}
cx.platform().quit();
@@ -724,8 +715,8 @@ mod tests {
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
use fs::{FakeFs, Fs};
use gpui::{
- actions, elements::Empty, executor::Deterministic, Action, AnyElement, AppContext,
- AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
+ actions, elements::Empty, executor::Deterministic, Action, AnyElement, AnyWindowHandle,
+ AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
};
use language::LanguageRegistry;
use node_runtime::NodeRuntime;
@@ -782,17 +773,13 @@ mod tests {
})
.await
.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))
.await
.unwrap();
- assert_eq!(cx.window_ids().len(), 1);
- let workspace_1 = cx
- .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
- .unwrap()
- .downcast::<Workspace>()
- .unwrap();
+ assert_eq!(cx.windows().len(), 1);
+ let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
workspace_1.update(cx, |workspace, cx| {
assert_eq!(workspace.worktrees(cx).count(), 2);
assert!(workspace.left_dock().read(cx).is_open());
@@ -809,27 +796,22 @@ mod tests {
})
.await
.unwrap();
- assert_eq!(cx.window_ids().len(), 2);
+ assert_eq!(cx.windows().len(), 2);
// Replace existing windows
- let window_id = cx.window_ids()[0];
+ let window = cx.windows()[0].downcast::<Workspace>().unwrap();
cx.update(|cx| {
open_paths(
&[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
&app_state,
- Some(window_id),
+ Some(window),
cx,
)
})
.await
.unwrap();
- assert_eq!(cx.window_ids().len(), 2);
- let workspace_1 = cx
- .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
- .unwrap()
- .clone()
- .downcast::<Workspace>()
- .unwrap();
+ assert_eq!(cx.windows().len(), 2);
+ let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
workspace_1.update(cx, |workspace, cx| {
assert_eq!(
workspace
@@ -855,14 +837,11 @@ mod tests {
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
.await
.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.
- let workspace = cx
- .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
- .unwrap()
- .downcast::<Workspace>()
- .unwrap();
+ let window = cx.windows()[0].downcast::<Workspace>().unwrap();
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let editor = workspace.read_with(cx, |workspace, cx| {
workspace
@@ -871,19 +850,19 @@ mod tests {
.downcast::<Editor>()
.unwrap()
});
- assert!(!cx.is_window_edited(workspace.window_id()));
+ assert!(!window.is_edited(cx));
// Editing a buffer marks the window as edited.
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.
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.
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.
let close = pane.update(cx, |pane, cx| {
@@ -891,9 +870,10 @@ mod tests {
pane.close_active_item(&Default::default(), cx).unwrap()
});
executor.run_until_parked();
- cx.simulate_prompt_answer(workspace.window_id(), 1);
+
+ window.simulate_prompt_answer(1, cx);
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.
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
@@ -906,22 +886,22 @@ mod tests {
.downcast::<Editor>()
.unwrap()
});
- assert!(!cx.is_window_edited(workspace.window_id()));
+ assert!(!window.is_edited(cx));
// Editing the buffer marks the window as edited.
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
// buffer having unsaved changes.
- assert!(!cx.simulate_window_close(workspace.window_id()));
+ assert!(!window.simulate_close(cx));
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.
- cx.simulate_prompt_answer(workspace.window_id(), 1);
+ window.simulate_prompt_answer(1, cx);
executor.run_until_parked();
- assert_eq!(cx.window_ids().len(), 0);
+ assert_eq!(cx.windows().len(), 0);
}
#[gpui::test]
@@ -934,12 +914,13 @@ mod tests {
})
.await;
- let window_id = *cx.window_ids().first().unwrap();
- let workspace = cx
- .read_window(window_id, |cx| cx.root_view().clone())
+ let window = cx
+ .windows()
+ .first()
.unwrap()
.downcast::<Workspace>()
.unwrap();
+ let workspace = window.root(cx);
let editor = workspace.update(cx, |workspace, cx| {
workspace
@@ -982,7 +963,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@@ -1103,12 +1085,8 @@ mod tests {
cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
.await
.unwrap();
- assert_eq!(cx.window_ids().len(), 1);
- let workspace = cx
- .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
- .unwrap()
- .downcast::<Workspace>()
- .unwrap();
+ assert_eq!(cx.windows().len(), 1);
+ let workspace = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
#[track_caller]
fn assert_project_panel_selection(
@@ -1294,7 +1272,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
// Open a file within an existing worktree.
workspace
@@ -1320,7 +1299,7 @@ mod tests {
cx.read(|cx| assert!(editor.is_dirty(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();
editor.read_with(cx, |editor, cx| {
assert!(!editor.is_dirty(cx));
@@ -1335,11 +1314,12 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
// 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| {
workspace
.active_item(cx)
@@ -1394,7 +1374,7 @@ mod tests {
// Open the same newly-created file in another pane item. The new editor should reuse
// the same buffer.
- cx.dispatch_action(window_id, NewFile);
+ cx.dispatch_action(window.into(), NewFile);
workspace
.update(cx, |workspace, cx| {
workspace.split_and_clone(
@@ -1428,10 +1408,11 @@ mod tests {
let project = Project::test(app_state.fs.clone(), [], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
// 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| {
workspace
.active_item(cx)
@@ -1479,7 +1460,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@@ -1501,7 +1483,7 @@ mod tests {
(editor.downgrade(), buffer)
});
- cx.dispatch_action(window_id, pane::SplitRight);
+ cx.dispatch_action(window.into(), pane::SplitRight);
let editor_2 = cx.update(|cx| {
let pane_2 = workspace.read(cx).active_pane().clone();
assert_ne!(pane_1, pane_2);
@@ -1511,7 +1493,7 @@ mod tests {
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();
workspace.read_with(cx, |workspace, _| {
@@ -1519,9 +1501,9 @@ mod tests {
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.simulate_prompt_answer(window_id, 1);
+ window.simulate_prompt_answer(1, cx);
cx.foreground().run_until_parked();
workspace.read_with(cx, |workspace, cx| {
@@ -1553,7 +1535,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let entries = cx.read(|cx| workspace.file_project_paths(cx));
@@ -1830,7 +1814,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let entries = cx.read(|cx| workspace.file_project_paths(cx));
@@ -2072,11 +2058,11 @@ mod tests {
cx.foreground().run_until_parked();
- let (window_id, _view) = cx.add_window(|_| TestView);
+ let window = cx.add_window(|_| TestView);
// Test loading the keymap base at all
assert_key_bindings_for(
- window_id,
+ window.into(),
cx,
vec![("backspace", &A), ("k", &ActivatePreviousPane)],
line!(),
@@ -2103,7 +2089,7 @@ mod tests {
cx.foreground().run_until_parked();
assert_key_bindings_for(
- window_id,
+ window.into(),
cx,
vec![("backspace", &B), ("k", &ActivatePreviousPane)],
line!(),
@@ -2126,7 +2112,7 @@ mod tests {
cx.foreground().run_until_parked();
assert_key_bindings_for(
- window_id,
+ window.into(),
cx,
vec![("backspace", &B), ("[", &ActivatePrevItem)],
line!(),
@@ -2134,7 +2120,7 @@ mod tests {
#[track_caller]
fn assert_key_bindings_for<'a>(
- window_id: usize,
+ window: AnyWindowHandle,
cx: &TestAppContext,
actions: Vec<(&'static str, &'a dyn Action)>,
line: u32,
@@ -2142,7 +2128,7 @@ mod tests {
for (key, action) in actions {
// assert that...
assert!(
- cx.available_actions(window_id, 0)
+ cx.available_actions(window, 0)
.into_iter()
.any(|(_, bound_action, b)| {
// action names match...
@@ -2242,11 +2228,11 @@ mod tests {
cx.foreground().run_until_parked();
- let (window_id, _view) = cx.add_window(|_| TestView);
+ let window = cx.add_window(|_| TestView);
// Test loading the keymap base at all
assert_key_bindings_for(
- window_id,
+ window.into(),
cx,
vec![("backspace", &A), ("k", &ActivatePreviousPane)],
line!(),
@@ -2272,7 +2258,12 @@ mod tests {
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
fs.save(
@@ -2290,11 +2281,11 @@ mod tests {
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]
fn assert_key_bindings_for<'a>(
- window_id: usize,
+ window: AnyWindowHandle,
cx: &TestAppContext,
actions: Vec<(&'static str, &'a dyn Action)>,
line: u32,
@@ -2302,7 +2293,7 @@ mod tests {
for (key, action) in actions {
// assert that...
assert!(
- cx.available_actions(window_id, 0)
+ cx.available_actions(window, 0)
.into_iter()
.any(|(_, bound_action, b)| {
// action names match...
@@ -49,7 +49,7 @@ export default function status_bar(): any {
},
state: {
hovered: {
- message: text(layer, "sans"),
+ message: text(layer, "sans", { size: "xs" }),
icon_color: foreground(layer),
background: background(layer, "hovered"),
},
@@ -1,4 +1,4 @@
-import { Scale, Color } from "chroma-js"
+import chroma, { Scale, Color } from "chroma-js"
import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax"
export { Syntax, ThemeSyntax, SyntaxHighlightStyle }
import {
@@ -32,6 +32,7 @@ export interface Theme {
players: Players
syntax?: Partial<ThemeSyntax>
+ color_family: ColorFamily
}
export interface Meta {
@@ -69,6 +70,15 @@ export interface Players {
"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 {
blur: number
color: string
@@ -162,6 +172,8 @@ export function create_theme(theme: ThemeConfig): Theme {
"7": player(ramps.yellow),
}
+ const color_family = build_color_family(ramps)
+
return {
name,
is_light,
@@ -177,6 +189,7 @@ export function create_theme(theme: ThemeConfig): Theme {
players,
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 {
return {
base: build_style_set(ramps.neutral, 0.2, 1),