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