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