mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 02:46:43 +00:00
Merge pull request #1867 from zed-industries/drag-project-entry-to-pane
Drag project entry to pane
This commit is contained in:
commit
3d5a3634cf
15 changed files with 455 additions and 198 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4277,6 +4277,7 @@ name = "project_panel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"context_menu",
|
"context_menu",
|
||||||
|
"drag_and_drop",
|
||||||
"editor",
|
"editor",
|
||||||
"futures 0.3.24",
|
"futures 0.3.24",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
|
|
@ -909,7 +909,7 @@ async fn test_host_disconnect(
|
||||||
cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
|
cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
|
||||||
let editor_b = workspace_b
|
let editor_b = workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "b.txt"), true, cx)
|
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -3705,7 +3705,7 @@ async fn test_collaborating_with_code_actions(
|
||||||
cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
|
cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
|
||||||
let editor_b = workspace_b
|
let editor_b = workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "main.rs"), true, cx)
|
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -3926,7 +3926,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||||
cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
|
cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
|
||||||
let editor_b = workspace_b
|
let editor_b = workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "one.rs"), true, cx)
|
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -4726,7 +4726,7 @@ async fn test_following(
|
||||||
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
|
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
|
||||||
let 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"), true, cx)
|
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -4734,7 +4734,7 @@ async fn test_following(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let editor_a2 = workspace_a
|
let editor_a2 = workspace_a
|
||||||
.update(cx_a, |workspace, cx| {
|
.update(cx_a, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "2.txt"), true, cx)
|
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -4745,7 +4745,7 @@ async fn test_following(
|
||||||
let workspace_b = client_b.build_workspace(&project_b, cx_b);
|
let workspace_b = client_b.build_workspace(&project_b, cx_b);
|
||||||
let editor_b1 = workspace_b
|
let editor_b1 = workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "1.txt"), true, cx)
|
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -5003,7 +5003,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||||
let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
|
let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
|
||||||
let _editor_a1 = workspace_a
|
let _editor_a1 = workspace_a
|
||||||
.update(cx_a, |workspace, cx| {
|
.update(cx_a, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "1.txt"), true, cx)
|
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -5015,7 +5015,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||||
let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
|
let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
|
||||||
let _editor_b1 = workspace_b
|
let _editor_b1 = workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "2.txt"), true, cx)
|
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -5066,7 +5066,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||||
|
|
||||||
workspace_a
|
workspace_a
|
||||||
.update(cx_a, |workspace, cx| {
|
.update(cx_a, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "3.txt"), true, cx)
|
workspace.open_path((worktree_id, "3.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -5077,7 +5077,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||||
workspace_b
|
workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
assert_eq!(*workspace.active_pane(), pane_b1);
|
assert_eq!(*workspace.active_pane(), pane_b1);
|
||||||
workspace.open_path((worktree_id, "4.txt"), true, cx)
|
workspace.open_path((worktree_id, "4.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -5178,7 +5178,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
|
||||||
let workspace_a = client_a.build_workspace(&project_a, cx_a);
|
let workspace_a = client_a.build_workspace(&project_a, cx_a);
|
||||||
let _editor_a1 = workspace_a
|
let _editor_a1 = workspace_a
|
||||||
.update(cx_a, |workspace, cx| {
|
.update(cx_a, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "1.txt"), true, cx)
|
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -5291,7 +5291,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
|
||||||
// When client B activates a different item in the original pane, it automatically stops following client A.
|
// When client B activates a different item in the original pane, it automatically stops following client A.
|
||||||
workspace_b
|
workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "2.txt"), true, cx)
|
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -2,29 +2,44 @@ use std::{any::Any, rc::Rc};
|
||||||
|
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::{MouseEventHandler, Overlay},
|
elements::{Empty, MouseEventHandler, Overlay},
|
||||||
geometry::vector::Vector2F,
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
scene::MouseDrag,
|
scene::MouseDrag,
|
||||||
CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
|
CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
|
||||||
View, WeakViewHandle,
|
View, WeakViewHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct State<V: View> {
|
enum State<V: View> {
|
||||||
|
Dragging {
|
||||||
window_id: usize,
|
window_id: usize,
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
region_offset: Vector2F,
|
region_offset: Vector2F,
|
||||||
|
region: RectF,
|
||||||
payload: Rc<dyn Any + 'static>,
|
payload: Rc<dyn Any + 'static>,
|
||||||
render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
|
render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
|
||||||
|
},
|
||||||
|
Canceled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: View> Clone for State<V> {
|
impl<V: View> Clone for State<V> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
match self {
|
||||||
window_id: self.window_id.clone(),
|
State::Dragging {
|
||||||
position: self.position.clone(),
|
window_id,
|
||||||
region_offset: self.region_offset.clone(),
|
position,
|
||||||
payload: self.payload.clone(),
|
region_offset,
|
||||||
render: self.render.clone(),
|
region,
|
||||||
|
payload,
|
||||||
|
render,
|
||||||
|
} => Self::Dragging {
|
||||||
|
window_id: window_id.clone(),
|
||||||
|
position: position.clone(),
|
||||||
|
region_offset: region_offset.clone(),
|
||||||
|
region: region.clone(),
|
||||||
|
payload: payload.clone(),
|
||||||
|
render: render.clone(),
|
||||||
|
},
|
||||||
|
State::Canceled => State::Canceled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,24 +64,27 @@ impl<V: View> DragAndDrop<V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
|
pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
|
||||||
self.currently_dragged.as_ref().and_then(
|
self.currently_dragged.as_ref().and_then(|state| {
|
||||||
|State {
|
if let State::Dragging {
|
||||||
position,
|
position,
|
||||||
payload,
|
payload,
|
||||||
window_id: window_dragged_from,
|
window_id: window_dragged_from,
|
||||||
..
|
..
|
||||||
}| {
|
} = state
|
||||||
|
{
|
||||||
if &window_id != window_dragged_from {
|
if &window_id != window_dragged_from {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
payload
|
payload
|
||||||
.clone()
|
.is::<T>()
|
||||||
.downcast::<T>()
|
.then(|| payload.clone().downcast::<T>().ok())
|
||||||
.ok()
|
.flatten()
|
||||||
.map(|payload| (position.clone(), payload))
|
.map(|payload| (position.clone(), payload))
|
||||||
},
|
} else {
|
||||||
)
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dragging<T: Any>(
|
pub fn dragging<T: Any>(
|
||||||
|
@ -77,44 +95,59 @@ impl<V: View> DragAndDrop<V> {
|
||||||
) {
|
) {
|
||||||
let window_id = cx.window_id();
|
let window_id = cx.window_id();
|
||||||
cx.update_global::<Self, _, _>(|this, cx| {
|
cx.update_global::<Self, _, _>(|this, cx| {
|
||||||
let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() {
|
this.notify_containers_for_window(window_id, cx);
|
||||||
previous_state.region_offset
|
|
||||||
|
if matches!(this.currently_dragged, Some(State::Canceled)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (region_offset, region) = if let Some(State::Dragging {
|
||||||
|
region_offset,
|
||||||
|
region,
|
||||||
|
..
|
||||||
|
}) = this.currently_dragged.as_ref()
|
||||||
|
{
|
||||||
|
(*region_offset, *region)
|
||||||
} else {
|
} else {
|
||||||
event.region.origin() - event.prev_mouse_position
|
(
|
||||||
|
event.region.origin() - event.prev_mouse_position,
|
||||||
|
event.region,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.currently_dragged = Some(State {
|
this.currently_dragged = Some(State::Dragging {
|
||||||
window_id,
|
window_id,
|
||||||
region_offset,
|
region_offset,
|
||||||
|
region,
|
||||||
position: event.position,
|
position: event.position,
|
||||||
payload,
|
payload,
|
||||||
render: Rc::new(move |payload, cx| {
|
render: Rc::new(move |payload, cx| {
|
||||||
render(payload.downcast_ref::<T>().unwrap(), cx)
|
render(payload.downcast_ref::<T>().unwrap(), cx)
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.notify_containers_for_window(window_id, cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
|
pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
|
||||||
let currently_dragged = cx.global::<Self>().currently_dragged.clone();
|
enum DraggedElementHandler {}
|
||||||
|
cx.global::<Self>()
|
||||||
currently_dragged.and_then(
|
.currently_dragged
|
||||||
|State {
|
.clone()
|
||||||
|
.and_then(|state| {
|
||||||
|
match state {
|
||||||
|
State::Dragging {
|
||||||
window_id,
|
window_id,
|
||||||
region_offset,
|
region_offset,
|
||||||
position,
|
position,
|
||||||
|
region,
|
||||||
payload,
|
payload,
|
||||||
render,
|
render,
|
||||||
}| {
|
} => {
|
||||||
if cx.window_id() != window_id {
|
if cx.window_id() != window_id {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = position + region_offset;
|
let position = position + region_offset;
|
||||||
|
|
||||||
enum DraggedElementHandler {}
|
|
||||||
Some(
|
Some(
|
||||||
Overlay::new(
|
Overlay::new(
|
||||||
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
|
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
|
||||||
|
@ -123,28 +156,74 @@ impl<V: View> DragAndDrop<V> {
|
||||||
.with_cursor_style(CursorStyle::Arrow)
|
.with_cursor_style(CursorStyle::Arrow)
|
||||||
.on_up(MouseButton::Left, |_, cx| {
|
.on_up(MouseButton::Left, |_, cx| {
|
||||||
cx.defer(|cx| {
|
cx.defer(|cx| {
|
||||||
cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
|
cx.update_global::<Self, _, _>(|this, cx| {
|
||||||
|
this.finish_dragging(cx)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
cx.propagate_event();
|
cx.propagate_event();
|
||||||
})
|
})
|
||||||
.on_up_out(MouseButton::Left, |_, cx| {
|
.on_up_out(MouseButton::Left, |_, cx| {
|
||||||
cx.defer(|cx| {
|
cx.defer(|cx| {
|
||||||
cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
|
cx.update_global::<Self, _, _>(|this, cx| {
|
||||||
|
this.finish_dragging(cx)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
// Don't block hover events or invalidations
|
// Don't block hover events or invalidations
|
||||||
.with_hoverable(false)
|
.with_hoverable(false)
|
||||||
|
.constrained()
|
||||||
|
.with_width(region.width())
|
||||||
|
.with_height(region.height())
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_anchor_position(position)
|
.with_anchor_position(position)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_dragging(&mut self, cx: &mut MutableAppContext) {
|
State::Canceled => Some(
|
||||||
if let Some(State { window_id, .. }) = self.currently_dragged.take() {
|
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
|
||||||
|
Empty::new()
|
||||||
|
.constrained()
|
||||||
|
.with_width(0.)
|
||||||
|
.with_height(0.)
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.on_up(MouseButton::Left, |_, cx| {
|
||||||
|
cx.defer(|cx| {
|
||||||
|
cx.update_global::<Self, _, _>(|this, _| {
|
||||||
|
this.currently_dragged = None;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on_up_out(MouseButton::Left, |_, cx| {
|
||||||
|
cx.defer(|cx| {
|
||||||
|
cx.update_global::<Self, _, _>(|this, _| {
|
||||||
|
this.currently_dragged = None;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.boxed(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut MutableAppContext) {
|
||||||
|
if let Some(State::Dragging {
|
||||||
|
payload, window_id, ..
|
||||||
|
}) = &self.currently_dragged
|
||||||
|
{
|
||||||
|
if payload.is::<P>() {
|
||||||
|
let window_id = *window_id;
|
||||||
|
self.currently_dragged = Some(State::Canceled);
|
||||||
|
self.notify_containers_for_window(window_id, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_dragging(&mut self, cx: &mut MutableAppContext) {
|
||||||
|
if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
|
||||||
self.notify_containers_for_window(window_id, cx);
|
self.notify_containers_for_window(window_id, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6464,7 +6464,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn jump(workspace: &mut Workspace, action: &Jump, cx: &mut ViewContext<Workspace>) {
|
fn jump(workspace: &mut Workspace, action: &Jump, cx: &mut ViewContext<Workspace>) {
|
||||||
let editor = workspace.open_path(action.path.clone(), true, cx);
|
let editor = workspace.open_path(action.path.clone(), None, true, cx);
|
||||||
let position = action.position;
|
let position = action.position;
|
||||||
let anchor = action.anchor;
|
let anchor = action.anchor;
|
||||||
cx.spawn_weak(|_, mut cx| async move {
|
cx.spawn_weak(|_, mut cx| async move {
|
||||||
|
|
|
@ -76,7 +76,9 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
|
|
||||||
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||||
let item = workspace
|
let item = workspace
|
||||||
.update(cx, |workspace, cx| workspace.open_path(file, true, cx))
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace.open_path(file, None, true, cx)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.expect("Could not open test file");
|
.expect("Could not open test file");
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ impl FileFinder {
|
||||||
match event {
|
match event {
|
||||||
Event::Selected(project_path) => {
|
Event::Selected(project_path) => {
|
||||||
workspace
|
workspace
|
||||||
.open_path(project_path.clone(), true, cx)
|
.open_path(project_path.clone(), None, true, cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
workspace.dismiss_modal(cx);
|
workspace.dismiss_modal(cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
context_menu = { path = "../context_menu" }
|
context_menu = { path = "../context_menu" }
|
||||||
|
drag_and_drop = { path = "../drag_and_drop" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
menu = { path = "../menu" }
|
menu = { path = "../menu" }
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use context_menu::{ContextMenu, ContextMenuItem};
|
use context_menu::{ContextMenu, ContextMenuItem};
|
||||||
|
use drag_and_drop::{DragAndDrop, Draggable};
|
||||||
use editor::{Cancel, Editor};
|
use editor::{Cancel, Editor};
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
anyhow::{anyhow, Result},
|
anyhow::{anyhow, Result},
|
||||||
elements::{
|
elements::{
|
||||||
AnchorCorner, ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler,
|
AnchorCorner, ChildView, ConstrainedBox, ContainerStyle, Empty, Flex, Label,
|
||||||
ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState,
|
MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState,
|
||||||
},
|
},
|
||||||
geometry::vector::Vector2F,
|
geometry::vector::Vector2F,
|
||||||
impl_internal_actions, keymap,
|
impl_internal_actions, keymap,
|
||||||
|
@ -25,6 +26,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use theme::ProjectPanelEntry;
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
@ -71,8 +73,9 @@ pub enum ClipboardEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
struct EntryDetails {
|
pub struct EntryDetails {
|
||||||
filename: String,
|
filename: String,
|
||||||
|
path: Arc<Path>,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
kind: EntryKind,
|
kind: EntryKind,
|
||||||
is_ignored: bool,
|
is_ignored: bool,
|
||||||
|
@ -220,6 +223,7 @@ impl ProjectPanel {
|
||||||
this.update_visible_entries(None, cx);
|
this.update_visible_entries(None, cx);
|
||||||
this
|
this
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.subscribe(&project_panel, {
|
cx.subscribe(&project_panel, {
|
||||||
let project_panel = project_panel.downgrade();
|
let project_panel = project_panel.downgrade();
|
||||||
move |workspace, _, event, cx| match event {
|
move |workspace, _, event, cx| match event {
|
||||||
|
@ -235,6 +239,7 @@ impl ProjectPanel {
|
||||||
worktree_id: worktree.read(cx).id(),
|
worktree_id: worktree.read(cx).id(),
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
focus_opened_item,
|
focus_opened_item,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -601,6 +606,10 @@ impl ProjectPanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.update_global(|drag_and_drop: &mut DragAndDrop<Workspace>, cx| {
|
||||||
|
drag_and_drop.cancel_dragging::<ProjectEntryId>(cx);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -950,14 +959,15 @@ impl ProjectPanel {
|
||||||
let end_ix = range.end.min(ix + visible_worktree_entries.len());
|
let end_ix = range.end.min(ix + visible_worktree_entries.len());
|
||||||
if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) {
|
if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) {
|
||||||
let snapshot = worktree.read(cx).snapshot();
|
let snapshot = worktree.read(cx).snapshot();
|
||||||
|
let root_name = OsStr::new(snapshot.root_name());
|
||||||
let expanded_entry_ids = self
|
let expanded_entry_ids = self
|
||||||
.expanded_dir_ids
|
.expanded_dir_ids
|
||||||
.get(&snapshot.id())
|
.get(&snapshot.id())
|
||||||
.map(Vec::as_slice)
|
.map(Vec::as_slice)
|
||||||
.unwrap_or(&[]);
|
.unwrap_or(&[]);
|
||||||
let root_name = OsStr::new(snapshot.root_name());
|
|
||||||
for entry in &visible_worktree_entries[range.start.saturating_sub(ix)..end_ix - ix]
|
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
|
||||||
{
|
for entry in &visible_worktree_entries[entry_range] {
|
||||||
let mut details = EntryDetails {
|
let mut details = EntryDetails {
|
||||||
filename: entry
|
filename: entry
|
||||||
.path
|
.path
|
||||||
|
@ -965,6 +975,7 @@ impl ProjectPanel {
|
||||||
.unwrap_or(root_name)
|
.unwrap_or(root_name)
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
path: entry.path.clone(),
|
||||||
depth: entry.path.components().count(),
|
depth: entry.path.components().count(),
|
||||||
kind: entry.kind,
|
kind: entry.kind,
|
||||||
is_ignored: entry.is_ignored,
|
is_ignored: entry.is_ignored,
|
||||||
|
@ -978,12 +989,14 @@ impl ProjectPanel {
|
||||||
.clipboard_entry
|
.clipboard_entry
|
||||||
.map_or(false, |e| e.is_cut() && e.entry_id() == entry.id),
|
.map_or(false, |e| e.is_cut() && e.entry_id() == entry.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(edit_state) = &self.edit_state {
|
if let Some(edit_state) = &self.edit_state {
|
||||||
let is_edited_entry = if edit_state.is_new_entry {
|
let is_edited_entry = if edit_state.is_new_entry {
|
||||||
entry.id == NEW_ENTRY_ID
|
entry.id == NEW_ENTRY_ID
|
||||||
} else {
|
} else {
|
||||||
entry.id == edit_state.entry_id
|
entry.id == edit_state.entry_id
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_edited_entry {
|
if is_edited_entry {
|
||||||
if let Some(processing_filename) = &edit_state.processing_filename {
|
if let Some(processing_filename) = &edit_state.processing_filename {
|
||||||
details.is_processing = true;
|
details.is_processing = true;
|
||||||
|
@ -1005,33 +1018,17 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_entry(
|
fn render_entry_visual_element<V: View>(
|
||||||
entry_id: ProjectEntryId,
|
details: &EntryDetails,
|
||||||
details: EntryDetails,
|
editor: Option<&ViewHandle<Editor>>,
|
||||||
editor: &ViewHandle<Editor>,
|
padding: f32,
|
||||||
theme: &theme::ProjectPanel,
|
row_container_style: ContainerStyle,
|
||||||
cx: &mut RenderContext<Self>,
|
style: &ProjectPanelEntry,
|
||||||
|
cx: &mut RenderContext<V>,
|
||||||
) -> ElementBox {
|
) -> ElementBox {
|
||||||
let kind = details.kind;
|
let kind = details.kind;
|
||||||
let show_editor = details.is_editing && !details.is_processing;
|
let show_editor = details.is_editing && !details.is_processing;
|
||||||
MouseEventHandler::<Self>::new(entry_id.to_usize(), cx, |state, cx| {
|
|
||||||
let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width;
|
|
||||||
|
|
||||||
let entry_style = if details.is_cut {
|
|
||||||
&theme.cut_entry
|
|
||||||
} else if details.is_ignored {
|
|
||||||
&theme.ignored_entry
|
|
||||||
} else {
|
|
||||||
&theme.entry
|
|
||||||
};
|
|
||||||
|
|
||||||
let style = entry_style.style_for(state, details.is_selected).clone();
|
|
||||||
|
|
||||||
let row_container_style = if show_editor {
|
|
||||||
theme.filename_editor.container
|
|
||||||
} else {
|
|
||||||
style.container
|
|
||||||
};
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
ConstrainedBox::new(if kind == EntryKind::Dir {
|
ConstrainedBox::new(if kind == EntryKind::Dir {
|
||||||
|
@ -1054,16 +1051,16 @@ impl ProjectPanel {
|
||||||
.with_width(style.icon_size)
|
.with_width(style.icon_size)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_child(if show_editor {
|
.with_child(if show_editor && editor.is_some() {
|
||||||
ChildView::new(editor.clone(), cx)
|
ChildView::new(editor.unwrap().clone(), cx)
|
||||||
.contained()
|
.contained()
|
||||||
.with_margin_left(theme.entry.default.icon_spacing)
|
.with_margin_left(style.icon_spacing)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.flex(1.0, true)
|
.flex(1.0, true)
|
||||||
.boxed()
|
.boxed()
|
||||||
} else {
|
} else {
|
||||||
Label::new(details.filename, style.text.clone())
|
Label::new(details.filename.clone(), style.text.clone())
|
||||||
.contained()
|
.contained()
|
||||||
.with_margin_left(style.icon_spacing)
|
.with_margin_left(style.icon_spacing)
|
||||||
.aligned()
|
.aligned()
|
||||||
|
@ -1071,11 +1068,49 @@ impl ProjectPanel {
|
||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(theme.entry.default.height)
|
.with_height(style.height)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(row_container_style)
|
.with_style(row_container_style)
|
||||||
.with_padding_left(padding)
|
.with_padding_left(padding)
|
||||||
.boxed()
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_entry(
|
||||||
|
entry_id: ProjectEntryId,
|
||||||
|
details: EntryDetails,
|
||||||
|
editor: &ViewHandle<Editor>,
|
||||||
|
theme: &theme::ProjectPanel,
|
||||||
|
cx: &mut RenderContext<Self>,
|
||||||
|
) -> ElementBox {
|
||||||
|
let kind = details.kind;
|
||||||
|
let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width;
|
||||||
|
|
||||||
|
let entry_style = if details.is_cut {
|
||||||
|
&theme.cut_entry
|
||||||
|
} else if details.is_ignored {
|
||||||
|
&theme.ignored_entry
|
||||||
|
} else {
|
||||||
|
&theme.entry
|
||||||
|
};
|
||||||
|
|
||||||
|
let show_editor = details.is_editing && !details.is_processing;
|
||||||
|
|
||||||
|
MouseEventHandler::<Self>::new(entry_id.to_usize(), cx, |state, cx| {
|
||||||
|
let style = entry_style.style_for(state, details.is_selected).clone();
|
||||||
|
let row_container_style = if show_editor {
|
||||||
|
theme.filename_editor.container
|
||||||
|
} else {
|
||||||
|
style.container
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::render_entry_visual_element(
|
||||||
|
&details,
|
||||||
|
Some(editor),
|
||||||
|
padding,
|
||||||
|
row_container_style,
|
||||||
|
&style,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.on_click(MouseButton::Left, move |e, cx| {
|
.on_click(MouseButton::Left, move |e, cx| {
|
||||||
if kind == EntryKind::Dir {
|
if kind == EntryKind::Dir {
|
||||||
|
@ -1093,6 +1128,21 @@ impl ProjectPanel {
|
||||||
position: e.position,
|
position: e.position,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.as_draggable(entry_id, {
|
||||||
|
let row_container_style = theme.dragged_entry.container;
|
||||||
|
|
||||||
|
move |_, cx: &mut RenderContext<Workspace>| {
|
||||||
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
|
Self::render_entry_visual_element(
|
||||||
|
&details,
|
||||||
|
None,
|
||||||
|
padding,
|
||||||
|
row_container_style,
|
||||||
|
&theme.project_panel.dragged_entry,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -326,6 +326,7 @@ pub struct ProjectPanel {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub container: ContainerStyle,
|
pub container: ContainerStyle,
|
||||||
pub entry: Interactive<ProjectPanelEntry>,
|
pub entry: Interactive<ProjectPanelEntry>,
|
||||||
|
pub dragged_entry: ProjectPanelEntry,
|
||||||
pub ignored_entry: Interactive<ProjectPanelEntry>,
|
pub ignored_entry: Interactive<ProjectPanelEntry>,
|
||||||
pub cut_entry: Interactive<ProjectPanelEntry>,
|
pub cut_entry: Interactive<ProjectPanelEntry>,
|
||||||
pub filename_editor: FieldEditor,
|
pub filename_editor: FieldEditor,
|
||||||
|
|
|
@ -67,7 +67,9 @@ impl<'a> VimTestContext<'a> {
|
||||||
|
|
||||||
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||||
let item = workspace
|
let item = workspace
|
||||||
.update(cx, |workspace, cx| workspace.open_path(file, true, cx))
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace.open_path(file, None, true, cx)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.expect("Could not open test file");
|
.expect("Could not open test file");
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,13 @@ use gpui::{
|
||||||
AppContext, Element, ElementBox, EventContext, MouseButton, MouseState, Quad, RenderContext,
|
AppContext, Element, ElementBox, EventContext, MouseButton, MouseState, Quad, RenderContext,
|
||||||
WeakViewHandle,
|
WeakViewHandle,
|
||||||
};
|
};
|
||||||
|
use project::ProjectEntryId;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
|
||||||
use crate::{MoveItem, Pane, SplitDirection, SplitWithItem, Workspace};
|
use crate::{
|
||||||
|
MoveItem, OpenProjectEntryInPane, Pane, SplitDirection, SplitWithItem, SplitWithProjectEntry,
|
||||||
|
Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
use super::DraggedItem;
|
use super::DraggedItem;
|
||||||
|
|
||||||
|
@ -28,12 +32,18 @@ where
|
||||||
MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
|
MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
|
||||||
// Observing hovered will cause a render when the mouse enters regardless
|
// Observing hovered will cause a render when the mouse enters regardless
|
||||||
// of if mouse position was accessed before
|
// of if mouse position was accessed before
|
||||||
let hovered = state.hovered();
|
let drag_position = if state.hovered() {
|
||||||
let drag_position = cx
|
cx.global::<DragAndDrop<Workspace>>()
|
||||||
.global::<DragAndDrop<Workspace>>()
|
|
||||||
.currently_dragged::<DraggedItem>(cx.window_id())
|
.currently_dragged::<DraggedItem>(cx.window_id())
|
||||||
.filter(|_| hovered)
|
.map(|(drag_position, _)| drag_position)
|
||||||
.map(|(drag_position, _)| drag_position);
|
.or_else(|| {
|
||||||
|
cx.global::<DragAndDrop<Workspace>>()
|
||||||
|
.currently_dragged::<ProjectEntryId>(cx.window_id())
|
||||||
|
.map(|(drag_position, _)| drag_position)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Stack::new()
|
Stack::new()
|
||||||
.with_child(render_child(state, cx))
|
.with_child(render_child(state, cx))
|
||||||
|
@ -70,10 +80,14 @@ where
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_move(|_, cx| {
|
.on_move(|_, cx| {
|
||||||
if cx
|
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
||||||
.global::<DragAndDrop<Workspace>>()
|
|
||||||
|
if drag_and_drop
|
||||||
.currently_dragged::<DraggedItem>(cx.window_id())
|
.currently_dragged::<DraggedItem>(cx.window_id())
|
||||||
.is_some()
|
.is_some()
|
||||||
|
|| drag_and_drop
|
||||||
|
.currently_dragged::<ProjectEntryId>(cx.window_id())
|
||||||
|
.is_some()
|
||||||
{
|
{
|
||||||
cx.notify();
|
cx.notify();
|
||||||
} else {
|
} else {
|
||||||
|
@ -90,32 +104,61 @@ pub fn handle_dropped_item(
|
||||||
split_margin: Option<f32>,
|
split_margin: Option<f32>,
|
||||||
cx: &mut EventContext,
|
cx: &mut EventContext,
|
||||||
) {
|
) {
|
||||||
if let Some((_, dragged_item)) = cx
|
enum Action {
|
||||||
.global::<DragAndDrop<Workspace>>()
|
Move(WeakViewHandle<Pane>, usize),
|
||||||
.currently_dragged::<DraggedItem>(cx.window_id)
|
Open(ProjectEntryId),
|
||||||
|
}
|
||||||
|
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
||||||
|
let action = if let Some((_, dragged_item)) =
|
||||||
|
drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id)
|
||||||
{
|
{
|
||||||
if let Some(split_direction) = split_margin
|
Action::Move(dragged_item.pane.clone(), dragged_item.item.id())
|
||||||
.and_then(|margin| drop_split_direction(event.position, event.region, margin))
|
} else if let Some((_, project_entry)) =
|
||||||
|
drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id)
|
||||||
{
|
{
|
||||||
cx.dispatch_action(SplitWithItem {
|
Action::Open(*project_entry)
|
||||||
from: dragged_item.pane.clone(),
|
} else {
|
||||||
item_id_to_move: dragged_item.item.id(),
|
return;
|
||||||
pane_to_split: pane.clone(),
|
};
|
||||||
|
|
||||||
|
if let Some(split_direction) =
|
||||||
|
split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
|
||||||
|
{
|
||||||
|
let pane_to_split = pane.clone();
|
||||||
|
match action {
|
||||||
|
Action::Move(from, item_id_to_move) => cx.dispatch_action(SplitWithItem {
|
||||||
|
from,
|
||||||
|
item_id_to_move,
|
||||||
|
pane_to_split,
|
||||||
split_direction,
|
split_direction,
|
||||||
});
|
}),
|
||||||
} else if pane != &dragged_item.pane || allow_same_pane {
|
Action::Open(project_entry) => cx.dispatch_action(SplitWithProjectEntry {
|
||||||
// If no split margin or not close enough to the edge, just move the item
|
pane_to_split,
|
||||||
|
split_direction,
|
||||||
|
project_entry,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
match action {
|
||||||
|
Action::Move(from, item_id) => {
|
||||||
|
if pane != &from || allow_same_pane {
|
||||||
cx.dispatch_action(MoveItem {
|
cx.dispatch_action(MoveItem {
|
||||||
item_id: dragged_item.item.id(),
|
item_id,
|
||||||
from: dragged_item.pane.clone(),
|
from,
|
||||||
to: pane.clone(),
|
to: pane.clone(),
|
||||||
destination_index: index,
|
destination_index: index,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
cx.propagate_event();
|
cx.propagate_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action::Open(project_entry) => cx.dispatch_action(OpenProjectEntryInPane {
|
||||||
|
pane: pane.clone(),
|
||||||
|
project_entry,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn drop_split_direction(
|
fn drop_split_direction(
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
|
|
|
@ -128,12 +128,25 @@ pub struct OpenSharedScreen {
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct SplitWithItem {
|
pub struct SplitWithItem {
|
||||||
from: WeakViewHandle<Pane>,
|
|
||||||
pane_to_split: WeakViewHandle<Pane>,
|
pane_to_split: WeakViewHandle<Pane>,
|
||||||
split_direction: SplitDirection,
|
split_direction: SplitDirection,
|
||||||
|
from: WeakViewHandle<Pane>,
|
||||||
item_id_to_move: usize,
|
item_id_to_move: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct SplitWithProjectEntry {
|
||||||
|
pane_to_split: WeakViewHandle<Pane>,
|
||||||
|
split_direction: SplitDirection,
|
||||||
|
project_entry: ProjectEntryId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct OpenProjectEntryInPane {
|
||||||
|
pane: WeakViewHandle<Pane>,
|
||||||
|
project_entry: ProjectEntryId,
|
||||||
|
}
|
||||||
|
|
||||||
impl_internal_actions!(
|
impl_internal_actions!(
|
||||||
workspace,
|
workspace,
|
||||||
[
|
[
|
||||||
|
@ -143,6 +156,8 @@ impl_internal_actions!(
|
||||||
OpenSharedScreen,
|
OpenSharedScreen,
|
||||||
RemoveWorktreeFromProject,
|
RemoveWorktreeFromProject,
|
||||||
SplitWithItem,
|
SplitWithItem,
|
||||||
|
SplitWithProjectEntry,
|
||||||
|
OpenProjectEntryInPane,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
impl_actions!(workspace, [ActivatePane]);
|
impl_actions!(workspace, [ActivatePane]);
|
||||||
|
@ -234,6 +249,57 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cx.add_async_action(
|
||||||
|
|workspace: &mut Workspace,
|
||||||
|
SplitWithProjectEntry {
|
||||||
|
pane_to_split,
|
||||||
|
split_direction,
|
||||||
|
project_entry,
|
||||||
|
}: &_,
|
||||||
|
cx| {
|
||||||
|
pane_to_split.upgrade(cx).and_then(|pane_to_split| {
|
||||||
|
let new_pane = workspace.add_pane(cx);
|
||||||
|
workspace
|
||||||
|
.center
|
||||||
|
.split(&pane_to_split, &new_pane, *split_direction)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
workspace
|
||||||
|
.project
|
||||||
|
.read(cx)
|
||||||
|
.path_for_entry(*project_entry, cx)
|
||||||
|
.map(|path| {
|
||||||
|
let task = workspace.open_path(path, Some(new_pane.downgrade()), true, cx);
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
task.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.add_async_action(
|
||||||
|
|workspace: &mut Workspace,
|
||||||
|
OpenProjectEntryInPane {
|
||||||
|
pane,
|
||||||
|
project_entry,
|
||||||
|
}: &_,
|
||||||
|
cx| {
|
||||||
|
workspace
|
||||||
|
.project
|
||||||
|
.read(cx)
|
||||||
|
.path_for_entry(*project_entry, cx)
|
||||||
|
.map(|path| {
|
||||||
|
let task = workspace.open_path(path, Some(pane.clone()), true, cx);
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
task.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let client = &app_state.client;
|
let client = &app_state.client;
|
||||||
client.add_view_request_handler(Workspace::handle_follow);
|
client.add_view_request_handler(Workspace::handle_follow);
|
||||||
client.add_view_message_handler(Workspace::handle_unfollow);
|
client.add_view_message_handler(Workspace::handle_unfollow);
|
||||||
|
@ -1399,7 +1465,7 @@ impl Workspace {
|
||||||
mut abs_paths: Vec<PathBuf>,
|
mut abs_paths: Vec<PathBuf>,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
|
) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
|
|
||||||
// Sort the paths to ensure we add worktrees for parents before their children.
|
// Sort the paths to ensure we add worktrees for parents before their children.
|
||||||
|
@ -1429,7 +1495,7 @@ impl Workspace {
|
||||||
if fs.is_file(&abs_path).await {
|
if fs.is_file(&abs_path).await {
|
||||||
Some(
|
Some(
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.open_path(project_path, true, cx)
|
this.open_path(project_path, None, true, cx)
|
||||||
})
|
})
|
||||||
.await,
|
.await,
|
||||||
)
|
)
|
||||||
|
@ -1749,10 +1815,11 @@ impl Workspace {
|
||||||
pub fn open_path(
|
pub fn open_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: impl Into<ProjectPath>,
|
path: impl Into<ProjectPath>,
|
||||||
|
pane: Option<WeakViewHandle<Pane>>,
|
||||||
focus_item: bool,
|
focus_item: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
|
) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
|
||||||
let pane = self.active_pane().downgrade();
|
let pane = pane.unwrap_or_else(|| self.active_pane().downgrade());
|
||||||
let task = self.load_path(path.into(), cx);
|
let task = self.load_path(path.into(), cx);
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let (project_entry_id, build_item) = task.await?;
|
let (project_entry_id, build_item) = task.await?;
|
||||||
|
@ -2874,7 +2941,7 @@ pub fn open_paths(
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
) -> Task<(
|
) -> Task<(
|
||||||
ViewHandle<Workspace>,
|
ViewHandle<Workspace>,
|
||||||
Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
|
Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
|
||||||
)> {
|
)> {
|
||||||
log::info!("open paths {:?}", abs_paths);
|
log::info!("open paths {:?}", abs_paths);
|
||||||
|
|
||||||
|
|
|
@ -818,7 +818,7 @@ mod tests {
|
||||||
|
|
||||||
// Open the first entry
|
// Open the first entry
|
||||||
let entry_1 = workspace
|
let entry_1 = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
|
@ -832,7 +832,7 @@ mod tests {
|
||||||
|
|
||||||
// Open the second entry
|
// Open the second entry
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
|
@ -846,7 +846,7 @@ mod tests {
|
||||||
|
|
||||||
// Open the first entry again. The existing pane item is activated.
|
// Open the first entry again. The existing pane item is activated.
|
||||||
let entry_1b = workspace
|
let entry_1b = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(entry_1.id(), entry_1b.id());
|
assert_eq!(entry_1.id(), entry_1b.id());
|
||||||
|
@ -864,7 +864,7 @@ mod tests {
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |w, cx| {
|
.update(cx, |w, cx| {
|
||||||
w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx);
|
w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx);
|
||||||
w.open_path(file2.clone(), true, cx)
|
w.open_path(file2.clone(), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -883,8 +883,8 @@ mod tests {
|
||||||
// Open the third entry twice concurrently. Only one pane item is added.
|
// Open the third entry twice concurrently. Only one pane item is added.
|
||||||
let (t1, t2) = workspace.update(cx, |w, cx| {
|
let (t1, t2) = workspace.update(cx, |w, cx| {
|
||||||
(
|
(
|
||||||
w.open_path(file3.clone(), true, cx),
|
w.open_path(file3.clone(), None, true, cx),
|
||||||
w.open_path(file3.clone(), true, cx),
|
w.open_path(file3.clone(), None, true, cx),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
t1.await.unwrap();
|
t1.await.unwrap();
|
||||||
|
@ -1195,7 +1195,7 @@ mod tests {
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||||
workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), true, cx)
|
workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1284,7 +1284,7 @@ mod tests {
|
||||||
let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
||||||
|
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -1359,7 +1359,7 @@ mod tests {
|
||||||
let file3 = entries[2].clone();
|
let file3 = entries[2].clone();
|
||||||
|
|
||||||
let editor1 = workspace
|
let editor1 = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.downcast::<Editor>()
|
.downcast::<Editor>()
|
||||||
|
@ -1370,13 +1370,13 @@ mod tests {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let editor2 = workspace
|
let editor2 = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.downcast::<Editor>()
|
.downcast::<Editor>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let editor3 = workspace
|
let editor3 = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file3.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.downcast::<Editor>()
|
.downcast::<Editor>()
|
||||||
|
@ -1626,22 +1626,22 @@ mod tests {
|
||||||
let file4 = entries[3].clone();
|
let file4 = entries[3].clone();
|
||||||
|
|
||||||
let file1_item_id = workspace
|
let file1_item_id = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.id();
|
.id();
|
||||||
let file2_item_id = workspace
|
let file2_item_id = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.id();
|
.id();
|
||||||
let file3_item_id = workspace
|
let file3_item_id = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file3.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.id();
|
.id();
|
||||||
let file4_item_id = workspace
|
let file4_item_id = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file4.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file4.clone(), None, true, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.id();
|
.id();
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import { ColorScheme } from "../themes/common/colorScheme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { background, foreground, text } from "./components";
|
import { withOpacity } from "../utils/color";
|
||||||
|
import { background, border, foreground, text } from "./components";
|
||||||
|
|
||||||
export default function projectPanel(colorScheme: ColorScheme) {
|
export default function projectPanel(colorScheme: ColorScheme) {
|
||||||
let layer = colorScheme.middle;
|
let layer = colorScheme.middle;
|
||||||
|
|
||||||
let entry = {
|
let baseEntry = {
|
||||||
height: 24,
|
height: 24,
|
||||||
iconColor: foreground(layer, "variant"),
|
iconColor: foreground(layer, "variant"),
|
||||||
iconSize: 8,
|
iconSize: 8,
|
||||||
iconSpacing: 8,
|
iconSpacing: 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = {
|
||||||
|
...baseEntry,
|
||||||
text: text(layer, "mono", "variant", { size: "sm" }),
|
text: text(layer, "mono", "variant", { size: "sm" }),
|
||||||
hover: {
|
hover: {
|
||||||
background: background(layer, "variant", "hovered"),
|
background: background(layer, "variant", "hovered"),
|
||||||
|
@ -28,6 +33,12 @@ export default function projectPanel(colorScheme: ColorScheme) {
|
||||||
padding: { left: 12, right: 12, top: 6, bottom: 6 },
|
padding: { left: 12, right: 12, top: 6, bottom: 6 },
|
||||||
indentWidth: 8,
|
indentWidth: 8,
|
||||||
entry,
|
entry,
|
||||||
|
draggedEntry: {
|
||||||
|
...baseEntry,
|
||||||
|
text: text(layer, "mono", "on", { size: "sm" }),
|
||||||
|
background: withOpacity(background(layer, "on"), 0.9),
|
||||||
|
border: border(layer),
|
||||||
|
},
|
||||||
ignoredEntry: {
|
ignoredEntry: {
|
||||||
...entry,
|
...entry,
|
||||||
text: text(layer, "mono", "disabled"),
|
text: text(layer, "mono", "disabled"),
|
||||||
|
|
|
@ -67,7 +67,7 @@ export default function tabBar(colorScheme: ColorScheme) {
|
||||||
|
|
||||||
const draggedTab = {
|
const draggedTab = {
|
||||||
...activePaneActiveTab,
|
...activePaneActiveTab,
|
||||||
background: withOpacity(tab.background, 0.95),
|
background: withOpacity(tab.background, 0.9),
|
||||||
border: undefined as any,
|
border: undefined as any,
|
||||||
shadow: colorScheme.popoverShadow,
|
shadow: colorScheme.popoverShadow,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue