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