parent
fc7bf7bcb9
commit
d57f5937d4
3 changed files with 278 additions and 163 deletions
|
@ -67,7 +67,10 @@ use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
|
|||
pub use element::{
|
||||
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
|
||||
};
|
||||
use futures::{future, FutureExt};
|
||||
use futures::{
|
||||
future::{self, Shared},
|
||||
FutureExt,
|
||||
};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
|
||||
use code_context_menus::{
|
||||
|
@ -761,6 +764,7 @@ pub struct Editor {
|
|||
next_scroll_position: NextScrollCursorCenterTopBottom,
|
||||
addons: HashMap<TypeId, Box<dyn Addon>>,
|
||||
registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
|
||||
load_diff_task: Option<Shared<Task<()>>>,
|
||||
selection_mark_mode: bool,
|
||||
toggle_fold_multiple_buffers: Task<()>,
|
||||
_scroll_cursor_center_top_bottom_task: Task<()>,
|
||||
|
@ -1318,12 +1322,16 @@ impl Editor {
|
|||
};
|
||||
|
||||
let mut code_action_providers = Vec::new();
|
||||
let mut load_uncommitted_diff = None;
|
||||
if let Some(project) = project.clone() {
|
||||
get_uncommitted_diff_for_buffer(
|
||||
&project,
|
||||
buffer.read(cx).all_buffers(),
|
||||
buffer.clone(),
|
||||
cx,
|
||||
load_uncommitted_diff = Some(
|
||||
get_uncommitted_diff_for_buffer(
|
||||
&project,
|
||||
buffer.read(cx).all_buffers(),
|
||||
buffer.clone(),
|
||||
cx,
|
||||
)
|
||||
.shared(),
|
||||
);
|
||||
code_action_providers.push(Rc::new(project) as Rc<_>);
|
||||
}
|
||||
|
@ -1471,6 +1479,7 @@ impl Editor {
|
|||
selection_mark_mode: false,
|
||||
toggle_fold_multiple_buffers: Task::ready(()),
|
||||
text_style_refinement: None,
|
||||
load_diff_task: load_uncommitted_diff,
|
||||
};
|
||||
this.tasks_update_task = Some(this.refresh_runnables(window, cx));
|
||||
this._subscriptions.extend(project_subscriptions);
|
||||
|
@ -14120,11 +14129,14 @@ impl Editor {
|
|||
let buffer_id = buffer.read(cx).remote_id();
|
||||
if self.buffer.read(cx).diff_for(buffer_id).is_none() {
|
||||
if let Some(project) = &self.project {
|
||||
get_uncommitted_diff_for_buffer(
|
||||
project,
|
||||
[buffer.clone()],
|
||||
self.buffer.clone(),
|
||||
cx,
|
||||
self.load_diff_task = Some(
|
||||
get_uncommitted_diff_for_buffer(
|
||||
project,
|
||||
[buffer.clone()],
|
||||
self.buffer.clone(),
|
||||
cx,
|
||||
)
|
||||
.shared(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14879,6 +14891,10 @@ impl Editor {
|
|||
|
||||
gpui::Size::new(em_width, line_height)
|
||||
}
|
||||
|
||||
pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
|
||||
self.load_diff_task.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_uncommitted_diff_for_buffer(
|
||||
|
@ -14886,7 +14902,7 @@ fn get_uncommitted_diff_for_buffer(
|
|||
buffers: impl IntoIterator<Item = Entity<Buffer>>,
|
||||
buffer: Entity<MultiBuffer>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
) -> Task<()> {
|
||||
let mut tasks = Vec::new();
|
||||
project.update(cx, |project, cx| {
|
||||
for buffer in buffers {
|
||||
|
@ -14903,7 +14919,6 @@ fn get_uncommitted_diff_for_buffer(
|
|||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
|
||||
|
|
|
@ -16,7 +16,7 @@ use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
|||
use gpui::*;
|
||||
use itertools::Itertools;
|
||||
use language::{markdown, Buffer, File, ParsedMarkdown};
|
||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||
use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||
use multi_buffer::ExcerptInfo;
|
||||
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, PanelHeader};
|
||||
use project::{
|
||||
|
@ -28,10 +28,11 @@ use settings::Settings as _;
|
|||
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
|
||||
use time::OffsetDateTime;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, Checkbox, Divider, DividerColor, ElevationIndex, IndentGuideColors,
|
||||
ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
||||
prelude::*, ButtonLike, Checkbox, ContextMenu, Divider, DividerColor, ElevationIndex, ListItem,
|
||||
ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
||||
};
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use workspace::SaveIntent;
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::{DetachAndPromptErr, NotificationId},
|
||||
|
@ -79,7 +80,6 @@ pub fn init(cx: &mut App) {
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum Event {
|
||||
Focus,
|
||||
OpenedEntry { path: ProjectPath },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -112,7 +112,7 @@ impl GitHeaderEntry {
|
|||
pub fn title(&self) -> &'static str {
|
||||
match self.header {
|
||||
Section::Conflict => "Conflicts",
|
||||
Section::Tracked => "Changed",
|
||||
Section::Tracked => "Changes",
|
||||
Section::New => "New",
|
||||
}
|
||||
}
|
||||
|
@ -177,6 +177,7 @@ pub struct GitPanel {
|
|||
update_visible_entries_task: Task<()>,
|
||||
width: Option<Pixels>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||
}
|
||||
|
||||
fn commit_message_editor(
|
||||
|
@ -215,7 +216,7 @@ impl GitPanel {
|
|||
let active_repository = project.read(cx).active_repository(cx);
|
||||
let workspace = cx.entity().downgrade();
|
||||
|
||||
let git_panel = cx.new(|cx| {
|
||||
cx.new(|cx| {
|
||||
let focus_handle = cx.focus_handle();
|
||||
cx.on_focus(&focus_handle, window, Self::focus_in).detach();
|
||||
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
|
||||
|
@ -282,30 +283,13 @@ impl GitPanel {
|
|||
tracked_staged_count: 0,
|
||||
update_visible_entries_task: Task::ready(()),
|
||||
width: Some(px(360.)),
|
||||
context_menu: None,
|
||||
workspace,
|
||||
};
|
||||
git_panel.schedule_update(false, window, cx);
|
||||
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
||||
git_panel
|
||||
});
|
||||
|
||||
cx.subscribe_in(
|
||||
&git_panel,
|
||||
window,
|
||||
move |workspace, _, event: &Event, window, cx| match event.clone() {
|
||||
Event::OpenedEntry { path } => {
|
||||
workspace
|
||||
.open_path_preview(path, None, false, false, window, cx)
|
||||
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
|
||||
Some(format!("{e}"))
|
||||
});
|
||||
}
|
||||
Event::Focus => { /* TODO */ }
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
git_panel
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select_entry_by_path(
|
||||
|
@ -468,7 +452,7 @@ impl GitPanel {
|
|||
|
||||
fn select_first(&mut self, _: &SelectFirst, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.entries.first().is_some() {
|
||||
self.selected_entry = Some(0);
|
||||
self.selected_entry = Some(1);
|
||||
self.scroll_to_selected_entry(cx);
|
||||
}
|
||||
}
|
||||
|
@ -486,7 +470,16 @@ impl GitPanel {
|
|||
selected_entry
|
||||
};
|
||||
|
||||
self.selected_entry = Some(new_selected_entry);
|
||||
if matches!(
|
||||
self.entries.get(new_selected_entry),
|
||||
Some(GitListEntry::Header(..))
|
||||
) {
|
||||
if new_selected_entry > 0 {
|
||||
self.selected_entry = Some(new_selected_entry - 1)
|
||||
}
|
||||
} else {
|
||||
self.selected_entry = Some(new_selected_entry);
|
||||
}
|
||||
|
||||
self.scroll_to_selected_entry(cx);
|
||||
}
|
||||
|
@ -506,8 +499,14 @@ impl GitPanel {
|
|||
} else {
|
||||
selected_entry
|
||||
};
|
||||
|
||||
self.selected_entry = Some(new_selected_entry);
|
||||
if matches!(
|
||||
self.entries.get(new_selected_entry),
|
||||
Some(GitListEntry::Header(..))
|
||||
) {
|
||||
self.selected_entry = Some(new_selected_entry + 1);
|
||||
} else {
|
||||
self.selected_entry = Some(new_selected_entry);
|
||||
}
|
||||
|
||||
self.scroll_to_selected_entry(cx);
|
||||
}
|
||||
|
@ -537,7 +536,7 @@ impl GitPanel {
|
|||
active_repository.read(cx).entry_count() > 0
|
||||
});
|
||||
if have_entries && self.selected_entry.is_none() {
|
||||
self.selected_entry = Some(0);
|
||||
self.selected_entry = Some(1);
|
||||
self.scroll_to_selected_entry(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -559,7 +558,7 @@ impl GitPanel {
|
|||
self.selected_entry.and_then(|i| self.entries.get(i))
|
||||
}
|
||||
|
||||
fn open_selected(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn open_diff(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
maybe!({
|
||||
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
|
||||
|
||||
|
@ -572,6 +571,121 @@ impl GitPanel {
|
|||
self.focus_handle.focus(window);
|
||||
}
|
||||
|
||||
fn open_file(
|
||||
&mut self,
|
||||
_: &menu::SecondaryConfirm,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
maybe!({
|
||||
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
|
||||
let active_repo = self.active_repository.as_ref()?;
|
||||
let path = active_repo
|
||||
.read(cx)
|
||||
.repo_path_to_project_path(&entry.repo_path)?;
|
||||
if entry.status.is_deleted() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.open_path_preview(path, None, false, false, window, cx)
|
||||
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
|
||||
Some(format!("{e}"))
|
||||
});
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
}
|
||||
|
||||
fn revert(
|
||||
&mut self,
|
||||
_: &editor::actions::RevertFile,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
maybe!({
|
||||
let list_entry = self.entries.get(self.selected_entry?)?.clone();
|
||||
let entry = list_entry.status_entry()?;
|
||||
let active_repo = self.active_repository.as_ref()?;
|
||||
let path = active_repo
|
||||
.read(cx)
|
||||
.repo_path_to_project_path(&entry.repo_path)?;
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
if entry.status.is_staged() != Some(false) {
|
||||
self.update_staging_area_for_entries(false, vec![entry.repo_path.clone()], cx);
|
||||
}
|
||||
|
||||
if entry.status.is_created() {
|
||||
let prompt = window.prompt(
|
||||
PromptLevel::Info,
|
||||
"Do you want to trash this file?",
|
||||
None,
|
||||
&["Trash", "Cancel"],
|
||||
cx,
|
||||
);
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
match prompt.await {
|
||||
Ok(0) => {}
|
||||
_ => return Ok(()),
|
||||
}
|
||||
let task = workspace.update(&mut cx, |workspace, cx| {
|
||||
workspace
|
||||
.project()
|
||||
.update(cx, |project, cx| project.delete_file(path, true, cx))
|
||||
})?;
|
||||
if let Some(task) = task {
|
||||
task.await?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.detach_and_prompt_err(
|
||||
"Failed to trash file",
|
||||
window,
|
||||
cx,
|
||||
|e, _, _| Some(format!("{e}")),
|
||||
);
|
||||
return Some(());
|
||||
}
|
||||
|
||||
let open_path = workspace.update(cx, |workspace, cx| {
|
||||
workspace.open_path_preview(path, None, true, false, window, cx)
|
||||
});
|
||||
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
let item = open_path?.await?;
|
||||
let editor = cx.update(|_, cx| {
|
||||
item.act_as::<Editor>(cx)
|
||||
.ok_or_else(|| anyhow::anyhow!("didn't open editor"))
|
||||
})??;
|
||||
|
||||
if let Some(task) =
|
||||
editor.update(&mut cx, |editor, _| editor.wait_for_diff_to_load())?
|
||||
{
|
||||
task.await
|
||||
};
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
editor.revert_file(&Default::default(), window, cx);
|
||||
})?;
|
||||
|
||||
workspace
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
workspace.save_active_item(SaveIntent::Save, window, cx)
|
||||
})?
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
|
||||
Some(format!("{e}"))
|
||||
});
|
||||
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
||||
fn toggle_staged_for_entry(
|
||||
&mut self,
|
||||
entry: &GitListEntry,
|
||||
|
@ -606,7 +720,18 @@ impl GitPanel {
|
|||
(goal_staged_state, entries)
|
||||
}
|
||||
};
|
||||
self.update_staging_area_for_entries(stage, repo_paths, cx);
|
||||
}
|
||||
|
||||
fn update_staging_area_for_entries(
|
||||
&mut self,
|
||||
stage: bool,
|
||||
repo_paths: Vec<RepoPath>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(active_repository) = self.active_repository.clone() else {
|
||||
return;
|
||||
};
|
||||
let op_id = self.pending.iter().map(|p| p.op_id).max().unwrap_or(0) + 1;
|
||||
self.pending.push(PendingOperation {
|
||||
op_id,
|
||||
|
@ -615,7 +740,6 @@ impl GitPanel {
|
|||
finished: false,
|
||||
});
|
||||
let repo_paths = repo_paths.clone();
|
||||
let active_repository = active_repository.clone();
|
||||
let repository = active_repository.read(cx);
|
||||
self.update_counts(repository);
|
||||
cx.notify();
|
||||
|
@ -1530,7 +1654,7 @@ impl GitPanel {
|
|||
fn render_entries(
|
||||
&self,
|
||||
has_write_access: bool,
|
||||
window: &Window,
|
||||
_: &Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let entry_count = self.entries.len();
|
||||
|
@ -1571,61 +1695,6 @@ impl GitPanel {
|
|||
items
|
||||
}
|
||||
})
|
||||
.with_decoration(
|
||||
ui::indent_guides(
|
||||
cx.entity().clone(),
|
||||
self.indent_size(window, cx),
|
||||
IndentGuideColors::panel(cx),
|
||||
|this, range, _windows, _cx| {
|
||||
this.entries
|
||||
.iter()
|
||||
.skip(range.start)
|
||||
.map(|entry| match entry {
|
||||
GitListEntry::GitStatusEntry(_) => 1,
|
||||
GitListEntry::Header(_) => 0,
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.with_render_fn(
|
||||
cx.entity().clone(),
|
||||
move |_, params, _, _| {
|
||||
let indent_size = params.indent_size;
|
||||
let left_offset = indent_size - px(3.0);
|
||||
let item_height = params.item_height;
|
||||
|
||||
params
|
||||
.indent_guides
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(_, layout)| {
|
||||
let offset = if layout.continues_offscreen {
|
||||
px(0.)
|
||||
} else {
|
||||
px(4.0)
|
||||
};
|
||||
let bounds = Bounds::new(
|
||||
point(
|
||||
px(layout.offset.x as f32) * indent_size + left_offset,
|
||||
px(layout.offset.y as f32) * item_height + offset,
|
||||
),
|
||||
size(
|
||||
px(1.),
|
||||
px(layout.length as f32) * item_height
|
||||
- px(offset.0 * 2.),
|
||||
),
|
||||
);
|
||||
ui::RenderedIndentGuide {
|
||||
bounds,
|
||||
layout,
|
||||
is_active: false,
|
||||
hitbox: None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
),
|
||||
)
|
||||
.size_full()
|
||||
.with_sizing_behavior(ListSizingBehavior::Infer)
|
||||
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
|
||||
|
@ -1642,59 +1711,22 @@ impl GitPanel {
|
|||
&self,
|
||||
ix: usize,
|
||||
header: &GitHeaderEntry,
|
||||
has_write_access: bool,
|
||||
window: &Window,
|
||||
cx: &Context<Self>,
|
||||
_: bool,
|
||||
_: &Window,
|
||||
_: &Context<Self>,
|
||||
) -> AnyElement {
|
||||
let selected = self.selected_entry == Some(ix);
|
||||
let header_state = if self.has_staged_changes() {
|
||||
self.header_state(header.header)
|
||||
} else {
|
||||
match header.header {
|
||||
Section::Tracked | Section::Conflict => ToggleState::Selected,
|
||||
Section::New => ToggleState::Unselected,
|
||||
}
|
||||
};
|
||||
|
||||
let checkbox = Checkbox::new(("checkbox", ix), header_state)
|
||||
.disabled(!has_write_access)
|
||||
.fill()
|
||||
.placeholder(!self.has_staged_changes())
|
||||
.elevation(ElevationIndex::Surface)
|
||||
.on_click({
|
||||
let header = header.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.toggle_staged_for_entry(&GitListEntry::Header(header.clone()), window, cx);
|
||||
cx.stop_propagation();
|
||||
})
|
||||
});
|
||||
|
||||
let start_slot = h_flex()
|
||||
.id(("start-slot", ix))
|
||||
.gap(DynamicSpacing::Base04.rems(cx))
|
||||
.child(checkbox)
|
||||
.tooltip(|window, cx| Tooltip::for_action("Stage File", &ToggleStaged, window, cx))
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| {
|
||||
// prevent the list item active state triggering when toggling checkbox
|
||||
cx.stop_propagation();
|
||||
});
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.child(
|
||||
ListItem::new(ix)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot(start_slot)
|
||||
.toggle_state(selected)
|
||||
.focused(selected && self.focus_handle(cx).is_focused(window))
|
||||
.disabled(!has_write_access)
|
||||
.on_click({
|
||||
cx.listener(move |this, _, _, cx| {
|
||||
this.selected_entry = Some(ix);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.child(h_flex().child(self.entry_label(header.title(), Color::Muted))),
|
||||
.disabled(true)
|
||||
.child(
|
||||
Label::new(header.title())
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.single_line(),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
@ -1710,6 +1742,50 @@ impl GitPanel {
|
|||
repo.update(cx, |repo, cx| repo.show(sha, cx))
|
||||
}
|
||||
|
||||
fn deploy_context_menu(
|
||||
&mut self,
|
||||
position: Point<Pixels>,
|
||||
ix: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(entry) = self.entries.get(ix).and_then(|e| e.status_entry()) else {
|
||||
return;
|
||||
};
|
||||
let revert_title = if entry.status.is_deleted() {
|
||||
"Restore file"
|
||||
} else if entry.status.is_created() {
|
||||
"Trash file"
|
||||
} else {
|
||||
"Discard changes"
|
||||
};
|
||||
let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||
context_menu
|
||||
.action("Stage File", ToggleStaged.boxed_clone())
|
||||
.action(revert_title, editor::actions::RevertFile.boxed_clone())
|
||||
.separator()
|
||||
.action("Open Diff", Confirm.boxed_clone())
|
||||
.action("Open File", SecondaryConfirm.boxed_clone())
|
||||
});
|
||||
|
||||
let subscription = cx.subscribe_in(
|
||||
&context_menu,
|
||||
window,
|
||||
|this, _, _: &DismissEvent, window, cx| {
|
||||
if this.context_menu.as_ref().is_some_and(|context_menu| {
|
||||
context_menu.0.focus_handle(cx).contains_focused(window, cx)
|
||||
}) {
|
||||
cx.focus_self(window);
|
||||
}
|
||||
this.context_menu.take();
|
||||
cx.notify();
|
||||
},
|
||||
);
|
||||
self.selected_entry = Some(ix);
|
||||
self.context_menu = Some((context_menu, position, subscription));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_entry(
|
||||
&self,
|
||||
ix: usize,
|
||||
|
@ -1789,26 +1865,31 @@ impl GitPanel {
|
|||
cx.stop_propagation();
|
||||
});
|
||||
|
||||
let id = ElementId::Name(format!("entry_{}", display_name).into());
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.child(
|
||||
ListItem::new(id)
|
||||
.indent_level(1)
|
||||
.indent_step_size(Checkbox::container_size(cx).to_pixels(window.rem_size()))
|
||||
ListItem::new(ix)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot(start_slot)
|
||||
.toggle_state(selected)
|
||||
.focused(selected && self.focus_handle(cx).is_focused(window))
|
||||
.disabled(!has_write_access)
|
||||
.on_click({
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
cx.listener(move |this, event: &ClickEvent, window, cx| {
|
||||
this.selected_entry = Some(ix);
|
||||
cx.notify();
|
||||
this.open_selected(&Default::default(), window, cx);
|
||||
if event.modifiers().secondary() {
|
||||
this.open_file(&Default::default(), window, cx)
|
||||
} else {
|
||||
this.open_diff(&Default::default(), window, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
.on_secondary_mouse_down(cx.listener(
|
||||
move |this, event: &MouseDownEvent, window, cx| {
|
||||
this.deploy_context_menu(event.position, ix, window, cx)
|
||||
},
|
||||
))
|
||||
.child(
|
||||
h_flex()
|
||||
.when_some(repo_path.parent(), |this, parent| {
|
||||
|
@ -1870,14 +1951,14 @@ impl Render for GitPanel {
|
|||
}))
|
||||
.on_action(cx.listener(GitPanel::commit))
|
||||
})
|
||||
.when(self.is_focused(window, cx), |this| {
|
||||
this.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
.on_action(cx.listener(Self::select_prev))
|
||||
.on_action(cx.listener(Self::select_last))
|
||||
.on_action(cx.listener(Self::close_panel))
|
||||
})
|
||||
.on_action(cx.listener(Self::open_selected))
|
||||
.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
.on_action(cx.listener(Self::select_prev))
|
||||
.on_action(cx.listener(Self::select_last))
|
||||
.on_action(cx.listener(Self::close_panel))
|
||||
.on_action(cx.listener(Self::open_diff))
|
||||
.on_action(cx.listener(Self::open_file))
|
||||
.on_action(cx.listener(Self::revert))
|
||||
.on_action(cx.listener(Self::focus_changes_list))
|
||||
.on_action(cx.listener(Self::focus_editor))
|
||||
.on_action(cx.listener(Self::toggle_staged_for_selected))
|
||||
|
@ -1906,6 +1987,15 @@ impl Render for GitPanel {
|
|||
})
|
||||
.children(self.render_previous_commit(cx))
|
||||
.child(self.render_commit_editor(window, cx))
|
||||
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
||||
deferred(
|
||||
anchored()
|
||||
.position(*position)
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.with_priority(1)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1612,6 +1612,16 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn delete_file(
|
||||
&mut self,
|
||||
path: ProjectPath,
|
||||
trash: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let entry = self.entry_for_path(&path, cx)?;
|
||||
self.delete_entry(entry.id, trash, cx)
|
||||
}
|
||||
|
||||
pub fn delete_entry(
|
||||
&mut self,
|
||||
entry_id: ProjectEntryId,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue