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