1use anyhow::Result;
2use buffer_diff::BufferDiff;
3use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
4use itertools::Itertools;
5use language::{
6 Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer,
7};
8use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};
9use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
10use util::ResultExt;
11
12pub enum Diff {
13 Pending(PendingDiff),
14 Finalized(FinalizedDiff),
15}
16
17impl Diff {
18 pub fn finalized(
19 path: String,
20 old_text: Option<String>,
21 new_text: String,
22 language_registry: Arc<LanguageRegistry>,
23 cx: &mut Context<Self>,
24 ) -> Self {
25 let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));
26 let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));
27 let base_text = old_text.clone().unwrap_or(String::new()).into();
28 let task = cx.spawn({
29 let multibuffer = multibuffer.clone();
30 let path = path.clone();
31 let buffer = new_buffer.clone();
32 async move |_, cx| {
33 let language = language_registry
34 .load_language_for_file_path(Path::new(&path))
35 .await
36 .log_err();
37
38 buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx));
39
40 let diff = build_buffer_diff(
41 old_text.unwrap_or("".into()).into(),
42 &buffer,
43 Some(language_registry.clone()),
44 cx,
45 )
46 .await?;
47
48 multibuffer.update(cx, |multibuffer, cx| {
49 let hunk_ranges = {
50 let buffer = buffer.read(cx);
51 diff.read(cx)
52 .snapshot(cx)
53 .hunks_intersecting_range(
54 Anchor::min_for_buffer(buffer.remote_id())
55 ..Anchor::max_for_buffer(buffer.remote_id()),
56 buffer,
57 )
58 .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
59 .collect::<Vec<_>>()
60 };
61
62 multibuffer.set_excerpts_for_path(
63 PathKey::for_buffer(&buffer, cx),
64 buffer.clone(),
65 hunk_ranges,
66 excerpt_context_lines(cx),
67 cx,
68 );
69 multibuffer.add_diff(diff, cx);
70 });
71
72 anyhow::Ok(())
73 }
74 });
75
76 Self::Finalized(FinalizedDiff {
77 multibuffer,
78 path,
79 base_text,
80 new_buffer,
81 _update_diff: task,
82 })
83 }
84
85 pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
86 let buffer_text_snapshot = buffer.read(cx).text_snapshot();
87 let language = buffer.read(cx).language().cloned();
88 let language_registry = buffer.read(cx).language_registry();
89 let buffer_diff = cx.new(|cx| {
90 let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);
91 diff.language_changed(language.clone(), language_registry.clone(), cx);
92 let secondary_diff = cx.new(|cx| {
93 // For the secondary diff buffer we skip assigning the language as we do not really need to perform any syntax highlighting on
94 // it. As a result, by skipping it we are potentially shaving off a lot of RSS plus we get a snappier feel for large diff
95 // view multibuffers.
96 BufferDiff::new_unchanged(&buffer_text_snapshot, cx)
97 });
98 diff.set_secondary_diff(secondary_diff);
99 diff
100 });
101
102 let multibuffer = cx.new(|cx| {
103 let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
104 multibuffer.add_diff(buffer_diff.clone(), cx);
105 multibuffer
106 });
107
108 Self::Pending(PendingDiff {
109 multibuffer,
110 base_text: Arc::from(buffer_text_snapshot.text().as_str()),
111 _subscription: cx.observe(&buffer, |this, _, cx| {
112 if let Diff::Pending(diff) = this {
113 diff.update(cx);
114 }
115 }),
116 new_buffer: buffer,
117 diff: buffer_diff,
118 revealed_ranges: Vec::new(),
119 update_diff: Task::ready(Ok(())),
120 })
121 }
122
123 pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
124 if let Self::Pending(diff) = self {
125 diff.reveal_range(range, cx);
126 }
127 }
128
129 pub fn finalize(&mut self, cx: &mut Context<Self>) {
130 if let Self::Pending(diff) = self {
131 *self = Self::Finalized(diff.finalize(cx));
132 }
133 }
134
135 /// Returns the original text before any edits were applied.
136 pub fn base_text(&self) -> &Arc<str> {
137 match self {
138 Self::Pending(PendingDiff { base_text, .. }) => base_text,
139 Self::Finalized(FinalizedDiff { base_text, .. }) => base_text,
140 }
141 }
142
143 /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs).
144 pub fn buffer(&self) -> &Entity<Buffer> {
145 match self {
146 Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer,
147 Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer,
148 }
149 }
150
151 pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
152 match self {
153 Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
154 Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
155 }
156 }
157
158 pub fn to_markdown(&self, cx: &App) -> String {
159 let buffer_text = self
160 .multibuffer()
161 .read(cx)
162 .all_buffers()
163 .iter()
164 .map(|buffer| buffer.read(cx).text())
165 .join("\n");
166 let path = match self {
167 Diff::Pending(PendingDiff {
168 new_buffer: buffer, ..
169 }) => buffer
170 .read(cx)
171 .file()
172 .map(|file| file.path().display(file.path_style(cx))),
173 Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
174 };
175 format!(
176 "Diff: {}\n```\n{}\n```\n",
177 path.unwrap_or("untitled".into()),
178 buffer_text
179 )
180 }
181
182 pub fn has_revealed_range(&self, cx: &App) -> bool {
183 self.multibuffer().read(cx).paths().next().is_some()
184 }
185
186 pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
187 match self {
188 Diff::Pending(PendingDiff {
189 base_text,
190 new_buffer,
191 ..
192 }) => {
193 base_text.as_ref() != old_text
194 || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
195 }
196 Diff::Finalized(FinalizedDiff {
197 base_text,
198 new_buffer,
199 ..
200 }) => {
201 base_text.as_ref() != old_text
202 || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
203 }
204 }
205 }
206}
207
208pub struct PendingDiff {
209 multibuffer: Entity<MultiBuffer>,
210 base_text: Arc<str>,
211 new_buffer: Entity<Buffer>,
212 diff: Entity<BufferDiff>,
213 revealed_ranges: Vec<Range<Anchor>>,
214 _subscription: Subscription,
215 update_diff: Task<Result<()>>,
216}
217
218impl PendingDiff {
219 pub fn update(&mut self, cx: &mut Context<Diff>) {
220 let buffer = self.new_buffer.clone();
221 let buffer_diff = self.diff.clone();
222 let base_text = self.base_text.clone();
223 self.update_diff = cx.spawn(async move |diff, cx| {
224 let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
225 let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
226 let update = buffer_diff
227 .update(cx, |diff, cx| {
228 diff.update_diff(
229 text_snapshot.clone(),
230 Some(base_text.clone()),
231 None,
232 language,
233 cx,
234 )
235 })
236 .await;
237 let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
238 let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx);
239 let task2 = diff
240 .secondary_diff()
241 .unwrap()
242 .update(cx, |diff, cx| diff.set_snapshot(update, &text_snapshot, cx));
243 (task1, task2)
244 });
245 task1.await;
246 task2.await;
247 diff.update(cx, |diff, cx| {
248 if let Diff::Pending(diff) = diff {
249 diff.update_visible_ranges(cx);
250 }
251 })
252 });
253 }
254
255 pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
256 self.revealed_ranges.push(range);
257 self.update_visible_ranges(cx);
258 }
259
260 fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
261 let ranges = self.excerpt_ranges(cx);
262 let base_text = self.base_text.clone();
263 let new_buffer = self.new_buffer.read(cx);
264 let language_registry = new_buffer.language_registry();
265
266 let path = new_buffer
267 .file()
268 .map(|file| file.path().display(file.path_style(cx)))
269 .unwrap_or("untitled".into())
270 .into();
271 let replica_id = new_buffer.replica_id();
272
273 // Replace the buffer in the multibuffer with the snapshot
274 let buffer = cx.new(|cx| {
275 let language = self.new_buffer.read(cx).language().cloned();
276 let buffer = TextBuffer::new_normalized(
277 replica_id,
278 cx.entity_id().as_non_zero_u64().into(),
279 self.new_buffer.read(cx).line_ending(),
280 self.new_buffer.read(cx).as_rope().clone(),
281 );
282 let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
283 buffer.set_language(language, cx);
284 buffer
285 });
286
287 let buffer_diff = cx.spawn({
288 let buffer = buffer.clone();
289 async move |_this, cx| {
290 build_buffer_diff(base_text, &buffer, language_registry, cx).await
291 }
292 });
293
294 let update_diff = cx.spawn(async move |this, cx| {
295 let buffer_diff = buffer_diff.await?;
296 this.update(cx, |this, cx| {
297 this.multibuffer().update(cx, |multibuffer, cx| {
298 let path_key = PathKey::for_buffer(&buffer, cx);
299 multibuffer.clear(cx);
300 multibuffer.set_excerpts_for_path(
301 path_key,
302 buffer,
303 ranges,
304 excerpt_context_lines(cx),
305 cx,
306 );
307 multibuffer.add_diff(buffer_diff.clone(), cx);
308 });
309
310 cx.notify();
311 })
312 });
313
314 FinalizedDiff {
315 path,
316 base_text: self.base_text.clone(),
317 multibuffer: self.multibuffer.clone(),
318 new_buffer: self.new_buffer.clone(),
319 _update_diff: update_diff,
320 }
321 }
322
323 fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
324 let ranges = self.excerpt_ranges(cx);
325 self.multibuffer.update(cx, |multibuffer, cx| {
326 multibuffer.set_excerpts_for_path(
327 PathKey::for_buffer(&self.new_buffer, cx),
328 self.new_buffer.clone(),
329 ranges,
330 excerpt_context_lines(cx),
331 cx,
332 );
333 let end = multibuffer.len(cx);
334 Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
335 });
336 cx.notify();
337 }
338
339 fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
340 let buffer = self.new_buffer.read(cx);
341 let mut ranges = self
342 .diff
343 .read(cx)
344 .snapshot(cx)
345 .hunks_intersecting_range(
346 Anchor::min_for_buffer(buffer.remote_id())
347 ..Anchor::max_for_buffer(buffer.remote_id()),
348 buffer,
349 )
350 .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
351 .collect::<Vec<_>>();
352 ranges.extend(
353 self.revealed_ranges
354 .iter()
355 .map(|range| range.to_point(buffer)),
356 );
357 ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
358
359 // Merge adjacent ranges
360 let mut ranges = ranges.into_iter().peekable();
361 let mut merged_ranges = Vec::new();
362 while let Some(mut range) = ranges.next() {
363 while let Some(next_range) = ranges.peek() {
364 if range.end >= next_range.start {
365 range.end = range.end.max(next_range.end);
366 ranges.next();
367 } else {
368 break;
369 }
370 }
371
372 merged_ranges.push(range);
373 }
374 merged_ranges
375 }
376}
377
378pub struct FinalizedDiff {
379 path: String,
380 base_text: Arc<str>,
381 new_buffer: Entity<Buffer>,
382 multibuffer: Entity<MultiBuffer>,
383 _update_diff: Task<Result<()>>,
384}
385
386async fn build_buffer_diff(
387 old_text: Arc<str>,
388 buffer: &Entity<Buffer>,
389 language_registry: Option<Arc<LanguageRegistry>>,
390 cx: &mut AsyncApp,
391) -> Result<Entity<BufferDiff>> {
392 let language = cx.update(|cx| buffer.read(cx).language().cloned());
393 let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot());
394 let buffer = cx.update(|cx| buffer.read(cx).snapshot());
395
396 let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
397
398 let update = secondary_diff
399 .update(cx, |secondary_diff, cx| {
400 secondary_diff.update_diff(
401 text_snapshot.clone(),
402 Some(old_text),
403 Some(false),
404 language.clone(),
405 cx,
406 )
407 })
408 .await;
409
410 secondary_diff
411 .update(cx, |secondary_diff, cx| {
412 secondary_diff.set_snapshot(update.clone(), &buffer, cx)
413 })
414 .await;
415
416 let diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
417 diff.update(cx, |diff, cx| {
418 diff.language_changed(language, language_registry, cx);
419 diff.set_secondary_diff(secondary_diff);
420 diff.set_snapshot(update.clone(), &buffer, cx)
421 })
422 .await;
423 Ok(diff)
424}
425
426#[cfg(test)]
427mod tests {
428 use gpui::{AppContext as _, TestAppContext};
429 use language::Buffer;
430
431 use crate::Diff;
432
433 #[gpui::test]
434 async fn test_pending_diff(cx: &mut TestAppContext) {
435 let buffer = cx.new(|cx| Buffer::local("hello!", cx));
436 let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
437 buffer.update(cx, |buffer, cx| {
438 buffer.set_text("HELLO!", cx);
439 });
440 cx.run_until_parked();
441 }
442}