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, Subscription, Task,
11 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::{BreadcrumbText, 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 = if let Some(editor) = editor {
84 editor
85 } else {
86 pane.update(&mut cx, |_, cx| {
87 let multibuffer = cx.add_model(|cx| {
88 let mut multibuffer;
89 if state.singleton && buffers.len() == 1 {
90 multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
91 } else {
92 multibuffer = MultiBuffer::new(replica_id);
93 let mut excerpts = state.excerpts.into_iter().peekable();
94 while let Some(excerpt) = excerpts.peek() {
95 let buffer_id = excerpt.buffer_id;
96 let buffer_excerpts = iter::from_fn(|| {
97 let excerpt = excerpts.peek()?;
98 (excerpt.buffer_id == buffer_id)
99 .then(|| excerpts.next().unwrap())
100 });
101 let buffer =
102 buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
103 if let Some(buffer) = buffer {
104 multibuffer.push_excerpts(
105 buffer.clone(),
106 buffer_excerpts.filter_map(deserialize_excerpt_range),
107 cx,
108 );
109 }
110 }
111 };
112
113 if let Some(title) = &state.title {
114 multibuffer = multibuffer.with_title(title.clone())
115 }
116
117 multibuffer
118 });
119
120 cx.add_view(|cx| 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 ) -> Element<Pane> {
532 Flex::row()
533 .with_child(
534 Label::new(self.title(cx).to_string(), 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 util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN),
544 style.description.text.clone(),
545 )
546 .contained()
547 .with_style(style.description.container)
548 .aligned()
549 .boxed(),
550 )
551 }))
552 .boxed()
553 }
554
555 fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
556 self.buffer
557 .read(cx)
558 .for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx)));
559 }
560
561 fn is_singleton(&self, cx: &AppContext) -> bool {
562 self.buffer.read(cx).is_singleton()
563 }
564
565 fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
566 where
567 Self: Sized,
568 {
569 Some(self.clone(cx))
570 }
571
572 fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
573 self.nav_history = Some(history);
574 }
575
576 fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
577 let selection = self.selections.newest_anchor();
578 self.push_to_nav_history(selection.head(), None, cx);
579 }
580
581 fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
582 hide_link_definition(self, cx);
583 self.link_go_to_definition_state.last_mouse_location = None;
584 }
585
586 fn is_dirty(&self, cx: &AppContext) -> bool {
587 self.buffer().read(cx).read(cx).is_dirty()
588 }
589
590 fn has_conflict(&self, cx: &AppContext) -> bool {
591 self.buffer().read(cx).read(cx).has_conflict()
592 }
593
594 fn can_save(&self, cx: &AppContext) -> bool {
595 let buffer = &self.buffer().read(cx);
596 if let Some(buffer) = buffer.as_singleton() {
597 buffer.read(cx).project_path(cx).is_some()
598 } else {
599 true
600 }
601 }
602
603 fn save(
604 &mut self,
605 project: ModelHandle<Project>,
606 cx: &mut ViewContext<Self>,
607 ) -> Task<Result<()>> {
608 self.report_event("save editor", cx);
609 let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
610 let buffers = self.buffer().clone().read(cx).all_buffers();
611 cx.spawn(|_, mut cx| async move {
612 format.await?;
613
614 if buffers.len() == 1 {
615 project
616 .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))
617 .await?;
618 } else {
619 // For multi-buffers, only save those ones that contain changes. For clean buffers
620 // we simulate saving by calling `Buffer::did_save`, so that language servers or
621 // other downstream listeners of save events get notified.
622 let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
623 buffer.read_with(&cx, |buffer, _| buffer.is_dirty() || buffer.has_conflict())
624 });
625
626 project
627 .update(&mut cx, |project, cx| {
628 project.save_buffers(dirty_buffers, cx)
629 })
630 .await?;
631 for buffer in clean_buffers {
632 buffer.update(&mut cx, |buffer, cx| {
633 let version = buffer.saved_version().clone();
634 let fingerprint = buffer.saved_version_fingerprint();
635 let mtime = buffer.saved_mtime();
636 buffer.did_save(version, fingerprint, mtime, cx);
637 });
638 }
639 }
640
641 Ok(())
642 })
643 }
644
645 fn save_as(
646 &mut self,
647 project: ModelHandle<Project>,
648 abs_path: PathBuf,
649 cx: &mut ViewContext<Self>,
650 ) -> Task<Result<()>> {
651 let buffer = self
652 .buffer()
653 .read(cx)
654 .as_singleton()
655 .expect("cannot call save_as on an excerpt list");
656
657 project.update(cx, |project, cx| {
658 project.save_buffer_as(buffer, abs_path, cx)
659 })
660 }
661
662 fn reload(
663 &mut self,
664 project: ModelHandle<Project>,
665 cx: &mut ViewContext<Self>,
666 ) -> Task<Result<()>> {
667 let buffer = self.buffer().clone();
668 let buffers = self.buffer.read(cx).all_buffers();
669 let reload_buffers =
670 project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx));
671 cx.spawn(|this, mut cx| async move {
672 let transaction = reload_buffers.log_err().await;
673 this.update(&mut cx, |editor, cx| {
674 editor.request_autoscroll(Autoscroll::fit(), cx)
675 })?;
676 buffer.update(&mut cx, |buffer, _| {
677 if let Some(transaction) = transaction {
678 if !buffer.is_singleton() {
679 buffer.push_transaction(&transaction.0);
680 }
681 }
682 });
683 Ok(())
684 })
685 }
686
687 fn git_diff_recalc(
688 &mut self,
689 _project: ModelHandle<Project>,
690 cx: &mut ViewContext<Self>,
691 ) -> Task<Result<()>> {
692 self.buffer().update(cx, |multibuffer, cx| {
693 multibuffer.git_diff_recalc(cx);
694 });
695 Task::ready(Ok(()))
696 }
697
698 fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
699 let mut result = SmallVec::new();
700 match event {
701 Event::Closed => result.push(ItemEvent::CloseItem),
702 Event::Saved | Event::TitleChanged => {
703 result.push(ItemEvent::UpdateTab);
704 result.push(ItemEvent::UpdateBreadcrumbs);
705 }
706 Event::Reparsed => {
707 result.push(ItemEvent::UpdateBreadcrumbs);
708 }
709 Event::SelectionsChanged { local } if *local => {
710 result.push(ItemEvent::UpdateBreadcrumbs);
711 }
712 Event::DirtyChanged => {
713 result.push(ItemEvent::UpdateTab);
714 }
715 Event::BufferEdited => {
716 result.push(ItemEvent::Edit);
717 result.push(ItemEvent::UpdateBreadcrumbs);
718 }
719 _ => {}
720 }
721 result
722 }
723
724 fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
725 Some(Box::new(handle.clone()))
726 }
727
728 fn breadcrumb_location(&self) -> ToolbarItemLocation {
729 ToolbarItemLocation::PrimaryLeft { flex: None }
730 }
731
732 fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
733 let cursor = self.selections.newest_anchor().head();
734 let multibuffer = &self.buffer().read(cx);
735 let (buffer_id, symbols) =
736 multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
737 let buffer = multibuffer.buffer(buffer_id)?;
738
739 let buffer = buffer.read(cx);
740 let filename = buffer
741 .snapshot()
742 .resolve_file_path(
743 cx,
744 self.project
745 .as_ref()
746 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
747 .unwrap_or_default(),
748 )
749 .map(|path| path.to_string_lossy().to_string())
750 .unwrap_or_else(|| "untitled".to_string());
751
752 let mut breadcrumbs = vec![BreadcrumbText {
753 text: filename,
754 highlights: None,
755 }];
756 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
757 text: symbol.text,
758 highlights: Some(symbol.highlight_ranges),
759 }));
760 Some(breadcrumbs)
761 }
762
763 fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
764 let workspace_id = workspace.database_id();
765 let item_id = cx.view_id();
766 self.workspace_id = Some(workspace_id);
767
768 fn serialize(
769 buffer: ModelHandle<Buffer>,
770 workspace_id: WorkspaceId,
771 item_id: ItemId,
772 cx: &mut AppContext,
773 ) {
774 if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
775 let path = file.abs_path(cx);
776
777 cx.background()
778 .spawn(async move {
779 DB.save_path(item_id, workspace_id, path.clone())
780 .await
781 .log_err()
782 })
783 .detach();
784 }
785 }
786
787 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
788 serialize(buffer.clone(), workspace_id, item_id, cx);
789
790 cx.subscribe(&buffer, |this, buffer, event, cx| {
791 if let Some(workspace_id) = this.workspace_id {
792 if let language::Event::FileHandleChanged = event {
793 serialize(buffer, workspace_id, cx.view_id(), cx);
794 }
795 }
796 })
797 .detach();
798 }
799 }
800
801 fn serialized_item_kind() -> Option<&'static str> {
802 Some("Editor")
803 }
804
805 fn deserialize(
806 project: ModelHandle<Project>,
807 _workspace: WeakViewHandle<Workspace>,
808 workspace_id: workspace::WorkspaceId,
809 item_id: ItemId,
810 cx: &mut ViewContext<Pane>,
811 ) -> Task<Result<ViewHandle<Self>>> {
812 let project_item: Result<_> = project.update(cx, |project, cx| {
813 // Look up the path with this key associated, create a self with that path
814 let path = DB
815 .get_path(item_id, workspace_id)?
816 .context("No path stored for this editor")?;
817
818 let (worktree, path) = project
819 .find_local_worktree(&path, cx)
820 .with_context(|| format!("No worktree for path: {path:?}"))?;
821 let project_path = ProjectPath {
822 worktree_id: worktree.read(cx).id(),
823 path: path.into(),
824 };
825
826 Ok(project.open_path(project_path, cx))
827 });
828
829 project_item
830 .map(|project_item| {
831 cx.spawn(|pane, mut cx| async move {
832 let (_, project_item) = project_item.await?;
833 let buffer = project_item
834 .downcast::<Buffer>()
835 .context("Project item at stored path was not a buffer")?;
836
837 Ok(cx.update(|cx| {
838 cx.add_view(&pane, |cx| {
839 let mut editor = Editor::for_buffer(buffer, Some(project), cx);
840 editor.read_scroll_position_from_db(item_id, workspace_id, cx);
841 editor
842 })
843 }))
844 })
845 })
846 .unwrap_or_else(|error| Task::ready(Err(error)))
847 }
848}
849
850impl ProjectItem for Editor {
851 type Item = Buffer;
852
853 fn for_project_item(
854 project: ModelHandle<Project>,
855 buffer: ModelHandle<Buffer>,
856 cx: &mut ViewContext<Self>,
857 ) -> Self {
858 Self::for_buffer(buffer, Some(project), cx)
859 }
860}
861
862enum BufferSearchHighlights {}
863impl SearchableItem for Editor {
864 type Match = Range<Anchor>;
865
866 fn to_search_event(event: &Self::Event) -> Option<SearchEvent> {
867 match event {
868 Event::BufferEdited => Some(SearchEvent::MatchesInvalidated),
869 Event::SelectionsChanged { .. } => Some(SearchEvent::ActiveMatchChanged),
870 _ => None,
871 }
872 }
873
874 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
875 self.clear_background_highlights::<BufferSearchHighlights>(cx);
876 }
877
878 fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
879 self.highlight_background::<BufferSearchHighlights>(
880 matches,
881 |theme| theme.search.match_background,
882 cx,
883 );
884 }
885
886 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
887 let display_map = self.snapshot(cx).display_snapshot;
888 let selection = self.selections.newest::<usize>(cx);
889 if selection.start == selection.end {
890 let point = selection.start.to_display_point(&display_map);
891 let range = surrounding_word(&display_map, point);
892 let range = range.start.to_offset(&display_map, Bias::Left)
893 ..range.end.to_offset(&display_map, Bias::Right);
894 let text: String = display_map.buffer_snapshot.text_for_range(range).collect();
895 if text.trim().is_empty() {
896 String::new()
897 } else {
898 text
899 }
900 } else {
901 display_map
902 .buffer_snapshot
903 .text_for_range(selection.start..selection.end)
904 .collect()
905 }
906 }
907
908 fn activate_match(
909 &mut self,
910 index: usize,
911 matches: Vec<Range<Anchor>>,
912 cx: &mut ViewContext<Self>,
913 ) {
914 self.unfold_ranges([matches[index].clone()], false, true, cx);
915 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
916 s.select_ranges([matches[index].clone()])
917 });
918 }
919
920 fn match_index_for_direction(
921 &mut self,
922 matches: &Vec<Range<Anchor>>,
923 mut current_index: usize,
924 direction: Direction,
925 cx: &mut ViewContext<Self>,
926 ) -> usize {
927 let buffer = self.buffer().read(cx).snapshot(cx);
928 let cursor = self.selections.newest_anchor().head();
929 if matches[current_index].start.cmp(&cursor, &buffer).is_gt() {
930 if direction == Direction::Prev {
931 if current_index == 0 {
932 current_index = matches.len() - 1;
933 } else {
934 current_index -= 1;
935 }
936 }
937 } else if matches[current_index].end.cmp(&cursor, &buffer).is_lt() {
938 if direction == Direction::Next {
939 current_index = 0;
940 }
941 } else if direction == Direction::Prev {
942 if current_index == 0 {
943 current_index = matches.len() - 1;
944 } else {
945 current_index -= 1;
946 }
947 } else if direction == Direction::Next {
948 if current_index == matches.len() - 1 {
949 current_index = 0
950 } else {
951 current_index += 1;
952 }
953 };
954 current_index
955 }
956
957 fn find_matches(
958 &mut self,
959 query: project::search::SearchQuery,
960 cx: &mut ViewContext<Self>,
961 ) -> Task<Vec<Range<Anchor>>> {
962 let buffer = self.buffer().read(cx).snapshot(cx);
963 cx.background().spawn(async move {
964 let mut ranges = Vec::new();
965 if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
966 ranges.extend(
967 query
968 .search(excerpt_buffer.as_rope())
969 .await
970 .into_iter()
971 .map(|range| {
972 buffer.anchor_after(range.start)..buffer.anchor_before(range.end)
973 }),
974 );
975 } else {
976 for excerpt in buffer.excerpt_boundaries_in_range(0..buffer.len()) {
977 let excerpt_range = excerpt.range.context.to_offset(&excerpt.buffer);
978 let rope = excerpt.buffer.as_rope().slice(excerpt_range.clone());
979 ranges.extend(query.search(&rope).await.into_iter().map(|range| {
980 let start = excerpt
981 .buffer
982 .anchor_after(excerpt_range.start + range.start);
983 let end = excerpt
984 .buffer
985 .anchor_before(excerpt_range.start + range.end);
986 buffer.anchor_in_excerpt(excerpt.id.clone(), start)
987 ..buffer.anchor_in_excerpt(excerpt.id.clone(), end)
988 }));
989 }
990 }
991 ranges
992 })
993 }
994
995 fn active_match_index(
996 &mut self,
997 matches: Vec<Range<Anchor>>,
998 cx: &mut ViewContext<Self>,
999 ) -> Option<usize> {
1000 active_match_index(
1001 &matches,
1002 &self.selections.newest_anchor().head(),
1003 &self.buffer().read(cx).snapshot(cx),
1004 )
1005 }
1006}
1007
1008pub fn active_match_index(
1009 ranges: &[Range<Anchor>],
1010 cursor: &Anchor,
1011 buffer: &MultiBufferSnapshot,
1012) -> Option<usize> {
1013 if ranges.is_empty() {
1014 None
1015 } else {
1016 match ranges.binary_search_by(|probe| {
1017 if probe.end.cmp(cursor, &*buffer).is_lt() {
1018 Ordering::Less
1019 } else if probe.start.cmp(cursor, &*buffer).is_gt() {
1020 Ordering::Greater
1021 } else {
1022 Ordering::Equal
1023 }
1024 }) {
1025 Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
1026 }
1027 }
1028}
1029
1030pub struct CursorPosition {
1031 position: Option<Point>,
1032 selected_count: usize,
1033 _observe_active_editor: Option<Subscription>,
1034}
1035
1036impl Default for CursorPosition {
1037 fn default() -> Self {
1038 Self::new()
1039 }
1040}
1041
1042impl CursorPosition {
1043 pub fn new() -> Self {
1044 Self {
1045 position: None,
1046 selected_count: 0,
1047 _observe_active_editor: None,
1048 }
1049 }
1050
1051 fn update_position(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
1052 let editor = editor.read(cx);
1053 let buffer = editor.buffer().read(cx).snapshot(cx);
1054
1055 self.selected_count = 0;
1056 let mut last_selection: Option<Selection<usize>> = None;
1057 for selection in editor.selections.all::<usize>(cx) {
1058 self.selected_count += selection.end - selection.start;
1059 if last_selection
1060 .as_ref()
1061 .map_or(true, |last_selection| selection.id > last_selection.id)
1062 {
1063 last_selection = Some(selection);
1064 }
1065 }
1066 self.position = last_selection.map(|s| s.head().to_point(&buffer));
1067
1068 cx.notify();
1069 }
1070}
1071
1072impl Entity for CursorPosition {
1073 type Event = ();
1074}
1075
1076impl View for CursorPosition {
1077 fn ui_name() -> &'static str {
1078 "CursorPosition"
1079 }
1080
1081 fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
1082 if let Some(position) = self.position {
1083 let theme = &cx.global::<Settings>().theme.workspace.status_bar;
1084 let mut text = format!("{},{}", position.row + 1, position.column + 1);
1085 if self.selected_count > 0 {
1086 write!(text, " ({} selected)", self.selected_count).unwrap();
1087 }
1088 Label::new(text, theme.cursor_position.clone()).boxed()
1089 } else {
1090 Empty::new().boxed()
1091 }
1092 }
1093}
1094
1095impl StatusItemView for CursorPosition {
1096 fn set_active_pane_item(
1097 &mut self,
1098 active_pane_item: Option<&dyn ItemHandle>,
1099 cx: &mut ViewContext<Self>,
1100 ) {
1101 if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
1102 self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
1103 self.update_position(editor, cx);
1104 } else {
1105 self.position = None;
1106 self._observe_active_editor = None;
1107 }
1108
1109 cx.notify();
1110 }
1111}
1112
1113fn path_for_buffer<'a>(
1114 buffer: &ModelHandle<MultiBuffer>,
1115 height: usize,
1116 include_filename: bool,
1117 cx: &'a AppContext,
1118) -> Option<Cow<'a, Path>> {
1119 let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
1120 path_for_file(file.as_ref(), height, include_filename, cx)
1121}
1122
1123fn path_for_file<'a>(
1124 file: &'a dyn language::File,
1125 mut height: usize,
1126 include_filename: bool,
1127 cx: &'a AppContext,
1128) -> Option<Cow<'a, Path>> {
1129 // Ensure we always render at least the filename.
1130 height += 1;
1131
1132 let mut prefix = file.path().as_ref();
1133 while height > 0 {
1134 if let Some(parent) = prefix.parent() {
1135 prefix = parent;
1136 height -= 1;
1137 } else {
1138 break;
1139 }
1140 }
1141
1142 // Here we could have just always used `full_path`, but that is very
1143 // allocation-heavy and so we try to use a `Cow<Path>` if we haven't
1144 // traversed all the way up to the worktree's root.
1145 if height > 0 {
1146 let full_path = file.full_path(cx);
1147 if include_filename {
1148 Some(full_path.into())
1149 } else {
1150 Some(full_path.parent()?.to_path_buf().into())
1151 }
1152 } else {
1153 let mut path = file.path().strip_prefix(prefix).ok()?;
1154 if !include_filename {
1155 path = path.parent()?;
1156 }
1157 Some(path.into())
1158 }
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163 use super::*;
1164 use gpui::AppContext;
1165 use std::{
1166 path::{Path, PathBuf},
1167 sync::Arc,
1168 time::SystemTime,
1169 };
1170
1171 #[gpui::test]
1172 fn test_path_for_file(cx: &mut AppContext) {
1173 let file = TestFile {
1174 path: Path::new("").into(),
1175 full_path: PathBuf::from(""),
1176 };
1177 assert_eq!(path_for_file(&file, 0, false, cx), None);
1178 }
1179
1180 struct TestFile {
1181 path: Arc<Path>,
1182 full_path: PathBuf,
1183 }
1184
1185 impl language::File for TestFile {
1186 fn path(&self) -> &Arc<Path> {
1187 &self.path
1188 }
1189
1190 fn full_path(&self, _: &gpui::AppContext) -> PathBuf {
1191 self.full_path.clone()
1192 }
1193
1194 fn as_local(&self) -> Option<&dyn language::LocalFile> {
1195 todo!()
1196 }
1197
1198 fn mtime(&self) -> SystemTime {
1199 todo!()
1200 }
1201
1202 fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr {
1203 todo!()
1204 }
1205
1206 fn is_deleted(&self) -> bool {
1207 todo!()
1208 }
1209
1210 fn as_any(&self) -> &dyn std::any::Any {
1211 todo!()
1212 }
1213
1214 fn to_proto(&self) -> rpc::proto::File {
1215 todo!()
1216 }
1217 }
1218}