Compare commits
4 Commits
perf/proje
...
branch-dif
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
323511f3c2 | ||
|
|
eb0436e84e | ||
|
|
ddb467de90 | ||
|
|
6dc68e02a7 |
@@ -636,8 +636,11 @@ impl EditFileToolCard {
|
||||
// Create a buffer diff with the current text as the base
|
||||
let buffer_diff = cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&text_snapshot, cx);
|
||||
let base_text = buffer_snapshot.text();
|
||||
let language = buffer_snapshot.language().cloned();
|
||||
let _ = diff.set_base_text(
|
||||
buffer_snapshot.clone(),
|
||||
Some(Arc::new(base_text)),
|
||||
language,
|
||||
language_registry,
|
||||
text_snapshot,
|
||||
cx,
|
||||
|
||||
@@ -1158,34 +1158,22 @@ impl BufferDiff {
|
||||
self.hunks_intersecting_range(start..end, buffer, cx)
|
||||
}
|
||||
|
||||
pub fn set_base_text_buffer(
|
||||
&mut self,
|
||||
base_buffer: Entity<language::Buffer>,
|
||||
buffer: text::BufferSnapshot,
|
||||
cx: &mut Context<Self>,
|
||||
) -> oneshot::Receiver<()> {
|
||||
let base_buffer = base_buffer.read(cx);
|
||||
let language_registry = base_buffer.language_registry();
|
||||
let base_buffer = base_buffer.snapshot();
|
||||
self.set_base_text(base_buffer, language_registry, buffer, cx)
|
||||
}
|
||||
|
||||
/// Used in cases where the change set isn't derived from git.
|
||||
pub fn set_base_text(
|
||||
&mut self,
|
||||
base_buffer: language::BufferSnapshot,
|
||||
base_text: Option<Arc<String>>,
|
||||
language: Option<Arc<Language>>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
buffer: text::BufferSnapshot,
|
||||
cx: &mut Context<Self>,
|
||||
) -> oneshot::Receiver<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let this = cx.weak_entity();
|
||||
let base_text = Arc::new(base_buffer.text());
|
||||
|
||||
let snapshot = BufferDiffSnapshot::new_with_base_text(
|
||||
buffer.clone(),
|
||||
Some(base_text),
|
||||
base_buffer.language().cloned(),
|
||||
base_text,
|
||||
language,
|
||||
language_registry,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -34,7 +34,6 @@ mod lsp_ext;
|
||||
mod mouse_context_menu;
|
||||
pub mod movement;
|
||||
mod persistence;
|
||||
mod proposed_changes_editor;
|
||||
mod rust_analyzer_ext;
|
||||
pub mod scroll;
|
||||
mod selections_collection;
|
||||
@@ -70,9 +69,7 @@ pub use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
|
||||
RowInfo, ToOffset, ToPoint,
|
||||
};
|
||||
pub use proposed_changes_editor::{
|
||||
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
|
||||
};
|
||||
|
||||
pub use text::Bias;
|
||||
|
||||
use ::git::{
|
||||
@@ -20472,65 +20469,6 @@ impl Editor {
|
||||
self.searchable
|
||||
}
|
||||
|
||||
fn open_proposed_changes_editor(
|
||||
&mut self,
|
||||
_: &OpenProposedChangesEditor,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(workspace) = self.workspace() else {
|
||||
cx.propagate();
|
||||
return;
|
||||
};
|
||||
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let multi_buffer = self.buffer.read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let mut new_selections_by_buffer = HashMap::default();
|
||||
for selection in selections {
|
||||
for (buffer, range, _) in
|
||||
multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
|
||||
{
|
||||
let mut range = range.to_point(buffer);
|
||||
range.start.column = 0;
|
||||
range.end.column = buffer.line_len(range.end.row);
|
||||
new_selections_by_buffer
|
||||
.entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
|
||||
.or_insert(Vec::new())
|
||||
.push(range)
|
||||
}
|
||||
}
|
||||
|
||||
let proposed_changes_buffers = new_selections_by_buffer
|
||||
.into_iter()
|
||||
.map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
|
||||
.collect::<Vec<_>>();
|
||||
let proposed_changes_editor = cx.new(|cx| {
|
||||
ProposedChangesEditor::new(
|
||||
"Proposed changes",
|
||||
proposed_changes_buffers,
|
||||
self.project.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
window.defer(cx, move |window, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.add_item(
|
||||
Box::new(proposed_changes_editor),
|
||||
true,
|
||||
true,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn open_excerpts_in_split(
|
||||
&mut self,
|
||||
_: &OpenExcerptsSplit,
|
||||
|
||||
@@ -440,7 +440,6 @@ impl EditorElement {
|
||||
register_action(editor, window, Editor::toggle_code_actions);
|
||||
register_action(editor, window, Editor::open_excerpts);
|
||||
register_action(editor, window, Editor::open_excerpts_in_split);
|
||||
register_action(editor, window, Editor::open_proposed_changes_editor);
|
||||
register_action(editor, window, Editor::toggle_soft_wrap);
|
||||
register_action(editor, window, Editor::toggle_tab_bar);
|
||||
register_action(editor, window, Editor::toggle_line_numbers);
|
||||
|
||||
@@ -1,516 +0,0 @@
|
||||
use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SelectionEffects, SemanticsProvider};
|
||||
use buffer_diff::BufferDiff;
|
||||
use collections::HashSet;
|
||||
use futures::{channel::mpsc, future::join_all};
|
||||
use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task};
|
||||
use language::{Buffer, BufferEvent, Capability};
|
||||
use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||
use project::Project;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{any::TypeId, ops::Range, rc::Rc, time::Duration};
|
||||
use text::ToOffset;
|
||||
use ui::{ButtonLike, KeyBinding, prelude::*};
|
||||
use workspace::{
|
||||
Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
item::SaveOptions, searchable::SearchableItemHandle,
|
||||
};
|
||||
|
||||
pub struct ProposedChangesEditor {
|
||||
editor: Entity<Editor>,
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
title: SharedString,
|
||||
buffer_entries: Vec<BufferEntry>,
|
||||
_recalculate_diffs_task: Task<Option<()>>,
|
||||
recalculate_diffs_tx: mpsc::UnboundedSender<RecalculateDiff>,
|
||||
}
|
||||
|
||||
pub struct ProposedChangeLocation<T> {
|
||||
pub buffer: Entity<Buffer>,
|
||||
pub ranges: Vec<Range<T>>,
|
||||
}
|
||||
|
||||
struct BufferEntry {
|
||||
base: Entity<Buffer>,
|
||||
branch: Entity<Buffer>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
pub struct ProposedChangesEditorToolbar {
|
||||
current_editor: Option<Entity<ProposedChangesEditor>>,
|
||||
}
|
||||
|
||||
struct RecalculateDiff {
|
||||
buffer: Entity<Buffer>,
|
||||
debounce: bool,
|
||||
}
|
||||
|
||||
/// A provider of code semantics for branch buffers.
|
||||
///
|
||||
/// Requests in edited regions will return nothing, but requests in unchanged
|
||||
/// regions will be translated into the base buffer's coordinates.
|
||||
struct BranchBufferSemanticsProvider(Rc<dyn SemanticsProvider>);
|
||||
|
||||
impl ProposedChangesEditor {
|
||||
pub fn new<T: Clone + ToOffset>(
|
||||
title: impl Into<SharedString>,
|
||||
locations: Vec<ProposedChangeLocation<T>>,
|
||||
project: Option<Entity<Project>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded();
|
||||
let mut this = Self {
|
||||
editor: cx.new(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, window, cx);
|
||||
editor.set_expand_all_diff_hunks(cx);
|
||||
editor.set_completion_provider(None);
|
||||
editor.clear_code_action_providers();
|
||||
editor.set_semantics_provider(
|
||||
editor
|
||||
.semantics_provider()
|
||||
.map(|provider| Rc::new(BranchBufferSemanticsProvider(provider)) as _),
|
||||
);
|
||||
editor
|
||||
}),
|
||||
multibuffer,
|
||||
title: title.into(),
|
||||
buffer_entries: Vec::new(),
|
||||
recalculate_diffs_tx,
|
||||
_recalculate_diffs_task: cx.spawn_in(window, async move |this, cx| {
|
||||
let mut buffers_to_diff = HashSet::default();
|
||||
while let Some(mut recalculate_diff) = recalculate_diffs_rx.next().await {
|
||||
buffers_to_diff.insert(recalculate_diff.buffer);
|
||||
|
||||
while recalculate_diff.debounce {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(50))
|
||||
.await;
|
||||
let mut had_further_changes = false;
|
||||
while let Ok(next_recalculate_diff) = recalculate_diffs_rx.try_next() {
|
||||
let next_recalculate_diff = next_recalculate_diff?;
|
||||
recalculate_diff.debounce &= next_recalculate_diff.debounce;
|
||||
buffers_to_diff.insert(next_recalculate_diff.buffer);
|
||||
had_further_changes = true;
|
||||
}
|
||||
if !had_further_changes {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let recalculate_diff_futures = this
|
||||
.update(cx, |this, cx| {
|
||||
buffers_to_diff
|
||||
.drain()
|
||||
.filter_map(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let base_buffer = buffer.base_buffer()?;
|
||||
let buffer = buffer.text_snapshot();
|
||||
let diff =
|
||||
this.multibuffer.read(cx).diff_for(buffer.remote_id())?;
|
||||
Some(diff.update(cx, |diff, cx| {
|
||||
diff.set_base_text_buffer(base_buffer.clone(), buffer, cx)
|
||||
}))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
join_all(recalculate_diff_futures).await;
|
||||
}
|
||||
None
|
||||
}),
|
||||
};
|
||||
this.reset_locations(locations, window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn branch_buffer_for_base(&self, base_buffer: &Entity<Buffer>) -> Option<Entity<Buffer>> {
|
||||
self.buffer_entries.iter().find_map(|entry| {
|
||||
if &entry.base == base_buffer {
|
||||
Some(entry.branch.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_title(&mut self, title: SharedString, cx: &mut Context<Self>) {
|
||||
self.title = title;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn reset_locations<T: Clone + ToOffset>(
|
||||
&mut self,
|
||||
locations: Vec<ProposedChangeLocation<T>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// Undo all branch changes
|
||||
for entry in &self.buffer_entries {
|
||||
let base_version = entry.base.read(cx).version();
|
||||
entry.branch.update(cx, |buffer, cx| {
|
||||
let undo_counts = buffer
|
||||
.operations()
|
||||
.iter()
|
||||
.filter_map(|(timestamp, _)| {
|
||||
if !base_version.observed(*timestamp) {
|
||||
Some((*timestamp, u32::MAX))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
buffer.undo_operations(undo_counts, cx);
|
||||
});
|
||||
}
|
||||
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.clear(cx);
|
||||
});
|
||||
|
||||
let mut buffer_entries = Vec::new();
|
||||
let mut new_diffs = Vec::new();
|
||||
for location in locations {
|
||||
let branch_buffer;
|
||||
if let Some(ix) = self
|
||||
.buffer_entries
|
||||
.iter()
|
||||
.position(|entry| entry.base == location.buffer)
|
||||
{
|
||||
let entry = self.buffer_entries.remove(ix);
|
||||
branch_buffer = entry.branch.clone();
|
||||
buffer_entries.push(entry);
|
||||
} else {
|
||||
branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
|
||||
new_diffs.push(cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&branch_buffer.read(cx).snapshot(), cx);
|
||||
let _ = diff.set_base_text_buffer(
|
||||
location.buffer.clone(),
|
||||
branch_buffer.read(cx).text_snapshot(),
|
||||
cx,
|
||||
);
|
||||
diff
|
||||
}));
|
||||
buffer_entries.push(BufferEntry {
|
||||
branch: branch_buffer.clone(),
|
||||
base: location.buffer.clone(),
|
||||
_subscription: cx.subscribe(&branch_buffer, Self::on_buffer_event),
|
||||
});
|
||||
}
|
||||
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.push_excerpts(
|
||||
branch_buffer,
|
||||
location
|
||||
.ranges
|
||||
.into_iter()
|
||||
.map(|range| ExcerptRange::new(range)),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
self.buffer_entries = buffer_entries;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.refresh()
|
||||
});
|
||||
editor.buffer.update(cx, |buffer, cx| {
|
||||
for diff in new_diffs {
|
||||
buffer.add_diff(diff, cx)
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn recalculate_all_buffer_diffs(&self) {
|
||||
for (ix, entry) in self.buffer_entries.iter().enumerate().rev() {
|
||||
self.recalculate_diffs_tx
|
||||
.unbounded_send(RecalculateDiff {
|
||||
buffer: entry.branch.clone(),
|
||||
debounce: ix > 0,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_buffer_event(
|
||||
&mut self,
|
||||
buffer: Entity<Buffer>,
|
||||
event: &BufferEvent,
|
||||
_cx: &mut Context<Self>,
|
||||
) {
|
||||
if let BufferEvent::Operation { .. } = event {
|
||||
self.recalculate_diffs_tx
|
||||
.unbounded_send(RecalculateDiff {
|
||||
buffer,
|
||||
debounce: true,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ProposedChangesEditor {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.key_context("ProposedChangesEditor")
|
||||
.child(self.editor.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for ProposedChangesEditor {
|
||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for ProposedChangesEditor {}
|
||||
|
||||
impl Item for ProposedChangesEditor {
|
||||
type Event = EditorEvent;
|
||||
|
||||
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
|
||||
Some(Icon::new(IconName::Diff))
|
||||
}
|
||||
|
||||
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
self_handle: &'a Entity<Self>,
|
||||
_: &'a App,
|
||||
) -> Option<gpui::AnyView> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.to_any())
|
||||
} else if type_id == TypeId::of::<Editor>() {
|
||||
Some(self.editor.to_any())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn added_to_workspace(
|
||||
&mut self,
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
Item::added_to_workspace(editor, workspace, window, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.deactivated(window, cx));
|
||||
}
|
||||
|
||||
fn navigate(
|
||||
&mut self,
|
||||
data: Box<dyn std::any::Any>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| Item::navigate(editor, data, window, cx))
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: workspace::ItemNavHistory,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
Item::set_nav_history(editor, nav_history, window, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn can_save(&self, cx: &App) -> bool {
|
||||
self.editor.read(cx).can_save(cx)
|
||||
}
|
||||
|
||||
fn save(
|
||||
&mut self,
|
||||
options: SaveOptions,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
Item::save(editor, options, project, window, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProposedChangesEditorToolbar {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current_editor: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
|
||||
if self.current_editor.is_some() {
|
||||
ToolbarItemLocation::PrimaryRight
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ProposedChangesEditorToolbar {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let button_like = ButtonLike::new("apply-changes").child(Label::new("Apply All"));
|
||||
|
||||
match &self.current_editor {
|
||||
Some(editor) => {
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
let keybinding =
|
||||
KeyBinding::for_action_in(&ApplyAllDiffHunks, &focus_handle, window, cx)
|
||||
.map(|binding| binding.into_any_element());
|
||||
|
||||
button_like.children(keybinding).on_click({
|
||||
move |_event, window, cx| {
|
||||
focus_handle.dispatch_action(&ApplyAllDiffHunks, window, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
None => button_like.disabled(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolbarItemEvent> for ProposedChangesEditorToolbar {}
|
||||
|
||||
impl ToolbarItemView for ProposedChangesEditorToolbar {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn workspace::ItemHandle>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> workspace::ToolbarItemLocation {
|
||||
self.current_editor =
|
||||
active_pane_item.and_then(|item| item.downcast::<ProposedChangesEditor>());
|
||||
self.get_toolbar_item_location()
|
||||
}
|
||||
}
|
||||
|
||||
impl BranchBufferSemanticsProvider {
|
||||
fn to_base(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
positions: &[text::Anchor],
|
||||
cx: &App,
|
||||
) -> Option<Entity<Buffer>> {
|
||||
let base_buffer = buffer.read(cx).base_buffer()?;
|
||||
let version = base_buffer.read(cx).version();
|
||||
if positions
|
||||
.iter()
|
||||
.any(|position| !version.observed(position.timestamp))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(base_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticsProvider for BranchBufferSemanticsProvider {
|
||||
fn hover(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>> {
|
||||
let buffer = self.to_base(buffer, &[position], cx)?;
|
||||
self.0.hover(&buffer, position, cx)
|
||||
}
|
||||
|
||||
fn inlay_hints(
|
||||
&self,
|
||||
buffer: Entity<Buffer>,
|
||||
range: Range<text::Anchor>,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<anyhow::Result<Vec<project::InlayHint>>>> {
|
||||
let buffer = self.to_base(&buffer, &[range.start, range.end], cx)?;
|
||||
self.0.inlay_hints(buffer, range, cx)
|
||||
}
|
||||
|
||||
fn inline_values(
|
||||
&self,
|
||||
_: Entity<Buffer>,
|
||||
_: Range<text::Anchor>,
|
||||
_: &mut App,
|
||||
) -> Option<Task<anyhow::Result<Vec<project::InlayHint>>>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn resolve_inlay_hint(
|
||||
&self,
|
||||
hint: project::InlayHint,
|
||||
buffer: Entity<Buffer>,
|
||||
server_id: lsp::LanguageServerId,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<anyhow::Result<project::InlayHint>>> {
|
||||
let buffer = self.to_base(&buffer, &[], cx)?;
|
||||
self.0.resolve_inlay_hint(hint, buffer, server_id, cx)
|
||||
}
|
||||
|
||||
fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
|
||||
if let Some(buffer) = self.to_base(buffer, &[], cx) {
|
||||
self.0.supports_inlay_hints(&buffer, cx)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn document_highlights(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<anyhow::Result<Vec<project::DocumentHighlight>>>> {
|
||||
let buffer = self.to_base(buffer, &[position], cx)?;
|
||||
self.0.document_highlights(&buffer, position, cx)
|
||||
}
|
||||
|
||||
fn definitions(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
kind: crate::GotoDefinitionKind,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<anyhow::Result<Option<Vec<project::LocationLink>>>>> {
|
||||
let buffer = self.to_base(buffer, &[position], cx)?;
|
||||
self.0.definitions(&buffer, position, kind, cx)
|
||||
}
|
||||
|
||||
fn range_for_rename(
|
||||
&self,
|
||||
_: &Entity<Buffer>,
|
||||
_: text::Anchor,
|
||||
_: &mut App,
|
||||
) -> Option<Task<anyhow::Result<Option<Range<text::Anchor>>>>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn perform_rename(
|
||||
&self,
|
||||
_: &Entity<Buffer>,
|
||||
_: text::Anchor,
|
||||
_: String,
|
||||
_: &mut App,
|
||||
) -> Option<Task<anyhow::Result<project::ProjectTransaction>>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,18 @@ impl GitRepository for FakeGitRepository {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn diff_to_commit(
|
||||
&self,
|
||||
_commit: String,
|
||||
_cx: AsyncApp,
|
||||
) -> BoxFuture<'_, Result<git::repository::CommitDiff>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn merge_base(&self, _commit_a: String, _commit_b: String) -> BoxFuture<'_, Option<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn load_commit(
|
||||
&self,
|
||||
_commit: String,
|
||||
|
||||
@@ -309,6 +309,8 @@ pub trait GitRepository: Send + Sync {
|
||||
/// Also returns `None` for symlinks.
|
||||
fn load_committed_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>>;
|
||||
|
||||
fn merge_base(&self, commit_a: String, commit_b: String) -> BoxFuture<'_, Option<String>>;
|
||||
|
||||
fn set_index_text(
|
||||
&self,
|
||||
path: RepoPath,
|
||||
@@ -360,6 +362,7 @@ pub trait GitRepository: Send + Sync {
|
||||
fn show(&self, commit: String) -> BoxFuture<'_, Result<CommitDetails>>;
|
||||
|
||||
fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>>;
|
||||
fn diff_to_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>>;
|
||||
fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result<crate::blame::Blame>>;
|
||||
|
||||
/// Returns the absolute path to the repository. For worktrees, this will be the path to the
|
||||
@@ -614,6 +617,115 @@ impl GitRepository for RealGitRepository {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn merge_base(&self, commit_a: String, commit_b: String) -> BoxFuture<'_, Option<String>> {
|
||||
let Some(working_directory) = self.repository.lock().workdir().map(ToOwned::to_owned)
|
||||
else {
|
||||
return future::ready(None).boxed();
|
||||
};
|
||||
let git = GitBinary::new(
|
||||
self.git_binary_path.clone(),
|
||||
working_directory,
|
||||
self.executor.clone(),
|
||||
);
|
||||
async move {
|
||||
let merge_base = git
|
||||
.run(&["merge-base", &commit_a, &commit_b])
|
||||
.await
|
||||
.log_err()?;
|
||||
Some(merge_base.to_string())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn diff_to_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>> {
|
||||
let Some(working_directory) = self.repository.lock().workdir().map(ToOwned::to_owned)
|
||||
else {
|
||||
return future::ready(Err(anyhow!("no working directory"))).boxed();
|
||||
};
|
||||
cx.background_spawn(async move {
|
||||
let diff_output = util::command::new_std_command("git")
|
||||
.current_dir(&working_directory)
|
||||
.args([
|
||||
"--no-optional-locks",
|
||||
"diff",
|
||||
"--format=%P",
|
||||
"-z",
|
||||
"--no-renames",
|
||||
"--name-status",
|
||||
])
|
||||
.arg(&commit)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.context("starting git show process")?;
|
||||
|
||||
let diff_stdout = String::from_utf8_lossy(&diff_output.stdout);
|
||||
dbg!(&diff_stdout);
|
||||
let changes = parse_git_diff_name_status(&diff_stdout);
|
||||
|
||||
let mut cat_file_process = util::command::new_std_command("git")
|
||||
.current_dir(&working_directory)
|
||||
.args(["--no-optional-locks", "cat-file", "--batch=%(objectsize)"])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.context("starting git cat-file process")?;
|
||||
|
||||
use std::io::Write as _;
|
||||
let mut files = Vec::<CommitFile>::new();
|
||||
let mut stdin = BufWriter::with_capacity(512, cat_file_process.stdin.take().unwrap());
|
||||
let mut stdout = BufReader::new(cat_file_process.stdout.take().unwrap());
|
||||
let mut info_line = String::new();
|
||||
let mut newline = [b'\0'];
|
||||
for (path, status_code) in changes {
|
||||
match status_code {
|
||||
StatusCode::Modified => {
|
||||
writeln!(&mut stdin, "{commit}:{}", path.display())?;
|
||||
}
|
||||
StatusCode::Added => {
|
||||
files.push(CommitFile {
|
||||
path: path.into(),
|
||||
old_text: None,
|
||||
new_text: None,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
StatusCode::Deleted => {
|
||||
writeln!(&mut stdin, "{commit}:{}", path.display())?;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
stdin.flush()?;
|
||||
|
||||
info_line.clear();
|
||||
stdout.read_line(&mut info_line)?;
|
||||
|
||||
dbg!(&info_line);
|
||||
|
||||
let len = info_line.trim_end().parse().with_context(|| {
|
||||
format!("invalid object size output from cat-file {info_line}")
|
||||
})?;
|
||||
let mut text = vec![0; len];
|
||||
stdout.read_exact(&mut text)?;
|
||||
stdout.read_exact(&mut newline)?;
|
||||
|
||||
let text = String::from_utf8_lossy(&text).to_string();
|
||||
dbg!(&text);
|
||||
|
||||
files.push(CommitFile {
|
||||
path: path.into(),
|
||||
old_text: Some(text),
|
||||
new_text: None,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(CommitDiff { files })
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>> {
|
||||
let Some(working_directory) = self.repository.lock().workdir().map(ToOwned::to_owned)
|
||||
else {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::commit_modal::CommitModal;
|
||||
use crate::commit_tooltip::CommitTooltip;
|
||||
use crate::commit_view::CommitView;
|
||||
use crate::git_panel_settings::StatusStyle;
|
||||
use crate::project_diff::{self, Diff, ProjectDiff};
|
||||
use crate::project_diff::{self, Diff, DiffBaseKind, ProjectDiff};
|
||||
use crate::remote_output::{self, RemoteAction, SuccessMessage};
|
||||
use crate::{branch_picker, picker_prompt, render_remote_button};
|
||||
use crate::{
|
||||
@@ -937,7 +937,13 @@ impl GitPanel {
|
||||
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
|
||||
ProjectDiff::deploy_at(
|
||||
workspace,
|
||||
DiffBaseKind::Head,
|
||||
Some(entry.clone()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
self.focus_handle.focus(window);
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
remote_button::{render_publish_button, render_push_button},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot, DiffHunkSecondaryStatus};
|
||||
use collections::HashSet;
|
||||
use editor::{
|
||||
Editor, EditorEvent, SelectionEffects,
|
||||
@@ -17,7 +17,7 @@ use futures::StreamExt;
|
||||
use git::{
|
||||
Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
|
||||
repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
|
||||
status::FileStatus,
|
||||
status::{FileStatus, TrackedStatus},
|
||||
};
|
||||
use gpui::{
|
||||
Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity, EventEmitter,
|
||||
@@ -30,8 +30,11 @@ use project::{
|
||||
git_store::{GitStore, GitStoreEvent, RepositoryEvent},
|
||||
};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::any::{Any, TypeId};
|
||||
use std::ops::Range;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::ActiveTheme;
|
||||
use ui::{KeyBinding, Tooltip, prelude::*, vertical_divider};
|
||||
use util::ResultExt as _;
|
||||
@@ -48,7 +51,9 @@ actions!(
|
||||
/// Shows the diff between the working directory and the index.
|
||||
Diff,
|
||||
/// Adds files to the git staging area.
|
||||
Add
|
||||
Add,
|
||||
/// Shows the diff between the working directory and the default branch.
|
||||
DiffToDefaultBranch,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -61,10 +66,17 @@ pub struct ProjectDiff {
|
||||
focus_handle: FocusHandle,
|
||||
update_needed: postage::watch::Sender<()>,
|
||||
pending_scroll: Option<PathKey>,
|
||||
diff_base_kind: DiffBaseKind,
|
||||
_task: Task<Result<()>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum DiffBaseKind {
|
||||
Head,
|
||||
MergeBaseOfDefaultBranch,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DiffBuffer {
|
||||
path_key: PathKey,
|
||||
@@ -80,6 +92,7 @@ const NEW_NAMESPACE: u32 = 3;
|
||||
impl ProjectDiff {
|
||||
pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {
|
||||
workspace.register_action(Self::deploy);
|
||||
workspace.register_action(Self::diff_to_default_branch);
|
||||
workspace.register_action(|workspace, _: &Add, window, cx| {
|
||||
Self::deploy(workspace, &Diff, window, cx);
|
||||
});
|
||||
@@ -92,39 +105,66 @@ impl ProjectDiff {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
Self::deploy_at(workspace, None, window, cx)
|
||||
Self::deploy_at(workspace, DiffBaseKind::Head, None, window, cx)
|
||||
}
|
||||
|
||||
fn diff_to_default_branch(
|
||||
workspace: &mut Workspace,
|
||||
_: &DiffToDefaultBranch,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
Self::deploy_at(
|
||||
workspace,
|
||||
DiffBaseKind::MergeBaseOfDefaultBranch,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn deploy_at(
|
||||
workspace: &mut Workspace,
|
||||
diff_base_kind: DiffBaseKind,
|
||||
entry: Option<GitStatusEntry>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
telemetry::event!(
|
||||
"Git Diff Opened",
|
||||
match diff_base_kind {
|
||||
DiffBaseKind::MergeBaseOfDefaultBranch => "Git Branch Diff Opened",
|
||||
DiffBaseKind::Head => "Git Diff Opened",
|
||||
},
|
||||
source = if entry.is_some() {
|
||||
"Git Panel"
|
||||
} else {
|
||||
"Action"
|
||||
}
|
||||
);
|
||||
let project_diff = if let Some(existing) = workspace.item_of_type::<Self>(cx) {
|
||||
workspace.activate_item(&existing, true, true, window, cx);
|
||||
existing
|
||||
} else {
|
||||
let workspace_handle = cx.entity();
|
||||
let project_diff =
|
||||
cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx));
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(project_diff.clone()),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
project_diff
|
||||
};
|
||||
let project_diff =
|
||||
if let Some(existing) = Self::existing_project_diff(workspace, diff_base_kind, cx) {
|
||||
workspace.activate_item(&existing, true, true, window, cx);
|
||||
existing
|
||||
} else {
|
||||
let workspace_handle = cx.entity();
|
||||
let project_diff = cx.new(|cx| {
|
||||
Self::new(
|
||||
workspace.project().clone(),
|
||||
workspace_handle,
|
||||
diff_base_kind,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(project_diff.clone()),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
project_diff
|
||||
};
|
||||
if let Some(entry) = entry {
|
||||
project_diff.update(cx, |project_diff, cx| {
|
||||
project_diff.move_to_entry(entry, window, cx);
|
||||
@@ -132,6 +172,16 @@ impl ProjectDiff {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn existing_project_diff(
|
||||
workspace: &mut Workspace,
|
||||
diff_base_kind: DiffBaseKind,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<Self>> {
|
||||
workspace
|
||||
.items_of_type::<Self>(cx)
|
||||
.find(|item| item.read(cx).diff_base_kind == diff_base_kind)
|
||||
}
|
||||
|
||||
pub fn autoscroll(&self, cx: &mut Context<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.request_autoscroll(Autoscroll::fit(), cx);
|
||||
@@ -141,6 +191,7 @@ impl ProjectDiff {
|
||||
fn new(
|
||||
project: Entity<Project>,
|
||||
workspace: Entity<Workspace>,
|
||||
diff_base_kind: DiffBaseKind,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -152,9 +203,21 @@ impl ProjectDiff {
|
||||
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
|
||||
diff_display_editor.disable_diagnostics(cx);
|
||||
diff_display_editor.set_expand_all_diff_hunks(cx);
|
||||
diff_display_editor.register_addon(GitPanelAddon {
|
||||
workspace: workspace.downgrade(),
|
||||
});
|
||||
match diff_base_kind {
|
||||
DiffBaseKind::Head => {
|
||||
diff_display_editor.register_addon(GitPanelAddon {
|
||||
workspace: workspace.downgrade(),
|
||||
});
|
||||
}
|
||||
DiffBaseKind::MergeBaseOfDefaultBranch => {
|
||||
diff_display_editor.start_temporary_diff_override();
|
||||
diff_display_editor.set_render_diff_hunk_controls(
|
||||
Arc::new(|_, _, _, _, _, _, _, _| gpui::Empty.into_any_element()),
|
||||
cx,
|
||||
);
|
||||
//
|
||||
}
|
||||
}
|
||||
diff_display_editor
|
||||
});
|
||||
window.defer(cx, {
|
||||
@@ -205,7 +268,7 @@ impl ProjectDiff {
|
||||
let (mut send, recv) = postage::watch::channel::<()>();
|
||||
let worker = window.spawn(cx, {
|
||||
let this = cx.weak_entity();
|
||||
async |cx| Self::handle_status_updates(this, recv, cx).await
|
||||
async move |cx| Self::handle_status_updates(this, diff_base_kind, recv, cx).await
|
||||
});
|
||||
// Kick off a refresh immediately
|
||||
*send.borrow_mut() = ();
|
||||
@@ -214,6 +277,7 @@ impl ProjectDiff {
|
||||
project,
|
||||
git_store: git_store.clone(),
|
||||
workspace: workspace.downgrade(),
|
||||
diff_base_kind,
|
||||
focus_handle,
|
||||
editor,
|
||||
multibuffer,
|
||||
@@ -351,6 +415,9 @@ impl ProjectDiff {
|
||||
let Some(project_path) = self.active_path(cx) else {
|
||||
return;
|
||||
};
|
||||
if self.diff_base_kind != DiffBaseKind::Head {
|
||||
return;
|
||||
}
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
|
||||
@@ -506,21 +573,149 @@ impl ProjectDiff {
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_merge_base_of_default_branch(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let project = self.project.clone();
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let Some(repo) = self.git_store.read(cx).active_repository() else {
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.clear(cx);
|
||||
});
|
||||
return Task::ready(Ok(()));
|
||||
};
|
||||
let default_branch = repo.update(cx, |repo, _| repo.default_branch());
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let Some(default_branch) = default_branch.await?? else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(merge_base) = repo
|
||||
.update(cx, |repo, _| {
|
||||
repo.merge_base("HEAD".to_string(), default_branch.into())
|
||||
})?
|
||||
.await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let diff = repo
|
||||
.update(cx, |repo, _| repo.diff_to_commit(merge_base))?
|
||||
.await??;
|
||||
|
||||
for file in diff.files {
|
||||
let Some(path) = repo.update(cx, |repo, cx| {
|
||||
repo.repo_path_to_project_path(&file.path, cx)
|
||||
})?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let open_buffer = project
|
||||
.update(cx, |project, cx| project.open_buffer(path.clone(), cx))?
|
||||
.await;
|
||||
|
||||
let mut status = FileStatus::Tracked(TrackedStatus {
|
||||
index_status: git::status::StatusCode::Unmodified,
|
||||
worktree_status: git::status::StatusCode::Modified,
|
||||
});
|
||||
let buffer = match open_buffer {
|
||||
Ok(buffer) => buffer,
|
||||
Err(err) => {
|
||||
let exists = project.read_with(cx, |project, cx| {
|
||||
project.entry_for_path(&path, cx).is_some()
|
||||
})?;
|
||||
if exists {
|
||||
return Err(err);
|
||||
}
|
||||
status = FileStatus::Tracked(TrackedStatus {
|
||||
index_status: git::status::StatusCode::Unmodified,
|
||||
worktree_status: git::status::StatusCode::Deleted,
|
||||
});
|
||||
cx.new(|cx| Buffer::local("", cx))?
|
||||
}
|
||||
};
|
||||
|
||||
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
let namespace = if file.old_text.is_none() {
|
||||
NEW_NAMESPACE
|
||||
} else {
|
||||
TRACKED_NAMESPACE
|
||||
};
|
||||
|
||||
let buffer_diff = cx.new(|cx| BufferDiff::new(&buffer_snapshot, cx))?;
|
||||
buffer_diff
|
||||
.update(cx, |buffer_diff, cx| {
|
||||
buffer_diff.set_base_text(
|
||||
file.old_text.map(Arc::new),
|
||||
buffer_snapshot.language().cloned(),
|
||||
Some(language_registry.clone()),
|
||||
buffer_snapshot.text,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.read_with(cx, |this, cx| {
|
||||
BufferDiffSnapshot::new_with_base_buffer(
|
||||
buffer.clone(),
|
||||
base_text,
|
||||
this.base_text().clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.add_diff(buffer_diff.clone(), cx);
|
||||
});
|
||||
this.register_buffer(
|
||||
DiffBuffer {
|
||||
path_key: PathKey::namespaced(namespace, file.path.0),
|
||||
buffer,
|
||||
diff: buffer_diff,
|
||||
file_status: status,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn handle_status_updates(
|
||||
this: WeakEntity<Self>,
|
||||
diff_base_kind: DiffBaseKind,
|
||||
mut recv: postage::watch::Receiver<()>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
while (recv.next().await).is_some() {
|
||||
let buffers_to_load = this.update(cx, |this, cx| this.load_buffers(cx))?;
|
||||
for buffer_to_load in buffers_to_load {
|
||||
if let Some(buffer) = buffer_to_load.await.log_err() {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| this.register_buffer(buffer, window, cx))
|
||||
.ok();
|
||||
})?;
|
||||
match diff_base_kind {
|
||||
DiffBaseKind::Head => {
|
||||
let buffers_to_load = this.update(cx, |this, cx| this.load_buffers(cx))?;
|
||||
for buffer_to_load in buffers_to_load {
|
||||
if let Some(buffer) = buffer_to_load.await.log_err() {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.register_buffer(buffer, window, cx)
|
||||
})
|
||||
.ok();
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DiffBaseKind::MergeBaseOfDefaultBranch => {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.refresh_merge_base_of_default_branch(window, cx)
|
||||
})?
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
};
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.pending_scroll.take();
|
||||
cx.notify();
|
||||
@@ -637,7 +832,15 @@ impl Item for ProjectDiff {
|
||||
Self: Sized,
|
||||
{
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
Some(cx.new(|cx| ProjectDiff::new(self.project.clone(), workspace, window, cx)))
|
||||
Some(cx.new(|cx| {
|
||||
ProjectDiff::new(
|
||||
self.project.clone(),
|
||||
workspace,
|
||||
self.diff_base_kind,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &App) -> bool {
|
||||
@@ -805,7 +1008,16 @@ impl SerializableItem for ProjectDiff {
|
||||
window.spawn(cx, async move |cx| {
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let workspace_handle = cx.entity();
|
||||
cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx))
|
||||
// todo!()
|
||||
cx.new(|cx| {
|
||||
Self::new(
|
||||
workspace.project().clone(),
|
||||
workspace_handle,
|
||||
DiffBaseKind::Head,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1399,7 +1611,7 @@ mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
ProjectDiff::new(project.clone(), workspace, DiffBaseKind::Head, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
@@ -1454,7 +1666,7 @@ mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
ProjectDiff::new(project.clone(), workspace, DiffBaseKind::Head, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
@@ -1536,7 +1748,7 @@ mod tests {
|
||||
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
|
||||
});
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
ProjectDiff::new(project.clone(), workspace, DiffBaseKind::Head, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
@@ -1827,7 +2039,7 @@ mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
ProjectDiff::new(project.clone(), workspace, DiffBaseKind::Head, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
|
||||
@@ -1249,15 +1249,6 @@ impl BufferStore {
|
||||
.log_err();
|
||||
}
|
||||
|
||||
// TODO(max): do something
|
||||
// client
|
||||
// .send(proto::UpdateStagedText {
|
||||
// project_id,
|
||||
// buffer_id: buffer_id.into(),
|
||||
// diff_base: buffer.diff_base().map(ToString::to_string),
|
||||
// })
|
||||
// .log_err();
|
||||
|
||||
client
|
||||
.send(proto::BufferReloaded {
|
||||
project_id,
|
||||
|
||||
@@ -91,6 +91,7 @@ struct SharedDiffs {
|
||||
struct BufferGitState {
|
||||
unstaged_diff: Option<WeakEntity<BufferDiff>>,
|
||||
uncommitted_diff: Option<WeakEntity<BufferDiff>>,
|
||||
branch_diff: Option<WeakEntity<BufferDiff>>,
|
||||
conflict_set: Option<WeakEntity<ConflictSet>>,
|
||||
recalculate_diff_task: Option<Task<Result<()>>>,
|
||||
reparse_conflict_markers_task: Option<Task<Result<()>>>,
|
||||
@@ -592,6 +593,12 @@ impl GitStore {
|
||||
cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
|
||||
}
|
||||
|
||||
pub fn open_diff_from_default_branch(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<(Entity<Buffer>, Entity<BufferDiff>)>>> {
|
||||
}
|
||||
|
||||
pub fn open_uncommitted_diff(
|
||||
&mut self,
|
||||
buffer: Entity<Buffer>,
|
||||
@@ -670,11 +677,10 @@ impl GitStore {
|
||||
let text_snapshot = buffer.text_snapshot();
|
||||
this.loading_diffs.remove(&(buffer_id, kind));
|
||||
|
||||
let git_store = cx.weak_entity();
|
||||
let diff_state = this
|
||||
.diffs
|
||||
.entry(buffer_id)
|
||||
.or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
|
||||
.or_insert_with(|| cx.new(|_| BufferGitState::new()));
|
||||
|
||||
let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
|
||||
|
||||
@@ -755,11 +761,10 @@ impl GitStore {
|
||||
let is_unmerged = self
|
||||
.repository_and_path_for_buffer_id(buffer_id, cx)
|
||||
.is_some_and(|(repo, path)| repo.read(cx).snapshot.has_conflict(&path));
|
||||
let git_store = cx.weak_entity();
|
||||
let buffer_git_state = self
|
||||
.diffs
|
||||
.entry(buffer_id)
|
||||
.or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
|
||||
.or_insert_with(|| cx.new(|_| BufferGitState::new()));
|
||||
let conflict_set = cx.new(|cx| ConflictSet::new(buffer_id, is_unmerged, cx));
|
||||
|
||||
self._subscriptions
|
||||
@@ -2347,10 +2352,11 @@ impl GitStore {
|
||||
}
|
||||
|
||||
impl BufferGitState {
|
||||
fn new(_git_store: WeakEntity<GitStore>) -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
unstaged_diff: Default::default(),
|
||||
uncommitted_diff: Default::default(),
|
||||
branch_diff: Default::default(),
|
||||
recalculate_diff_task: Default::default(),
|
||||
language: Default::default(),
|
||||
language_registry: Default::default(),
|
||||
@@ -3467,6 +3473,58 @@ impl Repository {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn merge_base(
|
||||
&mut self,
|
||||
commit_a: String,
|
||||
commit_b: String,
|
||||
) -> oneshot::Receiver<Option<String>> {
|
||||
let id = self.id;
|
||||
self.send_job(None, move |git_repo, cx| async move {
|
||||
match git_repo {
|
||||
RepositoryState::Local { backend, .. } => {
|
||||
backend.merge_base(commit_a, commit_b).await
|
||||
}
|
||||
RepositoryState::Remote {
|
||||
client, project_id, ..
|
||||
} => {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn diff_to_commit(&mut self, commit: String) -> oneshot::Receiver<Result<CommitDiff>> {
|
||||
let id = self.id;
|
||||
self.send_job(None, move |git_repo, cx| async move {
|
||||
match git_repo {
|
||||
RepositoryState::Local { backend, .. } => backend.diff_to_commit(commit, cx).await,
|
||||
RepositoryState::Remote {
|
||||
client, project_id, ..
|
||||
} => {
|
||||
todo!();
|
||||
let response = client
|
||||
.request(proto::LoadCommitDiff {
|
||||
project_id: project_id.0,
|
||||
repository_id: id.to_proto(),
|
||||
commit,
|
||||
})
|
||||
.await?;
|
||||
Ok(CommitDiff {
|
||||
files: response
|
||||
.files
|
||||
.into_iter()
|
||||
.map(|file| CommitFile {
|
||||
path: Path::new(&file.path).into(),
|
||||
old_text: file.old_text,
|
||||
new_text: file.new_text,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
|
||||
Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ use breadcrumbs::Breadcrumbs;
|
||||
use client::zed_urls;
|
||||
use collections::VecDeque;
|
||||
use debugger_ui::debugger_panel::DebugPanel;
|
||||
use editor::ProposedChangesEditorToolbar;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use feature_flags::{FeatureFlagAppExt, PanicFeatureFlag};
|
||||
use futures::future::Either;
|
||||
@@ -980,8 +979,6 @@ fn initialize_pane(
|
||||
)
|
||||
});
|
||||
toolbar.add_item(buffer_search_bar.clone(), window, cx);
|
||||
let proposed_change_bar = cx.new(|_| ProposedChangesEditorToolbar::new());
|
||||
toolbar.add_item(proposed_change_bar, window, cx);
|
||||
let quick_action_bar =
|
||||
cx.new(|cx| QuickActionBar::new(buffer_search_bar, workspace, cx));
|
||||
toolbar.add_item(quick_action_bar, window, cx);
|
||||
|
||||
Reference in New Issue
Block a user