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