Skip to content

Commit 743feb9

Browse files
Add the ability to propose changes to a set of buffers (#18170)
This PR introduces functionality for creating *branches* of buffers that can be used to preview and edit change sets that haven't yet been applied to the buffers themselves. Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <elliott.codes@gmail.com> Co-authored-by: Marshall <marshall@zed.dev>
1 parent e309fbd commit 743feb9

File tree

20 files changed

+622
-186
lines changed

20 files changed

+622
-186
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/assistant/src/context.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,9 +1006,12 @@ impl Context {
10061006
cx: &mut ModelContext<Self>,
10071007
) {
10081008
match event {
1009-
language::BufferEvent::Operation(operation) => cx.emit(ContextEvent::Operation(
1010-
ContextOperation::BufferOperation(operation.clone()),
1011-
)),
1009+
language::BufferEvent::Operation {
1010+
operation,
1011+
is_local: true,
1012+
} => cx.emit(ContextEvent::Operation(ContextOperation::BufferOperation(
1013+
operation.clone(),
1014+
))),
10121015
language::BufferEvent::Edited => {
10131016
self.count_remaining_tokens(cx);
10141017
self.reparse(cx);

crates/channel/src/channel_buffer.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ impl ChannelBuffer {
175175
cx: &mut ModelContext<Self>,
176176
) {
177177
match event {
178-
language::BufferEvent::Operation(operation) => {
178+
language::BufferEvent::Operation {
179+
operation,
180+
is_local: true,
181+
} => {
179182
if *ZED_ALWAYS_ACTIVE {
180183
if let language::Operation::UpdateSelections { selections, .. } = operation {
181184
if selections.is_empty() {

crates/clock/src/clock.rs

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use std::{
99

1010
pub use system_clock::*;
1111

12+
pub const LOCAL_BRANCH_REPLICA_ID: u16 = u16::MAX;
13+
1214
/// A unique identifier for each distributed node.
1315
pub type ReplicaId = u16;
1416

@@ -25,49 +27,62 @@ pub struct Lamport {
2527

2628
/// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock).
2729
#[derive(Clone, Default, Hash, Eq, PartialEq)]
28-
pub struct Global(SmallVec<[u32; 8]>);
30+
pub struct Global {
31+
values: SmallVec<[u32; 8]>,
32+
local_branch_value: u32,
33+
}
2934

3035
impl Global {
3136
pub fn new() -> Self {
3237
Self::default()
3338
}
3439

3540
pub fn get(&self, replica_id: ReplicaId) -> Seq {
36-
self.0.get(replica_id as usize).copied().unwrap_or(0) as Seq
41+
if replica_id == LOCAL_BRANCH_REPLICA_ID {
42+
self.local_branch_value
43+
} else {
44+
self.values.get(replica_id as usize).copied().unwrap_or(0) as Seq
45+
}
3746
}
3847

3948
pub fn observe(&mut self, timestamp: Lamport) {
4049
if timestamp.value > 0 {
41-
let new_len = timestamp.replica_id as usize + 1;
42-
if new_len > self.0.len() {
43-
self.0.resize(new_len, 0);
50+
if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID {
51+
self.local_branch_value = cmp::max(self.local_branch_value, timestamp.value);
52+
} else {
53+
let new_len = timestamp.replica_id as usize + 1;
54+
if new_len > self.values.len() {
55+
self.values.resize(new_len, 0);
56+
}
57+
58+
let entry = &mut self.values[timestamp.replica_id as usize];
59+
*entry = cmp::max(*entry, timestamp.value);
4460
}
45-
46-
let entry = &mut self.0[timestamp.replica_id as usize];
47-
*entry = cmp::max(*entry, timestamp.value);
4861
}
4962
}
5063

5164
pub fn join(&mut self, other: &Self) {
52-
if other.0.len() > self.0.len() {
53-
self.0.resize(other.0.len(), 0);
65+
if other.values.len() > self.values.len() {
66+
self.values.resize(other.values.len(), 0);
5467
}
5568

56-
for (left, right) in self.0.iter_mut().zip(&other.0) {
69+
for (left, right) in self.values.iter_mut().zip(&other.values) {
5770
*left = cmp::max(*left, *right);
5871
}
72+
73+
self.local_branch_value = cmp::max(self.local_branch_value, other.local_branch_value);
5974
}
6075

6176
pub fn meet(&mut self, other: &Self) {
62-
if other.0.len() > self.0.len() {
63-
self.0.resize(other.0.len(), 0);
77+
if other.values.len() > self.values.len() {
78+
self.values.resize(other.values.len(), 0);
6479
}
6580

6681
let mut new_len = 0;
6782
for (ix, (left, right)) in self
68-
.0
83+
.values
6984
.iter_mut()
70-
.zip(other.0.iter().chain(iter::repeat(&0)))
85+
.zip(other.values.iter().chain(iter::repeat(&0)))
7186
.enumerate()
7287
{
7388
if *left == 0 {
@@ -80,42 +95,53 @@ impl Global {
8095
new_len = ix + 1;
8196
}
8297
}
83-
self.0.resize(new_len, 0);
98+
self.values.resize(new_len, 0);
99+
self.local_branch_value = cmp::min(self.local_branch_value, other.local_branch_value);
84100
}
85101

86102
pub fn observed(&self, timestamp: Lamport) -> bool {
87103
self.get(timestamp.replica_id) >= timestamp.value
88104
}
89105

90106
pub fn observed_any(&self, other: &Self) -> bool {
91-
self.0
107+
self.values
92108
.iter()
93-
.zip(other.0.iter())
109+
.zip(other.values.iter())
94110
.any(|(left, right)| *right > 0 && left >= right)
111+
|| (other.local_branch_value > 0 && self.local_branch_value >= other.local_branch_value)
95112
}
96113

97114
pub fn observed_all(&self, other: &Self) -> bool {
98-
let mut rhs = other.0.iter();
99-
self.0.iter().all(|left| match rhs.next() {
115+
let mut rhs = other.values.iter();
116+
self.values.iter().all(|left| match rhs.next() {
100117
Some(right) => left >= right,
101118
None => true,
102119
}) && rhs.next().is_none()
120+
&& self.local_branch_value >= other.local_branch_value
103121
}
104122

105123
pub fn changed_since(&self, other: &Self) -> bool {
106-
self.0.len() > other.0.len()
124+
self.values.len() > other.values.len()
107125
|| self
108-
.0
126+
.values
109127
.iter()
110-
.zip(other.0.iter())
128+
.zip(other.values.iter())
111129
.any(|(left, right)| left > right)
130+
|| self.local_branch_value > other.local_branch_value
112131
}
113132

114133
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {
115-
self.0.iter().enumerate().map(|(replica_id, seq)| Lamport {
116-
replica_id: replica_id as ReplicaId,
117-
value: *seq,
118-
})
134+
self.values
135+
.iter()
136+
.enumerate()
137+
.map(|(replica_id, seq)| Lamport {
138+
replica_id: replica_id as ReplicaId,
139+
value: *seq,
140+
})
141+
.chain((self.local_branch_value > 0).then_some(Lamport {
142+
replica_id: LOCAL_BRANCH_REPLICA_ID,
143+
value: self.local_branch_value,
144+
}))
119145
}
120146
}
121147

@@ -192,6 +218,9 @@ impl fmt::Debug for Global {
192218
}
193219
write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?;
194220
}
221+
if self.local_branch_value > 0 {
222+
write!(f, "<branch>: {}", self.local_branch_value)?;
223+
}
195224
write!(f, "}}")
196225
}
197226
}

crates/editor/src/actions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ gpui::actions!(
273273
NextScreen,
274274
OpenExcerpts,
275275
OpenExcerptsSplit,
276+
OpenProposedChangesEditor,
276277
OpenFile,
277278
OpenPermalinkToLine,
278279
OpenUrl,

crates/editor/src/editor.rs

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ mod lsp_ext;
3535
mod mouse_context_menu;
3636
pub mod movement;
3737
mod persistence;
38+
mod proposed_changes_editor;
3839
mod rust_analyzer_ext;
3940
pub mod scroll;
4041
mod selections_collection;
@@ -46,7 +47,7 @@ mod signature_help;
4647
#[cfg(any(test, feature = "test-support"))]
4748
pub mod test;
4849

49-
use ::git::diff::{DiffHunk, DiffHunkStatus};
50+
use ::git::diff::DiffHunkStatus;
5051
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
5152
pub(crate) use actions::*;
5253
use aho_corasick::AhoCorasick;
@@ -98,6 +99,7 @@ use language::{
9899
};
99100
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
100101
use linked_editing_ranges::refresh_linked_ranges;
102+
use proposed_changes_editor::{ProposedChangesBuffer, ProposedChangesEditor};
101103
use similar::{ChangeTag, TextDiff};
102104
use task::{ResolvedTask, TaskTemplate, TaskVariables};
103105

@@ -113,7 +115,9 @@ pub use multi_buffer::{
113115
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
114116
ToPoint,
115117
};
116-
use multi_buffer::{ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16};
118+
use multi_buffer::{
119+
ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
120+
};
117121
use ordered_float::OrderedFloat;
118122
use parking_lot::{Mutex, RwLock};
119123
use project::project_settings::{GitGutterSetting, ProjectSettings};
@@ -6152,7 +6156,7 @@ impl Editor {
61526156
pub fn prepare_revert_change(
61536157
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
61546158
multi_buffer: &Model<MultiBuffer>,
6155-
hunk: &DiffHunk<MultiBufferRow>,
6159+
hunk: &MultiBufferDiffHunk,
61566160
cx: &AppContext,
61576161
) -> Option<()> {
61586162
let buffer = multi_buffer.read(cx).buffer(hunk.buffer_id)?;
@@ -9338,7 +9342,7 @@ impl Editor {
93389342
snapshot: &DisplaySnapshot,
93399343
initial_point: Point,
93409344
is_wrapped: bool,
9341-
hunks: impl Iterator<Item = DiffHunk<MultiBufferRow>>,
9345+
hunks: impl Iterator<Item = MultiBufferDiffHunk>,
93429346
cx: &mut ViewContext<Editor>,
93439347
) -> bool {
93449348
let display_point = initial_point.to_display_point(snapshot);
@@ -11885,6 +11889,52 @@ impl Editor {
1188511889
self.searchable
1188611890
}
1188711891

11892+
fn open_proposed_changes_editor(
11893+
&mut self,
11894+
_: &OpenProposedChangesEditor,
11895+
cx: &mut ViewContext<Self>,
11896+
) {
11897+
let Some(workspace) = self.workspace() else {
11898+
cx.propagate();
11899+
return;
11900+
};
11901+
11902+
let buffer = self.buffer.read(cx);
11903+
let mut new_selections_by_buffer = HashMap::default();
11904+
for selection in self.selections.all::<usize>(cx) {
11905+
for (buffer, mut range, _) in
11906+
buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
11907+
{
11908+
if selection.reversed {
11909+
mem::swap(&mut range.start, &mut range.end);
11910+
}
11911+
let mut range = range.to_point(buffer.read(cx));
11912+
range.start.column = 0;
11913+
range.end.column = buffer.read(cx).line_len(range.end.row);
11914+
new_selections_by_buffer
11915+
.entry(buffer)
11916+
.or_insert(Vec::new())
11917+
.push(range)
11918+
}
11919+
}
11920+
11921+
let proposed_changes_buffers = new_selections_by_buffer
11922+
.into_iter()
11923+
.map(|(buffer, ranges)| ProposedChangesBuffer { buffer, ranges })
11924+
.collect::<Vec<_>>();
11925+
let proposed_changes_editor = cx.new_view(|cx| {
11926+
ProposedChangesEditor::new(proposed_changes_buffers, self.project.clone(), cx)
11927+
});
11928+
11929+
cx.window_context().defer(move |cx| {
11930+
workspace.update(cx, |workspace, cx| {
11931+
workspace.active_pane().update(cx, |pane, cx| {
11932+
pane.add_item(Box::new(proposed_changes_editor), true, true, None, cx);
11933+
});
11934+
});
11935+
});
11936+
}
11937+
1188811938
fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
1188911939
self.open_excerpts_common(true, cx)
1189011940
}
@@ -12399,7 +12449,7 @@ impl Editor {
1239912449
fn hunks_for_selections(
1240012450
multi_buffer_snapshot: &MultiBufferSnapshot,
1240112451
selections: &[Selection<Anchor>],
12402-
) -> Vec<DiffHunk<MultiBufferRow>> {
12452+
) -> Vec<MultiBufferDiffHunk> {
1240312453
let buffer_rows_for_selections = selections.iter().map(|selection| {
1240412454
let head = selection.head();
1240512455
let tail = selection.tail();
@@ -12418,7 +12468,7 @@ fn hunks_for_selections(
1241812468
pub fn hunks_for_rows(
1241912469
rows: impl Iterator<Item = Range<MultiBufferRow>>,
1242012470
multi_buffer_snapshot: &MultiBufferSnapshot,
12421-
) -> Vec<DiffHunk<MultiBufferRow>> {
12471+
) -> Vec<MultiBufferDiffHunk> {
1242212472
let mut hunks = Vec::new();
1242312473
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
1242412474
HashMap::default();
@@ -12430,14 +12480,14 @@ pub fn hunks_for_rows(
1243012480
// when the caret is just above or just below the deleted hunk.
1243112481
let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
1243212482
let related_to_selection = if allow_adjacent {
12433-
hunk.associated_range.overlaps(&query_rows)
12434-
|| hunk.associated_range.start == query_rows.end
12435-
|| hunk.associated_range.end == query_rows.start
12483+
hunk.row_range.overlaps(&query_rows)
12484+
|| hunk.row_range.start == query_rows.end
12485+
|| hunk.row_range.end == query_rows.start
1243612486
} else {
1243712487
// `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected)
12438-
// `hunk.associated_range` is exclusive (e.g. [2..3] means 2nd row is selected)
12439-
hunk.associated_range.overlaps(&selected_multi_buffer_rows)
12440-
|| selected_multi_buffer_rows.end == hunk.associated_range.start
12488+
// `hunk.row_range` is exclusive (e.g. [2..3] means 2nd row is selected)
12489+
hunk.row_range.overlaps(&selected_multi_buffer_rows)
12490+
|| selected_multi_buffer_rows.end == hunk.row_range.start
1244112491
};
1244212492
if related_to_selection {
1244312493
if !processed_buffer_rows
@@ -13738,10 +13788,10 @@ impl RowRangeExt for Range<DisplayRow> {
1373813788
}
1373913789
}
1374013790

13741-
fn hunk_status(hunk: &DiffHunk<MultiBufferRow>) -> DiffHunkStatus {
13791+
fn hunk_status(hunk: &MultiBufferDiffHunk) -> DiffHunkStatus {
1374213792
if hunk.diff_base_byte_range.is_empty() {
1374313793
DiffHunkStatus::Added
13744-
} else if hunk.associated_range.is_empty() {
13794+
} else if hunk.row_range.is_empty() {
1374513795
DiffHunkStatus::Removed
1374613796
} else {
1374713797
DiffHunkStatus::Modified

crates/editor/src/element.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ impl EditorElement {
346346
register_action(view, cx, Editor::toggle_code_actions);
347347
register_action(view, cx, Editor::open_excerpts);
348348
register_action(view, cx, Editor::open_excerpts_in_split);
349+
register_action(view, cx, Editor::open_proposed_changes_editor);
349350
register_action(view, cx, Editor::toggle_soft_wrap);
350351
register_action(view, cx, Editor::toggle_tab_bar);
351352
register_action(view, cx, Editor::toggle_line_numbers);
@@ -3710,11 +3711,11 @@ impl EditorElement {
37103711
)
37113712
.map(|hunk| {
37123713
let start_display_row =
3713-
MultiBufferPoint::new(hunk.associated_range.start.0, 0)
3714+
MultiBufferPoint::new(hunk.row_range.start.0, 0)
37143715
.to_display_point(&snapshot.display_snapshot)
37153716
.row();
37163717
let mut end_display_row =
3717-
MultiBufferPoint::new(hunk.associated_range.end.0, 0)
3718+
MultiBufferPoint::new(hunk.row_range.end.0, 0)
37183719
.to_display_point(&snapshot.display_snapshot)
37193720
.row();
37203721
if end_display_row != start_display_row {

0 commit comments

Comments
 (0)