mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 19:08:00 +03:00
Merge remote-tracking branch 'origin/main' into assistant-2
This commit is contained in:
commit
9eb98122ec
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -7112,6 +7112,17 @@ dependencies = [
|
|||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick_action_bar2"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"editor2",
|
||||||
|
"gpui2",
|
||||||
|
"search2",
|
||||||
|
"ui2",
|
||||||
|
"workspace2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.33"
|
version = "1.0.33"
|
||||||
@ -11932,6 +11943,7 @@ dependencies = [
|
|||||||
"postage",
|
"postage",
|
||||||
"project2",
|
"project2",
|
||||||
"project_panel2",
|
"project_panel2",
|
||||||
|
"quick_action_bar2",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"rope2",
|
"rope2",
|
||||||
|
@ -90,6 +90,7 @@ members = [
|
|||||||
"crates/project_panel",
|
"crates/project_panel",
|
||||||
"crates/project_panel2",
|
"crates/project_panel2",
|
||||||
"crates/project_symbols",
|
"crates/project_symbols",
|
||||||
|
"crates/quick_action_bar2",
|
||||||
"crates/recent_projects",
|
"crates/recent_projects",
|
||||||
"crates/rope",
|
"crates/rope",
|
||||||
"crates/rpc",
|
"crates/rpc",
|
||||||
|
@ -17,18 +17,8 @@
|
|||||||
"cmd-enter": "menu::SecondaryConfirm",
|
"cmd-enter": "menu::SecondaryConfirm",
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"ctrl-c": "menu::Cancel",
|
"ctrl-c": "menu::Cancel",
|
||||||
"cmd-{": "pane::ActivatePrevItem",
|
|
||||||
"cmd-}": "pane::ActivateNextItem",
|
|
||||||
"alt-cmd-left": "pane::ActivatePrevItem",
|
|
||||||
"alt-cmd-right": "pane::ActivateNextItem",
|
|
||||||
"cmd-w": "pane::CloseActiveItem",
|
|
||||||
"alt-cmd-t": "pane::CloseInactiveItems",
|
|
||||||
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
|
||||||
"cmd-k u": "pane::CloseCleanItems",
|
|
||||||
"cmd-k cmd-w": "pane::CloseAllItems",
|
|
||||||
"cmd-shift-w": "workspace::CloseWindow",
|
"cmd-shift-w": "workspace::CloseWindow",
|
||||||
"cmd-s": "workspace::Save",
|
"cmd-o": "workspace::Open",
|
||||||
"cmd-shift-s": "workspace::SaveAs",
|
|
||||||
"cmd-=": "zed::IncreaseBufferFontSize",
|
"cmd-=": "zed::IncreaseBufferFontSize",
|
||||||
"cmd-+": "zed::IncreaseBufferFontSize",
|
"cmd-+": "zed::IncreaseBufferFontSize",
|
||||||
"cmd--": "zed::DecreaseBufferFontSize",
|
"cmd--": "zed::DecreaseBufferFontSize",
|
||||||
@ -38,15 +28,7 @@
|
|||||||
"cmd-h": "zed::Hide",
|
"cmd-h": "zed::Hide",
|
||||||
"alt-cmd-h": "zed::HideOthers",
|
"alt-cmd-h": "zed::HideOthers",
|
||||||
"cmd-m": "zed::Minimize",
|
"cmd-m": "zed::Minimize",
|
||||||
"ctrl-cmd-f": "zed::ToggleFullScreen",
|
"ctrl-cmd-f": "zed::ToggleFullScreen"
|
||||||
"cmd-n": "workspace::NewFile",
|
|
||||||
"cmd-shift-n": "workspace::NewWindow",
|
|
||||||
"cmd-o": "workspace::Open",
|
|
||||||
"alt-cmd-o": "projects::OpenRecent",
|
|
||||||
"alt-cmd-b": "branches::OpenRecent",
|
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
|
||||||
"ctrl-`": "terminal_panel::ToggleFocus",
|
|
||||||
"shift-escape": "workspace::ToggleZoom"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -284,6 +266,15 @@
|
|||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"cmd-{": "pane::ActivatePrevItem",
|
||||||
|
"cmd-}": "pane::ActivateNextItem",
|
||||||
|
"alt-cmd-left": "pane::ActivatePrevItem",
|
||||||
|
"alt-cmd-right": "pane::ActivateNextItem",
|
||||||
|
"cmd-w": "pane::CloseActiveItem",
|
||||||
|
"alt-cmd-t": "pane::CloseInactiveItems",
|
||||||
|
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
||||||
|
"cmd-k u": "pane::CloseCleanItems",
|
||||||
|
"cmd-k cmd-w": "pane::CloseAllItems",
|
||||||
"cmd-f": "project_search::ToggleFocus",
|
"cmd-f": "project_search::ToggleFocus",
|
||||||
"cmd-g": "search::SelectNextMatch",
|
"cmd-g": "search::SelectNextMatch",
|
||||||
"cmd-shift-g": "search::SelectPrevMatch",
|
"cmd-shift-g": "search::SelectPrevMatch",
|
||||||
@ -389,6 +380,15 @@
|
|||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"alt-cmd-o": "projects::OpenRecent",
|
||||||
|
"alt-cmd-b": "branches::OpenRecent",
|
||||||
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
|
"cmd-s": "workspace::Save",
|
||||||
|
"cmd-shift-s": "workspace::SaveAs",
|
||||||
|
"cmd-n": "workspace::NewFile",
|
||||||
|
"cmd-shift-n": "workspace::NewWindow",
|
||||||
|
"ctrl-`": "terminal_panel::ToggleFocus",
|
||||||
|
"shift-escape": "workspace::ToggleZoom",
|
||||||
"cmd-1": ["workspace::ActivatePane", 0],
|
"cmd-1": ["workspace::ActivatePane", 0],
|
||||||
"cmd-2": ["workspace::ActivatePane", 1],
|
"cmd-2": ["workspace::ActivatePane", 1],
|
||||||
"cmd-3": ["workspace::ActivatePane", 2],
|
"cmd-3": ["workspace::ActivatePane", 2],
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
Component, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
|
Div, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
|
||||||
ViewContext, WeakView,
|
ViewContext, WeakView,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{ButtonCommon, ButtonLike, ButtonStyle, Clickable, Disableable, Label};
|
use ui::{prelude::*, ButtonLike, ButtonStyle, Label};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{ItemEvent, ItemHandle},
|
item::{ItemEvent, ItemHandle},
|
||||||
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||||
@ -36,54 +36,51 @@ impl EventEmitter<Event> for Breadcrumbs {}
|
|||||||
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
|
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
|
||||||
|
|
||||||
impl Render for Breadcrumbs {
|
impl Render for Breadcrumbs {
|
||||||
type Element = Component<ButtonLike>;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
let button = ButtonLike::new("breadcrumbs")
|
let element = h_stack().text_ui();
|
||||||
.style(ButtonStyle::Transparent)
|
|
||||||
.disabled(true);
|
|
||||||
|
|
||||||
let active_item = match &self.active_item {
|
let Some(active_item) = &self
|
||||||
Some(active_item) => active_item,
|
.active_item
|
||||||
None => return button.into_element(),
|
.as_ref()
|
||||||
|
.filter(|item| item.downcast::<editor::Editor>().is_some())
|
||||||
|
else {
|
||||||
|
return element;
|
||||||
};
|
};
|
||||||
let not_editor = active_item.downcast::<editor::Editor>().is_none();
|
|
||||||
|
|
||||||
let breadcrumbs = match active_item.breadcrumbs(cx.theme(), cx) {
|
let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
|
||||||
Some(breadcrumbs) => breadcrumbs,
|
return element;
|
||||||
None => return button.into_element(),
|
};
|
||||||
}
|
|
||||||
.into_iter()
|
let highlighted_segments = segments.into_iter().map(|segment| {
|
||||||
.map(|breadcrumb| {
|
StyledText::new(segment.text)
|
||||||
StyledText::new(breadcrumb.text)
|
.with_highlights(&cx.text_style(), segment.highlights.unwrap_or_default())
|
||||||
.with_highlights(&cx.text_style(), breadcrumb.highlights.unwrap_or_default())
|
|
||||||
.into_any()
|
.into_any()
|
||||||
});
|
});
|
||||||
|
let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
|
||||||
|
Label::new("›").into_any_element()
|
||||||
|
});
|
||||||
|
|
||||||
let button = button.children(Itertools::intersperse_with(breadcrumbs, || {
|
element.child(
|
||||||
Label::new(" › ").into_any_element()
|
ButtonLike::new("toggle outline view")
|
||||||
}));
|
.style(ButtonStyle::Subtle)
|
||||||
|
.child(h_stack().gap_1().children(breadcrumbs))
|
||||||
if not_editor || !self.pane_focused {
|
// We disable the button when it is not focused
|
||||||
return button.into_element();
|
// due to ... @julia what was the reason again?
|
||||||
}
|
.disabled(!self.pane_focused)
|
||||||
|
.on_click(move |_, _cx| {
|
||||||
// let this = cx.view().downgrade();
|
todo!("outline::toggle");
|
||||||
button
|
// this.update(cx, |this, cx| {
|
||||||
.style(ButtonStyle::Filled)
|
// if let Some(workspace) = this.workspace.upgrade() {
|
||||||
.disabled(false)
|
// workspace.update(cx, |_workspace, _cx| {
|
||||||
.on_click(move |_, _cx| {
|
// outline::toggle(workspace, &Default::default(), cx)
|
||||||
todo!("outline::toggle");
|
// })
|
||||||
// this.update(cx, |this, cx| {
|
// }
|
||||||
// if let Some(workspace) = this.workspace.upgrade() {
|
// })
|
||||||
// workspace.update(cx, |_workspace, _cx| {
|
// .ok();
|
||||||
// outline::toggle(workspace, &Default::default(), cx)
|
}),
|
||||||
// })
|
)
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .ok();
|
|
||||||
})
|
|
||||||
.into_element()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,10 @@ use collab_ui::notifications::project_shared_notification::ProjectSharedNotifica
|
|||||||
use editor::{Editor, ExcerptRange, MultiBuffer};
|
use editor::{Editor, ExcerptRange, MultiBuffer};
|
||||||
use gpui::{executor::Deterministic, geometry::vector::vec2f, TestAppContext, ViewHandle};
|
use gpui::{executor::Deterministic, geometry::vector::vec2f, TestAppContext, ViewHandle};
|
||||||
use live_kit_client::MacOSDisplay;
|
use live_kit_client::MacOSDisplay;
|
||||||
|
use project::project_settings::ProjectSettings;
|
||||||
use rpc::proto::PeerId;
|
use rpc::proto::PeerId;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use settings::SettingsStore;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{test::TestPanel, DockPosition},
|
dock::{test::TestPanel, DockPosition},
|
||||||
@ -1602,6 +1604,141 @@ async fn test_following_across_workspaces(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_following_into_excluded_file(
|
||||||
|
deterministic: Arc<Deterministic>,
|
||||||
|
mut cx_a: &mut TestAppContext,
|
||||||
|
mut cx_b: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
deterministic.forbid_parking();
|
||||||
|
|
||||||
|
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;
|
||||||
|
for cx in [&mut cx_a, &mut cx_b] {
|
||||||
|
cx.update(|cx| {
|
||||||
|
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||||
|
store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
|
||||||
|
project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
server
|
||||||
|
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||||
|
.await;
|
||||||
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
let active_call_b = cx_b.read(ActiveCall::global);
|
||||||
|
|
||||||
|
cx_a.update(editor::init);
|
||||||
|
cx_b.update(editor::init);
|
||||||
|
|
||||||
|
client_a
|
||||||
|
.fs()
|
||||||
|
.insert_tree(
|
||||||
|
"/a",
|
||||||
|
json!({
|
||||||
|
".git": {
|
||||||
|
"COMMIT_EDITMSG": "write your commit message here",
|
||||||
|
},
|
||||||
|
"1.txt": "one\none\none",
|
||||||
|
"2.txt": "two\ntwo\ntwo",
|
||||||
|
"3.txt": "three\nthree\nthree",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||||
|
active_call_a
|
||||||
|
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
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;
|
||||||
|
active_call_b
|
||||||
|
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let window_a = client_a.build_workspace(&project_a, cx_a);
|
||||||
|
let workspace_a = window_a.root(cx_a);
|
||||||
|
let peer_id_a = client_a.peer_id().unwrap();
|
||||||
|
let window_b = client_b.build_workspace(&project_b, cx_b);
|
||||||
|
let workspace_b = window_b.root(cx_b);
|
||||||
|
|
||||||
|
// Client A opens editors for a regular file and an excluded file.
|
||||||
|
let editor_for_regular = workspace_a
|
||||||
|
.update(cx_a, |workspace, cx| {
|
||||||
|
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.unwrap();
|
||||||
|
let editor_for_excluded_a = workspace_a
|
||||||
|
.update(cx_a, |workspace, cx| {
|
||||||
|
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Client A updates their selections in those editors
|
||||||
|
editor_for_regular.update(cx_a, |editor, cx| {
|
||||||
|
editor.handle_input("a", cx);
|
||||||
|
editor.handle_input("b", cx);
|
||||||
|
editor.handle_input("c", cx);
|
||||||
|
editor.select_left(&Default::default(), cx);
|
||||||
|
assert_eq!(editor.selections.ranges(cx), vec![3..2]);
|
||||||
|
});
|
||||||
|
editor_for_excluded_a.update(cx_a, |editor, cx| {
|
||||||
|
editor.select_all(&Default::default(), cx);
|
||||||
|
editor.handle_input("new commit message", cx);
|
||||||
|
editor.select_left(&Default::default(), cx);
|
||||||
|
assert_eq!(editor.selections.ranges(cx), vec![18..17]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When client B starts following client A, currently visible file is replicated
|
||||||
|
workspace_b
|
||||||
|
.update(cx_b, |workspace, cx| {
|
||||||
|
workspace.follow(peer_id_a, cx).unwrap()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let editor_for_excluded_b = workspace_b.read_with(cx_b, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.active_item(cx)
|
||||||
|
.unwrap()
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
|
||||||
|
Some((worktree_id, ".git/COMMIT_EDITMSG").into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
editor_for_excluded_b.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||||
|
vec![18..17]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Changes from B to the excluded file are replicated in A's editor
|
||||||
|
editor_for_excluded_b.update(cx_b, |editor, cx| {
|
||||||
|
editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
|
||||||
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
editor_for_excluded_a.update(cx_a, |editor, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor.text(cx),
|
||||||
|
"new commit messag\nCo-Authored-By: B <b@b.b>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn visible_push_notifications(
|
fn visible_push_notifications(
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) -> Vec<gpui::ViewHandle<ProjectSharedNotification>> {
|
) -> Vec<gpui::ViewHandle<ProjectSharedNotification>> {
|
||||||
|
@ -2981,11 +2981,10 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
let entry = project_b
|
let entry = project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "c.txt"), false, cx)
|
||||||
.create_entry((worktree_id, "c.txt"), false, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
worktree_a.read_with(cx_a, |worktree, _| {
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3010,7 +3009,6 @@ async fn test_fs_operations(
|
|||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project.rename_entry(entry.id, Path::new("d.txt"), cx)
|
project.rename_entry(entry.id, Path::new("d.txt"), cx)
|
||||||
})
|
})
|
||||||
.unwrap()
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
worktree_a.read_with(cx_a, |worktree, _| {
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
@ -3034,11 +3032,10 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
let dir_entry = project_b
|
let dir_entry = project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "DIR"), true, cx)
|
||||||
.create_entry((worktree_id, "DIR"), true, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
worktree_a.read_with(cx_a, |worktree, _| {
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3061,25 +3058,19 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
|
||||||
.create_entry((worktree_id, "DIR/e.txt"), false, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
|
||||||
.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
|
||||||
.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -3120,9 +3111,7 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.copy_entry(entry.id, Path::new("f.txt"), cx)
|
||||||
.copy_entry(entry.id, Path::new("f.txt"), cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -665,7 +665,6 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
ensure_project_shared(&project, client, cx).await;
|
ensure_project_shared(&project, client, cx).await;
|
||||||
project
|
project
|
||||||
.update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
|
.update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
|
||||||
.unwrap()
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
// use call::ActiveCall;
|
// use call::ActiveCall;
|
||||||
// use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
|
// use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
|
||||||
// use editor::{Editor, ExcerptRange, MultiBuffer};
|
// use editor::{Editor, ExcerptRange, MultiBuffer};
|
||||||
// use gpui::{BackgroundExecutor, TestAppContext, View};
|
// use gpui::{point, BackgroundExecutor, TestAppContext, View, VisualTestContext, WindowContext};
|
||||||
// use live_kit_client::MacOSDisplay;
|
// use live_kit_client::MacOSDisplay;
|
||||||
|
// use project::project_settings::ProjectSettings;
|
||||||
// use rpc::proto::PeerId;
|
// use rpc::proto::PeerId;
|
||||||
// use serde_json::json;
|
// use serde_json::json;
|
||||||
|
// use settings::SettingsStore;
|
||||||
// use std::borrow::Cow;
|
// use std::borrow::Cow;
|
||||||
// use workspace::{
|
// use workspace::{
|
||||||
// dock::{test::TestPanel, DockPosition},
|
// dock::{test::TestPanel, DockPosition},
|
||||||
@ -24,7 +26,7 @@
|
|||||||
// cx_c: &mut TestAppContext,
|
// cx_c: &mut TestAppContext,
|
||||||
// cx_d: &mut TestAppContext,
|
// cx_d: &mut TestAppContext,
|
||||||
// ) {
|
// ) {
|
||||||
// let mut server = TestServer::start(&executor).await;
|
// let mut server = TestServer::start(executor.clone()).await;
|
||||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
// let client_c = server.create_client(cx_c, "user_c").await;
|
// let client_c = server.create_client(cx_c, "user_c").await;
|
||||||
@ -71,12 +73,22 @@
|
|||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// let window_a = client_a.build_workspace(&project_a, cx_a);
|
// let window_a = client_a.build_workspace(&project_a, cx_a);
|
||||||
// let workspace_a = window_a.root(cx_a);
|
// let workspace_a = window_a.root(cx_a).unwrap();
|
||||||
// let window_b = client_b.build_workspace(&project_b, cx_b);
|
// let window_b = client_b.build_workspace(&project_b, cx_b);
|
||||||
// let workspace_b = window_b.root(cx_b);
|
// let workspace_b = window_b.root(cx_b).unwrap();
|
||||||
|
|
||||||
|
// todo!("could be wrong")
|
||||||
|
// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
|
||||||
|
// let cx_a = &mut cx_a;
|
||||||
|
// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
|
||||||
|
// let cx_b = &mut cx_b;
|
||||||
|
// let mut cx_c = VisualTestContext::from_window(*window_c, cx_c);
|
||||||
|
// let cx_c = &mut cx_c;
|
||||||
|
// let mut cx_d = VisualTestContext::from_window(*window_d, cx_d);
|
||||||
|
// let cx_d = &mut cx_d;
|
||||||
|
|
||||||
// // Client A opens some editors.
|
// // Client A opens some editors.
|
||||||
// let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
|
// let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
|
||||||
// let editor_a1 = workspace_a
|
// let editor_a1 = workspace_a
|
||||||
// .update(cx_a, |workspace, cx| {
|
// .update(cx_a, |workspace, cx| {
|
||||||
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
@ -132,8 +144,8 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// cx_c.foreground().run_until_parked();
|
// cx_c.executor().run_until_parked();
|
||||||
// let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
|
// let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// workspace
|
// workspace
|
||||||
// .active_item(cx)
|
// .active_item(cx)
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
@ -145,19 +157,19 @@
|
|||||||
// Some((worktree_id, "2.txt").into())
|
// Some((worktree_id, "2.txt").into())
|
||||||
// );
|
// );
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
// editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||||
// vec![2..1]
|
// vec![2..1]
|
||||||
// );
|
// );
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
// editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||||
// vec![3..2]
|
// vec![3..2]
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// cx_c.foreground().run_until_parked();
|
// cx_c.executor().run_until_parked();
|
||||||
// let active_call_c = cx_c.read(ActiveCall::global);
|
// let active_call_c = cx_c.read(ActiveCall::global);
|
||||||
// let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
// let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||||
// let window_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);
|
// let workspace_c = window_c.root(cx_c).unwrap();
|
||||||
// active_call_c
|
// active_call_c
|
||||||
// .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
|
// .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
|
||||||
// .await
|
// .await
|
||||||
@ -172,10 +184,13 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// cx_d.foreground().run_until_parked();
|
// cx_d.executor().run_until_parked();
|
||||||
// let active_call_d = cx_d.read(ActiveCall::global);
|
// let active_call_d = cx_d.read(ActiveCall::global);
|
||||||
// let project_d = client_d.build_remote_project(project_id, cx_d).await;
|
// let project_d = client_d.build_remote_project(project_id, cx_d).await;
|
||||||
// let workspace_d = client_d.build_workspace(&project_d, cx_d).root(cx_d);
|
// let workspace_d = client_d
|
||||||
|
// .build_workspace(&project_d, cx_d)
|
||||||
|
// .root(cx_d)
|
||||||
|
// .unwrap();
|
||||||
// active_call_d
|
// active_call_d
|
||||||
// .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
|
// .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
|
||||||
// .await
|
// .await
|
||||||
@ -183,7 +198,7 @@
|
|||||||
// drop(project_d);
|
// drop(project_d);
|
||||||
|
|
||||||
// // All clients see that clients B and C are following client A.
|
// // All clients see that clients B and C are following client A.
|
||||||
// cx_c.foreground().run_until_parked();
|
// cx_c.executor().run_until_parked();
|
||||||
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// followers_by_leader(project_id, cx),
|
// followers_by_leader(project_id, cx),
|
||||||
@ -198,7 +213,7 @@
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
// // All clients see that clients B is following client A.
|
// // All clients see that clients B is following client A.
|
||||||
// cx_c.foreground().run_until_parked();
|
// cx_c.executor().run_until_parked();
|
||||||
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// followers_by_leader(project_id, cx),
|
// followers_by_leader(project_id, cx),
|
||||||
@ -216,7 +231,7 @@
|
|||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// // All clients see that clients B and C are following client A.
|
// // All clients see that clients B and C are following client A.
|
||||||
// cx_c.foreground().run_until_parked();
|
// cx_c.executor().run_until_parked();
|
||||||
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// followers_by_leader(project_id, cx),
|
// followers_by_leader(project_id, cx),
|
||||||
@ -240,7 +255,7 @@
|
|||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// // All clients see that D is following C
|
// // All clients see that D is following C
|
||||||
// cx_d.foreground().run_until_parked();
|
// cx_d.executor().run_until_parked();
|
||||||
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// followers_by_leader(project_id, cx),
|
// followers_by_leader(project_id, cx),
|
||||||
@ -257,7 +272,7 @@
|
|||||||
// cx_c.drop_last(workspace_c);
|
// cx_c.drop_last(workspace_c);
|
||||||
|
|
||||||
// // Clients A and B see that client B is following A, and client C is not present in the followers.
|
// // Clients A and B see that client B is following A, and client C is not present in the followers.
|
||||||
// cx_c.foreground().run_until_parked();
|
// cx_c.executor().run_until_parked();
|
||||||
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// followers_by_leader(project_id, cx),
|
// followers_by_leader(project_id, cx),
|
||||||
@ -271,12 +286,15 @@
|
|||||||
// workspace.activate_item(&editor_a1, cx)
|
// workspace.activate_item(&editor_a1, cx)
|
||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// workspace_b.read_with(cx_b, |workspace, cx| {
|
// workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
|
// assert_eq!(
|
||||||
|
// workspace.active_item(cx).unwrap().item_id(),
|
||||||
|
// editor_b1.item_id()
|
||||||
|
// );
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// // When client A opens a multibuffer, client B does so as well.
|
// // When client A opens a multibuffer, client B does so as well.
|
||||||
// let multibuffer_a = cx_a.add_model(|cx| {
|
// let multibuffer_a = cx_a.build_model(|cx| {
|
||||||
// let buffer_a1 = project_a.update(cx, |project, cx| {
|
// let buffer_a1 = project_a.update(cx, |project, cx| {
|
||||||
// project
|
// project
|
||||||
// .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
|
// .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
|
||||||
@ -308,12 +326,12 @@
|
|||||||
// });
|
// });
|
||||||
// let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
|
// let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
|
||||||
// let editor =
|
// let editor =
|
||||||
// cx.add_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
|
// cx.build_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
|
||||||
// workspace.add_item(Box::new(editor.clone()), cx);
|
// workspace.add_item(Box::new(editor.clone()), cx);
|
||||||
// editor
|
// editor
|
||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// let multibuffer_editor_b = workspace_b.read_with(cx_b, |workspace, cx| {
|
// let multibuffer_editor_b = workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// workspace
|
// workspace
|
||||||
// .active_item(cx)
|
// .active_item(cx)
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
@ -321,8 +339,8 @@
|
|||||||
// .unwrap()
|
// .unwrap()
|
||||||
// });
|
// });
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// multibuffer_editor_a.read_with(cx_a, |editor, cx| editor.text(cx)),
|
// multibuffer_editor_a.update(cx_a, |editor, cx| editor.text(cx)),
|
||||||
// multibuffer_editor_b.read_with(cx_b, |editor, cx| editor.text(cx)),
|
// multibuffer_editor_b.update(cx_b, |editor, cx| editor.text(cx)),
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// // When client A navigates back and forth, client B does so as well.
|
// // When client A navigates back and forth, client B does so as well.
|
||||||
@ -333,8 +351,11 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// workspace_b.read_with(cx_b, |workspace, cx| {
|
// workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
|
// assert_eq!(
|
||||||
|
// workspace.active_item(cx).unwrap().item_id(),
|
||||||
|
// editor_b1.item_id()
|
||||||
|
// );
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// workspace_a
|
// workspace_a
|
||||||
@ -344,8 +365,11 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// workspace_b.read_with(cx_b, |workspace, cx| {
|
// workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
|
// assert_eq!(
|
||||||
|
// workspace.active_item(cx).unwrap().item_id(),
|
||||||
|
// editor_b2.item_id()
|
||||||
|
// );
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// workspace_a
|
// workspace_a
|
||||||
@ -355,8 +379,11 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// workspace_b.read_with(cx_b, |workspace, cx| {
|
// workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
|
// assert_eq!(
|
||||||
|
// workspace.active_item(cx).unwrap().item_id(),
|
||||||
|
// editor_b1.item_id()
|
||||||
|
// );
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// // Changes to client A's editor are reflected on client B.
|
// // Changes to client A's editor are reflected on client B.
|
||||||
@ -364,20 +391,20 @@
|
|||||||
// editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
|
// editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
|
||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// editor_b1.read_with(cx_b, |editor, cx| {
|
// editor_b1.update(cx_b, |editor, cx| {
|
||||||
// assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
|
// assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
|
// editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// editor_b1.read_with(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
|
// editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
|
||||||
|
|
||||||
// editor_a1.update(cx_a, |editor, cx| {
|
// editor_a1.update(cx_a, |editor, cx| {
|
||||||
// editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
|
// editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
|
||||||
// editor.set_scroll_position(vec2f(0., 100.), cx);
|
// editor.set_scroll_position(point(0., 100.), cx);
|
||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// editor_b1.read_with(cx_b, |editor, cx| {
|
// editor_b1.update(cx_b, |editor, cx| {
|
||||||
// assert_eq!(editor.selections.ranges(cx), &[3..3]);
|
// assert_eq!(editor.selections.ranges(cx), &[3..3]);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
@ -390,11 +417,11 @@
|
|||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, cx| workspace
|
// workspace_b.update(cx_b, |workspace, cx| workspace
|
||||||
// .active_item(cx)
|
// .active_item(cx)
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
// .id()),
|
// .item_id()),
|
||||||
// editor_b1.id()
|
// editor_b1.item_id()
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// // Client A starts following client B.
|
// // Client A starts following client B.
|
||||||
@ -405,15 +432,15 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
|
// workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
|
||||||
// Some(peer_id_b)
|
// Some(peer_id_b)
|
||||||
// );
|
// );
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_a.read_with(cx_a, |workspace, cx| workspace
|
// workspace_a.update(cx_a, |workspace, cx| workspace
|
||||||
// .active_item(cx)
|
// .active_item(cx)
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
// .id()),
|
// .item_id()),
|
||||||
// editor_a1.id()
|
// editor_a1.item_id()
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
|
// // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
|
||||||
@ -432,7 +459,7 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// let shared_screen = workspace_a.read_with(cx_a, |workspace, cx| {
|
// let shared_screen = workspace_a.update(cx_a, |workspace, cx| {
|
||||||
// workspace
|
// workspace
|
||||||
// .active_item(cx)
|
// .active_item(cx)
|
||||||
// .expect("no active item")
|
// .expect("no active item")
|
||||||
@ -446,8 +473,11 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// workspace_a.read_with(cx_a, |workspace, cx| {
|
// workspace_a.update(cx_a, |workspace, cx| {
|
||||||
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_a1.id())
|
// assert_eq!(
|
||||||
|
// workspace.active_item(cx).unwrap().item_id(),
|
||||||
|
// editor_a1.item_id()
|
||||||
|
// )
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
|
// // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
|
||||||
@ -455,26 +485,26 @@
|
|||||||
// workspace.activate_item(&multibuffer_editor_b, cx)
|
// workspace.activate_item(&multibuffer_editor_b, cx)
|
||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// workspace_a.read_with(cx_a, |workspace, cx| {
|
// workspace_a.update(cx_a, |workspace, cx| {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace.active_item(cx).unwrap().id(),
|
// workspace.active_item(cx).unwrap().item_id(),
|
||||||
// multibuffer_editor_a.id()
|
// multibuffer_editor_a.item_id()
|
||||||
// )
|
// )
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
|
// // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
|
||||||
// let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
|
// let panel = window_b.build_view(cx_b, |_| TestPanel::new(DockPosition::Left));
|
||||||
// workspace_b.update(cx_b, |workspace, cx| {
|
// workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// workspace.add_panel(panel, cx);
|
// workspace.add_panel(panel, cx);
|
||||||
// workspace.toggle_panel_focus::<TestPanel>(cx);
|
// workspace.toggle_panel_focus::<TestPanel>(cx);
|
||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_a.read_with(cx_a, |workspace, cx| workspace
|
// workspace_a.update(cx_a, |workspace, cx| workspace
|
||||||
// .active_item(cx)
|
// .active_item(cx)
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
// .id()),
|
// .item_id()),
|
||||||
// shared_screen.id()
|
// shared_screen.item_id()
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// // Toggling the focus back to the pane causes client A to return to the multibuffer.
|
// // Toggling the focus back to the pane causes client A to return to the multibuffer.
|
||||||
@ -482,16 +512,16 @@
|
|||||||
// workspace.toggle_panel_focus::<TestPanel>(cx);
|
// workspace.toggle_panel_focus::<TestPanel>(cx);
|
||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// workspace_a.read_with(cx_a, |workspace, cx| {
|
// workspace_a.update(cx_a, |workspace, cx| {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace.active_item(cx).unwrap().id(),
|
// workspace.active_item(cx).unwrap().item_id(),
|
||||||
// multibuffer_editor_a.id()
|
// multibuffer_editor_a.item_id()
|
||||||
// )
|
// )
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// // Client B activates an item that doesn't implement following,
|
// // Client B activates an item that doesn't implement following,
|
||||||
// // so the previously-opened screen-sharing item gets activated.
|
// // so the previously-opened screen-sharing item gets activated.
|
||||||
// let unfollowable_item = window_b.add_view(cx_b, |_| TestItem::new());
|
// let unfollowable_item = window_b.build_view(cx_b, |_| TestItem::new());
|
||||||
// workspace_b.update(cx_b, |workspace, cx| {
|
// workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// workspace.active_pane().update(cx, |pane, cx| {
|
// workspace.active_pane().update(cx, |pane, cx| {
|
||||||
// pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
|
// pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
|
||||||
@ -499,18 +529,18 @@
|
|||||||
// });
|
// });
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_a.read_with(cx_a, |workspace, cx| workspace
|
// workspace_a.update(cx_a, |workspace, cx| workspace
|
||||||
// .active_item(cx)
|
// .active_item(cx)
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
// .id()),
|
// .item_id()),
|
||||||
// shared_screen.id()
|
// shared_screen.item_id()
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// // Following interrupts when client B disconnects.
|
// // Following interrupts when client B disconnects.
|
||||||
// client_b.disconnect(&cx_b.to_async());
|
// client_b.disconnect(&cx_b.to_async());
|
||||||
// executor.advance_clock(RECONNECT_TIMEOUT);
|
// executor.advance_clock(RECONNECT_TIMEOUT);
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
|
// workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
|
||||||
// None
|
// None
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
@ -521,7 +551,7 @@
|
|||||||
// cx_a: &mut TestAppContext,
|
// cx_a: &mut TestAppContext,
|
||||||
// cx_b: &mut TestAppContext,
|
// cx_b: &mut TestAppContext,
|
||||||
// ) {
|
// ) {
|
||||||
// let mut server = TestServer::start(&executor).await;
|
// let mut server = TestServer::start(executor.clone()).await;
|
||||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
// server
|
// server
|
||||||
@ -560,13 +590,19 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
// let workspace_a = client_a
|
||||||
// let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
|
// .build_workspace(&project_a, cx_a)
|
||||||
|
// .root(cx_a)
|
||||||
|
// .unwrap();
|
||||||
|
// let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
|
||||||
|
|
||||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
// let workspace_b = client_b
|
||||||
// let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
|
// .build_workspace(&project_b, cx_b)
|
||||||
|
// .root(cx_b)
|
||||||
|
// .unwrap();
|
||||||
|
// let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
|
||||||
|
|
||||||
// let client_b_id = project_a.read_with(cx_a, |project, _| {
|
// let client_b_id = project_a.update(cx_a, |project, _| {
|
||||||
// project.collaborators().values().next().unwrap().peer_id
|
// project.collaborators().values().next().unwrap().peer_id
|
||||||
// });
|
// });
|
||||||
|
|
||||||
@ -584,7 +620,7 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// let pane_paths = |pane: &ViewHandle<workspace::Pane>, cx: &mut TestAppContext| {
|
// let pane_paths = |pane: &View<workspace::Pane>, cx: &mut TestAppContext| {
|
||||||
// pane.update(cx, |pane, cx| {
|
// pane.update(cx, |pane, cx| {
|
||||||
// pane.items()
|
// pane.items()
|
||||||
// .map(|item| {
|
// .map(|item| {
|
||||||
@ -642,7 +678,7 @@
|
|||||||
// cx_a: &mut TestAppContext,
|
// cx_a: &mut TestAppContext,
|
||||||
// cx_b: &mut TestAppContext,
|
// cx_b: &mut TestAppContext,
|
||||||
// ) {
|
// ) {
|
||||||
// let mut server = TestServer::start(&executor).await;
|
// let mut server = TestServer::start(executor.clone()).await;
|
||||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
// server
|
// server
|
||||||
@ -685,7 +721,10 @@
|
|||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// // Client A opens a file.
|
// // Client A opens a file.
|
||||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
// let workspace_a = client_a
|
||||||
|
// .build_workspace(&project_a, cx_a)
|
||||||
|
// .root(cx_a)
|
||||||
|
// .unwrap();
|
||||||
// workspace_a
|
// workspace_a
|
||||||
// .update(cx_a, |workspace, cx| {
|
// .update(cx_a, |workspace, cx| {
|
||||||
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
@ -696,7 +735,10 @@
|
|||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// // Client B opens a different file.
|
// // Client B opens a different file.
|
||||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
// let workspace_b = client_b
|
||||||
|
// .build_workspace(&project_b, cx_b)
|
||||||
|
// .root(cx_b)
|
||||||
|
// .unwrap();
|
||||||
// workspace_b
|
// workspace_b
|
||||||
// .update(cx_b, |workspace, cx| {
|
// .update(cx_b, |workspace, cx| {
|
||||||
// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
|
// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
|
||||||
@ -1167,7 +1209,7 @@
|
|||||||
// cx_b: &mut TestAppContext,
|
// cx_b: &mut TestAppContext,
|
||||||
// ) {
|
// ) {
|
||||||
// // 2 clients connect to a server.
|
// // 2 clients connect to a server.
|
||||||
// let mut server = TestServer::start(&executor).await;
|
// let mut server = TestServer::start(executor.clone()).await;
|
||||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
// server
|
// server
|
||||||
@ -1207,8 +1249,17 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
|
// todo!("could be wrong")
|
||||||
|
// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
|
||||||
|
// let cx_a = &mut cx_a;
|
||||||
|
// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
|
||||||
|
// let cx_b = &mut cx_b;
|
||||||
|
|
||||||
// // Client A opens some editors.
|
// // Client A opens some editors.
|
||||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
// let workspace_a = client_a
|
||||||
|
// .build_workspace(&project_a, cx_a)
|
||||||
|
// .root(cx_a)
|
||||||
|
// .unwrap();
|
||||||
// let _editor_a1 = workspace_a
|
// let _editor_a1 = workspace_a
|
||||||
// .update(cx_a, |workspace, cx| {
|
// .update(cx_a, |workspace, cx| {
|
||||||
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
@ -1219,9 +1270,12 @@
|
|||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// // Client B starts following client A.
|
// // Client B starts following client A.
|
||||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
// let workspace_b = client_b
|
||||||
// let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
|
// .build_workspace(&project_b, cx_b)
|
||||||
// let leader_id = project_b.read_with(cx_b, |project, _| {
|
// .root(cx_b)
|
||||||
|
// .unwrap();
|
||||||
|
// let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
|
||||||
|
// let leader_id = project_b.update(cx_b, |project, _| {
|
||||||
// project.collaborators().values().next().unwrap().peer_id
|
// project.collaborators().values().next().unwrap().peer_id
|
||||||
// });
|
// });
|
||||||
// workspace_b
|
// workspace_b
|
||||||
@ -1231,10 +1285,10 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// Some(leader_id)
|
// Some(leader_id)
|
||||||
// );
|
// );
|
||||||
// let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
|
// let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
|
||||||
// workspace
|
// workspace
|
||||||
// .active_item(cx)
|
// .active_item(cx)
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
@ -1245,7 +1299,7 @@
|
|||||||
// // When client B moves, it automatically stops following client A.
|
// // When client B moves, it automatically stops following client A.
|
||||||
// editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
|
// editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// None
|
// None
|
||||||
// );
|
// );
|
||||||
|
|
||||||
@ -1256,14 +1310,14 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// Some(leader_id)
|
// Some(leader_id)
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// // When client B edits, it automatically stops following client A.
|
// // When client B edits, it automatically stops following client A.
|
||||||
// editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
|
// editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// None
|
// None
|
||||||
// );
|
// );
|
||||||
|
|
||||||
@ -1274,16 +1328,16 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// Some(leader_id)
|
// Some(leader_id)
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// // When client B scrolls, it automatically stops following client A.
|
// // When client B scrolls, it automatically stops following client A.
|
||||||
// editor_b2.update(cx_b, |editor, cx| {
|
// editor_b2.update(cx_b, |editor, cx| {
|
||||||
// editor.set_scroll_position(vec2f(0., 3.), cx)
|
// editor.set_scroll_position(point(0., 3.), cx)
|
||||||
// });
|
// });
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// None
|
// None
|
||||||
// );
|
// );
|
||||||
|
|
||||||
@ -1294,7 +1348,7 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// Some(leader_id)
|
// Some(leader_id)
|
||||||
// );
|
// );
|
||||||
|
|
||||||
@ -1303,13 +1357,13 @@
|
|||||||
// workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
|
// workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
|
||||||
// });
|
// });
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// Some(leader_id)
|
// Some(leader_id)
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
|
// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// Some(leader_id)
|
// Some(leader_id)
|
||||||
// );
|
// );
|
||||||
|
|
||||||
@ -1321,7 +1375,7 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||||
// None
|
// None
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
@ -1332,7 +1386,7 @@
|
|||||||
// cx_a: &mut TestAppContext,
|
// cx_a: &mut TestAppContext,
|
||||||
// cx_b: &mut TestAppContext,
|
// cx_b: &mut TestAppContext,
|
||||||
// ) {
|
// ) {
|
||||||
// let mut server = TestServer::start(&executor).await;
|
// let mut server = TestServer::start(executor.clone()).await;
|
||||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
// server
|
// server
|
||||||
@ -1345,20 +1399,26 @@
|
|||||||
|
|
||||||
// client_a.fs().insert_tree("/a", json!({})).await;
|
// client_a.fs().insert_tree("/a", json!({})).await;
|
||||||
// let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
|
// let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
|
||||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
// let workspace_a = client_a
|
||||||
|
// .build_workspace(&project_a, cx_a)
|
||||||
|
// .root(cx_a)
|
||||||
|
// .unwrap();
|
||||||
// let project_id = active_call_a
|
// let project_id = active_call_a
|
||||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
// let workspace_b = client_b
|
||||||
|
// .build_workspace(&project_b, cx_b)
|
||||||
|
// .root(cx_b)
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
// executor.run_until_parked();
|
// executor.run_until_parked();
|
||||||
// let client_a_id = project_b.read_with(cx_b, |project, _| {
|
// let client_a_id = project_b.update(cx_b, |project, _| {
|
||||||
// project.collaborators().values().next().unwrap().peer_id
|
// project.collaborators().values().next().unwrap().peer_id
|
||||||
// });
|
// });
|
||||||
// let client_b_id = project_a.read_with(cx_a, |project, _| {
|
// let client_b_id = project_a.update(cx_a, |project, _| {
|
||||||
// project.collaborators().values().next().unwrap().peer_id
|
// project.collaborators().values().next().unwrap().peer_id
|
||||||
// });
|
// });
|
||||||
|
|
||||||
@ -1370,13 +1430,13 @@
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
// futures::try_join!(a_follow_b, b_follow_a).unwrap();
|
// futures::try_join!(a_follow_b, b_follow_a).unwrap();
|
||||||
// workspace_a.read_with(cx_a, |workspace, _| {
|
// workspace_a.update(cx_a, |workspace, _| {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace.leader_for_pane(workspace.active_pane()),
|
// workspace.leader_for_pane(workspace.active_pane()),
|
||||||
// Some(client_b_id)
|
// Some(client_b_id)
|
||||||
// );
|
// );
|
||||||
// });
|
// });
|
||||||
// workspace_b.read_with(cx_b, |workspace, _| {
|
// workspace_b.update(cx_b, |workspace, _| {
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// workspace.leader_for_pane(workspace.active_pane()),
|
// workspace.leader_for_pane(workspace.active_pane()),
|
||||||
// Some(client_a_id)
|
// Some(client_a_id)
|
||||||
@ -1398,7 +1458,7 @@
|
|||||||
// // b opens a different file in project 2, a follows b
|
// // b opens a different file in project 2, a follows b
|
||||||
// // b opens a different file in project 1, a cannot follow b
|
// // b opens a different file in project 1, a cannot follow b
|
||||||
// // b shares the project, a joins the project and follows b
|
// // b shares the project, a joins the project and follows b
|
||||||
// let mut server = TestServer::start(&executor).await;
|
// let mut server = TestServer::start(executor.clone()).await;
|
||||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
// cx_a.update(editor::init);
|
// cx_a.update(editor::init);
|
||||||
@ -1435,8 +1495,14 @@
|
|||||||
// let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
|
// let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
|
||||||
// let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
|
// let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
|
||||||
|
|
||||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
// let workspace_a = client_a
|
||||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
// .build_workspace(&project_a, cx_a)
|
||||||
|
// .root(cx_a)
|
||||||
|
// .unwrap();
|
||||||
|
// let workspace_b = client_b
|
||||||
|
// .build_workspace(&project_b, cx_b)
|
||||||
|
// .root(cx_b)
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
// cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
|
// cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
|
||||||
// cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
|
// cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
|
||||||
@ -1455,6 +1521,12 @@
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
|
// todo!("could be wrong")
|
||||||
|
// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
|
||||||
|
// let cx_a = &mut cx_a;
|
||||||
|
// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
|
||||||
|
// let cx_b = &mut cx_b;
|
||||||
|
|
||||||
// workspace_a
|
// workspace_a
|
||||||
// .update(cx_a, |workspace, cx| {
|
// .update(cx_a, |workspace, cx| {
|
||||||
// workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
|
// workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
|
||||||
@ -1476,11 +1548,12 @@
|
|||||||
// let workspace_b_project_a = cx_b
|
// let workspace_b_project_a = cx_b
|
||||||
// .windows()
|
// .windows()
|
||||||
// .iter()
|
// .iter()
|
||||||
// .max_by_key(|window| window.id())
|
// .max_by_key(|window| window.item_id())
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
// .downcast::<Workspace>()
|
// .downcast::<Workspace>()
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
// .root(cx_b);
|
// .root(cx_b)
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
// // assert that b is following a in project a in w.rs
|
// // assert that b is following a in project a in w.rs
|
||||||
// workspace_b_project_a.update(cx_b, |workspace, cx| {
|
// workspace_b_project_a.update(cx_b, |workspace, cx| {
|
||||||
@ -1534,7 +1607,7 @@
|
|||||||
// workspace.leader_for_pane(workspace.active_pane())
|
// workspace.leader_for_pane(workspace.active_pane())
|
||||||
// );
|
// );
|
||||||
// let item = workspace.active_pane().read(cx).active_item().unwrap();
|
// let item = workspace.active_pane().read(cx).active_item().unwrap();
|
||||||
// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
|
// assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs".into());
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// // b moves to y.rs in b's project, a is still following but can't yet see
|
// // b moves to y.rs in b's project, a is still following but can't yet see
|
||||||
@ -1578,11 +1651,12 @@
|
|||||||
// let workspace_a_project_b = cx_a
|
// let workspace_a_project_b = cx_a
|
||||||
// .windows()
|
// .windows()
|
||||||
// .iter()
|
// .iter()
|
||||||
// .max_by_key(|window| window.id())
|
// .max_by_key(|window| window.item_id())
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
// .downcast::<Workspace>()
|
// .downcast::<Workspace>()
|
||||||
// .unwrap()
|
// .unwrap()
|
||||||
// .root(cx_a);
|
// .root(cx_a)
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
// workspace_a_project_b.update(cx_a, |workspace, cx| {
|
// workspace_a_project_b.update(cx_a, |workspace, cx| {
|
||||||
// assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
|
// assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
|
||||||
@ -1596,12 +1670,151 @@
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// #[gpui::test]
|
||||||
|
// async fn test_following_into_excluded_file(
|
||||||
|
// executor: BackgroundExecutor,
|
||||||
|
// mut cx_a: &mut TestAppContext,
|
||||||
|
// mut cx_b: &mut TestAppContext,
|
||||||
|
// ) {
|
||||||
|
// let mut server = TestServer::start(executor.clone()).await;
|
||||||
|
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
|
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
|
// for cx in [&mut cx_a, &mut cx_b] {
|
||||||
|
// cx.update(|cx| {
|
||||||
|
// cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||||
|
// store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
|
||||||
|
// project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// server
|
||||||
|
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||||
|
// .await;
|
||||||
|
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
// let active_call_b = cx_b.read(ActiveCall::global);
|
||||||
|
|
||||||
|
// cx_a.update(editor::init);
|
||||||
|
// cx_b.update(editor::init);
|
||||||
|
|
||||||
|
// client_a
|
||||||
|
// .fs()
|
||||||
|
// .insert_tree(
|
||||||
|
// "/a",
|
||||||
|
// json!({
|
||||||
|
// ".git": {
|
||||||
|
// "COMMIT_EDITMSG": "write your commit message here",
|
||||||
|
// },
|
||||||
|
// "1.txt": "one\none\none",
|
||||||
|
// "2.txt": "two\ntwo\ntwo",
|
||||||
|
// "3.txt": "three\nthree\nthree",
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||||
|
// active_call_a
|
||||||
|
// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||||
|
// .await
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// active_call_b
|
||||||
|
// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||||
|
// .await
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
// let window_a = client_a.build_workspace(&project_a, cx_a);
|
||||||
|
// let workspace_a = window_a.root(cx_a).unwrap();
|
||||||
|
// let peer_id_a = client_a.peer_id().unwrap();
|
||||||
|
// let window_b = client_b.build_workspace(&project_b, cx_b);
|
||||||
|
// let workspace_b = window_b.root(cx_b).unwrap();
|
||||||
|
|
||||||
|
// todo!("could be wrong")
|
||||||
|
// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
|
||||||
|
// let cx_a = &mut cx_a;
|
||||||
|
// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
|
||||||
|
// let cx_b = &mut cx_b;
|
||||||
|
|
||||||
|
// // Client A opens editors for a regular file and an excluded file.
|
||||||
|
// let editor_for_regular = workspace_a
|
||||||
|
// .update(cx_a, |workspace, cx| {
|
||||||
|
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
|
// })
|
||||||
|
// .await
|
||||||
|
// .unwrap()
|
||||||
|
// .downcast::<Editor>()
|
||||||
|
// .unwrap();
|
||||||
|
// let editor_for_excluded_a = workspace_a
|
||||||
|
// .update(cx_a, |workspace, cx| {
|
||||||
|
// workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
|
||||||
|
// })
|
||||||
|
// .await
|
||||||
|
// .unwrap()
|
||||||
|
// .downcast::<Editor>()
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
// // Client A updates their selections in those editors
|
||||||
|
// editor_for_regular.update(cx_a, |editor, cx| {
|
||||||
|
// editor.handle_input("a", cx);
|
||||||
|
// editor.handle_input("b", cx);
|
||||||
|
// editor.handle_input("c", cx);
|
||||||
|
// editor.select_left(&Default::default(), cx);
|
||||||
|
// assert_eq!(editor.selections.ranges(cx), vec![3..2]);
|
||||||
|
// });
|
||||||
|
// editor_for_excluded_a.update(cx_a, |editor, cx| {
|
||||||
|
// editor.select_all(&Default::default(), cx);
|
||||||
|
// editor.handle_input("new commit message", cx);
|
||||||
|
// editor.select_left(&Default::default(), cx);
|
||||||
|
// assert_eq!(editor.selections.ranges(cx), vec![18..17]);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // When client B starts following client A, currently visible file is replicated
|
||||||
|
// workspace_b
|
||||||
|
// .update(cx_b, |workspace, cx| {
|
||||||
|
// workspace.follow(peer_id_a, cx).unwrap()
|
||||||
|
// })
|
||||||
|
// .await
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
// let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
|
||||||
|
// workspace
|
||||||
|
// .active_item(cx)
|
||||||
|
// .unwrap()
|
||||||
|
// .downcast::<Editor>()
|
||||||
|
// .unwrap()
|
||||||
|
// });
|
||||||
|
// assert_eq!(
|
||||||
|
// cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
|
||||||
|
// Some((worktree_id, ".git/COMMIT_EDITMSG").into())
|
||||||
|
// );
|
||||||
|
// assert_eq!(
|
||||||
|
// editor_for_excluded_b.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||||
|
// vec![18..17]
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // Changes from B to the excluded file are replicated in A's editor
|
||||||
|
// editor_for_excluded_b.update(cx_b, |editor, cx| {
|
||||||
|
// editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
|
||||||
|
// });
|
||||||
|
// executor.run_until_parked();
|
||||||
|
// editor_for_excluded_a.update(cx_a, |editor, cx| {
|
||||||
|
// assert_eq!(
|
||||||
|
// editor.text(cx),
|
||||||
|
// "new commit messag\nCo-Authored-By: B <b@b.b>"
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
// fn visible_push_notifications(
|
// fn visible_push_notifications(
|
||||||
// cx: &mut TestAppContext,
|
// cx: &mut TestAppContext,
|
||||||
// ) -> Vec<gpui::ViewHandle<ProjectSharedNotification>> {
|
// ) -> Vec<gpui::View<ProjectSharedNotification>> {
|
||||||
// let mut ret = Vec::new();
|
// let mut ret = Vec::new();
|
||||||
// for window in cx.windows() {
|
// for window in cx.windows() {
|
||||||
// window.read_with(cx, |window| {
|
// window.update(cx, |window| {
|
||||||
// if let Some(handle) = window
|
// if let Some(handle) = window
|
||||||
// .root_view()
|
// .root_view()
|
||||||
// .clone()
|
// .clone()
|
||||||
@ -1645,8 +1858,8 @@
|
|||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn pane_summaries(workspace: &ViewHandle<Workspace>, cx: &mut TestAppContext) -> Vec<PaneSummary> {
|
// fn pane_summaries(workspace: &View<Workspace>, cx: &mut WindowContext<'_>) -> Vec<PaneSummary> {
|
||||||
// workspace.read_with(cx, |workspace, cx| {
|
// workspace.update(cx, |workspace, cx| {
|
||||||
// let active_pane = workspace.active_pane();
|
// let active_pane = workspace.active_pane();
|
||||||
// workspace
|
// workspace
|
||||||
// .panes()
|
// .panes()
|
||||||
|
@ -2781,11 +2781,10 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
let entry = project_b
|
let entry = project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "c.txt"), false, cx)
|
||||||
.create_entry((worktree_id, "c.txt"), false, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
worktree_a.read_with(cx_a, |worktree, _| {
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
@ -2812,8 +2811,8 @@ async fn test_fs_operations(
|
|||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project.rename_entry(entry.id, Path::new("d.txt"), cx)
|
project.rename_entry(entry.id, Path::new("d.txt"), cx)
|
||||||
})
|
})
|
||||||
.unwrap()
|
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
worktree_a.read_with(cx_a, |worktree, _| {
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
@ -2838,11 +2837,10 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
let dir_entry = project_b
|
let dir_entry = project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "DIR"), true, cx)
|
||||||
.create_entry((worktree_id, "DIR"), true, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
worktree_a.read_with(cx_a, |worktree, _| {
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
@ -2867,27 +2865,24 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
|
||||||
.create_entry((worktree_id, "DIR/e.txt"), false, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
|
||||||
.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
|
||||||
.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
worktree_a.read_with(cx_a, |worktree, _| {
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
@ -2928,11 +2923,10 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project.copy_entry(entry.id, Path::new("f.txt"), cx)
|
||||||
.copy_entry(entry.id, Path::new("f.txt"), cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
worktree_a.read_with(cx_a, |worktree, _| {
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
|
@ -665,7 +665,6 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
ensure_project_shared(&project, client, cx).await;
|
ensure_project_shared(&project, client, cx).await;
|
||||||
project
|
project
|
||||||
.update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
|
.update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
|
||||||
.unwrap()
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ use editor::Editor;
|
|||||||
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, img, overlay, point, prelude::*, px, rems, serde_json, Action,
|
actions, canvas, div, img, overlay, point, prelude::*, px, rems, serde_json, size, Action,
|
||||||
AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter,
|
AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter,
|
||||||
FocusHandle, Focusable, FocusableView, Hsla, InteractiveElement, IntoElement, Length, Model,
|
FocusHandle, Focusable, FocusableView, Hsla, InteractiveElement, IntoElement, Length, Model,
|
||||||
MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Quad, Render, RenderOnce,
|
MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Quad, Render, RenderOnce,
|
||||||
@ -1204,14 +1204,9 @@ impl CollabPanel {
|
|||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
});
|
});
|
||||||
}))
|
}))
|
||||||
.left_child(IconButton::new(0, Icon::Folder))
|
.left_child(render_tree_branch(is_last, cx))
|
||||||
.child(
|
.child(IconButton::new(0, Icon::Folder))
|
||||||
h_stack()
|
.child(Label::new(project_name.clone()))
|
||||||
.w_full()
|
|
||||||
.justify_between()
|
|
||||||
.child(render_tree_branch(is_last, cx))
|
|
||||||
.child(Label::new(project_name.clone())),
|
|
||||||
)
|
|
||||||
.tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
|
.tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
|
||||||
|
|
||||||
// enum JoinProject {}
|
// enum JoinProject {}
|
||||||
@ -1299,70 +1294,20 @@ impl CollabPanel {
|
|||||||
is_last: bool,
|
is_last: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
// enum OpenSharedScreen {}
|
let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize);
|
||||||
|
|
||||||
// let host_avatar_width = theme
|
ListItem::new(("screen", id))
|
||||||
// .contact_avatar
|
.left_child(render_tree_branch(is_last, cx))
|
||||||
// .width
|
.child(IconButton::new(0, Icon::Screen))
|
||||||
// .or(theme.contact_avatar.height)
|
.child(Label::new("Screen"))
|
||||||
// .unwrap_or(0.);
|
.when_some(peer_id, |this, _| {
|
||||||
// let tree_branch = theme.tree_branch;
|
this.on_click(cx.listener(move |this, _, cx| {
|
||||||
|
this.workspace.update(cx, |workspace, cx| {
|
||||||
// let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
|
workspace.open_shared_screen(peer_id.unwrap(), cx)
|
||||||
// peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
|
});
|
||||||
// cx,
|
}))
|
||||||
// |mouse_state, cx| {
|
.tooltip(move |cx| Tooltip::text(format!("Open shared screen"), cx))
|
||||||
// let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
|
})
|
||||||
// let row = theme
|
|
||||||
// .project_row
|
|
||||||
// .in_state(is_selected)
|
|
||||||
// .style_for(mouse_state);
|
|
||||||
|
|
||||||
// Flex::row()
|
|
||||||
// .with_child(render_tree_branch(
|
|
||||||
// tree_branch,
|
|
||||||
// &row.name.text,
|
|
||||||
// is_last,
|
|
||||||
// vec2f(host_avatar_width, theme.row_height),
|
|
||||||
// cx.font_cache(),
|
|
||||||
// ))
|
|
||||||
// .with_child(
|
|
||||||
// Svg::new("icons/desktop.svg")
|
|
||||||
// .with_color(theme.channel_hash.color)
|
|
||||||
// .constrained()
|
|
||||||
// .with_width(theme.channel_hash.width)
|
|
||||||
// .aligned()
|
|
||||||
// .left(),
|
|
||||||
// )
|
|
||||||
// .with_child(
|
|
||||||
// Label::new("Screen", row.name.text.clone())
|
|
||||||
// .aligned()
|
|
||||||
// .left()
|
|
||||||
// .contained()
|
|
||||||
// .with_style(row.name.container)
|
|
||||||
// .flex(1., false),
|
|
||||||
// )
|
|
||||||
// .constrained()
|
|
||||||
// .with_height(theme.row_height)
|
|
||||||
// .contained()
|
|
||||||
// .with_style(row.container)
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// if peer_id.is_none() {
|
|
||||||
// return handler.into_any();
|
|
||||||
// }
|
|
||||||
// handler
|
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
// if let Some(workspace) = this.workspace.upgrade(cx) {
|
|
||||||
// workspace.update(cx, |workspace, cx| {
|
|
||||||
// workspace.open_shared_screen(peer_id.unwrap(), cx)
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .into_any()
|
|
||||||
|
|
||||||
div()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
@ -1415,54 +1360,14 @@ impl CollabPanel {
|
|||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
// enum ChannelNotes {}
|
ListItem::new("channel-notes")
|
||||||
// let host_avatar_width = theme
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
// .contact_avatar
|
this.open_channel_notes(channel_id, cx);
|
||||||
// .width
|
}))
|
||||||
// .or(theme.contact_avatar.height)
|
.left_child(render_tree_branch(false, cx))
|
||||||
// .unwrap_or(0.);
|
.child(IconButton::new(0, Icon::File))
|
||||||
|
.child(Label::new("notes"))
|
||||||
// MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
|
.tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
|
||||||
// let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
|
|
||||||
// let row = theme.project_row.in_state(is_selected).style_for(state);
|
|
||||||
|
|
||||||
// Flex::<Self>::row()
|
|
||||||
// .with_child(render_tree_branch(
|
|
||||||
// tree_branch,
|
|
||||||
// &row.name.text,
|
|
||||||
// false,
|
|
||||||
// vec2f(host_avatar_width, theme.row_height),
|
|
||||||
// cx.font_cache(),
|
|
||||||
// ))
|
|
||||||
// .with_child(
|
|
||||||
// Svg::new("icons/file.svg")
|
|
||||||
// .with_color(theme.channel_hash.color)
|
|
||||||
// .constrained()
|
|
||||||
// .with_width(theme.channel_hash.width)
|
|
||||||
// .aligned()
|
|
||||||
// .left(),
|
|
||||||
// )
|
|
||||||
// .with_child(
|
|
||||||
// Label::new("notes", theme.channel_name.text.clone())
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.channel_name.container)
|
|
||||||
// .aligned()
|
|
||||||
// .left()
|
|
||||||
// .flex(1., true),
|
|
||||||
// )
|
|
||||||
// .constrained()
|
|
||||||
// .with_height(theme.row_height)
|
|
||||||
// .contained()
|
|
||||||
// .with_style(*theme.channel_row.style_for(is_selected, state))
|
|
||||||
// .with_padding_left(theme.channel_row.default_style().padding.left)
|
|
||||||
// })
|
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
// this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
|
|
||||||
// })
|
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
// .into_any()
|
|
||||||
|
|
||||||
div()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_channel_chat(
|
fn render_channel_chat(
|
||||||
@ -1470,53 +1375,14 @@ impl CollabPanel {
|
|||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
// enum ChannelChat {}
|
ListItem::new("channel-chat")
|
||||||
// let host_avatar_width = theme
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
// .contact_avatar
|
this.join_channel_chat(channel_id, cx);
|
||||||
// .width
|
}))
|
||||||
// .or(theme.contact_avatar.height)
|
.left_child(render_tree_branch(true, cx))
|
||||||
// .unwrap_or(0.);
|
.child(IconButton::new(0, Icon::MessageBubbles))
|
||||||
|
.child(Label::new("chat"))
|
||||||
// MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
|
.tooltip(move |cx| Tooltip::text("Open Chat", cx))
|
||||||
// let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
|
|
||||||
// let row = theme.project_row.in_state(is_selected).style_for(state);
|
|
||||||
|
|
||||||
// Flex::<Self>::row()
|
|
||||||
// .with_child(render_tree_branch(
|
|
||||||
// tree_branch,
|
|
||||||
// &row.name.text,
|
|
||||||
// true,
|
|
||||||
// vec2f(host_avatar_width, theme.row_height),
|
|
||||||
// cx.font_cache(),
|
|
||||||
// ))
|
|
||||||
// .with_child(
|
|
||||||
// Svg::new("icons/conversations.svg")
|
|
||||||
// .with_color(theme.channel_hash.color)
|
|
||||||
// .constrained()
|
|
||||||
// .with_width(theme.channel_hash.width)
|
|
||||||
// .aligned()
|
|
||||||
// .left(),
|
|
||||||
// )
|
|
||||||
// .with_child(
|
|
||||||
// Label::new("chat", theme.channel_name.text.clone())
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.channel_name.container)
|
|
||||||
// .aligned()
|
|
||||||
// .left()
|
|
||||||
// .flex(1., true),
|
|
||||||
// )
|
|
||||||
// .constrained()
|
|
||||||
// .with_height(theme.row_height)
|
|
||||||
// .contained()
|
|
||||||
// .with_style(*theme.channel_row.style_for(is_selected, state))
|
|
||||||
// .with_padding_left(theme.channel_row.default_style().padding.left)
|
|
||||||
// })
|
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
// this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
|
|
||||||
// })
|
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
// .into_any()
|
|
||||||
div()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn render_channel_invite(
|
// fn render_channel_invite(
|
||||||
@ -3119,30 +2985,24 @@ impl CollabPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_tree_branch(is_last: bool, cx: &mut WindowContext) -> impl IntoElement {
|
fn render_tree_branch(is_last: bool, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
let text_style = cx.text_style();
|
|
||||||
let rem_size = cx.rem_size();
|
let rem_size = cx.rem_size();
|
||||||
let text_system = cx.text_system();
|
let line_height = cx.text_style().line_height_in_pixels(rem_size);
|
||||||
let font_id = text_system.font_id(&text_style.font()).unwrap();
|
let width = rem_size * 1.5;
|
||||||
let font_size = text_style.font_size.to_pixels(rem_size);
|
|
||||||
let line_height = text_style.line_height_in_pixels(rem_size);
|
|
||||||
let cap_height = text_system.cap_height(font_id, font_size);
|
|
||||||
let baseline_offset = text_system.baseline_offset(font_id, font_size, line_height);
|
|
||||||
let width = cx.rem_size() * 2.5;
|
|
||||||
let thickness = px(2.);
|
let thickness = px(2.);
|
||||||
let color = cx.theme().colors().text;
|
let color = cx.theme().colors().text;
|
||||||
|
|
||||||
canvas(move |bounds, cx| {
|
canvas(move |bounds, cx| {
|
||||||
let start_x = bounds.left() + (bounds.size.width / 2.) - (width / 2.);
|
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
|
||||||
let end_x = bounds.right();
|
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
|
||||||
let start_y = bounds.top();
|
let right = bounds.right();
|
||||||
let end_y = bounds.top() + baseline_offset - (cap_height / 2.);
|
let top = bounds.top();
|
||||||
|
|
||||||
cx.paint_quad(
|
cx.paint_quad(
|
||||||
Bounds::from_corners(
|
Bounds::from_corners(
|
||||||
point(start_x, start_y),
|
point(start_x, top),
|
||||||
point(
|
point(
|
||||||
start_x + thickness,
|
start_x + thickness,
|
||||||
if is_last { end_y } else { bounds.bottom() },
|
if is_last { start_y } else { bounds.bottom() },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
@ -3151,7 +3011,7 @@ fn render_tree_branch(is_last: bool, cx: &mut WindowContext) -> impl IntoElement
|
|||||||
Hsla::transparent_black(),
|
Hsla::transparent_black(),
|
||||||
);
|
);
|
||||||
cx.paint_quad(
|
cx.paint_quad(
|
||||||
Bounds::from_corners(point(start_x, end_y), point(end_x, end_y + thickness)),
|
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
color,
|
color,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
@ -88,7 +88,7 @@ struct DiagnosticGroupState {
|
|||||||
block_count: usize,
|
block_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
|
impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
|
||||||
|
|
||||||
impl Render for ProjectDiagnosticsEditor {
|
impl Render for ProjectDiagnosticsEditor {
|
||||||
type Element = Focusable<Div>;
|
type Element = Focusable<Div>;
|
||||||
@ -158,7 +158,7 @@ impl ProjectDiagnosticsEditor {
|
|||||||
});
|
});
|
||||||
let editor_event_subscription =
|
let editor_event_subscription =
|
||||||
cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
|
cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
|
||||||
Self::emit_item_event_for_editor_event(event, cx);
|
cx.emit(event.clone());
|
||||||
if event == &EditorEvent::Focused && this.path_states.is_empty() {
|
if event == &EditorEvent::Focused && this.path_states.is_empty() {
|
||||||
cx.focus(&this.focus_handle);
|
cx.focus(&this.focus_handle);
|
||||||
}
|
}
|
||||||
@ -183,40 +183,6 @@ impl ProjectDiagnosticsEditor {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_item_event_for_editor_event(event: &EditorEvent, cx: &mut ViewContext<Self>) {
|
|
||||||
match event {
|
|
||||||
EditorEvent::Closed => cx.emit(ItemEvent::CloseItem),
|
|
||||||
|
|
||||||
EditorEvent::Saved | EditorEvent::TitleChanged => {
|
|
||||||
cx.emit(ItemEvent::UpdateTab);
|
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorEvent::Reparsed => {
|
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorEvent::SelectionsChanged { local } if *local => {
|
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorEvent::DirtyChanged => {
|
|
||||||
cx.emit(ItemEvent::UpdateTab);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorEvent::BufferEdited => {
|
|
||||||
cx.emit(ItemEvent::Edit);
|
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
|
|
||||||
cx.emit(ItemEvent::Edit);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
||||||
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||||
workspace.activate_item(&existing, cx);
|
workspace.activate_item(&existing, cx);
|
||||||
@ -333,8 +299,7 @@ impl ProjectDiagnosticsEditor {
|
|||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.summary = this.project.read(cx).diagnostic_summary(false, cx);
|
this.summary = this.project.read(cx).diagnostic_summary(false, cx);
|
||||||
cx.emit(ItemEvent::UpdateTab);
|
cx.emit(EditorEvent::TitleChanged);
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
})?;
|
})?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
@ -649,6 +614,12 @@ impl FocusableView for ProjectDiagnosticsEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Item for ProjectDiagnosticsEditor {
|
impl Item for ProjectDiagnosticsEditor {
|
||||||
|
type Event = EditorEvent;
|
||||||
|
|
||||||
|
fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
|
||||||
|
Editor::to_item_events(event, f)
|
||||||
|
}
|
||||||
|
|
||||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.editor.update(cx, |editor, cx| editor.deactivated(cx));
|
self.editor.update(cx, |editor, cx| editor.deactivated(cx));
|
||||||
}
|
}
|
||||||
|
@ -1675,8 +1675,7 @@ impl Editor {
|
|||||||
if let Some(project) = project.as_ref() {
|
if let Some(project) = project.as_ref() {
|
||||||
if buffer.read(cx).is_singleton() {
|
if buffer.read(cx).is_singleton() {
|
||||||
project_subscriptions.push(cx.observe(project, |_, _, cx| {
|
project_subscriptions.push(cx.observe(project, |_, _, cx| {
|
||||||
cx.emit(ItemEvent::UpdateTab);
|
cx.emit(EditorEvent::TitleChanged);
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
|
project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
|
||||||
@ -2132,10 +2131,6 @@ impl Editor {
|
|||||||
cx.emit(SearchEvent::ActiveMatchChanged)
|
cx.emit(SearchEvent::ActiveMatchChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
if local {
|
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -8564,8 +8559,6 @@ impl Editor {
|
|||||||
self.update_visible_copilot_suggestion(cx);
|
self.update_visible_copilot_suggestion(cx);
|
||||||
}
|
}
|
||||||
cx.emit(EditorEvent::BufferEdited);
|
cx.emit(EditorEvent::BufferEdited);
|
||||||
cx.emit(ItemEvent::Edit);
|
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
cx.emit(SearchEvent::MatchesInvalidated);
|
cx.emit(SearchEvent::MatchesInvalidated);
|
||||||
|
|
||||||
if *sigleton_buffer_edited {
|
if *sigleton_buffer_edited {
|
||||||
@ -8613,20 +8606,14 @@ impl Editor {
|
|||||||
self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
|
self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
|
||||||
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
|
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
|
||||||
}
|
}
|
||||||
multi_buffer::Event::Reparsed => {
|
multi_buffer::Event::Reparsed => cx.emit(EditorEvent::Reparsed),
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
|
||||||
}
|
multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
|
||||||
multi_buffer::Event::DirtyChanged => {
|
multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
|
||||||
cx.emit(ItemEvent::UpdateTab);
|
cx.emit(EditorEvent::TitleChanged)
|
||||||
}
|
|
||||||
multi_buffer::Event::Saved
|
|
||||||
| multi_buffer::Event::FileHandleChanged
|
|
||||||
| multi_buffer::Event::Reloaded => {
|
|
||||||
cx.emit(ItemEvent::UpdateTab);
|
|
||||||
cx.emit(ItemEvent::UpdateBreadcrumbs);
|
|
||||||
}
|
}
|
||||||
multi_buffer::Event::DiffBaseChanged => cx.emit(EditorEvent::DiffBaseChanged),
|
multi_buffer::Event::DiffBaseChanged => cx.emit(EditorEvent::DiffBaseChanged),
|
||||||
multi_buffer::Event::Closed => cx.emit(ItemEvent::CloseItem),
|
multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
|
||||||
multi_buffer::Event::DiagnosticsUpdated => {
|
multi_buffer::Event::DiagnosticsUpdated => {
|
||||||
self.refresh_active_diagnostics(cx);
|
self.refresh_active_diagnostics(cx);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ use util::{
|
|||||||
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
|
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
|
||||||
};
|
};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{FollowEvent, FollowableEvents, FollowableItem, Item, ItemHandle},
|
item::{FollowEvent, FollowableItem, Item, ItemHandle},
|
||||||
NavigationEntry, ViewId,
|
NavigationEntry, ViewId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -6478,7 +6478,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
|
|||||||
cx.subscribe(
|
cx.subscribe(
|
||||||
&follower.root_view(cx).unwrap(),
|
&follower.root_view(cx).unwrap(),
|
||||||
move |_, _, event: &EditorEvent, cx| {
|
move |_, _, event: &EditorEvent, cx| {
|
||||||
if matches!(event.to_follow_event(), Some(FollowEvent::Unfollow)) {
|
if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
|
||||||
*is_still_following.borrow_mut() = false;
|
*is_still_following.borrow_mut() = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +32,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
use text::Selection;
|
use text::Selection;
|
||||||
use theme::{ActiveTheme, Theme};
|
use theme::{ActiveTheme, Theme};
|
||||||
use ui::{Color, Label};
|
use ui::{h_stack, Color, Label};
|
||||||
use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
|
use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle},
|
item::{BreadcrumbText, FollowEvent, FollowableItemHandle},
|
||||||
StatusItemView,
|
StatusItemView,
|
||||||
};
|
};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
@ -46,27 +46,7 @@ use workspace::{
|
|||||||
|
|
||||||
pub const MAX_TAB_TITLE_LEN: usize = 24;
|
pub const MAX_TAB_TITLE_LEN: usize = 24;
|
||||||
|
|
||||||
impl FollowableEvents for EditorEvent {
|
|
||||||
fn to_follow_event(&self) -> Option<workspace::item::FollowEvent> {
|
|
||||||
match self {
|
|
||||||
EditorEvent::Edited => Some(FollowEvent::Unfollow),
|
|
||||||
EditorEvent::SelectionsChanged { local }
|
|
||||||
| EditorEvent::ScrollPositionChanged { local, .. } => {
|
|
||||||
if *local {
|
|
||||||
Some(FollowEvent::Unfollow)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<ItemEvent> for Editor {}
|
|
||||||
|
|
||||||
impl FollowableItem for Editor {
|
impl FollowableItem for Editor {
|
||||||
type FollowableEvent = EditorEvent;
|
|
||||||
fn remote_id(&self) -> Option<ViewId> {
|
fn remote_id(&self) -> Option<ViewId> {
|
||||||
self.remote_id
|
self.remote_id
|
||||||
}
|
}
|
||||||
@ -241,9 +221,24 @@ impl FollowableItem for Editor {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
|
||||||
|
match event {
|
||||||
|
EditorEvent::Edited => Some(FollowEvent::Unfollow),
|
||||||
|
EditorEvent::SelectionsChanged { local }
|
||||||
|
| EditorEvent::ScrollPositionChanged { local, .. } => {
|
||||||
|
if *local {
|
||||||
|
Some(FollowEvent::Unfollow)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn add_event_to_update_proto(
|
fn add_event_to_update_proto(
|
||||||
&self,
|
&self,
|
||||||
event: &Self::FollowableEvent,
|
event: &EditorEvent,
|
||||||
update: &mut Option<proto::update_view::Variant>,
|
update: &mut Option<proto::update_view::Variant>,
|
||||||
cx: &WindowContext,
|
cx: &WindowContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@ -528,6 +523,8 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Item for Editor {
|
impl Item for Editor {
|
||||||
|
type Event = EditorEvent;
|
||||||
|
|
||||||
fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
|
fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
|
||||||
if let Ok(data) = data.downcast::<NavigationData>() {
|
if let Ok(data) = data.downcast::<NavigationData>() {
|
||||||
let newest_selection = self.selections.newest::<Point>(cx);
|
let newest_selection = self.selections.newest::<Point>(cx);
|
||||||
@ -586,28 +583,25 @@ impl Item for Editor {
|
|||||||
fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
|
fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
|
|
||||||
AnyElement::new(
|
let description = detail.and_then(|detail| {
|
||||||
div()
|
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
|
||||||
.flex()
|
let description = path.to_string_lossy();
|
||||||
.flex_row()
|
let description = description.trim();
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.child(Label::new(self.title(cx).to_string()))
|
|
||||||
.children(detail.and_then(|detail| {
|
|
||||||
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
|
|
||||||
let description = path.to_string_lossy();
|
|
||||||
|
|
||||||
Some(
|
if description.is_empty() {
|
||||||
div().child(
|
return None;
|
||||||
Label::new(util::truncate_and_trailoff(
|
}
|
||||||
&description,
|
|
||||||
MAX_TAB_TITLE_LEN,
|
Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN))
|
||||||
))
|
});
|
||||||
.color(Color::Muted),
|
|
||||||
),
|
h_stack()
|
||||||
)
|
.gap_2()
|
||||||
})),
|
.child(Label::new(self.title(cx).to_string()))
|
||||||
)
|
.when_some(description, |this, description| {
|
||||||
|
this.child(Label::new(description).color(Color::Muted))
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn for_each_project_item(
|
fn for_each_project_item(
|
||||||
@ -841,6 +835,40 @@ impl Item for Editor {
|
|||||||
Some("Editor")
|
Some("Editor")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_item_events(event: &EditorEvent, mut f: impl FnMut(ItemEvent)) {
|
||||||
|
match event {
|
||||||
|
EditorEvent::Closed => f(ItemEvent::CloseItem),
|
||||||
|
|
||||||
|
EditorEvent::Saved | EditorEvent::TitleChanged => {
|
||||||
|
f(ItemEvent::UpdateTab);
|
||||||
|
f(ItemEvent::UpdateBreadcrumbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorEvent::Reparsed => {
|
||||||
|
f(ItemEvent::UpdateBreadcrumbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorEvent::SelectionsChanged { local } if *local => {
|
||||||
|
f(ItemEvent::UpdateBreadcrumbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorEvent::DirtyChanged => {
|
||||||
|
f(ItemEvent::UpdateTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorEvent::BufferEdited => {
|
||||||
|
f(ItemEvent::Edit);
|
||||||
|
f(ItemEvent::UpdateBreadcrumbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
|
||||||
|
f(ItemEvent::Edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn deserialize(
|
fn deserialize(
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use crate::{Bounds, Element, IntoElement, Pixels, StyleRefinement, Styled, WindowContext};
|
use refineable::Refineable as _;
|
||||||
|
|
||||||
|
use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
|
||||||
|
|
||||||
pub fn canvas(callback: impl 'static + FnOnce(Bounds<Pixels>, &mut WindowContext)) -> Canvas {
|
pub fn canvas(callback: impl 'static + FnOnce(Bounds<Pixels>, &mut WindowContext)) -> Canvas {
|
||||||
Canvas {
|
Canvas {
|
||||||
paint_callback: Box::new(callback),
|
paint_callback: Box::new(callback),
|
||||||
style: Default::default(),
|
style: StyleRefinement::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +34,9 @@ impl Element for Canvas {
|
|||||||
_: Option<Self::State>,
|
_: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (crate::LayoutId, Self::State) {
|
) -> (crate::LayoutId, Self::State) {
|
||||||
let layout_id = cx.request_layout(&self.style.clone().into(), []);
|
let mut style = Style::default();
|
||||||
|
style.refine(&self.style);
|
||||||
|
let layout_id = cx.request_layout(&style, []);
|
||||||
(layout_id, ())
|
(layout_id, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ use std::{
|
|||||||
use crate::DisplayId;
|
use crate::DisplayId;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
pub use sys::CVSMPTETime as SmtpeTime;
|
||||||
pub use sys::CVTimeStamp as VideoTimestamp;
|
pub use sys::CVTimeStamp as VideoTimestamp;
|
||||||
|
|
||||||
pub(crate) struct MacDisplayLinker {
|
pub(crate) struct MacDisplayLinker {
|
||||||
@ -153,7 +154,7 @@ mod sys {
|
|||||||
kCVTimeStampTopField | kCVTimeStampBottomField;
|
kCVTimeStampTopField | kCVTimeStampBottomField;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct CVSMPTETime {
|
pub struct CVSMPTETime {
|
||||||
pub subframes: i16,
|
pub subframes: i16,
|
||||||
pub subframe_divisor: i16,
|
pub subframe_divisor: i16,
|
||||||
|
@ -147,18 +147,25 @@ impl Platform for TestPlatform {
|
|||||||
fn set_display_link_output_callback(
|
fn set_display_link_output_callback(
|
||||||
&self,
|
&self,
|
||||||
_display_id: DisplayId,
|
_display_id: DisplayId,
|
||||||
_callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp) + Send>,
|
mut callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp) + Send>,
|
||||||
) {
|
) {
|
||||||
unimplemented!()
|
let timestamp = crate::VideoTimestamp {
|
||||||
|
version: 0,
|
||||||
|
video_time_scale: 0,
|
||||||
|
video_time: 0,
|
||||||
|
host_time: 0,
|
||||||
|
rate_scalar: 0.0,
|
||||||
|
video_refresh_period: 0,
|
||||||
|
smpte_time: crate::SmtpeTime::default(),
|
||||||
|
flags: 0,
|
||||||
|
reserved: 0,
|
||||||
|
};
|
||||||
|
callback(×tamp, ×tamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_display_link(&self, _display_id: DisplayId) {
|
fn start_display_link(&self, _display_id: DisplayId) {}
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_display_link(&self, _display_id: DisplayId) {
|
fn stop_display_link(&self, _display_id: DisplayId) {}
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_url(&self, _url: &str) {
|
fn open_url(&self, _url: &str) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -1121,20 +1121,22 @@ impl Project {
|
|||||||
project_path: impl Into<ProjectPath>,
|
project_path: impl Into<ProjectPath>,
|
||||||
is_directory: bool,
|
is_directory: bool,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let project_path = project_path.into();
|
let project_path = project_path.into();
|
||||||
let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
|
let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
|
||||||
|
return Task::ready(Ok(None));
|
||||||
|
};
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
Some(worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, cx| {
|
||||||
worktree
|
worktree
|
||||||
.as_local_mut()
|
.as_local_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.create_entry(project_path.path, is_directory, cx)
|
.create_entry(project_path.path, is_directory, cx)
|
||||||
}))
|
})
|
||||||
} else {
|
} else {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let project_id = self.remote_id().unwrap();
|
let project_id = self.remote_id().unwrap();
|
||||||
Some(cx.spawn_weak(|_, mut cx| async move {
|
cx.spawn_weak(|_, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::CreateProjectEntry {
|
.request(proto::CreateProjectEntry {
|
||||||
worktree_id: project_path.worktree_id.to_proto(),
|
worktree_id: project_path.worktree_id.to_proto(),
|
||||||
@ -1143,19 +1145,20 @@ impl Project {
|
|||||||
is_directory,
|
is_directory,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let entry = response
|
match response.entry {
|
||||||
.entry
|
Some(entry) => worktree
|
||||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
.update(&mut cx, |worktree, cx| {
|
||||||
worktree
|
worktree.as_remote_mut().unwrap().insert_entry(
|
||||||
.update(&mut cx, |worktree, cx| {
|
entry,
|
||||||
worktree.as_remote_mut().unwrap().insert_entry(
|
response.worktree_scan_id as usize,
|
||||||
entry,
|
cx,
|
||||||
response.worktree_scan_id as usize,
|
)
|
||||||
cx,
|
})
|
||||||
)
|
.await
|
||||||
})
|
.map(Some),
|
||||||
.await
|
None => Ok(None),
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1164,8 +1167,10 @@ impl Project {
|
|||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let worktree = self.worktree_for_entry(entry_id, cx)?;
|
let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
|
||||||
|
return Task::ready(Ok(None));
|
||||||
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, cx| {
|
||||||
@ -1178,7 +1183,7 @@ impl Project {
|
|||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let project_id = self.remote_id().unwrap();
|
let project_id = self.remote_id().unwrap();
|
||||||
|
|
||||||
Some(cx.spawn_weak(|_, mut cx| async move {
|
cx.spawn_weak(|_, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::CopyProjectEntry {
|
.request(proto::CopyProjectEntry {
|
||||||
project_id,
|
project_id,
|
||||||
@ -1186,19 +1191,20 @@ impl Project {
|
|||||||
new_path: new_path.to_string_lossy().into(),
|
new_path: new_path.to_string_lossy().into(),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let entry = response
|
match response.entry {
|
||||||
.entry
|
Some(entry) => worktree
|
||||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
.update(&mut cx, |worktree, cx| {
|
||||||
worktree
|
worktree.as_remote_mut().unwrap().insert_entry(
|
||||||
.update(&mut cx, |worktree, cx| {
|
entry,
|
||||||
worktree.as_remote_mut().unwrap().insert_entry(
|
response.worktree_scan_id as usize,
|
||||||
entry,
|
cx,
|
||||||
response.worktree_scan_id as usize,
|
)
|
||||||
cx,
|
})
|
||||||
)
|
.await
|
||||||
})
|
.map(Some),
|
||||||
.await
|
None => Ok(None),
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1207,8 +1213,10 @@ impl Project {
|
|||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let worktree = self.worktree_for_entry(entry_id, cx)?;
|
let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
|
||||||
|
return Task::ready(Ok(None));
|
||||||
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, cx| {
|
||||||
@ -1221,7 +1229,7 @@ impl Project {
|
|||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let project_id = self.remote_id().unwrap();
|
let project_id = self.remote_id().unwrap();
|
||||||
|
|
||||||
Some(cx.spawn_weak(|_, mut cx| async move {
|
cx.spawn_weak(|_, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::RenameProjectEntry {
|
.request(proto::RenameProjectEntry {
|
||||||
project_id,
|
project_id,
|
||||||
@ -1229,19 +1237,20 @@ impl Project {
|
|||||||
new_path: new_path.to_string_lossy().into(),
|
new_path: new_path.to_string_lossy().into(),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let entry = response
|
match response.entry {
|
||||||
.entry
|
Some(entry) => worktree
|
||||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
.update(&mut cx, |worktree, cx| {
|
||||||
worktree
|
worktree.as_remote_mut().unwrap().insert_entry(
|
||||||
.update(&mut cx, |worktree, cx| {
|
entry,
|
||||||
worktree.as_remote_mut().unwrap().insert_entry(
|
response.worktree_scan_id as usize,
|
||||||
entry,
|
cx,
|
||||||
response.worktree_scan_id as usize,
|
)
|
||||||
cx,
|
})
|
||||||
)
|
.await
|
||||||
})
|
.map(Some),
|
||||||
.await
|
None => Ok(None),
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1658,19 +1667,15 @@ impl Project {
|
|||||||
|
|
||||||
pub fn open_path(
|
pub fn open_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: impl Into<ProjectPath>,
|
path: ProjectPath,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<(ProjectEntryId, AnyModelHandle)>> {
|
) -> Task<Result<(Option<ProjectEntryId>, AnyModelHandle)>> {
|
||||||
let project_path = path.into();
|
let task = self.open_buffer(path.clone(), cx);
|
||||||
let task = self.open_buffer(project_path.clone(), cx);
|
|
||||||
cx.spawn_weak(|_, cx| async move {
|
cx.spawn_weak(|_, cx| async move {
|
||||||
let buffer = task.await?;
|
let buffer = task.await?;
|
||||||
let project_entry_id = buffer
|
let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
|
||||||
.read_with(&cx, |buffer, cx| {
|
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
|
||||||
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
|
});
|
||||||
})
|
|
||||||
.with_context(|| format!("no project entry for {project_path:?}"))?;
|
|
||||||
|
|
||||||
let buffer: &AnyModelHandle = &buffer;
|
let buffer: &AnyModelHandle = &buffer;
|
||||||
Ok((project_entry_id, buffer.clone()))
|
Ok((project_entry_id, buffer.clone()))
|
||||||
})
|
})
|
||||||
@ -1985,8 +1990,10 @@ impl Project {
|
|||||||
remote_id,
|
remote_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.local_buffer_ids_by_entry_id
|
if let Some(entry_id) = file.entry_id {
|
||||||
.insert(file.entry_id, remote_id);
|
self.local_buffer_ids_by_entry_id
|
||||||
|
.insert(entry_id, remote_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2441,24 +2448,25 @@ impl Project {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.local_buffer_ids_by_entry_id.get(&file.entry_id) {
|
let remote_id = buffer.read(cx).remote_id();
|
||||||
Some(_) => {
|
if let Some(entry_id) = file.entry_id {
|
||||||
return None;
|
match self.local_buffer_ids_by_entry_id.get(&entry_id) {
|
||||||
|
Some(_) => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.local_buffer_ids_by_entry_id
|
||||||
|
.insert(entry_id, remote_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
};
|
||||||
let remote_id = buffer.read(cx).remote_id();
|
self.local_buffer_ids_by_path.insert(
|
||||||
self.local_buffer_ids_by_entry_id
|
ProjectPath {
|
||||||
.insert(file.entry_id, remote_id);
|
worktree_id: file.worktree_id(cx),
|
||||||
|
path: file.path.clone(),
|
||||||
self.local_buffer_ids_by_path.insert(
|
},
|
||||||
ProjectPath {
|
remote_id,
|
||||||
worktree_id: file.worktree_id(cx),
|
);
|
||||||
path: file.path.clone(),
|
|
||||||
},
|
|
||||||
remote_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -5776,11 +5784,6 @@ impl Project {
|
|||||||
while let Some(ignored_abs_path) =
|
while let Some(ignored_abs_path) =
|
||||||
ignored_paths_to_process.pop_front()
|
ignored_paths_to_process.pop_front()
|
||||||
{
|
{
|
||||||
if !query.file_matches(Some(&ignored_abs_path))
|
|
||||||
|| snapshot.is_path_excluded(&ignored_abs_path)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(fs_metadata) = fs
|
if let Some(fs_metadata) = fs
|
||||||
.metadata(&ignored_abs_path)
|
.metadata(&ignored_abs_path)
|
||||||
.await
|
.await
|
||||||
@ -5808,6 +5811,13 @@ impl Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !fs_metadata.is_symlink {
|
} else if !fs_metadata.is_symlink {
|
||||||
|
if !query.file_matches(Some(&ignored_abs_path))
|
||||||
|
|| snapshot.is_path_excluded(
|
||||||
|
ignored_entry.path.to_path_buf(),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let matches = if let Some(file) = fs
|
let matches = if let Some(file) = fs
|
||||||
.open_sync(&ignored_abs_path)
|
.open_sync(&ignored_abs_path)
|
||||||
.await
|
.await
|
||||||
@ -6208,10 +6218,13 @@ impl Project {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_file = if let Some(entry) = snapshot.entry_for_id(old_file.entry_id) {
|
let new_file = if let Some(entry) = old_file
|
||||||
|
.entry_id
|
||||||
|
.and_then(|entry_id| snapshot.entry_for_id(entry_id))
|
||||||
|
{
|
||||||
File {
|
File {
|
||||||
is_local: true,
|
is_local: true,
|
||||||
entry_id: entry.id,
|
entry_id: Some(entry.id),
|
||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
worktree: worktree_handle.clone(),
|
worktree: worktree_handle.clone(),
|
||||||
@ -6220,7 +6233,7 @@ impl Project {
|
|||||||
} else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) {
|
} else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) {
|
||||||
File {
|
File {
|
||||||
is_local: true,
|
is_local: true,
|
||||||
entry_id: entry.id,
|
entry_id: Some(entry.id),
|
||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
worktree: worktree_handle.clone(),
|
worktree: worktree_handle.clone(),
|
||||||
@ -6250,10 +6263,12 @@ impl Project {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_file.entry_id != *entry_id {
|
if new_file.entry_id != Some(*entry_id) {
|
||||||
self.local_buffer_ids_by_entry_id.remove(entry_id);
|
self.local_buffer_ids_by_entry_id.remove(entry_id);
|
||||||
self.local_buffer_ids_by_entry_id
|
if let Some(entry_id) = new_file.entry_id {
|
||||||
.insert(new_file.entry_id, buffer_id);
|
self.local_buffer_ids_by_entry_id
|
||||||
|
.insert(entry_id, buffer_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_file != *old_file {
|
if new_file != *old_file {
|
||||||
@ -6816,7 +6831,7 @@ impl Project {
|
|||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
Ok(proto::ProjectEntryResponse {
|
Ok(proto::ProjectEntryResponse {
|
||||||
entry: Some((&entry).into()),
|
entry: entry.as_ref().map(|e| e.into()),
|
||||||
worktree_scan_id: worktree_scan_id as u64,
|
worktree_scan_id: worktree_scan_id as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -6840,11 +6855,10 @@ impl Project {
|
|||||||
.as_local_mut()
|
.as_local_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.rename_entry(entry_id, new_path, cx)
|
.rename_entry(entry_id, new_path, cx)
|
||||||
.ok_or_else(|| anyhow!("invalid entry"))
|
})
|
||||||
})?
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(proto::ProjectEntryResponse {
|
Ok(proto::ProjectEntryResponse {
|
||||||
entry: Some((&entry).into()),
|
entry: entry.as_ref().map(|e| e.into()),
|
||||||
worktree_scan_id: worktree_scan_id as u64,
|
worktree_scan_id: worktree_scan_id as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -6868,11 +6882,10 @@ impl Project {
|
|||||||
.as_local_mut()
|
.as_local_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.copy_entry(entry_id, new_path, cx)
|
.copy_entry(entry_id, new_path, cx)
|
||||||
.ok_or_else(|| anyhow!("invalid entry"))
|
})
|
||||||
})?
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(proto::ProjectEntryResponse {
|
Ok(proto::ProjectEntryResponse {
|
||||||
entry: Some((&entry).into()),
|
entry: entry.as_ref().map(|e| e.into()),
|
||||||
worktree_scan_id: worktree_scan_id as u64,
|
worktree_scan_id: worktree_scan_id as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -4050,6 +4050,94 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_search_in_gitignored_dirs(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/dir",
|
||||||
|
json!({
|
||||||
|
".git": {},
|
||||||
|
".gitignore": "**/target\n/node_modules\n",
|
||||||
|
"target": {
|
||||||
|
"index.txt": "index_key:index_value"
|
||||||
|
},
|
||||||
|
"node_modules": {
|
||||||
|
"eslint": {
|
||||||
|
"index.ts": "const eslint_key = 'eslint value'",
|
||||||
|
"package.json": r#"{ "some_key": "some value" }"#,
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"index.ts": "const prettier_key = 'prettier value'",
|
||||||
|
"package.json": r#"{ "other_key": "other value" }"#,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"package.json": r#"{ "main_key": "main value" }"#,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
|
|
||||||
|
let query = "key";
|
||||||
|
assert_eq!(
|
||||||
|
search(
|
||||||
|
&project,
|
||||||
|
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new()).unwrap(),
|
||||||
|
cx
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
HashMap::from_iter([("package.json".to_string(), vec![8..11])]),
|
||||||
|
"Only one non-ignored file should have the query"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
search(
|
||||||
|
&project,
|
||||||
|
SearchQuery::text(query, false, false, true, Vec::new(), Vec::new()).unwrap(),
|
||||||
|
cx
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
HashMap::from_iter([
|
||||||
|
("package.json".to_string(), vec![8..11]),
|
||||||
|
("target/index.txt".to_string(), vec![6..9]),
|
||||||
|
(
|
||||||
|
"node_modules/prettier/package.json".to_string(),
|
||||||
|
vec![9..12]
|
||||||
|
),
|
||||||
|
("node_modules/prettier/index.ts".to_string(), vec![15..18]),
|
||||||
|
("node_modules/eslint/index.ts".to_string(), vec![13..16]),
|
||||||
|
("node_modules/eslint/package.json".to_string(), vec![8..11]),
|
||||||
|
]),
|
||||||
|
"Unrestricted search with ignored directories should find every file with the query"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
search(
|
||||||
|
&project,
|
||||||
|
SearchQuery::text(
|
||||||
|
query,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
vec![PathMatcher::new("node_modules/prettier/**").unwrap()],
|
||||||
|
vec![PathMatcher::new("*.ts").unwrap()],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
cx
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
HashMap::from_iter([(
|
||||||
|
"node_modules/prettier/package.json".to_string(),
|
||||||
|
vec![9..12]
|
||||||
|
)]),
|
||||||
|
"With search including ignored prettier directory and excluding TS files, only one file should be found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_glob_literal_prefix() {
|
fn test_glob_literal_prefix() {
|
||||||
assert_eq!(glob_literal_prefix("**/*.js"), "");
|
assert_eq!(glob_literal_prefix("**/*.js"), "");
|
||||||
|
@ -371,15 +371,25 @@ impl SearchQuery {
|
|||||||
pub fn file_matches(&self, file_path: Option<&Path>) -> bool {
|
pub fn file_matches(&self, file_path: Option<&Path>) -> bool {
|
||||||
match file_path {
|
match file_path {
|
||||||
Some(file_path) => {
|
Some(file_path) => {
|
||||||
!self
|
let mut path = file_path.to_path_buf();
|
||||||
.files_to_exclude()
|
loop {
|
||||||
.iter()
|
if self
|
||||||
.any(|exclude_glob| exclude_glob.is_match(file_path))
|
.files_to_exclude()
|
||||||
&& (self.files_to_include().is_empty()
|
.iter()
|
||||||
|
.any(|exclude_glob| exclude_glob.is_match(&path))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
} else if self.files_to_include().is_empty()
|
||||||
|| self
|
|| self
|
||||||
.files_to_include()
|
.files_to_include()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|include_glob| include_glob.is_match(file_path)))
|
.any(|include_glob| include_glob.is_match(&path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
} else if !path.pop() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => self.files_to_include().is_empty(),
|
None => self.files_to_include().is_empty(),
|
||||||
}
|
}
|
||||||
|
@ -960,8 +960,6 @@ impl LocalWorktree {
|
|||||||
|
|
||||||
cx.spawn(|this, cx| async move {
|
cx.spawn(|this, cx| async move {
|
||||||
let text = fs.load(&abs_path).await?;
|
let text = fs.load(&abs_path).await?;
|
||||||
let entry = entry.await?;
|
|
||||||
|
|
||||||
let mut index_task = None;
|
let mut index_task = None;
|
||||||
let snapshot = this.read_with(&cx, |this, _| this.as_local().unwrap().snapshot());
|
let snapshot = this.read_with(&cx, |this, _| this.as_local().unwrap().snapshot());
|
||||||
if let Some(repo) = snapshot.repository_for_path(&path) {
|
if let Some(repo) = snapshot.repository_for_path(&path) {
|
||||||
@ -981,18 +979,43 @@ impl LocalWorktree {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
match entry.await? {
|
||||||
File {
|
Some(entry) => Ok((
|
||||||
entry_id: entry.id,
|
File {
|
||||||
worktree: this,
|
entry_id: Some(entry.id),
|
||||||
path: entry.path,
|
worktree: this,
|
||||||
mtime: entry.mtime,
|
path: entry.path,
|
||||||
is_local: true,
|
mtime: entry.mtime,
|
||||||
is_deleted: false,
|
is_local: true,
|
||||||
},
|
is_deleted: false,
|
||||||
text,
|
},
|
||||||
diff_base,
|
text,
|
||||||
))
|
diff_base,
|
||||||
|
)),
|
||||||
|
None => {
|
||||||
|
let metadata = fs
|
||||||
|
.metadata(&abs_path)
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Loading metadata for excluded file {abs_path:?}")
|
||||||
|
})?
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Excluded file {abs_path:?} got removed during loading")
|
||||||
|
})?;
|
||||||
|
Ok((
|
||||||
|
File {
|
||||||
|
entry_id: None,
|
||||||
|
worktree: this,
|
||||||
|
path,
|
||||||
|
mtime: metadata.mtime,
|
||||||
|
is_local: true,
|
||||||
|
is_deleted: false,
|
||||||
|
},
|
||||||
|
text,
|
||||||
|
diff_base,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1013,17 +1036,37 @@ impl LocalWorktree {
|
|||||||
let text = buffer.as_rope().clone();
|
let text = buffer.as_rope().clone();
|
||||||
let fingerprint = text.fingerprint();
|
let fingerprint = text.fingerprint();
|
||||||
let version = buffer.version();
|
let version = buffer.version();
|
||||||
let save = self.write_file(path, text, buffer.line_ending(), cx);
|
let save = self.write_file(path.as_ref(), text, buffer.line_ending(), cx);
|
||||||
|
let fs = Arc::clone(&self.fs);
|
||||||
|
let abs_path = self.absolutize(&path);
|
||||||
|
|
||||||
cx.as_mut().spawn(|mut cx| async move {
|
cx.as_mut().spawn(|mut cx| async move {
|
||||||
let entry = save.await?;
|
let entry = save.await?;
|
||||||
|
|
||||||
|
let (entry_id, mtime, path) = match entry {
|
||||||
|
Some(entry) => (Some(entry.id), entry.mtime, entry.path),
|
||||||
|
None => {
|
||||||
|
let metadata = fs
|
||||||
|
.metadata(&abs_path)
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Fetching metadata after saving the excluded buffer {abs_path:?}"
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Excluded buffer {path:?} got removed during saving")
|
||||||
|
})?;
|
||||||
|
(None, metadata.mtime, path)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if has_changed_file {
|
if has_changed_file {
|
||||||
let new_file = Arc::new(File {
|
let new_file = Arc::new(File {
|
||||||
entry_id: entry.id,
|
entry_id,
|
||||||
worktree: handle,
|
worktree: handle,
|
||||||
path: entry.path,
|
path,
|
||||||
mtime: entry.mtime,
|
mtime,
|
||||||
is_local: true,
|
is_local: true,
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
});
|
});
|
||||||
@ -1049,13 +1092,13 @@ impl LocalWorktree {
|
|||||||
project_id,
|
project_id,
|
||||||
buffer_id,
|
buffer_id,
|
||||||
version: serialize_version(&version),
|
version: serialize_version(&version),
|
||||||
mtime: Some(entry.mtime.into()),
|
mtime: Some(mtime.into()),
|
||||||
fingerprint: serialize_fingerprint(fingerprint),
|
fingerprint: serialize_fingerprint(fingerprint),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_handle.update(&mut cx, |buffer, cx| {
|
buffer_handle.update(&mut cx, |buffer, cx| {
|
||||||
buffer.did_save(version.clone(), fingerprint, entry.mtime, cx);
|
buffer.did_save(version.clone(), fingerprint, mtime, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1080,7 +1123,7 @@ impl LocalWorktree {
|
|||||||
path: impl Into<Arc<Path>>,
|
path: impl Into<Arc<Path>>,
|
||||||
is_dir: bool,
|
is_dir: bool,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Task<Result<Entry>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
let lowest_ancestor = self.lowest_ancestor(&path);
|
let lowest_ancestor = self.lowest_ancestor(&path);
|
||||||
let abs_path = self.absolutize(&path);
|
let abs_path = self.absolutize(&path);
|
||||||
@ -1097,7 +1140,7 @@ impl LocalWorktree {
|
|||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
write.await?;
|
write.await?;
|
||||||
let (result, refreshes) = this.update(&mut cx, |this, cx| {
|
let (result, refreshes) = this.update(&mut cx, |this, cx| {
|
||||||
let mut refreshes = Vec::<Task<anyhow::Result<Entry>>>::new();
|
let mut refreshes = Vec::new();
|
||||||
let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
|
let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
|
||||||
for refresh_path in refresh_paths.ancestors() {
|
for refresh_path in refresh_paths.ancestors() {
|
||||||
if refresh_path == Path::new("") {
|
if refresh_path == Path::new("") {
|
||||||
@ -1124,14 +1167,14 @@ impl LocalWorktree {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_file(
|
pub(crate) fn write_file(
|
||||||
&self,
|
&self,
|
||||||
path: impl Into<Arc<Path>>,
|
path: impl Into<Arc<Path>>,
|
||||||
text: Rope,
|
text: Rope,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Task<Result<Entry>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let path = path.into();
|
let path: Arc<Path> = path.into();
|
||||||
let abs_path = self.absolutize(&path);
|
let abs_path = self.absolutize(&path);
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
let write = cx
|
let write = cx
|
||||||
@ -1190,8 +1233,11 @@ impl LocalWorktree {
|
|||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let old_path = self.entry_for_id(entry_id)?.path.clone();
|
let old_path = match self.entry_for_id(entry_id) {
|
||||||
|
Some(entry) => entry.path.clone(),
|
||||||
|
None => return Task::ready(Ok(None)),
|
||||||
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
let abs_old_path = self.absolutize(&old_path);
|
let abs_old_path = self.absolutize(&old_path);
|
||||||
let abs_new_path = self.absolutize(&new_path);
|
let abs_new_path = self.absolutize(&new_path);
|
||||||
@ -1201,7 +1247,7 @@ impl LocalWorktree {
|
|||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
rename.await?;
|
rename.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.as_local_mut()
|
this.as_local_mut()
|
||||||
@ -1209,7 +1255,7 @@ impl LocalWorktree {
|
|||||||
.refresh_entry(new_path.clone(), Some(old_path), cx)
|
.refresh_entry(new_path.clone(), Some(old_path), cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_entry(
|
pub fn copy_entry(
|
||||||
@ -1217,8 +1263,11 @@ impl LocalWorktree {
|
|||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let old_path = self.entry_for_id(entry_id)?.path.clone();
|
let old_path = match self.entry_for_id(entry_id) {
|
||||||
|
Some(entry) => entry.path.clone(),
|
||||||
|
None => return Task::ready(Ok(None)),
|
||||||
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
let abs_old_path = self.absolutize(&old_path);
|
let abs_old_path = self.absolutize(&old_path);
|
||||||
let abs_new_path = self.absolutize(&new_path);
|
let abs_new_path = self.absolutize(&new_path);
|
||||||
@ -1233,7 +1282,7 @@ impl LocalWorktree {
|
|||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
copy.await?;
|
copy.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.as_local_mut()
|
this.as_local_mut()
|
||||||
@ -1241,7 +1290,7 @@ impl LocalWorktree {
|
|||||||
.refresh_entry(new_path.clone(), None, cx)
|
.refresh_entry(new_path.clone(), None, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_entry(
|
pub fn expand_entry(
|
||||||
@ -1277,7 +1326,10 @@ impl LocalWorktree {
|
|||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
old_path: Option<Arc<Path>>,
|
old_path: Option<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Task<Result<Entry>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
|
if self.is_path_excluded(path.to_path_buf()) {
|
||||||
|
return Task::ready(Ok(None));
|
||||||
|
}
|
||||||
let paths = if let Some(old_path) = old_path.as_ref() {
|
let paths = if let Some(old_path) = old_path.as_ref() {
|
||||||
vec![old_path.clone(), path.clone()]
|
vec![old_path.clone(), path.clone()]
|
||||||
} else {
|
} else {
|
||||||
@ -1286,13 +1338,15 @@ impl LocalWorktree {
|
|||||||
let mut refresh = self.refresh_entries_for_paths(paths);
|
let mut refresh = self.refresh_entries_for_paths(paths);
|
||||||
cx.spawn_weak(move |this, mut cx| async move {
|
cx.spawn_weak(move |this, mut cx| async move {
|
||||||
refresh.recv().await;
|
refresh.recv().await;
|
||||||
this.upgrade(&cx)
|
let new_entry = this
|
||||||
|
.upgrade(&cx)
|
||||||
.ok_or_else(|| anyhow!("worktree was dropped"))?
|
.ok_or_else(|| anyhow!("worktree was dropped"))?
|
||||||
.update(&mut cx, |this, _| {
|
.update(&mut cx, |this, _| {
|
||||||
this.entry_for_path(path)
|
this.entry_for_path(path)
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| anyhow!("failed to read path after update"))
|
.ok_or_else(|| anyhow!("failed to read path after update"))
|
||||||
})
|
})?;
|
||||||
|
Ok(Some(new_entry))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2226,10 +2280,19 @@ impl LocalSnapshot {
|
|||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_path_excluded(&self, abs_path: &Path) -> bool {
|
pub fn is_path_excluded(&self, mut path: PathBuf) -> bool {
|
||||||
self.file_scan_exclusions
|
loop {
|
||||||
.iter()
|
if self
|
||||||
.any(|exclude_matcher| exclude_matcher.is_match(abs_path))
|
.file_scan_exclusions
|
||||||
|
.iter()
|
||||||
|
.any(|exclude_matcher| exclude_matcher.is_match(&path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if !path.pop() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2458,8 +2521,7 @@ impl BackgroundScannerState {
|
|||||||
ids_to_preserve.insert(work_directory_id);
|
ids_to_preserve.insert(work_directory_id);
|
||||||
} else {
|
} else {
|
||||||
let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
|
let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
|
||||||
let git_dir_excluded = snapshot.is_path_excluded(&entry.git_dir_path)
|
let git_dir_excluded = snapshot.is_path_excluded(entry.git_dir_path.to_path_buf());
|
||||||
|| snapshot.is_path_excluded(&git_dir_abs_path);
|
|
||||||
if git_dir_excluded
|
if git_dir_excluded
|
||||||
&& !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
|
&& !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
|
||||||
{
|
{
|
||||||
@ -2666,7 +2728,7 @@ pub struct File {
|
|||||||
pub worktree: ModelHandle<Worktree>,
|
pub worktree: ModelHandle<Worktree>,
|
||||||
pub path: Arc<Path>,
|
pub path: Arc<Path>,
|
||||||
pub mtime: SystemTime,
|
pub mtime: SystemTime,
|
||||||
pub(crate) entry_id: ProjectEntryId,
|
pub(crate) entry_id: Option<ProjectEntryId>,
|
||||||
pub(crate) is_local: bool,
|
pub(crate) is_local: bool,
|
||||||
pub(crate) is_deleted: bool,
|
pub(crate) is_deleted: bool,
|
||||||
}
|
}
|
||||||
@ -2735,7 +2797,7 @@ impl language::File for File {
|
|||||||
fn to_proto(&self) -> rpc::proto::File {
|
fn to_proto(&self) -> rpc::proto::File {
|
||||||
rpc::proto::File {
|
rpc::proto::File {
|
||||||
worktree_id: self.worktree.id() as u64,
|
worktree_id: self.worktree.id() as u64,
|
||||||
entry_id: self.entry_id.to_proto(),
|
entry_id: self.entry_id.map(|id| id.to_proto()),
|
||||||
path: self.path.to_string_lossy().into(),
|
path: self.path.to_string_lossy().into(),
|
||||||
mtime: Some(self.mtime.into()),
|
mtime: Some(self.mtime.into()),
|
||||||
is_deleted: self.is_deleted,
|
is_deleted: self.is_deleted,
|
||||||
@ -2793,7 +2855,7 @@ impl File {
|
|||||||
worktree,
|
worktree,
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
entry_id: entry.id,
|
entry_id: Some(entry.id),
|
||||||
is_local: true,
|
is_local: true,
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
})
|
})
|
||||||
@ -2818,7 +2880,7 @@ impl File {
|
|||||||
worktree,
|
worktree,
|
||||||
path: Path::new(&proto.path).into(),
|
path: Path::new(&proto.path).into(),
|
||||||
mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
|
mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
|
||||||
entry_id: ProjectEntryId::from_proto(proto.entry_id),
|
entry_id: proto.entry_id.map(ProjectEntryId::from_proto),
|
||||||
is_local: false,
|
is_local: false,
|
||||||
is_deleted: proto.is_deleted,
|
is_deleted: proto.is_deleted,
|
||||||
})
|
})
|
||||||
@ -2836,7 +2898,7 @@ impl File {
|
|||||||
if self.is_deleted {
|
if self.is_deleted {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(self.entry_id)
|
self.entry_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3338,16 +3400,7 @@ impl BackgroundScanner {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FS events may come for files which parent directory is excluded, need to check ignore those.
|
if snapshot.is_path_excluded(relative_path.to_path_buf()) {
|
||||||
let mut path_to_test = abs_path.clone();
|
|
||||||
let mut excluded_file_event = snapshot.is_path_excluded(abs_path)
|
|
||||||
|| snapshot.is_path_excluded(&relative_path);
|
|
||||||
while !excluded_file_event && path_to_test.pop() {
|
|
||||||
if snapshot.is_path_excluded(&path_to_test) {
|
|
||||||
excluded_file_event = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if excluded_file_event {
|
|
||||||
if !is_git_related {
|
if !is_git_related {
|
||||||
log::debug!("ignoring FS event for excluded path {relative_path:?}");
|
log::debug!("ignoring FS event for excluded path {relative_path:?}");
|
||||||
}
|
}
|
||||||
@ -3531,7 +3584,7 @@ impl BackgroundScanner {
|
|||||||
let state = self.state.lock();
|
let state = self.state.lock();
|
||||||
let snapshot = &state.snapshot;
|
let snapshot = &state.snapshot;
|
||||||
root_abs_path = snapshot.abs_path().clone();
|
root_abs_path = snapshot.abs_path().clone();
|
||||||
if snapshot.is_path_excluded(&job.abs_path) {
|
if snapshot.is_path_excluded(job.path.to_path_buf()) {
|
||||||
log::error!("skipping excluded directory {:?}", job.path);
|
log::error!("skipping excluded directory {:?}", job.path);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -3603,8 +3656,8 @@ impl BackgroundScanner {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
if state.snapshot.is_path_excluded(&child_abs_path) {
|
let relative_path = job.path.join(child_name);
|
||||||
let relative_path = job.path.join(child_name);
|
if state.snapshot.is_path_excluded(relative_path.clone()) {
|
||||||
log::debug!("skipping excluded child entry {relative_path:?}");
|
log::debug!("skipping excluded child entry {relative_path:?}");
|
||||||
state.remove_path(&relative_path);
|
state.remove_path(&relative_path);
|
||||||
continue;
|
continue;
|
||||||
|
@ -1052,11 +1052,12 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||||||
&[
|
&[
|
||||||
".git/HEAD",
|
".git/HEAD",
|
||||||
".git/foo",
|
".git/foo",
|
||||||
|
"node_modules",
|
||||||
"node_modules/.DS_Store",
|
"node_modules/.DS_Store",
|
||||||
"node_modules/prettier",
|
"node_modules/prettier",
|
||||||
"node_modules/prettier/package.json",
|
"node_modules/prettier/package.json",
|
||||||
],
|
],
|
||||||
&["target", "node_modules"],
|
&["target"],
|
||||||
&[
|
&[
|
||||||
".DS_Store",
|
".DS_Store",
|
||||||
"src/.DS_Store",
|
"src/.DS_Store",
|
||||||
@ -1106,6 +1107,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||||||
".git/HEAD",
|
".git/HEAD",
|
||||||
".git/foo",
|
".git/foo",
|
||||||
".git/new_file",
|
".git/new_file",
|
||||||
|
"node_modules",
|
||||||
"node_modules/.DS_Store",
|
"node_modules/.DS_Store",
|
||||||
"node_modules/prettier",
|
"node_modules/prettier",
|
||||||
"node_modules/prettier/package.json",
|
"node_modules/prettier/package.json",
|
||||||
@ -1114,7 +1116,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||||||
"build_output/new_file",
|
"build_output/new_file",
|
||||||
"test_output/new_file",
|
"test_output/new_file",
|
||||||
],
|
],
|
||||||
&["target", "node_modules", "test_output"],
|
&["target", "test_output"],
|
||||||
&[
|
&[
|
||||||
".DS_Store",
|
".DS_Store",
|
||||||
"src/.DS_Store",
|
"src/.DS_Store",
|
||||||
@ -1174,6 +1176,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||||||
.create_entry("a/e".as_ref(), true, cx)
|
.create_entry("a/e".as_ref(), true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_dir());
|
assert!(entry.is_dir());
|
||||||
|
|
||||||
@ -1222,6 +1225,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||||||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_file());
|
assert!(entry.is_file());
|
||||||
|
|
||||||
@ -1257,6 +1261,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||||||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_file());
|
assert!(entry.is_file());
|
||||||
|
|
||||||
@ -1275,6 +1280,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||||||
.create_entry("a/b/c/e.txt".as_ref(), false, cx)
|
.create_entry("a/b/c/e.txt".as_ref(), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_file());
|
assert!(entry.is_file());
|
||||||
|
|
||||||
@ -1291,6 +1297,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||||||
.create_entry("d/e/f/g.txt".as_ref(), false, cx)
|
.create_entry("d/e/f/g.txt".as_ref(), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_file());
|
assert!(entry.is_file());
|
||||||
|
|
||||||
@ -1616,14 +1623,14 @@ fn randomly_mutate_worktree(
|
|||||||
entry.id.0,
|
entry.id.0,
|
||||||
new_path
|
new_path
|
||||||
);
|
);
|
||||||
let task = worktree.rename_entry(entry.id, new_path, cx).unwrap();
|
let task = worktree.rename_entry(entry.id, new_path, cx);
|
||||||
cx.foreground().spawn(async move {
|
cx.foreground().spawn(async move {
|
||||||
task.await?;
|
task.await?.unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let task = if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
let child_path = entry.path.join(random_filename(rng));
|
let child_path = entry.path.join(random_filename(rng));
|
||||||
let is_dir = rng.gen_bool(0.3);
|
let is_dir = rng.gen_bool(0.3);
|
||||||
log::info!(
|
log::info!(
|
||||||
@ -1631,15 +1638,20 @@ fn randomly_mutate_worktree(
|
|||||||
if is_dir { "dir" } else { "file" },
|
if is_dir { "dir" } else { "file" },
|
||||||
child_path,
|
child_path,
|
||||||
);
|
);
|
||||||
worktree.create_entry(child_path, is_dir, cx)
|
let task = worktree.create_entry(child_path, is_dir, cx);
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
task.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
|
log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
|
||||||
worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx)
|
let task =
|
||||||
};
|
worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx);
|
||||||
cx.foreground().spawn(async move {
|
cx.foreground().spawn(async move {
|
||||||
task.await?;
|
task.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1151,20 +1151,22 @@ impl Project {
|
|||||||
project_path: impl Into<ProjectPath>,
|
project_path: impl Into<ProjectPath>,
|
||||||
is_directory: bool,
|
is_directory: bool,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let project_path = project_path.into();
|
let project_path = project_path.into();
|
||||||
let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
|
let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
|
||||||
|
return Task::ready(Ok(None));
|
||||||
|
};
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
Some(worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, cx| {
|
||||||
worktree
|
worktree
|
||||||
.as_local_mut()
|
.as_local_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.create_entry(project_path.path, is_directory, cx)
|
.create_entry(project_path.path, is_directory, cx)
|
||||||
}))
|
})
|
||||||
} else {
|
} else {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let project_id = self.remote_id().unwrap();
|
let project_id = self.remote_id().unwrap();
|
||||||
Some(cx.spawn(move |_, mut cx| async move {
|
cx.spawn(move |_, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::CreateProjectEntry {
|
.request(proto::CreateProjectEntry {
|
||||||
worktree_id: project_path.worktree_id.to_proto(),
|
worktree_id: project_path.worktree_id.to_proto(),
|
||||||
@ -1173,19 +1175,20 @@ impl Project {
|
|||||||
is_directory,
|
is_directory,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let entry = response
|
match response.entry {
|
||||||
.entry
|
Some(entry) => worktree
|
||||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
.update(&mut cx, |worktree, cx| {
|
||||||
worktree
|
worktree.as_remote_mut().unwrap().insert_entry(
|
||||||
.update(&mut cx, |worktree, cx| {
|
entry,
|
||||||
worktree.as_remote_mut().unwrap().insert_entry(
|
response.worktree_scan_id as usize,
|
||||||
entry,
|
cx,
|
||||||
response.worktree_scan_id as usize,
|
)
|
||||||
cx,
|
})?
|
||||||
)
|
.await
|
||||||
})?
|
.map(Some),
|
||||||
.await
|
None => Ok(None),
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1194,8 +1197,10 @@ impl Project {
|
|||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let worktree = self.worktree_for_entry(entry_id, cx)?;
|
let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
|
||||||
|
return Task::ready(Ok(None));
|
||||||
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, cx| {
|
||||||
@ -1208,7 +1213,7 @@ impl Project {
|
|||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let project_id = self.remote_id().unwrap();
|
let project_id = self.remote_id().unwrap();
|
||||||
|
|
||||||
Some(cx.spawn(move |_, mut cx| async move {
|
cx.spawn(move |_, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::CopyProjectEntry {
|
.request(proto::CopyProjectEntry {
|
||||||
project_id,
|
project_id,
|
||||||
@ -1216,19 +1221,20 @@ impl Project {
|
|||||||
new_path: new_path.to_string_lossy().into(),
|
new_path: new_path.to_string_lossy().into(),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let entry = response
|
match response.entry {
|
||||||
.entry
|
Some(entry) => worktree
|
||||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
.update(&mut cx, |worktree, cx| {
|
||||||
worktree
|
worktree.as_remote_mut().unwrap().insert_entry(
|
||||||
.update(&mut cx, |worktree, cx| {
|
entry,
|
||||||
worktree.as_remote_mut().unwrap().insert_entry(
|
response.worktree_scan_id as usize,
|
||||||
entry,
|
cx,
|
||||||
response.worktree_scan_id as usize,
|
)
|
||||||
cx,
|
})?
|
||||||
)
|
.await
|
||||||
})?
|
.map(Some),
|
||||||
.await
|
None => Ok(None),
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1237,8 +1243,10 @@ impl Project {
|
|||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let worktree = self.worktree_for_entry(entry_id, cx)?;
|
let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
|
||||||
|
return Task::ready(Ok(None));
|
||||||
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, cx| {
|
||||||
@ -1251,7 +1259,7 @@ impl Project {
|
|||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let project_id = self.remote_id().unwrap();
|
let project_id = self.remote_id().unwrap();
|
||||||
|
|
||||||
Some(cx.spawn(move |_, mut cx| async move {
|
cx.spawn(move |_, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::RenameProjectEntry {
|
.request(proto::RenameProjectEntry {
|
||||||
project_id,
|
project_id,
|
||||||
@ -1259,19 +1267,20 @@ impl Project {
|
|||||||
new_path: new_path.to_string_lossy().into(),
|
new_path: new_path.to_string_lossy().into(),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let entry = response
|
match response.entry {
|
||||||
.entry
|
Some(entry) => worktree
|
||||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
.update(&mut cx, |worktree, cx| {
|
||||||
worktree
|
worktree.as_remote_mut().unwrap().insert_entry(
|
||||||
.update(&mut cx, |worktree, cx| {
|
entry,
|
||||||
worktree.as_remote_mut().unwrap().insert_entry(
|
response.worktree_scan_id as usize,
|
||||||
entry,
|
cx,
|
||||||
response.worktree_scan_id as usize,
|
)
|
||||||
cx,
|
})?
|
||||||
)
|
.await
|
||||||
})?
|
.map(Some),
|
||||||
.await
|
None => Ok(None),
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1688,18 +1697,15 @@ impl Project {
|
|||||||
|
|
||||||
pub fn open_path(
|
pub fn open_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: impl Into<ProjectPath>,
|
path: ProjectPath,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<(ProjectEntryId, AnyModel)>> {
|
) -> Task<Result<(Option<ProjectEntryId>, AnyModel)>> {
|
||||||
let project_path = path.into();
|
let task = self.open_buffer(path.clone(), cx);
|
||||||
let task = self.open_buffer(project_path.clone(), cx);
|
cx.spawn(move |_, cx| async move {
|
||||||
cx.spawn(move |_, mut cx| async move {
|
|
||||||
let buffer = task.await?;
|
let buffer = task.await?;
|
||||||
let project_entry_id = buffer
|
let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
|
||||||
.update(&mut cx, |buffer, cx| {
|
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
|
||||||
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
|
})?;
|
||||||
})?
|
|
||||||
.with_context(|| format!("no project entry for {project_path:?}"))?;
|
|
||||||
|
|
||||||
let buffer: &AnyModel = &buffer;
|
let buffer: &AnyModel = &buffer;
|
||||||
Ok((project_entry_id, buffer.clone()))
|
Ok((project_entry_id, buffer.clone()))
|
||||||
@ -2018,8 +2024,10 @@ impl Project {
|
|||||||
remote_id,
|
remote_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.local_buffer_ids_by_entry_id
|
if let Some(entry_id) = file.entry_id {
|
||||||
.insert(file.entry_id, remote_id);
|
self.local_buffer_ids_by_entry_id
|
||||||
|
.insert(entry_id, remote_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2474,24 +2482,25 @@ impl Project {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.local_buffer_ids_by_entry_id.get(&file.entry_id) {
|
let remote_id = buffer.read(cx).remote_id();
|
||||||
Some(_) => {
|
if let Some(entry_id) = file.entry_id {
|
||||||
return None;
|
match self.local_buffer_ids_by_entry_id.get(&entry_id) {
|
||||||
|
Some(_) => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.local_buffer_ids_by_entry_id
|
||||||
|
.insert(entry_id, remote_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
};
|
||||||
let remote_id = buffer.read(cx).remote_id();
|
self.local_buffer_ids_by_path.insert(
|
||||||
self.local_buffer_ids_by_entry_id
|
ProjectPath {
|
||||||
.insert(file.entry_id, remote_id);
|
worktree_id: file.worktree_id(cx),
|
||||||
|
path: file.path.clone(),
|
||||||
self.local_buffer_ids_by_path.insert(
|
},
|
||||||
ProjectPath {
|
remote_id,
|
||||||
worktree_id: file.worktree_id(cx),
|
);
|
||||||
path: file.path.clone(),
|
|
||||||
},
|
|
||||||
remote_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -5845,11 +5854,6 @@ impl Project {
|
|||||||
while let Some(ignored_abs_path) =
|
while let Some(ignored_abs_path) =
|
||||||
ignored_paths_to_process.pop_front()
|
ignored_paths_to_process.pop_front()
|
||||||
{
|
{
|
||||||
if !query.file_matches(Some(&ignored_abs_path))
|
|
||||||
|| snapshot.is_path_excluded(&ignored_abs_path)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(fs_metadata) = fs
|
if let Some(fs_metadata) = fs
|
||||||
.metadata(&ignored_abs_path)
|
.metadata(&ignored_abs_path)
|
||||||
.await
|
.await
|
||||||
@ -5877,6 +5881,13 @@ impl Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !fs_metadata.is_symlink {
|
} else if !fs_metadata.is_symlink {
|
||||||
|
if !query.file_matches(Some(&ignored_abs_path))
|
||||||
|
|| snapshot.is_path_excluded(
|
||||||
|
ignored_entry.path.to_path_buf(),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let matches = if let Some(file) = fs
|
let matches = if let Some(file) = fs
|
||||||
.open_sync(&ignored_abs_path)
|
.open_sync(&ignored_abs_path)
|
||||||
.await
|
.await
|
||||||
@ -6278,10 +6289,13 @@ impl Project {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_file = if let Some(entry) = snapshot.entry_for_id(old_file.entry_id) {
|
let new_file = if let Some(entry) = old_file
|
||||||
|
.entry_id
|
||||||
|
.and_then(|entry_id| snapshot.entry_for_id(entry_id))
|
||||||
|
{
|
||||||
File {
|
File {
|
||||||
is_local: true,
|
is_local: true,
|
||||||
entry_id: entry.id,
|
entry_id: Some(entry.id),
|
||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
worktree: worktree_handle.clone(),
|
worktree: worktree_handle.clone(),
|
||||||
@ -6290,7 +6304,7 @@ impl Project {
|
|||||||
} else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) {
|
} else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) {
|
||||||
File {
|
File {
|
||||||
is_local: true,
|
is_local: true,
|
||||||
entry_id: entry.id,
|
entry_id: Some(entry.id),
|
||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
worktree: worktree_handle.clone(),
|
worktree: worktree_handle.clone(),
|
||||||
@ -6320,10 +6334,12 @@ impl Project {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_file.entry_id != *entry_id {
|
if new_file.entry_id != Some(*entry_id) {
|
||||||
self.local_buffer_ids_by_entry_id.remove(entry_id);
|
self.local_buffer_ids_by_entry_id.remove(entry_id);
|
||||||
self.local_buffer_ids_by_entry_id
|
if let Some(entry_id) = new_file.entry_id {
|
||||||
.insert(new_file.entry_id, buffer_id);
|
self.local_buffer_ids_by_entry_id
|
||||||
|
.insert(entry_id, buffer_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_file != *old_file {
|
if new_file != *old_file {
|
||||||
@ -6890,7 +6906,7 @@ impl Project {
|
|||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
Ok(proto::ProjectEntryResponse {
|
Ok(proto::ProjectEntryResponse {
|
||||||
entry: Some((&entry).into()),
|
entry: entry.as_ref().map(|e| e.into()),
|
||||||
worktree_scan_id: worktree_scan_id as u64,
|
worktree_scan_id: worktree_scan_id as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -6914,11 +6930,10 @@ impl Project {
|
|||||||
.as_local_mut()
|
.as_local_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.rename_entry(entry_id, new_path, cx)
|
.rename_entry(entry_id, new_path, cx)
|
||||||
.ok_or_else(|| anyhow!("invalid entry"))
|
})?
|
||||||
})??
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(proto::ProjectEntryResponse {
|
Ok(proto::ProjectEntryResponse {
|
||||||
entry: Some((&entry).into()),
|
entry: entry.as_ref().map(|e| e.into()),
|
||||||
worktree_scan_id: worktree_scan_id as u64,
|
worktree_scan_id: worktree_scan_id as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -6942,11 +6957,10 @@ impl Project {
|
|||||||
.as_local_mut()
|
.as_local_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.copy_entry(entry_id, new_path, cx)
|
.copy_entry(entry_id, new_path, cx)
|
||||||
.ok_or_else(|| anyhow!("invalid entry"))
|
})?
|
||||||
})??
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(proto::ProjectEntryResponse {
|
Ok(proto::ProjectEntryResponse {
|
||||||
entry: Some((&entry).into()),
|
entry: entry.as_ref().map(|e| e.into()),
|
||||||
worktree_scan_id: worktree_scan_id as u64,
|
worktree_scan_id: worktree_scan_id as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -4182,6 +4182,94 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_search_in_gitignored_dirs(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background_executor.clone());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/dir",
|
||||||
|
json!({
|
||||||
|
".git": {},
|
||||||
|
".gitignore": "**/target\n/node_modules\n",
|
||||||
|
"target": {
|
||||||
|
"index.txt": "index_key:index_value"
|
||||||
|
},
|
||||||
|
"node_modules": {
|
||||||
|
"eslint": {
|
||||||
|
"index.ts": "const eslint_key = 'eslint value'",
|
||||||
|
"package.json": r#"{ "some_key": "some value" }"#,
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"index.ts": "const prettier_key = 'prettier value'",
|
||||||
|
"package.json": r#"{ "other_key": "other value" }"#,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"package.json": r#"{ "main_key": "main value" }"#,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
|
|
||||||
|
let query = "key";
|
||||||
|
assert_eq!(
|
||||||
|
search(
|
||||||
|
&project,
|
||||||
|
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new()).unwrap(),
|
||||||
|
cx
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
HashMap::from_iter([("package.json".to_string(), vec![8..11])]),
|
||||||
|
"Only one non-ignored file should have the query"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
search(
|
||||||
|
&project,
|
||||||
|
SearchQuery::text(query, false, false, true, Vec::new(), Vec::new()).unwrap(),
|
||||||
|
cx
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
HashMap::from_iter([
|
||||||
|
("package.json".to_string(), vec![8..11]),
|
||||||
|
("target/index.txt".to_string(), vec![6..9]),
|
||||||
|
(
|
||||||
|
"node_modules/prettier/package.json".to_string(),
|
||||||
|
vec![9..12]
|
||||||
|
),
|
||||||
|
("node_modules/prettier/index.ts".to_string(), vec![15..18]),
|
||||||
|
("node_modules/eslint/index.ts".to_string(), vec![13..16]),
|
||||||
|
("node_modules/eslint/package.json".to_string(), vec![8..11]),
|
||||||
|
]),
|
||||||
|
"Unrestricted search with ignored directories should find every file with the query"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
search(
|
||||||
|
&project,
|
||||||
|
SearchQuery::text(
|
||||||
|
query,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
vec![PathMatcher::new("node_modules/prettier/**").unwrap()],
|
||||||
|
vec![PathMatcher::new("*.ts").unwrap()],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
cx
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
HashMap::from_iter([(
|
||||||
|
"node_modules/prettier/package.json".to_string(),
|
||||||
|
vec![9..12]
|
||||||
|
)]),
|
||||||
|
"With search including ignored prettier directory and excluding TS files, only one file should be found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_glob_literal_prefix() {
|
fn test_glob_literal_prefix() {
|
||||||
assert_eq!(glob_literal_prefix("**/*.js"), "");
|
assert_eq!(glob_literal_prefix("**/*.js"), "");
|
||||||
|
@ -371,15 +371,25 @@ impl SearchQuery {
|
|||||||
pub fn file_matches(&self, file_path: Option<&Path>) -> bool {
|
pub fn file_matches(&self, file_path: Option<&Path>) -> bool {
|
||||||
match file_path {
|
match file_path {
|
||||||
Some(file_path) => {
|
Some(file_path) => {
|
||||||
!self
|
let mut path = file_path.to_path_buf();
|
||||||
.files_to_exclude()
|
loop {
|
||||||
.iter()
|
if self
|
||||||
.any(|exclude_glob| exclude_glob.is_match(file_path))
|
.files_to_exclude()
|
||||||
&& (self.files_to_include().is_empty()
|
.iter()
|
||||||
|
.any(|exclude_glob| exclude_glob.is_match(&path))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
} else if self.files_to_include().is_empty()
|
||||||
|| self
|
|| self
|
||||||
.files_to_include()
|
.files_to_include()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|include_glob| include_glob.is_match(file_path)))
|
.any(|include_glob| include_glob.is_match(&path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
} else if !path.pop() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => self.files_to_include().is_empty(),
|
None => self.files_to_include().is_empty(),
|
||||||
}
|
}
|
||||||
|
@ -958,8 +958,6 @@ impl LocalWorktree {
|
|||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let text = fs.load(&abs_path).await?;
|
let text = fs.load(&abs_path).await?;
|
||||||
let entry = entry.await?;
|
|
||||||
|
|
||||||
let mut index_task = None;
|
let mut index_task = None;
|
||||||
let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?;
|
let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?;
|
||||||
if let Some(repo) = snapshot.repository_for_path(&path) {
|
if let Some(repo) = snapshot.repository_for_path(&path) {
|
||||||
@ -982,18 +980,43 @@ impl LocalWorktree {
|
|||||||
let worktree = this
|
let worktree = this
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or_else(|| anyhow!("worktree was dropped"))?;
|
.ok_or_else(|| anyhow!("worktree was dropped"))?;
|
||||||
Ok((
|
match entry.await? {
|
||||||
File {
|
Some(entry) => Ok((
|
||||||
entry_id: entry.id,
|
File {
|
||||||
worktree,
|
entry_id: Some(entry.id),
|
||||||
path: entry.path,
|
worktree,
|
||||||
mtime: entry.mtime,
|
path: entry.path,
|
||||||
is_local: true,
|
mtime: entry.mtime,
|
||||||
is_deleted: false,
|
is_local: true,
|
||||||
},
|
is_deleted: false,
|
||||||
text,
|
},
|
||||||
diff_base,
|
text,
|
||||||
))
|
diff_base,
|
||||||
|
)),
|
||||||
|
None => {
|
||||||
|
let metadata = fs
|
||||||
|
.metadata(&abs_path)
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Loading metadata for excluded file {abs_path:?}")
|
||||||
|
})?
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Excluded file {abs_path:?} got removed during loading")
|
||||||
|
})?;
|
||||||
|
Ok((
|
||||||
|
File {
|
||||||
|
entry_id: None,
|
||||||
|
worktree,
|
||||||
|
path,
|
||||||
|
mtime: metadata.mtime,
|
||||||
|
is_local: true,
|
||||||
|
is_deleted: false,
|
||||||
|
},
|
||||||
|
text,
|
||||||
|
diff_base,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1013,18 +1036,38 @@ impl LocalWorktree {
|
|||||||
let text = buffer.as_rope().clone();
|
let text = buffer.as_rope().clone();
|
||||||
let fingerprint = text.fingerprint();
|
let fingerprint = text.fingerprint();
|
||||||
let version = buffer.version();
|
let version = buffer.version();
|
||||||
let save = self.write_file(path, text, buffer.line_ending(), cx);
|
let save = self.write_file(path.as_ref(), text, buffer.line_ending(), cx);
|
||||||
|
let fs = Arc::clone(&self.fs);
|
||||||
|
let abs_path = self.absolutize(&path);
|
||||||
|
|
||||||
cx.spawn(move |this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let entry = save.await?;
|
let entry = save.await?;
|
||||||
let this = this.upgrade().context("worktree dropped")?;
|
let this = this.upgrade().context("worktree dropped")?;
|
||||||
|
|
||||||
|
let (entry_id, mtime, path) = match entry {
|
||||||
|
Some(entry) => (Some(entry.id), entry.mtime, entry.path),
|
||||||
|
None => {
|
||||||
|
let metadata = fs
|
||||||
|
.metadata(&abs_path)
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Fetching metadata after saving the excluded buffer {abs_path:?}"
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Excluded buffer {path:?} got removed during saving")
|
||||||
|
})?;
|
||||||
|
(None, metadata.mtime, path)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if has_changed_file {
|
if has_changed_file {
|
||||||
let new_file = Arc::new(File {
|
let new_file = Arc::new(File {
|
||||||
entry_id: entry.id,
|
entry_id,
|
||||||
worktree: this,
|
worktree: this,
|
||||||
path: entry.path,
|
path,
|
||||||
mtime: entry.mtime,
|
mtime,
|
||||||
is_local: true,
|
is_local: true,
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
});
|
});
|
||||||
@ -1050,13 +1093,13 @@ impl LocalWorktree {
|
|||||||
project_id,
|
project_id,
|
||||||
buffer_id,
|
buffer_id,
|
||||||
version: serialize_version(&version),
|
version: serialize_version(&version),
|
||||||
mtime: Some(entry.mtime.into()),
|
mtime: Some(mtime.into()),
|
||||||
fingerprint: serialize_fingerprint(fingerprint),
|
fingerprint: serialize_fingerprint(fingerprint),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_handle.update(&mut cx, |buffer, cx| {
|
buffer_handle.update(&mut cx, |buffer, cx| {
|
||||||
buffer.did_save(version.clone(), fingerprint, entry.mtime, cx);
|
buffer.did_save(version.clone(), fingerprint, mtime, cx);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1081,7 +1124,7 @@ impl LocalWorktree {
|
|||||||
path: impl Into<Arc<Path>>,
|
path: impl Into<Arc<Path>>,
|
||||||
is_dir: bool,
|
is_dir: bool,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Task<Result<Entry>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
let lowest_ancestor = self.lowest_ancestor(&path);
|
let lowest_ancestor = self.lowest_ancestor(&path);
|
||||||
let abs_path = self.absolutize(&path);
|
let abs_path = self.absolutize(&path);
|
||||||
@ -1098,7 +1141,7 @@ impl LocalWorktree {
|
|||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
write.await?;
|
write.await?;
|
||||||
let (result, refreshes) = this.update(&mut cx, |this, cx| {
|
let (result, refreshes) = this.update(&mut cx, |this, cx| {
|
||||||
let mut refreshes = Vec::<Task<anyhow::Result<Entry>>>::new();
|
let mut refreshes = Vec::new();
|
||||||
let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
|
let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
|
||||||
for refresh_path in refresh_paths.ancestors() {
|
for refresh_path in refresh_paths.ancestors() {
|
||||||
if refresh_path == Path::new("") {
|
if refresh_path == Path::new("") {
|
||||||
@ -1125,14 +1168,14 @@ impl LocalWorktree {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_file(
|
pub(crate) fn write_file(
|
||||||
&self,
|
&self,
|
||||||
path: impl Into<Arc<Path>>,
|
path: impl Into<Arc<Path>>,
|
||||||
text: Rope,
|
text: Rope,
|
||||||
line_ending: LineEnding,
|
line_ending: LineEnding,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Task<Result<Entry>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let path = path.into();
|
let path: Arc<Path> = path.into();
|
||||||
let abs_path = self.absolutize(&path);
|
let abs_path = self.absolutize(&path);
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
let write = cx
|
let write = cx
|
||||||
@ -1191,8 +1234,11 @@ impl LocalWorktree {
|
|||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let old_path = self.entry_for_id(entry_id)?.path.clone();
|
let old_path = match self.entry_for_id(entry_id) {
|
||||||
|
Some(entry) => entry.path.clone(),
|
||||||
|
None => return Task::ready(Ok(None)),
|
||||||
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
let abs_old_path = self.absolutize(&old_path);
|
let abs_old_path = self.absolutize(&old_path);
|
||||||
let abs_new_path = self.absolutize(&new_path);
|
let abs_new_path = self.absolutize(&new_path);
|
||||||
@ -1202,7 +1248,7 @@ impl LocalWorktree {
|
|||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
rename.await?;
|
rename.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.as_local_mut()
|
this.as_local_mut()
|
||||||
@ -1210,7 +1256,7 @@ impl LocalWorktree {
|
|||||||
.refresh_entry(new_path.clone(), Some(old_path), cx)
|
.refresh_entry(new_path.clone(), Some(old_path), cx)
|
||||||
})?
|
})?
|
||||||
.await
|
.await
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_entry(
|
pub fn copy_entry(
|
||||||
@ -1218,8 +1264,11 @@ impl LocalWorktree {
|
|||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Option<Task<Result<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let old_path = self.entry_for_id(entry_id)?.path.clone();
|
let old_path = match self.entry_for_id(entry_id) {
|
||||||
|
Some(entry) => entry.path.clone(),
|
||||||
|
None => return Task::ready(Ok(None)),
|
||||||
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
let abs_old_path = self.absolutize(&old_path);
|
let abs_old_path = self.absolutize(&old_path);
|
||||||
let abs_new_path = self.absolutize(&new_path);
|
let abs_new_path = self.absolutize(&new_path);
|
||||||
@ -1234,7 +1283,7 @@ impl LocalWorktree {
|
|||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
copy.await?;
|
copy.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.as_local_mut()
|
this.as_local_mut()
|
||||||
@ -1242,7 +1291,7 @@ impl LocalWorktree {
|
|||||||
.refresh_entry(new_path.clone(), None, cx)
|
.refresh_entry(new_path.clone(), None, cx)
|
||||||
})?
|
})?
|
||||||
.await
|
.await
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_entry(
|
pub fn expand_entry(
|
||||||
@ -1278,7 +1327,10 @@ impl LocalWorktree {
|
|||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
old_path: Option<Arc<Path>>,
|
old_path: Option<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Task<Result<Entry>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
|
if self.is_path_excluded(path.to_path_buf()) {
|
||||||
|
return Task::ready(Ok(None));
|
||||||
|
}
|
||||||
let paths = if let Some(old_path) = old_path.as_ref() {
|
let paths = if let Some(old_path) = old_path.as_ref() {
|
||||||
vec![old_path.clone(), path.clone()]
|
vec![old_path.clone(), path.clone()]
|
||||||
} else {
|
} else {
|
||||||
@ -1287,11 +1339,12 @@ impl LocalWorktree {
|
|||||||
let mut refresh = self.refresh_entries_for_paths(paths);
|
let mut refresh = self.refresh_entries_for_paths(paths);
|
||||||
cx.spawn(move |this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
refresh.recv().await;
|
refresh.recv().await;
|
||||||
this.update(&mut cx, |this, _| {
|
let new_entry = this.update(&mut cx, |this, _| {
|
||||||
this.entry_for_path(path)
|
this.entry_for_path(path)
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| anyhow!("failed to read path after update"))
|
.ok_or_else(|| anyhow!("failed to read path after update"))
|
||||||
})?
|
})??;
|
||||||
|
Ok(Some(new_entry))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2222,10 +2275,19 @@ impl LocalSnapshot {
|
|||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_path_excluded(&self, abs_path: &Path) -> bool {
|
pub fn is_path_excluded(&self, mut path: PathBuf) -> bool {
|
||||||
self.file_scan_exclusions
|
loop {
|
||||||
.iter()
|
if self
|
||||||
.any(|exclude_matcher| exclude_matcher.is_match(abs_path))
|
.file_scan_exclusions
|
||||||
|
.iter()
|
||||||
|
.any(|exclude_matcher| exclude_matcher.is_match(&path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if !path.pop() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2455,8 +2517,7 @@ impl BackgroundScannerState {
|
|||||||
ids_to_preserve.insert(work_directory_id);
|
ids_to_preserve.insert(work_directory_id);
|
||||||
} else {
|
} else {
|
||||||
let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
|
let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
|
||||||
let git_dir_excluded = snapshot.is_path_excluded(&entry.git_dir_path)
|
let git_dir_excluded = snapshot.is_path_excluded(entry.git_dir_path.to_path_buf());
|
||||||
|| snapshot.is_path_excluded(&git_dir_abs_path);
|
|
||||||
if git_dir_excluded
|
if git_dir_excluded
|
||||||
&& !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
|
&& !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
|
||||||
{
|
{
|
||||||
@ -2663,7 +2724,7 @@ pub struct File {
|
|||||||
pub worktree: Model<Worktree>,
|
pub worktree: Model<Worktree>,
|
||||||
pub path: Arc<Path>,
|
pub path: Arc<Path>,
|
||||||
pub mtime: SystemTime,
|
pub mtime: SystemTime,
|
||||||
pub(crate) entry_id: ProjectEntryId,
|
pub(crate) entry_id: Option<ProjectEntryId>,
|
||||||
pub(crate) is_local: bool,
|
pub(crate) is_local: bool,
|
||||||
pub(crate) is_deleted: bool,
|
pub(crate) is_deleted: bool,
|
||||||
}
|
}
|
||||||
@ -2732,7 +2793,7 @@ impl language::File for File {
|
|||||||
fn to_proto(&self) -> rpc::proto::File {
|
fn to_proto(&self) -> rpc::proto::File {
|
||||||
rpc::proto::File {
|
rpc::proto::File {
|
||||||
worktree_id: self.worktree.entity_id().as_u64(),
|
worktree_id: self.worktree.entity_id().as_u64(),
|
||||||
entry_id: self.entry_id.to_proto(),
|
entry_id: self.entry_id.map(|id| id.to_proto()),
|
||||||
path: self.path.to_string_lossy().into(),
|
path: self.path.to_string_lossy().into(),
|
||||||
mtime: Some(self.mtime.into()),
|
mtime: Some(self.mtime.into()),
|
||||||
is_deleted: self.is_deleted,
|
is_deleted: self.is_deleted,
|
||||||
@ -2790,7 +2851,7 @@ impl File {
|
|||||||
worktree,
|
worktree,
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
entry_id: entry.id,
|
entry_id: Some(entry.id),
|
||||||
is_local: true,
|
is_local: true,
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
})
|
})
|
||||||
@ -2815,7 +2876,7 @@ impl File {
|
|||||||
worktree,
|
worktree,
|
||||||
path: Path::new(&proto.path).into(),
|
path: Path::new(&proto.path).into(),
|
||||||
mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
|
mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
|
||||||
entry_id: ProjectEntryId::from_proto(proto.entry_id),
|
entry_id: proto.entry_id.map(ProjectEntryId::from_proto),
|
||||||
is_local: false,
|
is_local: false,
|
||||||
is_deleted: proto.is_deleted,
|
is_deleted: proto.is_deleted,
|
||||||
})
|
})
|
||||||
@ -2833,7 +2894,7 @@ impl File {
|
|||||||
if self.is_deleted {
|
if self.is_deleted {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(self.entry_id)
|
self.entry_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3329,16 +3390,7 @@ impl BackgroundScanner {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FS events may come for files which parent directory is excluded, need to check ignore those.
|
if snapshot.is_path_excluded(relative_path.to_path_buf()) {
|
||||||
let mut path_to_test = abs_path.clone();
|
|
||||||
let mut excluded_file_event = snapshot.is_path_excluded(abs_path)
|
|
||||||
|| snapshot.is_path_excluded(&relative_path);
|
|
||||||
while !excluded_file_event && path_to_test.pop() {
|
|
||||||
if snapshot.is_path_excluded(&path_to_test) {
|
|
||||||
excluded_file_event = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if excluded_file_event {
|
|
||||||
if !is_git_related {
|
if !is_git_related {
|
||||||
log::debug!("ignoring FS event for excluded path {relative_path:?}");
|
log::debug!("ignoring FS event for excluded path {relative_path:?}");
|
||||||
}
|
}
|
||||||
@ -3522,7 +3574,7 @@ impl BackgroundScanner {
|
|||||||
let state = self.state.lock();
|
let state = self.state.lock();
|
||||||
let snapshot = &state.snapshot;
|
let snapshot = &state.snapshot;
|
||||||
root_abs_path = snapshot.abs_path().clone();
|
root_abs_path = snapshot.abs_path().clone();
|
||||||
if snapshot.is_path_excluded(&job.abs_path) {
|
if snapshot.is_path_excluded(job.path.to_path_buf()) {
|
||||||
log::error!("skipping excluded directory {:?}", job.path);
|
log::error!("skipping excluded directory {:?}", job.path);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -3593,9 +3645,9 @@ impl BackgroundScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
let relative_path = job.path.join(child_name);
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
if state.snapshot.is_path_excluded(&child_abs_path) {
|
if state.snapshot.is_path_excluded(relative_path.clone()) {
|
||||||
let relative_path = job.path.join(child_name);
|
|
||||||
log::debug!("skipping excluded child entry {relative_path:?}");
|
log::debug!("skipping excluded child entry {relative_path:?}");
|
||||||
state.remove_path(&relative_path);
|
state.remove_path(&relative_path);
|
||||||
continue;
|
continue;
|
||||||
|
@ -1055,11 +1055,12 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||||||
&[
|
&[
|
||||||
".git/HEAD",
|
".git/HEAD",
|
||||||
".git/foo",
|
".git/foo",
|
||||||
|
"node_modules",
|
||||||
"node_modules/.DS_Store",
|
"node_modules/.DS_Store",
|
||||||
"node_modules/prettier",
|
"node_modules/prettier",
|
||||||
"node_modules/prettier/package.json",
|
"node_modules/prettier/package.json",
|
||||||
],
|
],
|
||||||
&["target", "node_modules"],
|
&["target"],
|
||||||
&[
|
&[
|
||||||
".DS_Store",
|
".DS_Store",
|
||||||
"src/.DS_Store",
|
"src/.DS_Store",
|
||||||
@ -1109,6 +1110,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||||||
".git/HEAD",
|
".git/HEAD",
|
||||||
".git/foo",
|
".git/foo",
|
||||||
".git/new_file",
|
".git/new_file",
|
||||||
|
"node_modules",
|
||||||
"node_modules/.DS_Store",
|
"node_modules/.DS_Store",
|
||||||
"node_modules/prettier",
|
"node_modules/prettier",
|
||||||
"node_modules/prettier/package.json",
|
"node_modules/prettier/package.json",
|
||||||
@ -1117,7 +1119,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||||||
"build_output/new_file",
|
"build_output/new_file",
|
||||||
"test_output/new_file",
|
"test_output/new_file",
|
||||||
],
|
],
|
||||||
&["target", "node_modules", "test_output"],
|
&["target", "test_output"],
|
||||||
&[
|
&[
|
||||||
".DS_Store",
|
".DS_Store",
|
||||||
"src/.DS_Store",
|
"src/.DS_Store",
|
||||||
@ -1177,6 +1179,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||||||
.create_entry("a/e".as_ref(), true, cx)
|
.create_entry("a/e".as_ref(), true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_dir());
|
assert!(entry.is_dir());
|
||||||
|
|
||||||
@ -1226,6 +1229,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||||||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_file());
|
assert!(entry.is_file());
|
||||||
|
|
||||||
@ -1261,6 +1265,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||||||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_file());
|
assert!(entry.is_file());
|
||||||
|
|
||||||
@ -1279,6 +1284,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||||||
.create_entry("a/b/c/e.txt".as_ref(), false, cx)
|
.create_entry("a/b/c/e.txt".as_ref(), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_file());
|
assert!(entry.is_file());
|
||||||
|
|
||||||
@ -1295,6 +1301,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||||||
.create_entry("d/e/f/g.txt".as_ref(), false, cx)
|
.create_entry("d/e/f/g.txt".as_ref(), false, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(entry.is_file());
|
assert!(entry.is_file());
|
||||||
|
|
||||||
@ -1620,14 +1627,14 @@ fn randomly_mutate_worktree(
|
|||||||
entry.id.0,
|
entry.id.0,
|
||||||
new_path
|
new_path
|
||||||
);
|
);
|
||||||
let task = worktree.rename_entry(entry.id, new_path, cx).unwrap();
|
let task = worktree.rename_entry(entry.id, new_path, cx);
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
task.await?;
|
task.await?.unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let task = if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
let child_path = entry.path.join(random_filename(rng));
|
let child_path = entry.path.join(random_filename(rng));
|
||||||
let is_dir = rng.gen_bool(0.3);
|
let is_dir = rng.gen_bool(0.3);
|
||||||
log::info!(
|
log::info!(
|
||||||
@ -1635,15 +1642,20 @@ fn randomly_mutate_worktree(
|
|||||||
if is_dir { "dir" } else { "file" },
|
if is_dir { "dir" } else { "file" },
|
||||||
child_path,
|
child_path,
|
||||||
);
|
);
|
||||||
worktree.create_entry(child_path, is_dir, cx)
|
let task = worktree.create_entry(child_path, is_dir, cx);
|
||||||
|
cx.background_executor().spawn(async move {
|
||||||
|
task.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
|
log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
|
||||||
worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx)
|
let task =
|
||||||
};
|
worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx);
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
task.await?;
|
task.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -621,7 +621,7 @@ impl ProjectPanel {
|
|||||||
edited_entry_id = NEW_ENTRY_ID;
|
edited_entry_id = NEW_ENTRY_ID;
|
||||||
edit_task = self.project.update(cx, |project, cx| {
|
edit_task = self.project.update(cx, |project, cx| {
|
||||||
project.create_entry((worktree_id, &new_path), is_dir, cx)
|
project.create_entry((worktree_id, &new_path), is_dir, cx)
|
||||||
})?;
|
});
|
||||||
} else {
|
} else {
|
||||||
let new_path = if let Some(parent) = entry.path.clone().parent() {
|
let new_path = if let Some(parent) = entry.path.clone().parent() {
|
||||||
parent.join(&filename)
|
parent.join(&filename)
|
||||||
@ -635,7 +635,7 @@ impl ProjectPanel {
|
|||||||
edited_entry_id = entry.id;
|
edited_entry_id = entry.id;
|
||||||
edit_task = self.project.update(cx, |project, cx| {
|
edit_task = self.project.update(cx, |project, cx| {
|
||||||
project.rename_entry(entry.id, new_path.as_path(), cx)
|
project.rename_entry(entry.id, new_path.as_path(), cx)
|
||||||
})?;
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
edit_state.processing_filename = Some(filename);
|
edit_state.processing_filename = Some(filename);
|
||||||
@ -648,21 +648,22 @@ impl ProjectPanel {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let new_entry = new_entry?;
|
if let Some(new_entry) = new_entry? {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let Some(selection) = &mut this.selection {
|
if let Some(selection) = &mut this.selection {
|
||||||
if selection.entry_id == edited_entry_id {
|
if selection.entry_id == edited_entry_id {
|
||||||
selection.worktree_id = worktree_id;
|
selection.worktree_id = worktree_id;
|
||||||
selection.entry_id = new_entry.id;
|
selection.entry_id = new_entry.id;
|
||||||
this.expand_to_selection(cx);
|
this.expand_to_selection(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
this.update_visible_entries(None, cx);
|
||||||
this.update_visible_entries(None, cx);
|
if is_new_entry && !is_dir {
|
||||||
if is_new_entry && !is_dir {
|
this.open_entry(new_entry.id, true, cx);
|
||||||
this.open_entry(new_entry.id, true, cx);
|
}
|
||||||
}
|
cx.notify();
|
||||||
cx.notify();
|
})?;
|
||||||
})?;
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -935,15 +936,17 @@ impl ProjectPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if clipboard_entry.is_cut() {
|
if clipboard_entry.is_cut() {
|
||||||
if let Some(task) = self.project.update(cx, |project, cx| {
|
self.project
|
||||||
project.rename_entry(clipboard_entry.entry_id(), new_path, cx)
|
.update(cx, |project, cx| {
|
||||||
}) {
|
project.rename_entry(clipboard_entry.entry_id(), new_path, cx)
|
||||||
task.detach_and_log_err(cx)
|
})
|
||||||
}
|
.detach_and_log_err(cx)
|
||||||
} else if let Some(task) = self.project.update(cx, |project, cx| {
|
} else {
|
||||||
project.copy_entry(clipboard_entry.entry_id(), new_path, cx)
|
self.project
|
||||||
}) {
|
.update(cx, |project, cx| {
|
||||||
task.detach_and_log_err(cx)
|
project.copy_entry(clipboard_entry.entry_id(), new_path, cx)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -1026,7 +1029,7 @@ impl ProjectPanel {
|
|||||||
let mut new_path = destination_path.to_path_buf();
|
let mut new_path = destination_path.to_path_buf();
|
||||||
new_path.push(entry_path.path.file_name()?);
|
new_path.push(entry_path.path.file_name()?);
|
||||||
if new_path != entry_path.path.as_ref() {
|
if new_path != entry_path.path.as_ref() {
|
||||||
let task = project.rename_entry(entry_to_move, new_path, cx)?;
|
let task = project.rename_entry(entry_to_move, new_path, cx);
|
||||||
cx.foreground().spawn(task).detach_and_log_err(cx);
|
cx.foreground().spawn(task).detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,7 +602,7 @@ impl ProjectPanel {
|
|||||||
edited_entry_id = NEW_ENTRY_ID;
|
edited_entry_id = NEW_ENTRY_ID;
|
||||||
edit_task = self.project.update(cx, |project, cx| {
|
edit_task = self.project.update(cx, |project, cx| {
|
||||||
project.create_entry((worktree_id, &new_path), is_dir, cx)
|
project.create_entry((worktree_id, &new_path), is_dir, cx)
|
||||||
})?;
|
});
|
||||||
} else {
|
} else {
|
||||||
let new_path = if let Some(parent) = entry.path.clone().parent() {
|
let new_path = if let Some(parent) = entry.path.clone().parent() {
|
||||||
parent.join(&filename)
|
parent.join(&filename)
|
||||||
@ -616,7 +616,7 @@ impl ProjectPanel {
|
|||||||
edited_entry_id = entry.id;
|
edited_entry_id = entry.id;
|
||||||
edit_task = self.project.update(cx, |project, cx| {
|
edit_task = self.project.update(cx, |project, cx| {
|
||||||
project.rename_entry(entry.id, new_path.as_path(), cx)
|
project.rename_entry(entry.id, new_path.as_path(), cx)
|
||||||
})?;
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
edit_state.processing_filename = Some(filename);
|
edit_state.processing_filename = Some(filename);
|
||||||
@ -629,21 +629,22 @@ impl ProjectPanel {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let new_entry = new_entry?;
|
if let Some(new_entry) = new_entry? {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let Some(selection) = &mut this.selection {
|
if let Some(selection) = &mut this.selection {
|
||||||
if selection.entry_id == edited_entry_id {
|
if selection.entry_id == edited_entry_id {
|
||||||
selection.worktree_id = worktree_id;
|
selection.worktree_id = worktree_id;
|
||||||
selection.entry_id = new_entry.id;
|
selection.entry_id = new_entry.id;
|
||||||
this.expand_to_selection(cx);
|
this.expand_to_selection(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
this.update_visible_entries(None, cx);
|
||||||
this.update_visible_entries(None, cx);
|
if is_new_entry && !is_dir {
|
||||||
if is_new_entry && !is_dir {
|
this.open_entry(new_entry.id, true, cx);
|
||||||
this.open_entry(new_entry.id, true, cx);
|
}
|
||||||
}
|
cx.notify();
|
||||||
cx.notify();
|
})?;
|
||||||
})?;
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -923,15 +924,17 @@ impl ProjectPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if clipboard_entry.is_cut() {
|
if clipboard_entry.is_cut() {
|
||||||
if let Some(task) = self.project.update(cx, |project, cx| {
|
self.project
|
||||||
project.rename_entry(clipboard_entry.entry_id(), new_path, cx)
|
.update(cx, |project, cx| {
|
||||||
}) {
|
project.rename_entry(clipboard_entry.entry_id(), new_path, cx)
|
||||||
task.detach_and_log_err(cx);
|
})
|
||||||
}
|
.detach_and_log_err(cx)
|
||||||
} else if let Some(task) = self.project.update(cx, |project, cx| {
|
} else {
|
||||||
project.copy_entry(clipboard_entry.entry_id(), new_path, cx)
|
self.project
|
||||||
}) {
|
.update(cx, |project, cx| {
|
||||||
task.detach_and_log_err(cx);
|
project.copy_entry(clipboard_entry.entry_id(), new_path, cx)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
@ -1017,7 +1020,7 @@ impl ProjectPanel {
|
|||||||
// let mut new_path = destination_path.to_path_buf();
|
// let mut new_path = destination_path.to_path_buf();
|
||||||
// new_path.push(entry_path.path.file_name()?);
|
// new_path.push(entry_path.path.file_name()?);
|
||||||
// if new_path != entry_path.path.as_ref() {
|
// if new_path != entry_path.path.as_ref() {
|
||||||
// let task = project.rename_entry(entry_to_move, new_path, cx)?;
|
// let task = project.rename_entry(entry_to_move, new_path, cx);
|
||||||
// cx.foreground_executor().spawn(task).detach_and_log_err(cx);
|
// cx.foreground_executor().spawn(task).detach_and_log_err(cx);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
22
crates/quick_action_bar2/Cargo.toml
Normal file
22
crates/quick_action_bar2/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "quick_action_bar2"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/quick_action_bar.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
#assistant = { path = "../assistant" }
|
||||||
|
editor = { package = "editor2", path = "../editor2" }
|
||||||
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
search = { package = "search2", path = "../search2" }
|
||||||
|
workspace = { package = "workspace2", path = "../workspace2" }
|
||||||
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
|
||||||
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
|
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
288
crates/quick_action_bar2/src/quick_action_bar.rs
Normal file
288
crates/quick_action_bar2/src/quick_action_bar.rs
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
// use assistant::{assistant_panel::InlineAssist, AssistantPanel};
|
||||||
|
use editor::Editor;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
Action, Div, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Stateful,
|
||||||
|
Styled, Subscription, View, ViewContext, WeakView,
|
||||||
|
};
|
||||||
|
use search::BufferSearchBar;
|
||||||
|
use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};
|
||||||
|
use workspace::{
|
||||||
|
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct QuickActionBar {
|
||||||
|
buffer_search_bar: View<BufferSearchBar>,
|
||||||
|
active_item: Option<Box<dyn ItemHandle>>,
|
||||||
|
_inlay_hints_enabled_subscription: Option<Subscription>,
|
||||||
|
#[allow(unused)]
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuickActionBar {
|
||||||
|
pub fn new(buffer_search_bar: View<BufferSearchBar>, workspace: &Workspace) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer_search_bar,
|
||||||
|
active_item: None,
|
||||||
|
_inlay_hints_enabled_subscription: None,
|
||||||
|
workspace: workspace.weak_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn active_editor(&self) -> Option<View<Editor>> {
|
||||||
|
self.active_item
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|item| item.downcast::<Editor>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for QuickActionBar {
|
||||||
|
type Element = Stateful<Div>;
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
let search_button = QuickActionBarButton::new(
|
||||||
|
"toggle buffer search",
|
||||||
|
Icon::MagnifyingGlass,
|
||||||
|
!self.buffer_search_bar.read(cx).is_dismissed(),
|
||||||
|
Box::new(search::buffer_search::Deploy { focus: false }),
|
||||||
|
"Buffer Search",
|
||||||
|
);
|
||||||
|
let assistant_button = QuickActionBarButton::new(
|
||||||
|
"toggle inline assitant",
|
||||||
|
Icon::MagicWand,
|
||||||
|
false,
|
||||||
|
Box::new(gpui::NoAction),
|
||||||
|
"Inline assistant",
|
||||||
|
);
|
||||||
|
h_stack()
|
||||||
|
.id("quick action bar")
|
||||||
|
.p_1()
|
||||||
|
.gap_2()
|
||||||
|
.child(search_button)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.border()
|
||||||
|
.border_color(gpui::red())
|
||||||
|
.child(assistant_button),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
|
||||||
|
|
||||||
|
// impl View for QuickActionBar {
|
||||||
|
// fn ui_name() -> &'static str {
|
||||||
|
// "QuickActionsBar"
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
|
||||||
|
// let Some(editor) = self.active_editor() else {
|
||||||
|
// return div();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let mut bar = Flex::row();
|
||||||
|
// if editor.read(cx).supports_inlay_hints(cx) {
|
||||||
|
// bar = bar.with_child(render_quick_action_bar_button(
|
||||||
|
// 0,
|
||||||
|
// "icons/inlay_hint.svg",
|
||||||
|
// editor.read(cx).inlay_hints_enabled(),
|
||||||
|
// (
|
||||||
|
// "Toggle Inlay Hints".to_string(),
|
||||||
|
// Some(Box::new(editor::ToggleInlayHints)),
|
||||||
|
// ),
|
||||||
|
// cx,
|
||||||
|
// |this, cx| {
|
||||||
|
// if let Some(editor) = this.active_editor() {
|
||||||
|
// editor.update(cx, |editor, cx| {
|
||||||
|
// editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if editor.read(cx).buffer().read(cx).is_singleton() {
|
||||||
|
// let search_bar_shown = !self.buffer_search_bar.read(cx).is_dismissed();
|
||||||
|
// let search_action = buffer_search::Deploy { focus: true };
|
||||||
|
|
||||||
|
// bar = bar.with_child(render_quick_action_bar_button(
|
||||||
|
// 1,
|
||||||
|
// "icons/magnifying_glass.svg",
|
||||||
|
// search_bar_shown,
|
||||||
|
// (
|
||||||
|
// "Buffer Search".to_string(),
|
||||||
|
// Some(Box::new(search_action.clone())),
|
||||||
|
// ),
|
||||||
|
// cx,
|
||||||
|
// move |this, cx| {
|
||||||
|
// this.buffer_search_bar.update(cx, |buffer_search_bar, cx| {
|
||||||
|
// if search_bar_shown {
|
||||||
|
// buffer_search_bar.dismiss(&buffer_search::Dismiss, cx);
|
||||||
|
// } else {
|
||||||
|
// buffer_search_bar.deploy(&search_action, cx);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// bar.add_child(render_quick_action_bar_button(
|
||||||
|
// 2,
|
||||||
|
// "icons/magic-wand.svg",
|
||||||
|
// false,
|
||||||
|
// ("Inline Assist".into(), Some(Box::new(InlineAssist))),
|
||||||
|
// cx,
|
||||||
|
// move |this, cx| {
|
||||||
|
// if let Some(workspace) = this.workspace.upgrade(cx) {
|
||||||
|
// workspace.update(cx, |workspace, cx| {
|
||||||
|
// AssistantPanel::inline_assist(workspace, &Default::default(), cx);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// bar.into_any()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
struct QuickActionBarButton {
|
||||||
|
id: ElementId,
|
||||||
|
icon: Icon,
|
||||||
|
toggled: bool,
|
||||||
|
action: Box<dyn Action>,
|
||||||
|
tooltip: SharedString,
|
||||||
|
tooltip_meta: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuickActionBarButton {
|
||||||
|
fn new(
|
||||||
|
id: impl Into<ElementId>,
|
||||||
|
icon: Icon,
|
||||||
|
toggled: bool,
|
||||||
|
action: Box<dyn Action>,
|
||||||
|
tooltip: impl Into<SharedString>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.into(),
|
||||||
|
icon,
|
||||||
|
toggled,
|
||||||
|
action,
|
||||||
|
tooltip: tooltip.into(),
|
||||||
|
tooltip_meta: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn meta(mut self, meta: Option<impl Into<SharedString>>) -> Self {
|
||||||
|
self.tooltip_meta = meta.map(|meta| meta.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for QuickActionBarButton {
|
||||||
|
type Rendered = IconButton;
|
||||||
|
|
||||||
|
fn render(self, _: &mut WindowContext) -> Self::Rendered {
|
||||||
|
let tooltip = self.tooltip.clone();
|
||||||
|
let action = self.action.boxed_clone();
|
||||||
|
let tooltip_meta = self.tooltip_meta.clone();
|
||||||
|
|
||||||
|
IconButton::new(self.id.clone(), self.icon)
|
||||||
|
.size(ButtonSize::Compact)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.selected(self.toggled)
|
||||||
|
.tooltip(move |cx| {
|
||||||
|
if let Some(meta) = &tooltip_meta {
|
||||||
|
Tooltip::with_meta(tooltip.clone(), Some(&*action), meta.clone(), cx)
|
||||||
|
} else {
|
||||||
|
Tooltip::for_action(tooltip.clone(), &*action, cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click({
|
||||||
|
let action = self.action.boxed_clone();
|
||||||
|
move |_, cx| cx.dispatch_action(action.boxed_clone())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn render_quick_action_bar_button<
|
||||||
|
// F: 'static + Fn(&mut QuickActionBar, &mut ViewContext<QuickActionBar>),
|
||||||
|
// >(
|
||||||
|
// index: usize,
|
||||||
|
// icon: &'static str,
|
||||||
|
// toggled: bool,
|
||||||
|
// tooltip: (String, Option<Box<dyn Action>>),
|
||||||
|
// cx: &mut ViewContext<QuickActionBar>,
|
||||||
|
// on_click: F,
|
||||||
|
// ) -> AnyElement<QuickActionBar> {
|
||||||
|
// enum QuickActionBarButton {}
|
||||||
|
|
||||||
|
// let theme = theme::current(cx);
|
||||||
|
// let (tooltip_text, action) = tooltip;
|
||||||
|
|
||||||
|
// MouseEventHandler::new::<QuickActionBarButton, _>(index, cx, |mouse_state, _| {
|
||||||
|
// let style = theme
|
||||||
|
// .workspace
|
||||||
|
// .toolbar
|
||||||
|
// .toggleable_tool
|
||||||
|
// .in_state(toggled)
|
||||||
|
// .style_for(mouse_state);
|
||||||
|
// Svg::new(icon)
|
||||||
|
// .with_color(style.color)
|
||||||
|
// .constrained()
|
||||||
|
// .with_width(style.icon_width)
|
||||||
|
// .aligned()
|
||||||
|
// .constrained()
|
||||||
|
// .with_width(style.button_width)
|
||||||
|
// .with_height(style.button_width)
|
||||||
|
// .contained()
|
||||||
|
// .with_style(style.container)
|
||||||
|
// })
|
||||||
|
// .with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
// .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
|
||||||
|
// .with_tooltip::<QuickActionBarButton>(index, tooltip_text, action, theme.tooltip.clone(), cx)
|
||||||
|
// .into_any_named("quick action bar button")
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl ToolbarItemView for QuickActionBar {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
active_pane_item: Option<&dyn ItemHandle>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> ToolbarItemLocation {
|
||||||
|
match active_pane_item {
|
||||||
|
Some(active_item) => {
|
||||||
|
self.active_item = Some(active_item.boxed_clone());
|
||||||
|
self._inlay_hints_enabled_subscription.take();
|
||||||
|
|
||||||
|
if let Some(editor) = active_item.downcast::<Editor>() {
|
||||||
|
let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
|
||||||
|
let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
|
||||||
|
self._inlay_hints_enabled_subscription =
|
||||||
|
Some(cx.observe(&editor, move |_, editor, cx| {
|
||||||
|
let editor = editor.read(cx);
|
||||||
|
let new_inlay_hints_enabled = editor.inlay_hints_enabled();
|
||||||
|
let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
|
||||||
|
let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
|
||||||
|
|| supports_inlay_hints != new_supports_inlay_hints;
|
||||||
|
inlay_hints_enabled = new_inlay_hints_enabled;
|
||||||
|
supports_inlay_hints = new_supports_inlay_hints;
|
||||||
|
if should_notify {
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
ToolbarItemLocation::PrimaryRight
|
||||||
|
} else {
|
||||||
|
ToolbarItemLocation::Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.active_item = None;
|
||||||
|
ToolbarItemLocation::Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -430,7 +430,7 @@ message ExpandProjectEntryResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message ProjectEntryResponse {
|
message ProjectEntryResponse {
|
||||||
Entry entry = 1;
|
optional Entry entry = 1;
|
||||||
uint64 worktree_scan_id = 2;
|
uint64 worktree_scan_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1357,7 +1357,7 @@ message User {
|
|||||||
|
|
||||||
message File {
|
message File {
|
||||||
uint64 worktree_id = 1;
|
uint64 worktree_id = 1;
|
||||||
uint64 entry_id = 2;
|
optional uint64 entry_id = 2;
|
||||||
string path = 3;
|
string path = 3;
|
||||||
Timestamp mtime = 4;
|
Timestamp mtime = 4;
|
||||||
bool is_deleted = 5;
|
bool is_deleted = 5;
|
||||||
|
@ -9,4 +9,4 @@ pub use notification::*;
|
|||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 66;
|
pub const PROTOCOL_VERSION: u32 = 67;
|
||||||
|
@ -430,7 +430,7 @@ message ExpandProjectEntryResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message ProjectEntryResponse {
|
message ProjectEntryResponse {
|
||||||
Entry entry = 1;
|
optional Entry entry = 1;
|
||||||
uint64 worktree_scan_id = 2;
|
uint64 worktree_scan_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1357,7 +1357,7 @@ message User {
|
|||||||
|
|
||||||
message File {
|
message File {
|
||||||
uint64 worktree_id = 1;
|
uint64 worktree_id = 1;
|
||||||
uint64 entry_id = 2;
|
optional uint64 entry_id = 2;
|
||||||
string path = 3;
|
string path = 3;
|
||||||
Timestamp mtime = 4;
|
Timestamp mtime = 4;
|
||||||
bool is_deleted = 5;
|
bool is_deleted = 5;
|
||||||
|
@ -9,4 +9,4 @@ pub use notification::*;
|
|||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 66;
|
pub const PROTOCOL_VERSION: u32 = 67;
|
||||||
|
@ -1132,6 +1132,7 @@ mod tests {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
(wt, entry)
|
(wt, entry)
|
||||||
|
@ -736,6 +736,8 @@ impl InputHandler for TerminalView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Item for TerminalView {
|
impl Item for TerminalView {
|
||||||
|
type Event = ItemEvent;
|
||||||
|
|
||||||
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
|
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
|
||||||
Some(self.terminal().read(cx).title().into())
|
Some(self.terminal().read(cx).title().into())
|
||||||
}
|
}
|
||||||
@ -843,6 +845,10 @@ impl Item for TerminalView {
|
|||||||
// .detach();
|
// .detach();
|
||||||
self.workspace_id = workspace.database_id();
|
self.workspace_id = workspace.database_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
|
||||||
|
f(*event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchableItem for TerminalView {
|
impl SearchableItem for TerminalView {
|
||||||
@ -1170,6 +1176,7 @@ mod tests {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
(wt, entry)
|
(wt, entry)
|
||||||
|
@ -5,7 +5,7 @@ use crate::ColorScale;
|
|||||||
use crate::{SystemColors, ThemeColors};
|
use crate::{SystemColors, ThemeColors};
|
||||||
|
|
||||||
pub(crate) fn neutral() -> ColorScaleSet {
|
pub(crate) fn neutral() -> ColorScaleSet {
|
||||||
slate()
|
sand()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemeColors {
|
impl ThemeColors {
|
||||||
@ -29,12 +29,12 @@ impl ThemeColors {
|
|||||||
element_disabled: neutral().light_alpha().step_3(),
|
element_disabled: neutral().light_alpha().step_3(),
|
||||||
drop_target_background: blue().light_alpha().step_2(),
|
drop_target_background: blue().light_alpha().step_2(),
|
||||||
ghost_element_background: system.transparent,
|
ghost_element_background: system.transparent,
|
||||||
ghost_element_hover: neutral().light_alpha().step_4(),
|
ghost_element_hover: neutral().light_alpha().step_3(),
|
||||||
ghost_element_active: neutral().light_alpha().step_5(),
|
ghost_element_active: neutral().light_alpha().step_4(),
|
||||||
ghost_element_selected: neutral().light_alpha().step_5(),
|
ghost_element_selected: neutral().light_alpha().step_5(),
|
||||||
ghost_element_disabled: neutral().light_alpha().step_3(),
|
ghost_element_disabled: neutral().light_alpha().step_3(),
|
||||||
text: yellow().light().step_9(),
|
text: neutral().light().step_12(),
|
||||||
text_muted: neutral().light().step_11(),
|
text_muted: neutral().light().step_10(),
|
||||||
text_placeholder: neutral().light().step_10(),
|
text_placeholder: neutral().light().step_10(),
|
||||||
text_disabled: neutral().light().step_9(),
|
text_disabled: neutral().light().step_9(),
|
||||||
text_accent: blue().light().step_11(),
|
text_accent: blue().light().step_11(),
|
||||||
@ -53,13 +53,13 @@ impl ThemeColors {
|
|||||||
editor_gutter_background: neutral().light().step_1(), // todo!("pick the right colors")
|
editor_gutter_background: neutral().light().step_1(), // todo!("pick the right colors")
|
||||||
editor_subheader_background: neutral().light().step_2(),
|
editor_subheader_background: neutral().light().step_2(),
|
||||||
editor_active_line_background: neutral().light_alpha().step_3(),
|
editor_active_line_background: neutral().light_alpha().step_3(),
|
||||||
editor_line_number: neutral().light_alpha().step_3(), // todo!("pick the right colors")
|
editor_line_number: neutral().light().step_10(),
|
||||||
editor_active_line_number: neutral().light_alpha().step_3(), // todo!("pick the right colors")
|
editor_active_line_number: neutral().light().step_11(),
|
||||||
editor_highlighted_line_background: neutral().light_alpha().step_4(), // todo!("pick the right colors")
|
editor_highlighted_line_background: neutral().light_alpha().step_3(),
|
||||||
editor_invisible: neutral().light_alpha().step_4(), // todo!("pick the right colors")
|
editor_invisible: neutral().light().step_10(),
|
||||||
editor_wrap_guide: neutral().light_alpha().step_4(), // todo!("pick the right colors")
|
editor_wrap_guide: neutral().light_alpha().step_7(),
|
||||||
editor_active_wrap_guide: neutral().light_alpha().step_4(), // todo!("pick the right colors")
|
editor_active_wrap_guide: neutral().light_alpha().step_8(), // todo!("pick the right colors")
|
||||||
editor_document_highlight_read_background: neutral().light_alpha().step_4(), // todo!("pick the right colors")
|
editor_document_highlight_read_background: neutral().light_alpha().step_3(), // todo!("pick the right colors")
|
||||||
editor_document_highlight_write_background: neutral().light_alpha().step_4(), // todo!("pick the right colors")
|
editor_document_highlight_write_background: neutral().light_alpha().step_4(), // todo!("pick the right colors")
|
||||||
terminal_background: neutral().light().step_1(),
|
terminal_background: neutral().light().step_1(),
|
||||||
terminal_ansi_black: black().light().step_12(),
|
terminal_ansi_black: black().light().step_12(),
|
||||||
|
@ -1,47 +1,51 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
default_color_scales,
|
||||||
one_themes::{one_dark, one_family},
|
one_themes::{one_dark, one_family},
|
||||||
Theme, ThemeFamily,
|
Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme, ThemeColors,
|
||||||
|
ThemeFamily, ThemeStyles,
|
||||||
};
|
};
|
||||||
|
|
||||||
// fn zed_pro_daylight() -> Theme {
|
fn zed_pro_daylight() -> Theme {
|
||||||
// Theme {
|
Theme {
|
||||||
// id: "zed_pro_daylight".to_string(),
|
id: "zed_pro_daylight".to_string(),
|
||||||
// name: "Zed Pro Daylight".into(),
|
name: "Zed Pro Daylight".into(),
|
||||||
// appearance: Appearance::Light,
|
appearance: Appearance::Light,
|
||||||
// styles: ThemeStyles {
|
styles: ThemeStyles {
|
||||||
// system: SystemColors::default(),
|
system: SystemColors::default(),
|
||||||
// colors: ThemeColors::light(),
|
colors: ThemeColors::light(),
|
||||||
// status: StatusColors::light(),
|
status: StatusColors::light(),
|
||||||
// player: PlayerColors::light(),
|
player: PlayerColors::light(),
|
||||||
// syntax: Arc::new(SyntaxTheme::light()),
|
syntax: Arc::new(SyntaxTheme::light()),
|
||||||
// },
|
},
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub(crate) fn zed_pro_moonlight() -> Theme {
|
pub(crate) fn zed_pro_moonlight() -> Theme {
|
||||||
// Theme {
|
Theme {
|
||||||
// id: "zed_pro_moonlight".to_string(),
|
id: "zed_pro_moonlight".to_string(),
|
||||||
// name: "Zed Pro Moonlight".into(),
|
name: "Zed Pro Moonlight".into(),
|
||||||
// appearance: Appearance::Dark,
|
appearance: Appearance::Dark,
|
||||||
// styles: ThemeStyles {
|
styles: ThemeStyles {
|
||||||
// system: SystemColors::default(),
|
system: SystemColors::default(),
|
||||||
// colors: ThemeColors::dark(),
|
colors: ThemeColors::dark(),
|
||||||
// status: StatusColors::dark(),
|
status: StatusColors::dark(),
|
||||||
// player: PlayerColors::dark(),
|
player: PlayerColors::dark(),
|
||||||
// syntax: Arc::new(SyntaxTheme::dark()),
|
syntax: Arc::new(SyntaxTheme::dark()),
|
||||||
// },
|
},
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn zed_pro_family() -> ThemeFamily {
|
pub fn zed_pro_family() -> ThemeFamily {
|
||||||
// ThemeFamily {
|
ThemeFamily {
|
||||||
// id: "zed_pro".to_string(),
|
id: "zed_pro".to_string(),
|
||||||
// name: "Zed Pro".into(),
|
name: "Zed Pro".into(),
|
||||||
// author: "Zed Team".into(),
|
author: "Zed Team".into(),
|
||||||
// themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
|
themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
|
||||||
// scales: default_color_scales(),
|
scales: default_color_scales(),
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
impl Default for ThemeFamily {
|
impl Default for ThemeFamily {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -6,8 +6,8 @@ use gpui::{HighlightStyle, SharedString};
|
|||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
one_themes::one_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
|
one_themes::one_family, zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme,
|
||||||
Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
|
SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ThemeRegistry {
|
pub struct ThemeRegistry {
|
||||||
@ -117,7 +117,7 @@ impl Default for ThemeRegistry {
|
|||||||
themes: HashMap::default(),
|
themes: HashMap::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.insert_theme_families([one_family()]);
|
this.insert_theme_families([zed_pro_family(), one_family()]);
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ impl SyntaxTheme {
|
|||||||
highlights: vec![
|
highlights: vec![
|
||||||
("attribute".into(), cyan().light().step_11().into()),
|
("attribute".into(), cyan().light().step_11().into()),
|
||||||
("boolean".into(), tomato().light().step_11().into()),
|
("boolean".into(), tomato().light().step_11().into()),
|
||||||
("comment".into(), neutral().light().step_11().into()),
|
("comment".into(), neutral().light().step_10().into()),
|
||||||
("comment.doc".into(), iris().light().step_12().into()),
|
("comment.doc".into(), iris().light().step_11().into()),
|
||||||
("constant".into(), red().light().step_9().into()),
|
("constant".into(), red().light().step_9().into()),
|
||||||
("constructor".into(), red().light().step_9().into()),
|
("constructor".into(), red().light().step_9().into()),
|
||||||
("embedded".into(), red().light().step_9().into()),
|
("embedded".into(), red().light().step_9().into()),
|
||||||
@ -32,11 +32,11 @@ impl SyntaxTheme {
|
|||||||
("enum".into(), red().light().step_9().into()),
|
("enum".into(), red().light().step_9().into()),
|
||||||
("function".into(), red().light().step_9().into()),
|
("function".into(), red().light().step_9().into()),
|
||||||
("hint".into(), red().light().step_9().into()),
|
("hint".into(), red().light().step_9().into()),
|
||||||
("keyword".into(), orange().light().step_11().into()),
|
("keyword".into(), orange().light().step_9().into()),
|
||||||
("label".into(), red().light().step_9().into()),
|
("label".into(), red().light().step_9().into()),
|
||||||
("link_text".into(), red().light().step_9().into()),
|
("link_text".into(), red().light().step_9().into()),
|
||||||
("link_uri".into(), red().light().step_9().into()),
|
("link_uri".into(), red().light().step_9().into()),
|
||||||
("number".into(), red().light().step_9().into()),
|
("number".into(), purple().light().step_10().into()),
|
||||||
("operator".into(), red().light().step_9().into()),
|
("operator".into(), red().light().step_9().into()),
|
||||||
("predictive".into(), red().light().step_9().into()),
|
("predictive".into(), red().light().step_9().into()),
|
||||||
("preproc".into(), red().light().step_9().into()),
|
("preproc".into(), red().light().step_9().into()),
|
||||||
@ -49,16 +49,16 @@ impl SyntaxTheme {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
"punctuation.delimiter".into(),
|
"punctuation.delimiter".into(),
|
||||||
neutral().light().step_11().into(),
|
neutral().light().step_10().into(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"punctuation.list_marker".into(),
|
"punctuation.list_marker".into(),
|
||||||
blue().light().step_11().into(),
|
blue().light().step_11().into(),
|
||||||
),
|
),
|
||||||
("punctuation.special".into(), red().light().step_9().into()),
|
("punctuation.special".into(), red().light().step_9().into()),
|
||||||
("string".into(), jade().light().step_11().into()),
|
("string".into(), jade().light().step_9().into()),
|
||||||
("string.escape".into(), red().light().step_9().into()),
|
("string.escape".into(), red().light().step_9().into()),
|
||||||
("string.regex".into(), tomato().light().step_11().into()),
|
("string.regex".into(), tomato().light().step_9().into()),
|
||||||
("string.special".into(), red().light().step_9().into()),
|
("string.special".into(), red().light().step_9().into()),
|
||||||
(
|
(
|
||||||
"string.special.symbol".into(),
|
"string.special.symbol".into(),
|
||||||
@ -67,7 +67,7 @@ impl SyntaxTheme {
|
|||||||
("tag".into(), red().light().step_9().into()),
|
("tag".into(), red().light().step_9().into()),
|
||||||
("text.literal".into(), red().light().step_9().into()),
|
("text.literal".into(), red().light().step_9().into()),
|
||||||
("title".into(), red().light().step_9().into()),
|
("title".into(), red().light().step_9().into()),
|
||||||
("type".into(), red().light().step_9().into()),
|
("type".into(), cyan().light().step_9().into()),
|
||||||
("variable".into(), red().light().step_9().into()),
|
("variable".into(), red().light().step_9().into()),
|
||||||
("variable.special".into(), red().light().step_9().into()),
|
("variable.special".into(), red().light().step_9().into()),
|
||||||
("variant".into(), red().light().step_9().into()),
|
("variant".into(), red().light().step_9().into()),
|
||||||
|
@ -2,14 +2,14 @@ use feature_flags::FeatureFlagAppExt;
|
|||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, SharedString, View,
|
actions, AppContext, DismissEvent, Div, EventEmitter, FocusableView, Render, SharedString,
|
||||||
ViewContext, VisualContext, WeakView,
|
View, ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use settings::{update_settings_file, SettingsStore};
|
use settings::{update_settings_file, SettingsStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::{Theme, ThemeRegistry, ThemeSettings};
|
use theme::{Theme, ThemeRegistry, ThemeSettings};
|
||||||
use ui::{prelude::*, ListItem};
|
use ui::{prelude::*, v_stack, ListItem};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{ui::HighlightedLabel, Workspace};
|
use workspace::{ui::HighlightedLabel, Workspace};
|
||||||
|
|
||||||
@ -65,10 +65,10 @@ impl FocusableView for ThemeSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ThemeSelector {
|
impl Render for ThemeSelector {
|
||||||
type Element = View<Picker<ThemeSelectorDelegate>>;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
self.picker.clone()
|
v_stack().min_w_96().child(self.picker.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ impl ThemeSelectorDelegate {
|
|||||||
let original_theme = cx.theme().clone();
|
let original_theme = cx.theme().clone();
|
||||||
|
|
||||||
let staff_mode = cx.is_staff();
|
let staff_mode = cx.is_staff();
|
||||||
let registry = cx.global::<Arc<ThemeRegistry>>();
|
let registry = cx.global::<ThemeRegistry>();
|
||||||
let theme_names = registry.list(staff_mode).collect::<Vec<_>>();
|
let theme_names = registry.list(staff_mode).collect::<Vec<_>>();
|
||||||
//todo!(theme sorting)
|
//todo!(theme sorting)
|
||||||
// theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name)));
|
// theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name)));
|
||||||
@ -126,7 +126,7 @@ impl ThemeSelectorDelegate {
|
|||||||
|
|
||||||
fn show_selected_theme(&mut self, cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>) {
|
fn show_selected_theme(&mut self, cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>) {
|
||||||
if let Some(mat) = self.matches.get(self.selected_index) {
|
if let Some(mat) = self.matches.get(self.selected_index) {
|
||||||
let registry = cx.global::<Arc<ThemeRegistry>>();
|
let registry = cx.global::<ThemeRegistry>();
|
||||||
match registry.get(&mat.string) {
|
match registry.get(&mat.string) {
|
||||||
Ok(theme) => {
|
Ok(theme) => {
|
||||||
Self::set_theme(theme, cx);
|
Self::set_theme(theme, cx);
|
||||||
|
@ -5,6 +5,7 @@ mod context_menu;
|
|||||||
mod disclosure;
|
mod disclosure;
|
||||||
mod divider;
|
mod divider;
|
||||||
mod icon;
|
mod icon;
|
||||||
|
mod indicator;
|
||||||
mod keybinding;
|
mod keybinding;
|
||||||
mod label;
|
mod label;
|
||||||
mod list;
|
mod list;
|
||||||
@ -24,6 +25,7 @@ pub use context_menu::*;
|
|||||||
pub use disclosure::*;
|
pub use disclosure::*;
|
||||||
pub use divider::*;
|
pub use divider::*;
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
|
pub use indicator::*;
|
||||||
pub use keybinding::*;
|
pub use keybinding::*;
|
||||||
pub use label::*;
|
pub use label::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
|
@ -1,15 +1,26 @@
|
|||||||
use gpui::{rems, svg, IntoElement, Svg};
|
use gpui::{rems, svg, IntoElement, Rems, Svg};
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone)]
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
pub enum IconSize {
|
pub enum IconSize {
|
||||||
|
XSmall,
|
||||||
Small,
|
Small,
|
||||||
#[default]
|
#[default]
|
||||||
Medium,
|
Medium,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IconSize {
|
||||||
|
pub fn rems(self) -> Rems {
|
||||||
|
match self {
|
||||||
|
IconSize::XSmall => rems(12. / 16.),
|
||||||
|
IconSize::Small => rems(14. / 16.),
|
||||||
|
IconSize::Medium => rems(16. / 16.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
|
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
|
||||||
pub enum Icon {
|
pub enum Icon {
|
||||||
Ai,
|
Ai,
|
||||||
@ -172,13 +183,8 @@ impl RenderOnce for IconElement {
|
|||||||
type Rendered = Svg;
|
type Rendered = Svg;
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||||
let svg_size = match self.size {
|
|
||||||
IconSize::Small => rems(14. / 16.),
|
|
||||||
IconSize::Medium => rems(16. / 16.),
|
|
||||||
};
|
|
||||||
|
|
||||||
svg()
|
svg()
|
||||||
.size(svg_size)
|
.size(self.size.rems())
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.path(self.path)
|
.path(self.path)
|
||||||
.text_color(self.color.color(cx))
|
.text_color(self.color.color(cx))
|
||||||
|
60
crates/ui2/src/components/indicator.rs
Normal file
60
crates/ui2/src/components/indicator.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use gpui::{Div, Position};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum IndicatorStyle {
|
||||||
|
#[default]
|
||||||
|
Dot,
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct Indicator {
|
||||||
|
position: Position,
|
||||||
|
style: IndicatorStyle,
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Indicator {
|
||||||
|
pub fn dot() -> Self {
|
||||||
|
Self {
|
||||||
|
position: Position::Relative,
|
||||||
|
style: IndicatorStyle::Dot,
|
||||||
|
color: Color::Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bar() -> Self {
|
||||||
|
Self {
|
||||||
|
position: Position::Relative,
|
||||||
|
style: IndicatorStyle::Dot,
|
||||||
|
color: Color::Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color(mut self, color: Color) -> Self {
|
||||||
|
self.color = color;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn absolute(mut self) -> Self {
|
||||||
|
self.position = Position::Absolute;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for Indicator {
|
||||||
|
type Rendered = Div;
|
||||||
|
|
||||||
|
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
div()
|
||||||
|
.flex_none()
|
||||||
|
.map(|this| match self.style {
|
||||||
|
IndicatorStyle::Dot => this.w_1p5().h_1p5().rounded_full(),
|
||||||
|
IndicatorStyle::Bar => this.w_full().h_1p5().rounded_t_md(),
|
||||||
|
})
|
||||||
|
.when(self.position == Position::Absolute, |this| this.absolute())
|
||||||
|
.bg(self.color.color(cx))
|
||||||
|
}
|
||||||
|
}
|
@ -8,5 +8,6 @@ pub use crate::clickable::*;
|
|||||||
pub use crate::disableable::*;
|
pub use crate::disableable::*;
|
||||||
pub use crate::fixed::*;
|
pub use crate::fixed::*;
|
||||||
pub use crate::selectable::*;
|
pub use crate::selectable::*;
|
||||||
|
pub use crate::{h_stack, v_stack};
|
||||||
pub use crate::{ButtonCommon, Color, StyledExt};
|
pub use crate::{ButtonCommon, Color, StyledExt};
|
||||||
pub use theme::ActiveTheme;
|
pub use theme::ActiveTheme;
|
||||||
|
@ -219,9 +219,11 @@ impl PathMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_match<P: AsRef<Path>>(&self, other: P) -> bool {
|
pub fn is_match<P: AsRef<Path>>(&self, other: P) -> bool {
|
||||||
other.as_ref().starts_with(&self.maybe_path)
|
let other_path = other.as_ref();
|
||||||
|| self.glob.is_match(&other)
|
other_path.starts_with(&self.maybe_path)
|
||||||
|| self.check_with_end_separator(other.as_ref())
|
|| other_path.ends_with(&self.maybe_path)
|
||||||
|
|| self.glob.is_match(other_path)
|
||||||
|
|| self.check_with_end_separator(other_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_with_end_separator(&self, path: &Path) -> bool {
|
fn check_with_end_separator(&self, path: &Path) -> bool {
|
||||||
@ -418,4 +420,14 @@ mod tests {
|
|||||||
"Path matcher {path_matcher} should match {path:?}"
|
"Path matcher {path_matcher} should match {path:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn project_search() {
|
||||||
|
let path = Path::new("/Users/someonetoignore/work/zed/zed.dev/node_modules");
|
||||||
|
let path_matcher = PathMatcher::new("**/node_modules/**").unwrap();
|
||||||
|
assert!(
|
||||||
|
path_matcher.is_match(&path),
|
||||||
|
"Path matcher {path_matcher} should match {path:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,6 +259,8 @@ impl FocusableView for WelcomePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Item for WelcomePage {
|
impl Item for WelcomePage {
|
||||||
|
type Event = ItemEvent;
|
||||||
|
|
||||||
fn tab_content(&self, _: Option<usize>, _: &WindowContext) -> AnyElement {
|
fn tab_content(&self, _: Option<usize>, _: &WindowContext) -> AnyElement {
|
||||||
"Welcome to Zed!".into_any()
|
"Welcome to Zed!".into_any()
|
||||||
}
|
}
|
||||||
@ -278,4 +280,8 @@ impl Item for WelcomePage {
|
|||||||
_settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
|
_settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||||
|
f(*event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,18 +481,21 @@ impl Pane {
|
|||||||
|
|
||||||
pub(crate) fn open_item(
|
pub(crate) fn open_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_entry_id: ProjectEntryId,
|
project_entry_id: Option<ProjectEntryId>,
|
||||||
focus_item: bool,
|
focus_item: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
||||||
) -> Box<dyn ItemHandle> {
|
) -> Box<dyn ItemHandle> {
|
||||||
let mut existing_item = None;
|
let mut existing_item = None;
|
||||||
for (index, item) in self.items.iter().enumerate() {
|
if let Some(project_entry_id) = project_entry_id {
|
||||||
if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id]
|
for (index, item) in self.items.iter().enumerate() {
|
||||||
{
|
if item.is_singleton(cx)
|
||||||
let item = item.boxed_clone();
|
&& item.project_entry_ids(cx).as_slice() == [project_entry_id]
|
||||||
existing_item = Some((index, item));
|
{
|
||||||
break;
|
let item = item.boxed_clone();
|
||||||
|
existing_item = Some((index, item));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2129,13 +2129,13 @@ impl Workspace {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_path(
|
fn load_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: ProjectPath,
|
path: ProjectPath,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<
|
) -> Task<
|
||||||
Result<(
|
Result<(
|
||||||
ProjectEntryId,
|
Option<ProjectEntryId>,
|
||||||
impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
|
@ -78,7 +78,7 @@ impl Settings for ItemSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
|
||||||
pub enum ItemEvent {
|
pub enum ItemEvent {
|
||||||
CloseItem,
|
CloseItem,
|
||||||
UpdateTab,
|
UpdateTab,
|
||||||
@ -92,7 +92,9 @@ pub struct BreadcrumbText {
|
|||||||
pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
|
pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Item: FocusableView + EventEmitter<ItemEvent> {
|
pub trait Item: FocusableView + EventEmitter<Self::Event> {
|
||||||
|
type Event;
|
||||||
|
|
||||||
fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
||||||
fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
||||||
fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
|
fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
|
||||||
@ -155,6 +157,8 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
|
|||||||
unimplemented!("reload() must be implemented if can_save() returns true")
|
unimplemented!("reload() must be implemented if can_save() returns true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_item_events(event: &Self::Event, f: impl FnMut(ItemEvent));
|
||||||
|
|
||||||
fn act_as_type<'a>(
|
fn act_as_type<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
type_id: TypeId,
|
type_id: TypeId,
|
||||||
@ -206,12 +210,12 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait ItemHandle: 'static + Send {
|
pub trait ItemHandle: 'static + Send {
|
||||||
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
|
|
||||||
fn subscribe_to_item_events(
|
fn subscribe_to_item_events(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
handler: Box<dyn Fn(&ItemEvent, &mut WindowContext) + Send>,
|
handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
|
||||||
) -> gpui::Subscription;
|
) -> gpui::Subscription;
|
||||||
|
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
|
||||||
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
|
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
|
||||||
fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
|
fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
|
||||||
fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
|
fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
|
||||||
@ -285,20 +289,20 @@ impl dyn ItemHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Item> ItemHandle for View<T> {
|
impl<T: Item> ItemHandle for View<T> {
|
||||||
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
|
|
||||||
self.focus_handle(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subscribe_to_item_events(
|
fn subscribe_to_item_events(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
handler: Box<dyn Fn(&ItemEvent, &mut WindowContext) + Send>,
|
handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
|
||||||
) -> gpui::Subscription {
|
) -> gpui::Subscription {
|
||||||
cx.subscribe(self, move |_, event, cx| {
|
cx.subscribe(self, move |_, event, cx| {
|
||||||
handler(event, cx);
|
T::to_item_events(event, |item_event| handler(item_event, cx));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
|
||||||
|
self.focus_handle(cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
|
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
|
||||||
self.read(cx).tab_tooltip_text(cx)
|
self.read(cx).tab_tooltip_text(cx)
|
||||||
}
|
}
|
||||||
@ -461,7 +465,7 @@ impl<T: Item> ItemHandle for View<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match event {
|
T::to_item_events(event, |event| match event {
|
||||||
ItemEvent::CloseItem => {
|
ItemEvent::CloseItem => {
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
pane.close_item_by_id(item.item_id(), crate::SaveIntent::Close, cx)
|
pane.close_item_by_id(item.item_id(), crate::SaveIntent::Close, cx)
|
||||||
@ -489,7 +493,7 @@ impl<T: Item> ItemHandle for View<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
|
cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
|
||||||
@ -655,12 +659,7 @@ pub enum FollowEvent {
|
|||||||
Unfollow,
|
Unfollow,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FollowableEvents {
|
|
||||||
fn to_follow_event(&self) -> Option<FollowEvent>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FollowableItem: Item {
|
pub trait FollowableItem: Item {
|
||||||
type FollowableEvent: FollowableEvents;
|
|
||||||
fn remote_id(&self) -> Option<ViewId>;
|
fn remote_id(&self) -> Option<ViewId>;
|
||||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
|
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
|
||||||
fn from_state_proto(
|
fn from_state_proto(
|
||||||
@ -670,9 +669,10 @@ pub trait FollowableItem: Item {
|
|||||||
state: &mut Option<proto::view::Variant>,
|
state: &mut Option<proto::view::Variant>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Option<Task<Result<View<Self>>>>;
|
) -> Option<Task<Result<View<Self>>>>;
|
||||||
|
fn to_follow_event(event: &Self::Event) -> Option<FollowEvent>;
|
||||||
fn add_event_to_update_proto(
|
fn add_event_to_update_proto(
|
||||||
&self,
|
&self,
|
||||||
event: &Self::FollowableEvent,
|
event: &Self::Event,
|
||||||
update: &mut Option<proto::update_view::Variant>,
|
update: &mut Option<proto::update_view::Variant>,
|
||||||
cx: &WindowContext,
|
cx: &WindowContext,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
@ -683,7 +683,6 @@ pub trait FollowableItem: Item {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<()>>;
|
) -> Task<Result<()>>;
|
||||||
fn is_project_item(&self, cx: &WindowContext) -> bool;
|
fn is_project_item(&self, cx: &WindowContext) -> bool;
|
||||||
|
|
||||||
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
|
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -739,10 +738,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
|
fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
|
||||||
event
|
T::to_follow_event(event.downcast_ref()?)
|
||||||
.downcast_ref()
|
|
||||||
.map(T::FollowableEvent::to_follow_event)
|
|
||||||
.flatten()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_update_proto(
|
fn apply_update_proto(
|
||||||
@ -929,6 +925,12 @@ pub mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Item for TestItem {
|
impl Item for TestItem {
|
||||||
|
type Event = ItemEvent;
|
||||||
|
|
||||||
|
fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
|
||||||
|
f(*event)
|
||||||
|
}
|
||||||
|
|
||||||
fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
|
fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
|
||||||
self.tab_descriptions.as_ref().and_then(|descriptions| {
|
self.tab_descriptions.as_ref().and_then(|descriptions| {
|
||||||
let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
|
let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
|
item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle},
|
||||||
toolbar::Toolbar,
|
toolbar::Toolbar,
|
||||||
workspace_settings::{AutosaveSetting, WorkspaceSettings},
|
workspace_settings::{AutosaveSetting, WorkspaceSettings},
|
||||||
NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
|
NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
|
||||||
@ -27,7 +27,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
h_stack, prelude::*, right_click_menu, Color, Icon, IconButton, IconElement, Label, Tooltip,
|
h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize,
|
||||||
|
Indicator, Label, Tooltip,
|
||||||
};
|
};
|
||||||
use ui::{v_stack, ContextMenu};
|
use ui::{v_stack, ContextMenu};
|
||||||
use util::truncate_and_remove_front;
|
use util::truncate_and_remove_front;
|
||||||
@ -537,18 +538,21 @@ impl Pane {
|
|||||||
|
|
||||||
pub(crate) fn open_item(
|
pub(crate) fn open_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_entry_id: ProjectEntryId,
|
project_entry_id: Option<ProjectEntryId>,
|
||||||
focus_item: bool,
|
focus_item: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
||||||
) -> Box<dyn ItemHandle> {
|
) -> Box<dyn ItemHandle> {
|
||||||
let mut existing_item = None;
|
let mut existing_item = None;
|
||||||
for (index, item) in self.items.iter().enumerate() {
|
if let Some(project_entry_id) = project_entry_id {
|
||||||
if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id]
|
for (index, item) in self.items.iter().enumerate() {
|
||||||
{
|
if item.is_singleton(cx)
|
||||||
let item = item.boxed_clone();
|
&& item.project_entry_ids(cx).as_slice() == [project_entry_id]
|
||||||
existing_item = Some((index, item));
|
{
|
||||||
break;
|
let item = item.boxed_clone();
|
||||||
|
existing_item = Some((index, item));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1415,22 +1419,7 @@ impl Pane {
|
|||||||
cx: &mut ViewContext<'_, Pane>,
|
cx: &mut ViewContext<'_, Pane>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let label = item.tab_content(Some(detail), cx);
|
let label = item.tab_content(Some(detail), cx);
|
||||||
let close_icon = || {
|
let close_side = &ItemSettings::get_global(cx).close_position;
|
||||||
let id = item.item_id();
|
|
||||||
|
|
||||||
div()
|
|
||||||
.id(ix)
|
|
||||||
.invisible()
|
|
||||||
.group_hover("", |style| style.visible())
|
|
||||||
.child(
|
|
||||||
IconButton::new("close_tab", Icon::Close).on_click(cx.listener(
|
|
||||||
move |pane, _, cx| {
|
|
||||||
pane.close_item_by_id(id, SaveIntent::Close, cx)
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
|
let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
|
||||||
false => (
|
false => (
|
||||||
@ -1447,102 +1436,129 @@ impl Pane {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let close_right = ItemSettings::get_global(cx).close_position.right();
|
|
||||||
let is_active = ix == self.active_item_index;
|
let is_active = ix == self.active_item_index;
|
||||||
|
|
||||||
|
let indicator = {
|
||||||
|
let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
|
||||||
|
(true, _) => Some(Color::Warning),
|
||||||
|
(_, true) => Some(Color::Accent),
|
||||||
|
(false, false) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
h_stack()
|
||||||
|
.w_3()
|
||||||
|
.h_3()
|
||||||
|
.justify_center()
|
||||||
|
.absolute()
|
||||||
|
.map(|this| match close_side {
|
||||||
|
ClosePosition::Left => this.right_1(),
|
||||||
|
ClosePosition::Right => this.left_1(),
|
||||||
|
})
|
||||||
|
.when_some(indicator_color, |this, indicator_color| {
|
||||||
|
this.child(Indicator::dot().color(indicator_color))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let close_button = {
|
||||||
|
let id = item.item_id();
|
||||||
|
|
||||||
|
h_stack()
|
||||||
|
.invisible()
|
||||||
|
.w_3()
|
||||||
|
.h_3()
|
||||||
|
.justify_center()
|
||||||
|
.absolute()
|
||||||
|
.map(|this| match close_side {
|
||||||
|
ClosePosition::Left => this.left_1(),
|
||||||
|
ClosePosition::Right => this.right_1(),
|
||||||
|
})
|
||||||
|
.group_hover("", |style| style.visible())
|
||||||
|
.child(
|
||||||
|
// TODO: Fix button size
|
||||||
|
IconButton::new("close tab", Icon::Close)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.size(ButtonSize::None)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.on_click(cx.listener(move |pane, _, cx| {
|
||||||
|
pane.close_item_by_id(id, SaveIntent::Close, cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let tab = div()
|
let tab = div()
|
||||||
.group("")
|
|
||||||
.id(ix)
|
|
||||||
.cursor_pointer()
|
|
||||||
.when_some(item.tab_tooltip_text(cx), |div, text| {
|
|
||||||
div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)))
|
|
||||||
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
|
|
||||||
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
|
|
||||||
// .on_drop(|_view, state: View<DraggedTab>, cx| {
|
|
||||||
// eprintln!("{:?}", state.read(cx));
|
|
||||||
// })
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
// todo!("Nate - I need to do some work to balance all the items in the tab once things stablize")
|
|
||||||
.map(|this| {
|
|
||||||
if close_right {
|
|
||||||
this.pl_3().pr_1()
|
|
||||||
} else {
|
|
||||||
this.pr_1().pr_3()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.py_1()
|
|
||||||
.bg(tab_bg)
|
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.text_color(if is_active {
|
.bg(tab_bg)
|
||||||
cx.theme().colors().text
|
// 30px @ 16px/rem
|
||||||
} else {
|
.h(rems(1.875))
|
||||||
cx.theme().colors().text_muted
|
|
||||||
})
|
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
|
let is_first_item = ix == 0;
|
||||||
let is_last_item = ix == self.items.len() - 1;
|
let is_last_item = ix == self.items.len() - 1;
|
||||||
match ix.cmp(&self.active_item_index) {
|
match ix.cmp(&self.active_item_index) {
|
||||||
cmp::Ordering::Less => this.border_l().mr_px(),
|
cmp::Ordering::Less => {
|
||||||
cmp::Ordering::Greater => {
|
if is_first_item {
|
||||||
if is_last_item {
|
this.pl_px().pr_px().border_b()
|
||||||
this.mr_px().ml_px()
|
|
||||||
} else {
|
} else {
|
||||||
this.border_r().ml_px()
|
this.border_l().pr_px().border_b()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmp::Ordering::Greater => {
|
||||||
|
if is_last_item {
|
||||||
|
this.pr_px().pl_px().border_b()
|
||||||
|
} else {
|
||||||
|
this.border_r().pl_px().border_b()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmp::Ordering::Equal => {
|
||||||
|
if is_first_item {
|
||||||
|
this.pl_px().border_r().pb_px()
|
||||||
|
} else {
|
||||||
|
this.border_l().border_r().pb_px()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmp::Ordering::Equal => this.border_l().border_r(),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// .hover(|h| h.bg(tab_hover_bg))
|
|
||||||
// .active(|a| a.bg(tab_active_bg))
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
h_stack()
|
||||||
.flex()
|
.group("")
|
||||||
.items_center()
|
.id(ix)
|
||||||
|
.relative()
|
||||||
|
.h_full()
|
||||||
|
.cursor_pointer()
|
||||||
|
.when_some(item.tab_tooltip_text(cx), |div, text| {
|
||||||
|
div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
|
||||||
|
})
|
||||||
|
.on_click(
|
||||||
|
cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)),
|
||||||
|
)
|
||||||
|
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
|
||||||
|
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
|
||||||
|
// .on_drop(|_view, state: View<DraggedTab>, cx| {
|
||||||
|
// eprintln!("{:?}", state.read(cx));
|
||||||
|
// })
|
||||||
|
.px_5()
|
||||||
|
// .hover(|h| h.bg(tab_hover_bg))
|
||||||
|
// .active(|a| a.bg(tab_active_bg))
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_color(text_color)
|
.text_color(text_color)
|
||||||
.children(
|
.child(indicator)
|
||||||
item.has_conflict(cx)
|
.child(close_button)
|
||||||
.then(|| {
|
.child(label),
|
||||||
div().border().border_color(gpui::red()).child(
|
|
||||||
IconElement::new(Icon::ExclamationTriangle)
|
|
||||||
.size(ui::IconSize::Small)
|
|
||||||
.color(Color::Warning),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.or(item.is_dirty(cx).then(|| {
|
|
||||||
div().border().border_color(gpui::red()).child(
|
|
||||||
IconElement::new(Icon::ExclamationTriangle)
|
|
||||||
.size(ui::IconSize::Small)
|
|
||||||
.color(Color::Info),
|
|
||||||
)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.children((!close_right).then(|| close_icon()))
|
|
||||||
.child(label)
|
|
||||||
.children(close_right.then(|| close_icon())),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
right_click_menu(ix).trigger(tab).menu(|cx| {
|
right_click_menu(ix).trigger(tab).menu(|cx| {
|
||||||
ContextMenu::build(cx, |menu, cx| {
|
ContextMenu::build(cx, |menu, cx| {
|
||||||
menu.action(
|
menu.action("Close", CloseActiveItem { save_intent: None }.boxed_clone())
|
||||||
"Close Active Item",
|
.action("Close Others", CloseInactiveItems.boxed_clone())
|
||||||
CloseActiveItem { save_intent: None }.boxed_clone(),
|
.separator()
|
||||||
)
|
.action("Close Left", CloseItemsToTheLeft.boxed_clone())
|
||||||
.action("Close Inactive Items", CloseInactiveItems.boxed_clone())
|
.action("Close Right", CloseItemsToTheRight.boxed_clone())
|
||||||
.action("Close Clean Items", CloseCleanItems.boxed_clone())
|
.separator()
|
||||||
.action("Close Items To The Left", CloseItemsToTheLeft.boxed_clone())
|
.action("Close Clean", CloseCleanItems.boxed_clone())
|
||||||
.action(
|
.action(
|
||||||
"Close Items To The Right",
|
"Close All",
|
||||||
CloseItemsToTheRight.boxed_clone(),
|
CloseAllItems { save_intent: None }.boxed_clone(),
|
||||||
)
|
)
|
||||||
.action(
|
|
||||||
"Close All Items",
|
|
||||||
CloseAllItems { save_intent: None }.boxed_clone(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1562,116 +1578,118 @@ impl Pane {
|
|||||||
// Left Side
|
// Left Side
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.px_2()
|
|
||||||
.flex()
|
.flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.px_1()
|
||||||
|
.border_b()
|
||||||
|
.border_r()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
// Nav Buttons
|
// Nav Buttons
|
||||||
.child(
|
.child(
|
||||||
div().border().border_color(gpui::red()).child(
|
IconButton::new("navigate_backward", Icon::ArrowLeft)
|
||||||
IconButton::new("navigate_backward", Icon::ArrowLeft)
|
.icon_size(IconSize::Small)
|
||||||
.on_click({
|
.on_click({
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
move |_, cx| view.update(cx, Self::navigate_backward)
|
move |_, cx| view.update(cx, Self::navigate_backward)
|
||||||
})
|
})
|
||||||
.disabled(!self.can_navigate_backward()),
|
.disabled(!self.can_navigate_backward()),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div().border().border_color(gpui::red()).child(
|
IconButton::new("navigate_forward", Icon::ArrowRight)
|
||||||
IconButton::new("navigate_forward", Icon::ArrowRight)
|
.icon_size(IconSize::Small)
|
||||||
.on_click({
|
.on_click({
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
move |_, cx| view.update(cx, Self::navigate_backward)
|
move |_, cx| view.update(cx, Self::navigate_backward)
|
||||||
})
|
})
|
||||||
.disabled(!self.can_navigate_forward()),
|
.disabled(!self.can_navigate_forward()),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div().flex_1().h_full().child(
|
div()
|
||||||
div().id("tabs").flex().overflow_x_scroll().children(
|
.relative()
|
||||||
self.items
|
.flex_1()
|
||||||
.iter()
|
.h_full()
|
||||||
.enumerate()
|
.overflow_hidden_x()
|
||||||
.zip(self.tab_details(cx))
|
.child(
|
||||||
.map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
|
div()
|
||||||
|
.absolute()
|
||||||
|
.top_0()
|
||||||
|
.left_0()
|
||||||
|
.z_index(1)
|
||||||
|
.size_full()
|
||||||
|
.border_b()
|
||||||
|
.border_color(cx.theme().colors().border),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_stack().id("tabs").z_index(2).children(
|
||||||
|
self.items
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.zip(self.tab_details(cx))
|
||||||
|
.map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
// Right Side
|
// Right Side
|
||||||
.child(
|
.child(
|
||||||
div()
|
h_stack()
|
||||||
.px_1()
|
|
||||||
.flex()
|
.flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.gap_2()
|
.gap_1()
|
||||||
// Nav Buttons
|
.px_1()
|
||||||
|
.border_b()
|
||||||
|
.border_l()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_px()
|
.gap_px()
|
||||||
.child(
|
.child(
|
||||||
div()
|
IconButton::new("plus", Icon::Plus)
|
||||||
.bg(gpui::blue())
|
.icon_size(IconSize::Small)
|
||||||
.border()
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
.border_color(gpui::red())
|
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||||
.child(IconButton::new("plus", Icon::Plus).on_click(
|
menu.action("New File", NewFile.boxed_clone())
|
||||||
cx.listener(|this, _, cx| {
|
.action(
|
||||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
"New Terminal",
|
||||||
menu.action("New File", NewFile.boxed_clone())
|
NewCenterTerminal.boxed_clone(),
|
||||||
.action(
|
)
|
||||||
"New Terminal",
|
.action("New Search", NewSearch.boxed_clone())
|
||||||
NewCenterTerminal.boxed_clone(),
|
});
|
||||||
)
|
cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
|
||||||
.action("New Search", NewSearch.boxed_clone())
|
this.focus(cx);
|
||||||
});
|
this.new_item_menu = None;
|
||||||
cx.subscribe(
|
})
|
||||||
&menu,
|
.detach();
|
||||||
|this, _, event: &DismissEvent, cx| {
|
this.new_item_menu = Some(menu);
|
||||||
this.focus(cx);
|
})),
|
||||||
this.new_item_menu = None;
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.detach();
|
|
||||||
this.new_item_menu = Some(menu);
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
.when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
|
|
||||||
el.child(Self::render_menu_overlay(new_item_menu))
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
.when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
|
||||||
|
el.child(Self::render_menu_overlay(new_item_menu))
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
div()
|
IconButton::new("split", Icon::Split)
|
||||||
.border()
|
.icon_size(IconSize::Small)
|
||||||
.border_color(gpui::red())
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
.child(IconButton::new("split", Icon::Split).on_click(
|
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||||
cx.listener(|this, _, cx| {
|
menu.action("Split Right", SplitRight.boxed_clone())
|
||||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
.action("Split Left", SplitLeft.boxed_clone())
|
||||||
menu.action("Split Right", SplitRight.boxed_clone())
|
.action("Split Up", SplitUp.boxed_clone())
|
||||||
.action("Split Left", SplitLeft.boxed_clone())
|
.action("Split Down", SplitDown.boxed_clone())
|
||||||
.action("Split Up", SplitUp.boxed_clone())
|
});
|
||||||
.action("Split Down", SplitDown.boxed_clone())
|
cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
|
||||||
});
|
this.focus(cx);
|
||||||
cx.subscribe(
|
this.split_item_menu = None;
|
||||||
&menu,
|
})
|
||||||
|this, _, event: &DismissEvent, cx| {
|
.detach();
|
||||||
this.focus(cx);
|
this.split_item_menu = Some(menu);
|
||||||
this.split_item_menu = None;
|
})),
|
||||||
},
|
)
|
||||||
)
|
.when_some(self.split_item_menu.as_ref(), |el, split_item_menu| {
|
||||||
.detach();
|
el.child(Self::render_menu_overlay(split_item_menu))
|
||||||
this.split_item_menu = Some(menu);
|
}),
|
||||||
}),
|
|
||||||
))
|
|
||||||
.when_some(
|
|
||||||
self.split_item_menu.as_ref(),
|
|
||||||
|el, split_item_menu| {
|
|
||||||
el.child(Self::render_menu_overlay(split_item_menu))
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2089,6 +2107,8 @@ impl Render for Pane {
|
|||||||
v_stack()
|
v_stack()
|
||||||
.key_context("Pane")
|
.key_context("Pane")
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
|
.size_full()
|
||||||
|
.overflow_hidden()
|
||||||
.on_focus_in({
|
.on_focus_in({
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
move |event, cx| {
|
move |event, cx| {
|
||||||
@ -2156,7 +2176,6 @@ impl Render for Pane {
|
|||||||
pane.close_all_items(action, cx)
|
pane.close_all_items(action, cx)
|
||||||
.map(|task| task.detach_and_log_err(cx));
|
.map(|task| task.detach_and_log_err(cx));
|
||||||
}))
|
}))
|
||||||
.size_full()
|
|
||||||
.on_action(
|
.on_action(
|
||||||
cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
|
cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
|
||||||
pane.close_active_item(action, cx)
|
pane.close_active_item(action, cx)
|
||||||
|
@ -59,7 +59,6 @@ impl SharedScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<Event> for SharedScreen {}
|
impl EventEmitter<Event> for SharedScreen {}
|
||||||
impl EventEmitter<ItemEvent> for SharedScreen {}
|
|
||||||
|
|
||||||
impl FocusableView for SharedScreen {
|
impl FocusableView for SharedScreen {
|
||||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||||
@ -79,9 +78,12 @@ impl Render for SharedScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Item for SharedScreen {
|
impl Item for SharedScreen {
|
||||||
|
type Event = Event;
|
||||||
|
|
||||||
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
|
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
|
||||||
Some(format!("{}'s screen", self.user.github_login).into())
|
Some(format!("{}'s screen", self.user.github_login).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
if let Some(nav_history) = self.nav_history.as_mut() {
|
||||||
nav_history.push::<()>(None, cx);
|
nav_history.push::<()>(None, cx);
|
||||||
@ -111,4 +113,10 @@ impl Item for SharedScreen {
|
|||||||
let track = self.track.upgrade()?;
|
let track = self.track.upgrade()?;
|
||||||
Some(cx.build_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
|
Some(cx.build_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
|
||||||
|
match event {
|
||||||
|
Event::Close => f(ItemEvent::CloseItem),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use crate::ItemHandle;
|
use crate::ItemHandle;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
|
AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
|
||||||
ViewContext, WindowContext,
|
ViewContext, WindowContext,
|
||||||
};
|
};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::{h_stack, v_stack, Icon, IconButton};
|
use ui::{h_stack, v_stack};
|
||||||
|
|
||||||
pub enum ToolbarItemEvent {
|
pub enum ToolbarItemEvent {
|
||||||
ChangeLocation(ToolbarItemLocation),
|
ChangeLocation(ToolbarItemLocation),
|
||||||
@ -87,25 +87,7 @@ impl Render for Toolbar {
|
|||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
// Toolbar left side
|
.children(self.items.iter().map(|(child, _)| child.to_any())),
|
||||||
.children(self.items.iter().map(|(child, _)| child.to_any()))
|
|
||||||
// Toolbar right side
|
|
||||||
.child(
|
|
||||||
h_stack()
|
|
||||||
.p_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.border()
|
|
||||||
.border_color(gpui::red())
|
|
||||||
.child(IconButton::new("buffer-search", Icon::MagnifyingGlass)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.border()
|
|
||||||
.border_color(gpui::red())
|
|
||||||
.child(IconButton::new("inline-assist", Icon::MagicWand)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ mod persistence;
|
|||||||
pub mod searchable;
|
pub mod searchable;
|
||||||
// todo!()
|
// todo!()
|
||||||
mod modal_layer;
|
mod modal_layer;
|
||||||
mod shared_screen;
|
pub mod shared_screen;
|
||||||
mod status_bar;
|
mod status_bar;
|
||||||
mod toolbar;
|
mod toolbar;
|
||||||
mod workspace_settings;
|
mod workspace_settings;
|
||||||
@ -212,27 +212,31 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||||||
init_settings(cx);
|
init_settings(cx);
|
||||||
notifications::init(cx);
|
notifications::init(cx);
|
||||||
|
|
||||||
// cx.add_global_action({
|
cx.on_action(Workspace::close_global);
|
||||||
// let app_state = Arc::downgrade(&app_state);
|
cx.on_action(restart);
|
||||||
// move |_: &Open, cx: &mut AppContext| {
|
|
||||||
// let mut paths = cx.prompt_for_paths(PathPromptOptions {
|
|
||||||
// files: true,
|
|
||||||
// directories: true,
|
|
||||||
// multiple: true,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if let Some(app_state) = app_state.upgrade() {
|
cx.on_action({
|
||||||
// cx.spawn(move |mut cx| async move {
|
let app_state = Arc::downgrade(&app_state);
|
||||||
// if let Some(paths) = paths.recv().await.flatten() {
|
move |_: &Open, cx: &mut AppContext| {
|
||||||
// cx.update(|cx| {
|
let mut paths = cx.prompt_for_paths(PathPromptOptions {
|
||||||
// open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
|
files: true,
|
||||||
// });
|
directories: true,
|
||||||
// }
|
multiple: true,
|
||||||
// })
|
});
|
||||||
// .detach();
|
|
||||||
// }
|
if let Some(app_state) = app_state.upgrade() {
|
||||||
// }
|
cx.spawn(move |mut cx| async move {
|
||||||
// });
|
if let Some(paths) = paths.await.log_err().flatten() {
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectItemBuilders =
|
type ProjectItemBuilders =
|
||||||
@ -1076,7 +1080,6 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo!(Non-window-actions)
|
|
||||||
pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
|
pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
|
||||||
cx.windows().iter().find(|window| {
|
cx.windows().iter().find(|window| {
|
||||||
window
|
window
|
||||||
@ -1094,21 +1097,18 @@ impl Workspace {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(
|
pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &CloseWindow,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Option<Task<Result<()>>> {
|
|
||||||
let window = cx.window_handle();
|
let window = cx.window_handle();
|
||||||
let prepare = self.prepare_to_close(false, cx);
|
let prepare = self.prepare_to_close(false, cx);
|
||||||
Some(cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
if prepare.await? {
|
if prepare.await? {
|
||||||
window.update(&mut cx, |_, cx| {
|
window.update(&mut cx, |_, cx| {
|
||||||
cx.remove_window();
|
cx.remove_window();
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Ok(())
|
anyhow::Ok(())
|
||||||
}))
|
})
|
||||||
|
.detach_and_log_err(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_to_close(
|
pub fn prepare_to_close(
|
||||||
@ -1855,13 +1855,13 @@ impl Workspace {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_path(
|
fn load_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: ProjectPath,
|
path: ProjectPath,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<
|
) -> Task<
|
||||||
Result<(
|
Result<(
|
||||||
ProjectEntryId,
|
Option<ProjectEntryId>,
|
||||||
impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
@ -2327,42 +2327,44 @@ impl Workspace {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn follow_next_collaborator(
|
// pub fn follow_next_collaborator(
|
||||||
// &mut self,
|
// &mut self,
|
||||||
// _: &FollowNextCollaborator,
|
// _: &FollowNextCollaborator,
|
||||||
// cx: &mut ViewContext<Self>,
|
// cx: &mut ViewContext<Self>,
|
||||||
// ) -> Option<Task<Result<()>>> {
|
// ) {
|
||||||
// let collaborators = self.project.read(cx).collaborators();
|
// let collaborators = self.project.read(cx).collaborators();
|
||||||
// let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
|
// let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
|
||||||
// let mut collaborators = collaborators.keys().copied();
|
// let mut collaborators = collaborators.keys().copied();
|
||||||
// for peer_id in collaborators.by_ref() {
|
// for peer_id in collaborators.by_ref() {
|
||||||
// if peer_id == leader_id {
|
// if peer_id == leader_id {
|
||||||
// break;
|
// break;
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// collaborators.next()
|
|
||||||
// } else if let Some(last_leader_id) =
|
|
||||||
// self.last_leaders_by_pane.get(&self.active_pane.downgrade())
|
|
||||||
// {
|
|
||||||
// if collaborators.contains_key(last_leader_id) {
|
|
||||||
// Some(*last_leader_id)
|
|
||||||
// } else {
|
|
||||||
// None
|
|
||||||
// }
|
// }
|
||||||
|
// }
|
||||||
|
// collaborators.next()
|
||||||
|
// } else if let Some(last_leader_id) =
|
||||||
|
// self.last_leaders_by_pane.get(&self.active_pane.downgrade())
|
||||||
|
// {
|
||||||
|
// if collaborators.contains_key(last_leader_id) {
|
||||||
|
// Some(*last_leader_id)
|
||||||
// } else {
|
// } else {
|
||||||
// None
|
// None
|
||||||
// };
|
|
||||||
|
|
||||||
// let pane = self.active_pane.clone();
|
|
||||||
// let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
|
|
||||||
// else {
|
|
||||||
// return None;
|
|
||||||
// };
|
|
||||||
// if Some(leader_id) == self.unfollow(&pane, cx) {
|
|
||||||
// return None;
|
|
||||||
// }
|
// }
|
||||||
// self.follow(leader_id, cx)
|
// } else {
|
||||||
|
// None
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let pane = self.active_pane.clone();
|
||||||
|
// let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
|
||||||
|
// else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// if Some(leader_id) == self.unfollow(&pane, cx) {
|
||||||
|
// return;
|
||||||
// }
|
// }
|
||||||
|
// if let Some(task) = self.follow(leader_id, cx) {
|
||||||
|
// task.detach();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn follow(
|
pub fn follow(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -2411,6 +2413,18 @@ impl Workspace {
|
|||||||
self.start_following(leader_id, cx)
|
self.start_following(leader_id, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // if you're already following, find the right pane and focus it.
|
||||||
|
// for (pane, state) in &self.follower_states {
|
||||||
|
// if leader_id == state.leader_id {
|
||||||
|
// cx.focus(pane);
|
||||||
|
// return None;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Otherwise, follow.
|
||||||
|
// self.start_following(leader_id, cx)
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
|
pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
|
||||||
let state = self.follower_states.remove(pane)?;
|
let state = self.follower_states.remove(pane)?;
|
||||||
let leader_id = state.leader_id;
|
let leader_id = state.leader_id;
|
||||||
@ -2627,8 +2641,6 @@ impl Workspace {
|
|||||||
update: proto::UpdateFollowers,
|
update: proto::UpdateFollowers,
|
||||||
cx: &mut AsyncWindowContext,
|
cx: &mut AsyncWindowContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
dbg!("process_leader_update", &update);
|
|
||||||
|
|
||||||
match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
|
match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
|
||||||
proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
|
proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
|
||||||
this.update(cx, |this, _| {
|
this.update(cx, |this, _| {
|
||||||
@ -3223,13 +3235,8 @@ impl Workspace {
|
|||||||
|
|
||||||
fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
|
fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
|
||||||
self.add_workspace_actions_listeners(div, cx)
|
self.add_workspace_actions_listeners(div, cx)
|
||||||
// cx.add_async_action(Workspace::open);
|
|
||||||
// cx.add_async_action(Workspace::follow_next_collaborator);
|
|
||||||
// cx.add_async_action(Workspace::close);
|
|
||||||
.on_action(cx.listener(Self::close_inactive_items_and_panes))
|
.on_action(cx.listener(Self::close_inactive_items_and_panes))
|
||||||
.on_action(cx.listener(Self::close_all_items_and_panes))
|
.on_action(cx.listener(Self::close_all_items_and_panes))
|
||||||
// cx.add_global_action(Workspace::close_global);
|
|
||||||
// cx.add_global_action(restart);
|
|
||||||
.on_action(cx.listener(Self::save_all))
|
.on_action(cx.listener(Self::save_all))
|
||||||
.on_action(cx.listener(Self::add_folder_to_project))
|
.on_action(cx.listener(Self::add_folder_to_project))
|
||||||
.on_action(cx.listener(|workspace, _: &Unfollow, cx| {
|
.on_action(cx.listener(|workspace, _: &Unfollow, cx| {
|
||||||
@ -3278,6 +3285,9 @@ impl Workspace {
|
|||||||
workspace.close_all_docks(cx);
|
workspace.close_all_docks(cx);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.on_action(cx.listener(Workspace::open))
|
||||||
|
.on_action(cx.listener(Workspace::close_window))
|
||||||
|
|
||||||
// cx.add_action(Workspace::activate_pane_at_index);
|
// cx.add_action(Workspace::activate_pane_at_index);
|
||||||
// cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
|
// cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
|
||||||
// workspace.reopen_closed_item(cx).detach();
|
// workspace.reopen_closed_item(cx).detach();
|
||||||
@ -3882,8 +3892,6 @@ impl WorkspaceStore {
|
|||||||
let leader_id = envelope.original_sender_id()?;
|
let leader_id = envelope.original_sender_id()?;
|
||||||
let update = envelope.payload;
|
let update = envelope.payload;
|
||||||
|
|
||||||
dbg!("handle_upate_followers");
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
for workspace in &this.workspaces {
|
for workspace in &this.workspaces {
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
@ -615,8 +615,8 @@ fn open_local_settings_file(
|
|||||||
.update(&mut cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
project.create_entry((tree_id, dir_path), true, cx)
|
project.create_entry((tree_id, dir_path), true, cx)
|
||||||
})
|
})
|
||||||
.ok_or_else(|| anyhow!("worktree was removed"))?
|
.await
|
||||||
.await?;
|
.context("worktree was removed")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,8 +625,8 @@ fn open_local_settings_file(
|
|||||||
.update(&mut cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
project.create_entry((tree_id, file_path), false, cx)
|
project.create_entry((tree_id, file_path), false, cx)
|
||||||
})
|
})
|
||||||
.ok_or_else(|| anyhow!("worktree was removed"))?
|
.await
|
||||||
.await?;
|
.context("worktree was removed")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let editor = workspace
|
let editor = workspace
|
||||||
@ -763,7 +763,7 @@ mod tests {
|
|||||||
AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
|
AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
|
||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use project::{Project, ProjectPath};
|
use project::{project_settings::ProjectSettings, Project, ProjectPath};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
|
use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
|
||||||
use std::{
|
use std::{
|
||||||
@ -1308,6 +1308,122 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_opening_excluded_paths(cx: &mut TestAppContext) {
|
||||||
|
let app_state = init_test(cx);
|
||||||
|
cx.update(|cx| {
|
||||||
|
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||||
|
store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
|
||||||
|
project_settings.file_scan_exclusions =
|
||||||
|
Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree(
|
||||||
|
"/root",
|
||||||
|
json!({
|
||||||
|
".gitignore": "ignored_dir\n",
|
||||||
|
".git": {
|
||||||
|
"HEAD": "ref: refs/heads/main",
|
||||||
|
},
|
||||||
|
"regular_dir": {
|
||||||
|
"file": "regular file contents",
|
||||||
|
},
|
||||||
|
"ignored_dir": {
|
||||||
|
"ignored_subdir": {
|
||||||
|
"file": "ignored subfile contents",
|
||||||
|
},
|
||||||
|
"file": "ignored file contents",
|
||||||
|
},
|
||||||
|
"excluded_dir": {
|
||||||
|
"file": "excluded file contents",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||||
|
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||||
|
let workspace = window.root(cx);
|
||||||
|
|
||||||
|
let initial_entries = cx.read(|cx| workspace.file_project_paths(cx));
|
||||||
|
let paths_to_open = [
|
||||||
|
Path::new("/root/excluded_dir/file").to_path_buf(),
|
||||||
|
Path::new("/root/.git/HEAD").to_path_buf(),
|
||||||
|
Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(),
|
||||||
|
];
|
||||||
|
let (opened_workspace, new_items) = cx
|
||||||
|
.update(|cx| workspace::open_paths(&paths_to_open, &app_state, None, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
opened_workspace.id(),
|
||||||
|
workspace.id(),
|
||||||
|
"Excluded files in subfolders of a workspace root should be opened in the workspace"
|
||||||
|
);
|
||||||
|
let mut opened_paths = cx.read(|cx| {
|
||||||
|
assert_eq!(
|
||||||
|
new_items.len(),
|
||||||
|
paths_to_open.len(),
|
||||||
|
"Expect to get the same number of opened items as submitted paths to open"
|
||||||
|
);
|
||||||
|
new_items
|
||||||
|
.iter()
|
||||||
|
.zip(paths_to_open.iter())
|
||||||
|
.map(|(i, path)| {
|
||||||
|
match i {
|
||||||
|
Some(Ok(i)) => {
|
||||||
|
Some(i.project_path(cx).map(|p| p.path.display().to_string()))
|
||||||
|
}
|
||||||
|
Some(Err(e)) => panic!("Excluded file {path:?} failed to open: {e:?}"),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
.flatten()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
opened_paths.sort();
|
||||||
|
assert_eq!(
|
||||||
|
opened_paths,
|
||||||
|
vec![
|
||||||
|
None,
|
||||||
|
Some(".git/HEAD".to_string()),
|
||||||
|
Some("excluded_dir/file".to_string()),
|
||||||
|
],
|
||||||
|
"Excluded files should get opened, excluded dir should not get opened"
|
||||||
|
);
|
||||||
|
|
||||||
|
let entries = cx.read(|cx| workspace.file_project_paths(cx));
|
||||||
|
assert_eq!(
|
||||||
|
initial_entries, entries,
|
||||||
|
"Workspace entries should not change after opening excluded files and directories paths"
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.read(|cx| {
|
||||||
|
let pane = workspace.read(cx).active_pane().read(cx);
|
||||||
|
let mut opened_buffer_paths = pane
|
||||||
|
.items()
|
||||||
|
.map(|i| {
|
||||||
|
i.project_path(cx)
|
||||||
|
.expect("all excluded files that got open should have a path")
|
||||||
|
.path
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
opened_buffer_paths.sort();
|
||||||
|
assert_eq!(
|
||||||
|
opened_buffer_paths,
|
||||||
|
vec![".git/HEAD".to_string(), "excluded_dir/file".to_string()],
|
||||||
|
"Despite not being present in the worktrees, buffers for excluded files are opened and added to the pane"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_save_conflicting_item(cx: &mut TestAppContext) {
|
async fn test_save_conflicting_item(cx: &mut TestAppContext) {
|
||||||
let app_state = init_test(cx);
|
let app_state = init_test(cx);
|
||||||
|
@ -55,7 +55,7 @@ outline = { package = "outline2", path = "../outline2" }
|
|||||||
project = { package = "project2", path = "../project2" }
|
project = { package = "project2", path = "../project2" }
|
||||||
project_panel = { package = "project_panel2", path = "../project_panel2" }
|
project_panel = { package = "project_panel2", path = "../project_panel2" }
|
||||||
# project_symbols = { path = "../project_symbols" }
|
# project_symbols = { path = "../project_symbols" }
|
||||||
# quick_action_bar = { path = "../quick_action_bar" }
|
quick_action_bar = { package = "quick_action_bar2", path = "../quick_action_bar2" }
|
||||||
# recent_projects = { path = "../recent_projects" }
|
# recent_projects = { path = "../recent_projects" }
|
||||||
rope = { package = "rope2", path = "../rope2"}
|
rope = { package = "rope2", path = "../rope2"}
|
||||||
rpc = { package = "rpc2", path = "../rpc2" }
|
rpc = { package = "rpc2", path = "../rpc2" }
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user