items.rs

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