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