1use crate::{
2 display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition,
3 movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
4 Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
5};
6use anyhow::{Context, Result};
7use collections::HashSet;
8use futures::future::try_join_all;
9use gpui::{
10 elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
11 Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
12};
13use language::{
14 proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
15 SelectionGoal,
16};
17use project::{FormatTrigger, Item as _, Project, ProjectPath};
18use rpc::proto::{self, update_view};
19use settings::Settings;
20use smallvec::SmallVec;
21use std::{
22 borrow::Cow,
23 cmp::{self, Ordering},
24 fmt::Write,
25 iter,
26 ops::Range,
27 path::{Path, PathBuf},
28};
29use text::Selection;
30use util::{paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
31use workspace::item::{BreadcrumbText, FollowableItemHandle};
32use workspace::{
33 item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
34 searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
35 ItemId, ItemNavHistory, Pane, StatusItemView, ToolbarItemLocation, ViewId, Workspace,
36 WorkspaceId,
37};
38
39pub const MAX_TAB_TITLE_LEN: usize = 24;
40
41impl FollowableItem for Editor {
42 fn remote_id(&self) -> Option<ViewId> {
43 self.remote_id
44 }
45
46 fn from_state_proto(
47 pane: ViewHandle<workspace::Pane>,
48 project: ModelHandle<Project>,
49 remote_id: ViewId,
50 state: &mut Option<proto::view::Variant>,
51 cx: &mut AppContext,
52 ) -> Option<Task<Result<ViewHandle<Self>>>> {
53 let Some(proto::view::Variant::Editor(_)) = state else { return None };
54 let Some(proto::view::Variant::Editor(state)) = state.take() else { unreachable!() };
55
56 let client = project.read(cx).client();
57 let replica_id = project.read(cx).replica_id();
58 let buffer_ids = state
59 .excerpts
60 .iter()
61 .map(|excerpt| excerpt.buffer_id)
62 .collect::<HashSet<_>>();
63 let buffers = project.update(cx, |project, cx| {
64 buffer_ids
65 .iter()
66 .map(|id| project.open_buffer_by_id(*id, cx))
67 .collect::<Vec<_>>()
68 });
69
70 let pane = pane.downgrade();
71 Some(cx.spawn(|mut cx| async move {
72 let mut buffers = futures::future::try_join_all(buffers).await?;
73 let editor = pane.read_with(&cx, |pane, cx| {
74 let mut editors = pane.items_of_type::<Self>();
75 editors.find(|editor| {
76 let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
77 let singleton_buffer_matches = state.singleton
78 && buffers.first()
79 == editor.read(cx).buffer.read(cx).as_singleton().as_ref();
80 ids_match || singleton_buffer_matches
81 })
82 })?;
83
84 let editor = if let Some(editor) = editor {
85 editor
86 } else {
87 pane.update(&mut cx, |_, cx| {
88 let multibuffer = cx.add_model(|cx| {
89 let mut multibuffer;
90 if state.singleton && buffers.len() == 1 {
91 multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
92 } else {
93 multibuffer = MultiBuffer::new(replica_id);
94 let mut excerpts = state.excerpts.into_iter().peekable();
95 while let Some(excerpt) = excerpts.peek() {
96 let buffer_id = excerpt.buffer_id;
97 let buffer_excerpts = iter::from_fn(|| {
98 let excerpt = excerpts.peek()?;
99 (excerpt.buffer_id == buffer_id)
100 .then(|| excerpts.next().unwrap())
101 });
102 let buffer =
103 buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
104 if let Some(buffer) = buffer {
105 multibuffer.push_excerpts(
106 buffer.clone(),
107 buffer_excerpts.filter_map(deserialize_excerpt_range),
108 cx,
109 );
110 }
111 }
112 };
113
114 if let Some(title) = &state.title {
115 multibuffer = multibuffer.with_title(title.clone())
116 }
117
118 multibuffer
119 });
120
121 cx.add_view(|cx| {
122 let mut editor =
123 Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
124 editor.remote_id = Some(remote_id);
125 editor
126 })
127 })?
128 };
129
130 update_editor_from_message(
131 editor.downgrade(),
132 project,
133 proto::update_view::Editor {
134 selections: state.selections,
135 pending_selection: state.pending_selection,
136 scroll_top_anchor: state.scroll_top_anchor,
137 scroll_x: state.scroll_x,
138 scroll_y: state.scroll_y,
139 ..Default::default()
140 },
141 &mut cx,
142 )
143 .await?;
144
145 Ok(editor)
146 }))
147 }
148
149 fn set_leader_replica_id(
150 &mut self,
151 leader_replica_id: Option<u16>,
152 cx: &mut ViewContext<Self>,
153 ) {
154 self.leader_replica_id = leader_replica_id;
155 if self.leader_replica_id.is_some() {
156 self.buffer.update(cx, |buffer, cx| {
157 buffer.remove_active_selections(cx);
158 });
159 } else {
160 self.buffer.update(cx, |buffer, cx| {
161 if self.focused {
162 buffer.set_active_selections(
163 &self.selections.disjoint_anchors(),
164 self.selections.line_mode,
165 self.cursor_shape,
166 cx,
167 );
168 }
169 });
170 }
171 cx.notify();
172 }
173
174 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
175 let buffer = self.buffer.read(cx);
176 let scroll_anchor = self.scroll_manager.anchor();
177 let excerpts = buffer
178 .read(cx)
179 .excerpts()
180 .map(|(id, buffer, range)| proto::Excerpt {
181 id: id.to_proto(),
182 buffer_id: buffer.remote_id(),
183 context_start: Some(serialize_text_anchor(&range.context.start)),
184 context_end: Some(serialize_text_anchor(&range.context.end)),
185 primary_start: range
186 .primary
187 .as_ref()
188 .map(|range| serialize_text_anchor(&range.start)),
189 primary_end: range
190 .primary
191 .as_ref()
192 .map(|range| serialize_text_anchor(&range.end)),
193 })
194 .collect();
195
196 Some(proto::view::Variant::Editor(proto::view::Editor {
197 singleton: buffer.is_singleton(),
198 title: (!buffer.is_singleton()).then(|| buffer.title(cx).into()),
199 excerpts,
200 scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.top_anchor)),
201 scroll_x: scroll_anchor.offset.x(),
202 scroll_y: scroll_anchor.offset.y(),
203 selections: self
204 .selections
205 .disjoint_anchors()
206 .iter()
207 .map(serialize_selection)
208 .collect(),
209 pending_selection: self
210 .selections
211 .pending_anchor()
212 .as_ref()
213 .map(serialize_selection),
214 }))
215 }
216
217 fn add_event_to_update_proto(
218 &self,
219 event: &Self::Event,
220 update: &mut Option<proto::update_view::Variant>,
221 cx: &AppContext,
222 ) -> bool {
223 let update =
224 update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
225
226 match update {
227 proto::update_view::Variant::Editor(update) => match event {
228 Event::ExcerptsAdded {
229 buffer,
230 predecessor,
231 excerpts,
232 } => {
233 let buffer_id = buffer.read(cx).remote_id();
234 let mut excerpts = excerpts.iter();
235 if let Some((id, range)) = excerpts.next() {
236 update.inserted_excerpts.push(proto::ExcerptInsertion {
237 previous_excerpt_id: Some(predecessor.to_proto()),
238 excerpt: serialize_excerpt(buffer_id, id, range),
239 });
240 update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
241 proto::ExcerptInsertion {
242 previous_excerpt_id: None,
243 excerpt: serialize_excerpt(buffer_id, id, range),
244 }
245 }))
246 }
247 true
248 }
249 Event::ExcerptsRemoved { ids } => {
250 update
251 .deleted_excerpts
252 .extend(ids.iter().map(ExcerptId::to_proto));
253 true
254 }
255 Event::ScrollPositionChanged { .. } => {
256 let scroll_anchor = self.scroll_manager.anchor();
257 update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.top_anchor));
258 update.scroll_x = scroll_anchor.offset.x();
259 update.scroll_y = scroll_anchor.offset.y();
260 true
261 }
262 Event::SelectionsChanged { .. } => {
263 update.selections = self
264 .selections
265 .disjoint_anchors()
266 .iter()
267 .map(serialize_selection)
268 .collect();
269 update.pending_selection = self
270 .selections
271 .pending_anchor()
272 .as_ref()
273 .map(serialize_selection);
274 true
275 }
276 _ => false,
277 },
278 }
279 }
280
281 fn apply_update_proto(
282 &mut self,
283 project: &ModelHandle<Project>,
284 message: update_view::Variant,
285 cx: &mut ViewContext<Self>,
286 ) -> Task<Result<()>> {
287 let update_view::Variant::Editor(message) = message;
288 let project = project.clone();
289 cx.spawn(|this, mut cx| async move {
290 update_editor_from_message(this, project, message, &mut cx).await
291 })
292 }
293
294 fn should_unfollow_on_event(event: &Self::Event, _: &AppContext) -> bool {
295 match event {
296 Event::Edited => true,
297 Event::SelectionsChanged { local } => *local,
298 Event::ScrollPositionChanged { local } => *local,
299 _ => false,
300 }
301 }
302}
303
304async fn update_editor_from_message(
305 this: WeakViewHandle<Editor>,
306 project: ModelHandle<Project>,
307 message: proto::update_view::Editor,
308 cx: &mut AsyncAppContext,
309) -> Result<()> {
310 // Open all of the buffers of which excerpts were added to the editor.
311 let inserted_excerpt_buffer_ids = message
312 .inserted_excerpts
313 .iter()
314 .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
315 .collect::<HashSet<_>>();
316 let inserted_excerpt_buffers = project.update(cx, |project, cx| {
317 inserted_excerpt_buffer_ids
318 .into_iter()
319 .map(|id| project.open_buffer_by_id(id, cx))
320 .collect::<Vec<_>>()
321 });
322 let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
323
324 // Update the editor's excerpts.
325 this.update(cx, |editor, cx| {
326 editor.buffer.update(cx, |multibuffer, cx| {
327 let mut removed_excerpt_ids = message
328 .deleted_excerpts
329 .into_iter()
330 .map(ExcerptId::from_proto)
331 .collect::<Vec<_>>();
332 removed_excerpt_ids.sort_by({
333 let multibuffer = multibuffer.read(cx);
334 move |a, b| a.cmp(&b, &multibuffer)
335 });
336
337 let mut insertions = message.inserted_excerpts.into_iter().peekable();
338 while let Some(insertion) = insertions.next() {
339 let Some(excerpt) = insertion.excerpt else { continue };
340 let Some(previous_excerpt_id) = insertion.previous_excerpt_id else { continue };
341 let buffer_id = excerpt.buffer_id;
342 let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else { continue };
343
344 let adjacent_excerpts = iter::from_fn(|| {
345 let insertion = insertions.peek()?;
346 if insertion.previous_excerpt_id.is_none()
347 && insertion.excerpt.as_ref()?.buffer_id == buffer_id
348 {
349 insertions.next()?.excerpt
350 } else {
351 None
352 }
353 });
354
355 multibuffer.insert_excerpts_with_ids_after(
356 ExcerptId::from_proto(previous_excerpt_id),
357 buffer,
358 [excerpt]
359 .into_iter()
360 .chain(adjacent_excerpts)
361 .filter_map(|excerpt| {
362 Some((
363 ExcerptId::from_proto(excerpt.id),
364 deserialize_excerpt_range(excerpt)?,
365 ))
366 }),
367 cx,
368 );
369 }
370
371 multibuffer.remove_excerpts(removed_excerpt_ids, cx);
372 });
373 })?;
374
375 // Deserialize the editor state.
376 let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
377 let buffer = editor.buffer.read(cx).read(cx);
378 let selections = message
379 .selections
380 .into_iter()
381 .filter_map(|selection| deserialize_selection(&buffer, selection))
382 .collect::<Vec<_>>();
383 let pending_selection = message
384 .pending_selection
385 .and_then(|selection| deserialize_selection(&buffer, selection));
386 let scroll_top_anchor = message
387 .scroll_top_anchor
388 .and_then(|anchor| deserialize_anchor(&buffer, anchor));
389 anyhow::Ok((selections, pending_selection, scroll_top_anchor))
390 })??;
391
392 // Wait until the buffer has received all of the operations referenced by
393 // the editor's new state.
394 this.update(cx, |editor, cx| {
395 editor.buffer.update(cx, |buffer, cx| {
396 buffer.wait_for_anchors(
397 selections
398 .iter()
399 .chain(pending_selection.as_ref())
400 .flat_map(|selection| [selection.start, selection.end])
401 .chain(scroll_top_anchor),
402 cx,
403 )
404 })
405 })?
406 .await?;
407
408 // Update the editor's state.
409 this.update(cx, |editor, cx| {
410 if !selections.is_empty() || pending_selection.is_some() {
411 editor.set_selections_from_remote(selections, pending_selection, cx);
412 editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
413 } else if let Some(scroll_top_anchor) = scroll_top_anchor {
414 editor.set_scroll_anchor_remote(
415 ScrollAnchor {
416 top_anchor: scroll_top_anchor,
417 offset: vec2f(message.scroll_x, message.scroll_y),
418 },
419 cx,
420 );
421 }
422 })?;
423 Ok(())
424}
425
426fn serialize_excerpt(
427 buffer_id: u64,
428 id: &ExcerptId,
429 range: &ExcerptRange<language::Anchor>,
430) -> Option<proto::Excerpt> {
431 Some(proto::Excerpt {
432 id: id.to_proto(),
433 buffer_id,
434 context_start: Some(serialize_text_anchor(&range.context.start)),
435 context_end: Some(serialize_text_anchor(&range.context.end)),
436 primary_start: range
437 .primary
438 .as_ref()
439 .map(|r| serialize_text_anchor(&r.start)),
440 primary_end: range
441 .primary
442 .as_ref()
443 .map(|r| serialize_text_anchor(&r.end)),
444 })
445}
446
447fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
448 proto::Selection {
449 id: selection.id as u64,
450 start: Some(serialize_anchor(&selection.start)),
451 end: Some(serialize_anchor(&selection.end)),
452 reversed: selection.reversed,
453 }
454}
455
456fn serialize_anchor(anchor: &Anchor) -> proto::EditorAnchor {
457 proto::EditorAnchor {
458 excerpt_id: anchor.excerpt_id.to_proto(),
459 anchor: Some(serialize_text_anchor(&anchor.text_anchor)),
460 }
461}
462
463fn deserialize_excerpt_range(excerpt: proto::Excerpt) -> Option<ExcerptRange<language::Anchor>> {
464 let context = {
465 let start = language::proto::deserialize_anchor(excerpt.context_start?)?;
466 let end = language::proto::deserialize_anchor(excerpt.context_end?)?;
467 start..end
468 };
469 let primary = excerpt
470 .primary_start
471 .zip(excerpt.primary_end)
472 .and_then(|(start, end)| {
473 let start = language::proto::deserialize_anchor(start)?;
474 let end = language::proto::deserialize_anchor(end)?;
475 Some(start..end)
476 });
477 Some(ExcerptRange { context, primary })
478}
479
480fn deserialize_selection(
481 buffer: &MultiBufferSnapshot,
482 selection: proto::Selection,
483) -> Option<Selection<Anchor>> {
484 Some(Selection {
485 id: selection.id as usize,
486 start: deserialize_anchor(buffer, selection.start?)?,
487 end: deserialize_anchor(buffer, selection.end?)?,
488 reversed: selection.reversed,
489 goal: SelectionGoal::None,
490 })
491}
492
493fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor) -> Option<Anchor> {
494 let excerpt_id = ExcerptId::from_proto(anchor.excerpt_id);
495 Some(Anchor {
496 excerpt_id,
497 text_anchor: language::proto::deserialize_anchor(anchor.anchor?)?,
498 buffer_id: buffer.buffer_id_for_excerpt(excerpt_id),
499 })
500}
501
502impl Item for Editor {
503 fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
504 if let Ok(data) = data.downcast::<NavigationData>() {
505 let newest_selection = self.selections.newest::<Point>(cx);
506 let buffer = self.buffer.read(cx).read(cx);
507 let offset = if buffer.can_resolve(&data.cursor_anchor) {
508 data.cursor_anchor.to_point(&buffer)
509 } else {
510 buffer.clip_point(data.cursor_position, Bias::Left)
511 };
512
513 let mut scroll_anchor = data.scroll_anchor;
514 if !buffer.can_resolve(&scroll_anchor.top_anchor) {
515 scroll_anchor.top_anchor = buffer.anchor_before(
516 buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
517 );
518 }
519
520 drop(buffer);
521
522 if newest_selection.head() == offset {
523 false
524 } else {
525 let nav_history = self.nav_history.take();
526 self.set_scroll_anchor(scroll_anchor, cx);
527 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
528 s.select_ranges([offset..offset])
529 });
530 self.nav_history = nav_history;
531 true
532 }
533 } else {
534 false
535 }
536 }
537
538 fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
539 let file_path = self
540 .buffer()
541 .read(cx)
542 .as_singleton()?
543 .read(cx)
544 .file()
545 .and_then(|f| f.as_local())?
546 .abs_path(cx);
547
548 let file_path = util::paths::compact(&file_path)
549 .to_string_lossy()
550 .to_string();
551
552 Some(file_path.into())
553 }
554
555 fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<str>> {
556 match path_for_buffer(&self.buffer, detail, true, cx)? {
557 Cow::Borrowed(path) => Some(path.to_string_lossy()),
558 Cow::Owned(path) => Some(path.to_string_lossy().to_string().into()),
559 }
560 }
561
562 fn tab_content<T: View>(
563 &self,
564 detail: Option<usize>,
565 style: &theme::Tab,
566 cx: &AppContext,
567 ) -> AnyElement<T> {
568 Flex::row()
569 .with_child(Label::new(self.title(cx).to_string(), style.label.clone()).into_any())
570 .with_children(detail.and_then(|detail| {
571 let path = path_for_buffer(&self.buffer, detail, false, cx)?;
572 let description = path.to_string_lossy();
573 Some(
574 Label::new(
575 util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN),
576 style.description.text.clone(),
577 )
578 .contained()
579 .with_style(style.description.container)
580 .aligned(),
581 )
582 }))
583 .align_children_center()
584 .into_any()
585 }
586
587 fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
588 self.buffer
589 .read(cx)
590 .for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx)));
591 }
592
593 fn is_singleton(&self, cx: &AppContext) -> bool {
594 self.buffer.read(cx).is_singleton()
595 }
596
597 fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
598 where
599 Self: Sized,
600 {
601 Some(self.clone(cx))
602 }
603
604 fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
605 self.nav_history = Some(history);
606 }
607
608 fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
609 let selection = self.selections.newest_anchor();
610 self.push_to_nav_history(selection.head(), None, cx);
611 }
612
613 fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
614 hide_link_definition(self, cx);
615 self.link_go_to_definition_state.last_mouse_location = None;
616 }
617
618 fn is_dirty(&self, cx: &AppContext) -> bool {
619 self.buffer().read(cx).read(cx).is_dirty()
620 }
621
622 fn has_conflict(&self, cx: &AppContext) -> bool {
623 self.buffer().read(cx).read(cx).has_conflict()
624 }
625
626 fn can_save(&self, cx: &AppContext) -> bool {
627 let buffer = &self.buffer().read(cx);
628 if let Some(buffer) = buffer.as_singleton() {
629 buffer.read(cx).project_path(cx).is_some()
630 } else {
631 true
632 }
633 }
634
635 fn save(
636 &mut self,
637 project: ModelHandle<Project>,
638 cx: &mut ViewContext<Self>,
639 ) -> Task<Result<()>> {
640 self.report_editor_event("save", None, cx);
641 let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
642 let buffers = self.buffer().clone().read(cx).all_buffers();
643 cx.spawn(|_, mut cx| async move {
644 format.await?;
645
646 if buffers.len() == 1 {
647 project
648 .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))
649 .await?;
650 } else {
651 // For multi-buffers, only save those ones that contain changes. For clean buffers
652 // we simulate saving by calling `Buffer::did_save`, so that language servers or
653 // other downstream listeners of save events get notified.
654 let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
655 buffer.read_with(&cx, |buffer, _| buffer.is_dirty() || buffer.has_conflict())
656 });
657
658 project
659 .update(&mut cx, |project, cx| {
660 project.save_buffers(dirty_buffers, cx)
661 })
662 .await?;
663 for buffer in clean_buffers {
664 buffer.update(&mut cx, |buffer, cx| {
665 let version = buffer.saved_version().clone();
666 let fingerprint = buffer.saved_version_fingerprint();
667 let mtime = buffer.saved_mtime();
668 buffer.did_save(version, fingerprint, mtime, cx);
669 });
670 }
671 }
672
673 Ok(())
674 })
675 }
676
677 fn save_as(
678 &mut self,
679 project: ModelHandle<Project>,
680 abs_path: PathBuf,
681 cx: &mut ViewContext<Self>,
682 ) -> Task<Result<()>> {
683 let buffer = self
684 .buffer()
685 .read(cx)
686 .as_singleton()
687 .expect("cannot call save_as on an excerpt list");
688
689 let file_extension = abs_path
690 .extension()
691 .map(|a| a.to_string_lossy().to_string());
692 self.report_editor_event("save", file_extension, cx);
693
694 project.update(cx, |project, cx| {
695 project.save_buffer_as(buffer, abs_path, cx)
696 })
697 }
698
699 fn reload(
700 &mut self,
701 project: ModelHandle<Project>,
702 cx: &mut ViewContext<Self>,
703 ) -> Task<Result<()>> {
704 let buffer = self.buffer().clone();
705 let buffers = self.buffer.read(cx).all_buffers();
706 let reload_buffers =
707 project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx));
708 cx.spawn(|this, mut cx| async move {
709 let transaction = reload_buffers.log_err().await;
710 this.update(&mut cx, |editor, cx| {
711 editor.request_autoscroll(Autoscroll::fit(), cx)
712 })?;
713 buffer.update(&mut cx, |buffer, cx| {
714 if let Some(transaction) = transaction {
715 if !buffer.is_singleton() {
716 buffer.push_transaction(&transaction.0, cx);
717 }
718 }
719 });
720 Ok(())
721 })
722 }
723
724 fn git_diff_recalc(
725 &mut self,
726 _project: ModelHandle<Project>,
727 cx: &mut ViewContext<Self>,
728 ) -> Task<Result<()>> {
729 self.buffer().update(cx, |multibuffer, cx| {
730 multibuffer.git_diff_recalc(cx);
731 });
732 Task::ready(Ok(()))
733 }
734
735 fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
736 let mut result = SmallVec::new();
737 match event {
738 Event::Closed => result.push(ItemEvent::CloseItem),
739 Event::Saved | Event::TitleChanged => {
740 result.push(ItemEvent::UpdateTab);
741 result.push(ItemEvent::UpdateBreadcrumbs);
742 }
743 Event::Reparsed => {
744 result.push(ItemEvent::UpdateBreadcrumbs);
745 }
746 Event::SelectionsChanged { local } if *local => {
747 result.push(ItemEvent::UpdateBreadcrumbs);
748 }
749 Event::DirtyChanged => {
750 result.push(ItemEvent::UpdateTab);
751 }
752 Event::BufferEdited => {
753 result.push(ItemEvent::Edit);
754 result.push(ItemEvent::UpdateBreadcrumbs);
755 }
756 _ => {}
757 }
758 result
759 }
760
761 fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
762 Some(Box::new(handle.clone()))
763 }
764
765 fn breadcrumb_location(&self) -> ToolbarItemLocation {
766 ToolbarItemLocation::PrimaryLeft { flex: None }
767 }
768
769 fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
770 let cursor = self.selections.newest_anchor().head();
771 let multibuffer = &self.buffer().read(cx);
772 let (buffer_id, symbols) =
773 multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
774 let buffer = multibuffer.buffer(buffer_id)?;
775
776 let buffer = buffer.read(cx);
777 let filename = buffer
778 .snapshot()
779 .resolve_file_path(
780 cx,
781 self.project
782 .as_ref()
783 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
784 .unwrap_or_default(),
785 )
786 .map(|path| path.to_string_lossy().to_string())
787 .unwrap_or_else(|| "untitled".to_string());
788
789 let mut breadcrumbs = vec![BreadcrumbText {
790 text: filename,
791 highlights: None,
792 }];
793 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
794 text: symbol.text,
795 highlights: Some(symbol.highlight_ranges),
796 }));
797 Some(breadcrumbs)
798 }
799
800 fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
801 let workspace_id = workspace.database_id();
802 let item_id = cx.view_id();
803 self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
804
805 fn serialize(
806 buffer: ModelHandle<Buffer>,
807 workspace_id: WorkspaceId,
808 item_id: ItemId,
809 cx: &mut AppContext,
810 ) {
811 if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
812 let path = file.abs_path(cx);
813
814 cx.background()
815 .spawn(async move {
816 DB.save_path(item_id, workspace_id, path.clone())
817 .await
818 .log_err()
819 })
820 .detach();
821 }
822 }
823
824 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
825 serialize(buffer.clone(), workspace_id, item_id, cx);
826
827 cx.subscribe(&buffer, |this, buffer, event, cx| {
828 if let Some((_, workspace_id)) = this.workspace.as_ref() {
829 if let language::Event::FileHandleChanged = event {
830 serialize(buffer, *workspace_id, cx.view_id(), cx);
831 }
832 }
833 })
834 .detach();
835 }
836 }
837
838 fn serialized_item_kind() -> Option<&'static str> {
839 Some("Editor")
840 }
841
842 fn deserialize(
843 project: ModelHandle<Project>,
844 _workspace: WeakViewHandle<Workspace>,
845 workspace_id: workspace::WorkspaceId,
846 item_id: ItemId,
847 cx: &mut ViewContext<Pane>,
848 ) -> Task<Result<ViewHandle<Self>>> {
849 let project_item: Result<_> = project.update(cx, |project, cx| {
850 // Look up the path with this key associated, create a self with that path
851 let path = DB
852 .get_path(item_id, workspace_id)?
853 .context("No path stored for this editor")?;
854
855 let (worktree, path) = project
856 .find_local_worktree(&path, cx)
857 .with_context(|| format!("No worktree for path: {path:?}"))?;
858 let project_path = ProjectPath {
859 worktree_id: worktree.read(cx).id(),
860 path: path.into(),
861 };
862
863 Ok(project.open_path(project_path, cx))
864 });
865
866 project_item
867 .map(|project_item| {
868 cx.spawn(|pane, mut cx| async move {
869 let (_, project_item) = project_item.await?;
870 let buffer = project_item
871 .downcast::<Buffer>()
872 .context("Project item at stored path was not a buffer")?;
873 Ok(pane.update(&mut cx, |_, cx| {
874 cx.add_view(|cx| {
875 let mut editor = Editor::for_buffer(buffer, Some(project), cx);
876 editor.read_scroll_position_from_db(item_id, workspace_id, cx);
877 editor
878 })
879 })?)
880 })
881 })
882 .unwrap_or_else(|error| Task::ready(Err(error)))
883 }
884}
885
886impl ProjectItem for Editor {
887 type Item = Buffer;
888
889 fn for_project_item(
890 project: ModelHandle<Project>,
891 buffer: ModelHandle<Buffer>,
892 cx: &mut ViewContext<Self>,
893 ) -> Self {
894 Self::for_buffer(buffer, Some(project), cx)
895 }
896}
897
898enum BufferSearchHighlights {}
899impl SearchableItem for Editor {
900 type Match = Range<Anchor>;
901
902 fn to_search_event(event: &Self::Event) -> Option<SearchEvent> {
903 match event {
904 Event::BufferEdited => Some(SearchEvent::MatchesInvalidated),
905 Event::SelectionsChanged { .. } => Some(SearchEvent::ActiveMatchChanged),
906 _ => None,
907 }
908 }
909
910 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
911 self.clear_background_highlights::<BufferSearchHighlights>(cx);
912 }
913
914 fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
915 self.highlight_background::<BufferSearchHighlights>(
916 matches,
917 |theme| theme.search.match_background,
918 cx,
919 );
920 }
921
922 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
923 let display_map = self.snapshot(cx).display_snapshot;
924 let selection = self.selections.newest::<usize>(cx);
925 if selection.start == selection.end {
926 let point = selection.start.to_display_point(&display_map);
927 let range = surrounding_word(&display_map, point);
928 let range = range.start.to_offset(&display_map, Bias::Left)
929 ..range.end.to_offset(&display_map, Bias::Right);
930 let text: String = display_map.buffer_snapshot.text_for_range(range).collect();
931 if text.trim().is_empty() {
932 String::new()
933 } else {
934 text
935 }
936 } else {
937 display_map
938 .buffer_snapshot
939 .text_for_range(selection.start..selection.end)
940 .collect()
941 }
942 }
943
944 fn activate_match(
945 &mut self,
946 index: usize,
947 matches: Vec<Range<Anchor>>,
948 cx: &mut ViewContext<Self>,
949 ) {
950 self.unfold_ranges([matches[index].clone()], false, true, cx);
951 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
952 s.select_ranges([matches[index].clone()])
953 });
954 }
955
956 fn match_index_for_direction(
957 &mut self,
958 matches: &Vec<Range<Anchor>>,
959 mut current_index: usize,
960 direction: Direction,
961 cx: &mut ViewContext<Self>,
962 ) -> usize {
963 let buffer = self.buffer().read(cx).snapshot(cx);
964 let cursor = self.selections.newest_anchor().head();
965 if matches[current_index].start.cmp(&cursor, &buffer).is_gt() {
966 if direction == Direction::Prev {
967 if current_index == 0 {
968 current_index = matches.len() - 1;
969 } else {
970 current_index -= 1;
971 }
972 }
973 } else if matches[current_index].end.cmp(&cursor, &buffer).is_lt() {
974 if direction == Direction::Next {
975 current_index = 0;
976 }
977 } else if direction == Direction::Prev {
978 if current_index == 0 {
979 current_index = matches.len() - 1;
980 } else {
981 current_index -= 1;
982 }
983 } else if direction == Direction::Next {
984 if current_index == matches.len() - 1 {
985 current_index = 0
986 } else {
987 current_index += 1;
988 }
989 };
990 current_index
991 }
992
993 fn find_matches(
994 &mut self,
995 query: project::search::SearchQuery,
996 cx: &mut ViewContext<Self>,
997 ) -> Task<Vec<Range<Anchor>>> {
998 let buffer = self.buffer().read(cx).snapshot(cx);
999 cx.background().spawn(async move {
1000 let mut ranges = Vec::new();
1001 if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
1002 ranges.extend(
1003 query
1004 .search(excerpt_buffer.as_rope())
1005 .await
1006 .into_iter()
1007 .map(|range| {
1008 buffer.anchor_after(range.start)..buffer.anchor_before(range.end)
1009 }),
1010 );
1011 } else {
1012 for excerpt in buffer.excerpt_boundaries_in_range(0..buffer.len()) {
1013 let excerpt_range = excerpt.range.context.to_offset(&excerpt.buffer);
1014 let rope = excerpt.buffer.as_rope().slice(excerpt_range.clone());
1015 ranges.extend(query.search(&rope).await.into_iter().map(|range| {
1016 let start = excerpt
1017 .buffer
1018 .anchor_after(excerpt_range.start + range.start);
1019 let end = excerpt
1020 .buffer
1021 .anchor_before(excerpt_range.start + range.end);
1022 buffer.anchor_in_excerpt(excerpt.id.clone(), start)
1023 ..buffer.anchor_in_excerpt(excerpt.id.clone(), end)
1024 }));
1025 }
1026 }
1027 ranges
1028 })
1029 }
1030
1031 fn active_match_index(
1032 &mut self,
1033 matches: Vec<Range<Anchor>>,
1034 cx: &mut ViewContext<Self>,
1035 ) -> Option<usize> {
1036 active_match_index(
1037 &matches,
1038 &self.selections.newest_anchor().head(),
1039 &self.buffer().read(cx).snapshot(cx),
1040 )
1041 }
1042}
1043
1044pub fn active_match_index(
1045 ranges: &[Range<Anchor>],
1046 cursor: &Anchor,
1047 buffer: &MultiBufferSnapshot,
1048) -> Option<usize> {
1049 if ranges.is_empty() {
1050 None
1051 } else {
1052 match ranges.binary_search_by(|probe| {
1053 if probe.end.cmp(cursor, &*buffer).is_lt() {
1054 Ordering::Less
1055 } else if probe.start.cmp(cursor, &*buffer).is_gt() {
1056 Ordering::Greater
1057 } else {
1058 Ordering::Equal
1059 }
1060 }) {
1061 Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
1062 }
1063 }
1064}
1065
1066pub struct CursorPosition {
1067 position: Option<Point>,
1068 selected_count: usize,
1069 _observe_active_editor: Option<Subscription>,
1070}
1071
1072impl Default for CursorPosition {
1073 fn default() -> Self {
1074 Self::new()
1075 }
1076}
1077
1078impl CursorPosition {
1079 pub fn new() -> Self {
1080 Self {
1081 position: None,
1082 selected_count: 0,
1083 _observe_active_editor: None,
1084 }
1085 }
1086
1087 fn update_position(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
1088 let editor = editor.read(cx);
1089 let buffer = editor.buffer().read(cx).snapshot(cx);
1090
1091 self.selected_count = 0;
1092 let mut last_selection: Option<Selection<usize>> = None;
1093 for selection in editor.selections.all::<usize>(cx) {
1094 self.selected_count += selection.end - selection.start;
1095 if last_selection
1096 .as_ref()
1097 .map_or(true, |last_selection| selection.id > last_selection.id)
1098 {
1099 last_selection = Some(selection);
1100 }
1101 }
1102 self.position = last_selection.map(|s| s.head().to_point(&buffer));
1103
1104 cx.notify();
1105 }
1106}
1107
1108impl Entity for CursorPosition {
1109 type Event = ();
1110}
1111
1112impl View for CursorPosition {
1113 fn ui_name() -> &'static str {
1114 "CursorPosition"
1115 }
1116
1117 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
1118 if let Some(position) = self.position {
1119 let theme = &cx.global::<Settings>().theme.workspace.status_bar;
1120 let mut text = format!(
1121 "{}{FILE_ROW_COLUMN_DELIMITER}{}",
1122 position.row + 1,
1123 position.column + 1
1124 );
1125 if self.selected_count > 0 {
1126 write!(text, " ({} selected)", self.selected_count).unwrap();
1127 }
1128 Label::new(text, theme.cursor_position.clone()).into_any()
1129 } else {
1130 Empty::new().into_any()
1131 }
1132 }
1133}
1134
1135impl StatusItemView for CursorPosition {
1136 fn set_active_pane_item(
1137 &mut self,
1138 active_pane_item: Option<&dyn ItemHandle>,
1139 cx: &mut ViewContext<Self>,
1140 ) {
1141 if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
1142 self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
1143 self.update_position(editor, cx);
1144 } else {
1145 self.position = None;
1146 self._observe_active_editor = None;
1147 }
1148
1149 cx.notify();
1150 }
1151}
1152
1153fn path_for_buffer<'a>(
1154 buffer: &ModelHandle<MultiBuffer>,
1155 height: usize,
1156 include_filename: bool,
1157 cx: &'a AppContext,
1158) -> Option<Cow<'a, Path>> {
1159 let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
1160 path_for_file(file.as_ref(), height, include_filename, cx)
1161}
1162
1163fn path_for_file<'a>(
1164 file: &'a dyn language::File,
1165 mut height: usize,
1166 include_filename: bool,
1167 cx: &'a AppContext,
1168) -> Option<Cow<'a, Path>> {
1169 // Ensure we always render at least the filename.
1170 height += 1;
1171
1172 let mut prefix = file.path().as_ref();
1173 while height > 0 {
1174 if let Some(parent) = prefix.parent() {
1175 prefix = parent;
1176 height -= 1;
1177 } else {
1178 break;
1179 }
1180 }
1181
1182 // Here we could have just always used `full_path`, but that is very
1183 // allocation-heavy and so we try to use a `Cow<Path>` if we haven't
1184 // traversed all the way up to the worktree's root.
1185 if height > 0 {
1186 let full_path = file.full_path(cx);
1187 if include_filename {
1188 Some(full_path.into())
1189 } else {
1190 Some(full_path.parent()?.to_path_buf().into())
1191 }
1192 } else {
1193 let mut path = file.path().strip_prefix(prefix).ok()?;
1194 if !include_filename {
1195 path = path.parent()?;
1196 }
1197 Some(path.into())
1198 }
1199}
1200
1201#[cfg(test)]
1202mod tests {
1203 use super::*;
1204 use gpui::AppContext;
1205 use std::{
1206 path::{Path, PathBuf},
1207 sync::Arc,
1208 time::SystemTime,
1209 };
1210
1211 #[gpui::test]
1212 fn test_path_for_file(cx: &mut AppContext) {
1213 let file = TestFile {
1214 path: Path::new("").into(),
1215 full_path: PathBuf::from(""),
1216 };
1217 assert_eq!(path_for_file(&file, 0, false, cx), None);
1218 }
1219
1220 struct TestFile {
1221 path: Arc<Path>,
1222 full_path: PathBuf,
1223 }
1224
1225 impl language::File for TestFile {
1226 fn path(&self) -> &Arc<Path> {
1227 &self.path
1228 }
1229
1230 fn full_path(&self, _: &gpui::AppContext) -> PathBuf {
1231 self.full_path.clone()
1232 }
1233
1234 fn as_local(&self) -> Option<&dyn language::LocalFile> {
1235 todo!()
1236 }
1237
1238 fn mtime(&self) -> SystemTime {
1239 todo!()
1240 }
1241
1242 fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr {
1243 todo!()
1244 }
1245
1246 fn is_deleted(&self) -> bool {
1247 todo!()
1248 }
1249
1250 fn as_any(&self) -> &dyn std::any::Any {
1251 todo!()
1252 }
1253
1254 fn to_proto(&self) -> rpc::proto::File {
1255 todo!()
1256 }
1257 }
1258}