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