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