items.rs

   1use crate::{
   2    editor_settings::SeedQuerySetting,
   3    persistence::{SerializedEditor, DB},
   4    scroll::ScrollAnchor,
   5    Anchor, Autoscroll, Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer,
   6    MultiBufferSnapshot, NavigationData, SearchWithinRange, ToPoint as _,
   7};
   8use anyhow::{anyhow, Context as _, Result};
   9use collections::HashSet;
  10use file_icons::FileIcons;
  11use futures::future::try_join_all;
  12use git::repository::GitFileStatus;
  13use gpui::{
  14    point, AnyElement, AppContext, AsyncWindowContext, Context, Entity, EntityId, EventEmitter,
  15    IntoElement, Model, ParentElement, Pixels, SharedString, Styled, Task, View, ViewContext,
  16    VisualContext, WeakView, WindowContext,
  17};
  18use language::{
  19    proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, DiskState, Point,
  20    SelectionGoal,
  21};
  22use lsp::DiagnosticSeverity;
  23use multi_buffer::AnchorRangeExt;
  24use project::{
  25    lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Project,
  26    ProjectItem as _, ProjectPath,
  27};
  28use rpc::proto::{self, update_view, PeerId};
  29use settings::Settings;
  30use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
  31
  32use project::lsp_store::FormatTarget;
  33use std::{
  34    any::TypeId,
  35    borrow::Cow,
  36    cmp::{self, Ordering},
  37    iter,
  38    ops::Range,
  39    path::Path,
  40    sync::Arc,
  41};
  42use text::{BufferId, Selection};
  43use theme::{Theme, ThemeSettings};
  44use ui::{h_flex, prelude::*, IconDecorationKind, Label};
  45use util::{paths::PathExt, ResultExt, TryFutureExt};
  46use workspace::item::{BreadcrumbText, FollowEvent};
  47use workspace::{
  48    item::{FollowableItem, Item, ItemEvent, ProjectItem},
  49    searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
  50    ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
  51};
  52
  53pub const MAX_TAB_TITLE_LEN: usize = 24;
  54
  55impl FollowableItem for Editor {
  56    fn remote_id(&self) -> Option<ViewId> {
  57        self.remote_id
  58    }
  59
  60    fn from_state_proto(
  61        workspace: View<Workspace>,
  62        remote_id: ViewId,
  63        state: &mut Option<proto::view::Variant>,
  64        cx: &mut WindowContext,
  65    ) -> Option<Task<Result<View<Self>>>> {
  66        let project = workspace.read(cx).project().to_owned();
  67        let Some(proto::view::Variant::Editor(_)) = state else {
  68            return None;
  69        };
  70        let Some(proto::view::Variant::Editor(state)) = state.take() else {
  71            unreachable!()
  72        };
  73
  74        let buffer_ids = state
  75            .excerpts
  76            .iter()
  77            .map(|excerpt| excerpt.buffer_id)
  78            .collect::<HashSet<_>>();
  79        let buffers = project.update(cx, |project, cx| {
  80            buffer_ids
  81                .iter()
  82                .map(|id| BufferId::new(*id).map(|id| project.open_buffer_by_id(id, cx)))
  83                .collect::<Result<Vec<_>>>()
  84        });
  85
  86        Some(cx.spawn(|mut cx| async move {
  87            let mut buffers = futures::future::try_join_all(buffers?)
  88                .await
  89                .debug_assert_ok("leaders don't share views for unshared buffers")?;
  90
  91            let editor = cx.update(|cx| {
  92                let multibuffer = cx.new_model(|cx| {
  93                    let mut multibuffer;
  94                    if state.singleton && buffers.len() == 1 {
  95                        multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
  96                    } else {
  97                        multibuffer = MultiBuffer::new(project.read(cx).capability());
  98                        let mut excerpts = state.excerpts.into_iter().peekable();
  99                        while let Some(excerpt) = excerpts.peek() {
 100                            let Ok(buffer_id) = BufferId::new(excerpt.buffer_id) else {
 101                                continue;
 102                            };
 103                            let buffer_excerpts = iter::from_fn(|| {
 104                                let excerpt = excerpts.peek()?;
 105                                (excerpt.buffer_id == u64::from(buffer_id))
 106                                    .then(|| excerpts.next().unwrap())
 107                            });
 108                            let buffer =
 109                                buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
 110                            if let Some(buffer) = buffer {
 111                                multibuffer.push_excerpts(
 112                                    buffer.clone(),
 113                                    buffer_excerpts.filter_map(deserialize_excerpt_range),
 114                                    cx,
 115                                );
 116                            }
 117                        }
 118                    };
 119
 120                    if let Some(title) = &state.title {
 121                        multibuffer = multibuffer.with_title(title.clone())
 122                    }
 123
 124                    multibuffer
 125                });
 126
 127                cx.new_view(|cx| {
 128                    let mut editor =
 129                        Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx);
 130                    editor.remote_id = Some(remote_id);
 131                    editor
 132                })
 133            })?;
 134
 135            update_editor_from_message(
 136                editor.downgrade(),
 137                project,
 138                proto::update_view::Editor {
 139                    selections: state.selections,
 140                    pending_selection: state.pending_selection,
 141                    scroll_top_anchor: state.scroll_top_anchor,
 142                    scroll_x: state.scroll_x,
 143                    scroll_y: state.scroll_y,
 144                    ..Default::default()
 145                },
 146                &mut cx,
 147            )
 148            .await?;
 149
 150            Ok(editor)
 151        }))
 152    }
 153
 154    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
 155        self.leader_peer_id = leader_peer_id;
 156        if self.leader_peer_id.is_some() {
 157            self.buffer.update(cx, |buffer, cx| {
 158                buffer.remove_active_selections(cx);
 159            });
 160        } else if self.focus_handle.is_focused(cx) {
 161            self.buffer.update(cx, |buffer, cx| {
 162                buffer.set_active_selections(
 163                    &self.selections.disjoint_anchors(),
 164                    self.selections.line_mode,
 165                    self.cursor_shape,
 166                    cx,
 167                );
 168            });
 169        }
 170        cx.notify();
 171    }
 172
 173    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
 174        let buffer = self.buffer.read(cx);
 175        if buffer
 176            .as_singleton()
 177            .and_then(|buffer| buffer.read(cx).file())
 178            .map_or(false, |file| file.is_private())
 179        {
 180            return None;
 181        }
 182
 183        let scroll_anchor = self.scroll_manager.anchor();
 184        let excerpts = buffer
 185            .read(cx)
 186            .excerpts()
 187            .map(|(id, buffer, range)| proto::Excerpt {
 188                id: id.to_proto(),
 189                buffer_id: buffer.remote_id().into(),
 190                context_start: Some(serialize_text_anchor(&range.context.start)),
 191                context_end: Some(serialize_text_anchor(&range.context.end)),
 192                primary_start: range
 193                    .primary
 194                    .as_ref()
 195                    .map(|range| serialize_text_anchor(&range.start)),
 196                primary_end: range
 197                    .primary
 198                    .as_ref()
 199                    .map(|range| serialize_text_anchor(&range.end)),
 200            })
 201            .collect();
 202
 203        Some(proto::view::Variant::Editor(proto::view::Editor {
 204            singleton: buffer.is_singleton(),
 205            title: (!buffer.is_singleton()).then(|| buffer.title(cx).into()),
 206            excerpts,
 207            scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor)),
 208            scroll_x: scroll_anchor.offset.x,
 209            scroll_y: scroll_anchor.offset.y,
 210            selections: self
 211                .selections
 212                .disjoint_anchors()
 213                .iter()
 214                .map(serialize_selection)
 215                .collect(),
 216            pending_selection: self
 217                .selections
 218                .pending_anchor()
 219                .as_ref()
 220                .map(serialize_selection),
 221        }))
 222    }
 223
 224    fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
 225        match event {
 226            EditorEvent::Edited { .. } => Some(FollowEvent::Unfollow),
 227            EditorEvent::SelectionsChanged { local }
 228            | EditorEvent::ScrollPositionChanged { local, .. } => {
 229                if *local {
 230                    Some(FollowEvent::Unfollow)
 231                } else {
 232                    None
 233                }
 234            }
 235            _ => None,
 236        }
 237    }
 238
 239    fn add_event_to_update_proto(
 240        &self,
 241        event: &EditorEvent,
 242        update: &mut Option<proto::update_view::Variant>,
 243        cx: &WindowContext,
 244    ) -> bool {
 245        let update =
 246            update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
 247
 248        match update {
 249            proto::update_view::Variant::Editor(update) => match event {
 250                EditorEvent::ExcerptsAdded {
 251                    buffer,
 252                    predecessor,
 253                    excerpts,
 254                } => {
 255                    let buffer_id = buffer.read(cx).remote_id();
 256                    let mut excerpts = excerpts.iter();
 257                    if let Some((id, range)) = excerpts.next() {
 258                        update.inserted_excerpts.push(proto::ExcerptInsertion {
 259                            previous_excerpt_id: Some(predecessor.to_proto()),
 260                            excerpt: serialize_excerpt(buffer_id, id, range),
 261                        });
 262                        update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
 263                            proto::ExcerptInsertion {
 264                                previous_excerpt_id: None,
 265                                excerpt: serialize_excerpt(buffer_id, id, range),
 266                            }
 267                        }))
 268                    }
 269                    true
 270                }
 271                EditorEvent::ExcerptsRemoved { ids } => {
 272                    update
 273                        .deleted_excerpts
 274                        .extend(ids.iter().map(ExcerptId::to_proto));
 275                    true
 276                }
 277                EditorEvent::ScrollPositionChanged { autoscroll, .. } if !autoscroll => {
 278                    let scroll_anchor = self.scroll_manager.anchor();
 279                    update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor));
 280                    update.scroll_x = scroll_anchor.offset.x;
 281                    update.scroll_y = scroll_anchor.offset.y;
 282                    true
 283                }
 284                EditorEvent::SelectionsChanged { .. } => {
 285                    update.selections = self
 286                        .selections
 287                        .disjoint_anchors()
 288                        .iter()
 289                        .map(serialize_selection)
 290                        .collect();
 291                    update.pending_selection = self
 292                        .selections
 293                        .pending_anchor()
 294                        .as_ref()
 295                        .map(serialize_selection);
 296                    true
 297                }
 298                _ => false,
 299            },
 300        }
 301    }
 302
 303    fn apply_update_proto(
 304        &mut self,
 305        project: &Model<Project>,
 306        message: update_view::Variant,
 307        cx: &mut ViewContext<Self>,
 308    ) -> Task<Result<()>> {
 309        let update_view::Variant::Editor(message) = message;
 310        let project = project.clone();
 311        cx.spawn(|this, mut cx| async move {
 312            update_editor_from_message(this, project, message, &mut cx).await
 313        })
 314    }
 315
 316    fn is_project_item(&self, _cx: &WindowContext) -> bool {
 317        true
 318    }
 319
 320    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
 321        let self_singleton = self.buffer.read(cx).as_singleton()?;
 322        let other_singleton = existing.buffer.read(cx).as_singleton()?;
 323        if self_singleton == other_singleton {
 324            Some(Dedup::KeepExisting)
 325        } else {
 326            None
 327        }
 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, cx) 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_icon(&self, cx: &WindowContext) -> Option<Icon> {
 599        ItemSettings::get_global(cx)
 600            .file_icons
 601            .then(|| {
 602                self.buffer
 603                    .read(cx)
 604                    .as_singleton()
 605                    .and_then(|buffer| buffer.read(cx).project_path(cx))
 606                    .and_then(|path| FileIcons::get_icon(path.path.as_ref(), cx))
 607            })
 608            .flatten()
 609            .map(Icon::from_path)
 610    }
 611
 612    fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
 613        let label_color = if ItemSettings::get_global(cx).git_status {
 614            self.buffer()
 615                .read(cx)
 616                .as_singleton()
 617                .and_then(|buffer| buffer.read(cx).project_path(cx))
 618                .and_then(|path| self.project.as_ref()?.read(cx).entry_for_path(&path, cx))
 619                .map(|entry| {
 620                    entry_git_aware_label_color(entry.git_status, entry.is_ignored, params.selected)
 621                })
 622                .unwrap_or_else(|| entry_label_color(params.selected))
 623        } else {
 624            entry_label_color(params.selected)
 625        };
 626
 627        let description = params.detail.and_then(|detail| {
 628            let path = path_for_buffer(&self.buffer, detail, false, cx)?;
 629            let description = path.to_string_lossy();
 630            let description = description.trim();
 631
 632            if description.is_empty() {
 633                return None;
 634            }
 635
 636            Some(util::truncate_and_trailoff(description, MAX_TAB_TITLE_LEN))
 637        });
 638
 639        // Whether the file was saved in the past but is now deleted.
 640        let was_deleted: bool = self
 641            .buffer()
 642            .read(cx)
 643            .as_singleton()
 644            .and_then(|buffer| buffer.read(cx).file())
 645            .map_or(false, |file| file.disk_state() == DiskState::Deleted);
 646
 647        h_flex()
 648            .gap_2()
 649            .child(
 650                Label::new(self.title(cx).to_string())
 651                    .color(label_color)
 652                    .italic(params.preview)
 653                    .strikethrough(was_deleted),
 654            )
 655            .when_some(description, |this, description| {
 656                this.child(
 657                    Label::new(description)
 658                        .size(LabelSize::XSmall)
 659                        .color(Color::Muted),
 660                )
 661            })
 662            .into_any_element()
 663    }
 664
 665    fn for_each_project_item(
 666        &self,
 667        cx: &AppContext,
 668        f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
 669    ) {
 670        self.buffer
 671            .read(cx)
 672            .for_each_buffer(|buffer| f(buffer.entity_id(), buffer.read(cx)));
 673    }
 674
 675    fn is_singleton(&self, cx: &AppContext) -> bool {
 676        self.buffer.read(cx).is_singleton()
 677    }
 678
 679    fn clone_on_split(
 680        &self,
 681        _workspace_id: Option<WorkspaceId>,
 682        cx: &mut ViewContext<Self>,
 683    ) -> Option<View<Editor>>
 684    where
 685        Self: Sized,
 686    {
 687        Some(cx.new_view(|cx| self.clone(cx)))
 688    }
 689
 690    fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
 691        self.nav_history = Some(history);
 692    }
 693
 694    fn discarded(&self, _project: Model<Project>, cx: &mut ViewContext<Self>) {
 695        for buffer in self.buffer().clone().read(cx).all_buffers() {
 696            buffer.update(cx, |buffer, cx| buffer.discarded(cx))
 697        }
 698    }
 699
 700    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
 701        let selection = self.selections.newest_anchor();
 702        self.push_to_nav_history(selection.head(), None, cx);
 703    }
 704
 705    fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
 706        self.hide_hovered_link(cx);
 707    }
 708
 709    fn is_dirty(&self, cx: &AppContext) -> bool {
 710        self.buffer().read(cx).read(cx).is_dirty()
 711    }
 712
 713    fn has_deleted_file(&self, cx: &AppContext) -> bool {
 714        self.buffer().read(cx).read(cx).has_deleted_file()
 715    }
 716
 717    fn has_conflict(&self, cx: &AppContext) -> bool {
 718        self.buffer().read(cx).read(cx).has_conflict()
 719    }
 720
 721    fn can_save(&self, cx: &AppContext) -> bool {
 722        let buffer = &self.buffer().read(cx);
 723        if let Some(buffer) = buffer.as_singleton() {
 724            buffer.read(cx).project_path(cx).is_some()
 725        } else {
 726            true
 727        }
 728    }
 729
 730    fn save(
 731        &mut self,
 732        format: bool,
 733        project: Model<Project>,
 734        cx: &mut ViewContext<Self>,
 735    ) -> Task<Result<()>> {
 736        self.report_editor_event("save", None, cx);
 737        let buffers = self.buffer().clone().read(cx).all_buffers();
 738        let buffers = buffers
 739            .into_iter()
 740            .map(|handle| handle.read(cx).diff_base_buffer().unwrap_or(handle.clone()))
 741            .collect::<HashSet<_>>();
 742        cx.spawn(|this, mut cx| async move {
 743            if format {
 744                this.update(&mut cx, |editor, cx| {
 745                    editor.perform_format(
 746                        project.clone(),
 747                        FormatTrigger::Save,
 748                        FormatTarget::Buffer,
 749                        cx,
 750                    )
 751                })?
 752                .await?;
 753            }
 754
 755            if buffers.len() == 1 {
 756                // Apply full save routine for singleton buffers, to allow to `touch` the file via the editor.
 757                project
 758                    .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))?
 759                    .await?;
 760            } else {
 761                // For multi-buffers, only format and save the buffers with changes.
 762                // For clean buffers, we simulate saving by calling `Buffer::did_save`,
 763                // so that language servers or other downstream listeners of save events get notified.
 764                let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
 765                    buffer
 766                        .update(&mut cx, |buffer, _| {
 767                            buffer.is_dirty() || buffer.has_conflict()
 768                        })
 769                        .unwrap_or(false)
 770                });
 771
 772                project
 773                    .update(&mut cx, |project, cx| {
 774                        project.save_buffers(dirty_buffers, cx)
 775                    })?
 776                    .await?;
 777                for buffer in clean_buffers {
 778                    buffer
 779                        .update(&mut cx, |buffer, cx| {
 780                            let version = buffer.saved_version().clone();
 781                            let mtime = buffer.saved_mtime();
 782                            buffer.did_save(version, mtime, cx);
 783                        })
 784                        .ok();
 785                }
 786            }
 787
 788            Ok(())
 789        })
 790    }
 791
 792    fn save_as(
 793        &mut self,
 794        project: Model<Project>,
 795        path: ProjectPath,
 796        cx: &mut ViewContext<Self>,
 797    ) -> Task<Result<()>> {
 798        let buffer = self
 799            .buffer()
 800            .read(cx)
 801            .as_singleton()
 802            .expect("cannot call save_as on an excerpt list");
 803
 804        let file_extension = path
 805            .path
 806            .extension()
 807            .map(|a| a.to_string_lossy().to_string());
 808        self.report_editor_event("save", file_extension, cx);
 809
 810        project.update(cx, |project, cx| project.save_buffer_as(buffer, path, cx))
 811    }
 812
 813    fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
 814        let buffer = self.buffer().clone();
 815        let buffers = self.buffer.read(cx).all_buffers();
 816        let reload_buffers =
 817            project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx));
 818        cx.spawn(|this, mut cx| async move {
 819            let transaction = reload_buffers.log_err().await;
 820            this.update(&mut cx, |editor, cx| {
 821                editor.request_autoscroll(Autoscroll::fit(), cx)
 822            })?;
 823            buffer
 824                .update(&mut cx, |buffer, cx| {
 825                    if let Some(transaction) = transaction {
 826                        if !buffer.is_singleton() {
 827                            buffer.push_transaction(&transaction.0, cx);
 828                        }
 829                    }
 830                })
 831                .ok();
 832            Ok(())
 833        })
 834    }
 835
 836    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 837        Some(Box::new(handle.clone()))
 838    }
 839
 840    fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<gpui::Point<Pixels>> {
 841        self.pixel_position_of_newest_cursor
 842    }
 843
 844    fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
 845        if self.show_breadcrumbs {
 846            ToolbarItemLocation::PrimaryLeft
 847        } else {
 848            ToolbarItemLocation::Hidden
 849        }
 850    }
 851
 852    fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
 853        let cursor = self.selections.newest_anchor().head();
 854        let multibuffer = &self.buffer().read(cx);
 855        let (buffer_id, symbols) =
 856            multibuffer.symbols_containing(cursor, Some(variant.syntax()), cx)?;
 857        let buffer = multibuffer.buffer(buffer_id)?;
 858
 859        let buffer = buffer.read(cx);
 860        let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
 861            buffer
 862                .snapshot()
 863                .resolve_file_path(
 864                    cx,
 865                    self.project
 866                        .as_ref()
 867                        .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
 868                        .unwrap_or_default(),
 869                )
 870                .map(|path| path.to_string_lossy().to_string())
 871                .unwrap_or_else(|| {
 872                    if multibuffer.is_singleton() {
 873                        multibuffer.title(cx).to_string()
 874                    } else {
 875                        "untitled".to_string()
 876                    }
 877                })
 878        });
 879
 880        let settings = ThemeSettings::get_global(cx);
 881
 882        let mut breadcrumbs = vec![BreadcrumbText {
 883            text,
 884            highlights: None,
 885            font: Some(settings.buffer_font.clone()),
 886        }];
 887
 888        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
 889            text: symbol.text,
 890            highlights: Some(symbol.highlight_ranges),
 891            font: Some(settings.buffer_font.clone()),
 892        }));
 893        Some(breadcrumbs)
 894    }
 895
 896    fn added_to_workspace(&mut self, workspace: &mut Workspace, _: &mut ViewContext<Self>) {
 897        self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
 898    }
 899
 900    fn to_item_events(event: &EditorEvent, mut f: impl FnMut(ItemEvent)) {
 901        match event {
 902            EditorEvent::Closed => f(ItemEvent::CloseItem),
 903
 904            EditorEvent::Saved | EditorEvent::TitleChanged => {
 905                f(ItemEvent::UpdateTab);
 906                f(ItemEvent::UpdateBreadcrumbs);
 907            }
 908
 909            EditorEvent::Reparsed(_) => {
 910                f(ItemEvent::UpdateBreadcrumbs);
 911            }
 912
 913            EditorEvent::SelectionsChanged { local } if *local => {
 914                f(ItemEvent::UpdateBreadcrumbs);
 915            }
 916
 917            EditorEvent::DirtyChanged => {
 918                f(ItemEvent::UpdateTab);
 919            }
 920
 921            EditorEvent::BufferEdited => {
 922                f(ItemEvent::Edit);
 923                f(ItemEvent::UpdateBreadcrumbs);
 924            }
 925
 926            EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
 927                f(ItemEvent::Edit);
 928            }
 929
 930            _ => {}
 931        }
 932    }
 933
 934    fn preserve_preview(&self, cx: &AppContext) -> bool {
 935        self.buffer.read(cx).preserve_preview(cx)
 936    }
 937}
 938
 939impl SerializableItem for Editor {
 940    fn serialized_item_kind() -> &'static str {
 941        "Editor"
 942    }
 943
 944    fn cleanup(
 945        workspace_id: WorkspaceId,
 946        alive_items: Vec<ItemId>,
 947        cx: &mut WindowContext,
 948    ) -> Task<Result<()>> {
 949        cx.spawn(|_| DB.delete_unloaded_items(workspace_id, alive_items))
 950    }
 951
 952    fn deserialize(
 953        project: Model<Project>,
 954        workspace: WeakView<Workspace>,
 955        workspace_id: workspace::WorkspaceId,
 956        item_id: ItemId,
 957        cx: &mut WindowContext,
 958    ) -> Task<Result<View<Self>>> {
 959        let serialized_editor = match DB
 960            .get_serialized_editor(item_id, workspace_id)
 961            .context("Failed to query editor state")
 962        {
 963            Ok(Some(serialized_editor)) => {
 964                if ProjectSettings::get_global(cx)
 965                    .session
 966                    .restore_unsaved_buffers
 967                {
 968                    serialized_editor
 969                } else {
 970                    SerializedEditor {
 971                        abs_path: serialized_editor.abs_path,
 972                        contents: None,
 973                        language: None,
 974                        mtime: None,
 975                    }
 976                }
 977            }
 978            Ok(None) => {
 979                return Task::ready(Err(anyhow!("No path or contents found for buffer")));
 980            }
 981            Err(error) => {
 982                return Task::ready(Err(error));
 983            }
 984        };
 985
 986        match serialized_editor {
 987            SerializedEditor {
 988                abs_path: None,
 989                contents: Some(contents),
 990                language,
 991                ..
 992            } => cx.spawn(|mut cx| {
 993                let project = project.clone();
 994                async move {
 995                    let language = if let Some(language_name) = language {
 996                        let language_registry =
 997                            project.update(&mut cx, |project, _| project.languages().clone())?;
 998
 999                        // We don't fail here, because we'd rather not set the language if the name changed
1000                        // than fail to restore the buffer.
1001                        language_registry
1002                            .language_for_name(&language_name)
1003                            .await
1004                            .ok()
1005                    } else {
1006                        None
1007                    };
1008
1009                    // First create the empty buffer
1010                    let buffer = project
1011                        .update(&mut cx, |project, cx| project.create_buffer(cx))?
1012                        .await?;
1013
1014                    // Then set the text so that the dirty bit is set correctly
1015                    buffer.update(&mut cx, |buffer, cx| {
1016                        if let Some(language) = language {
1017                            buffer.set_language(Some(language), cx);
1018                        }
1019                        buffer.set_text(contents, cx);
1020                    })?;
1021
1022                    cx.update(|cx| {
1023                        cx.new_view(|cx| {
1024                            let mut editor = Editor::for_buffer(buffer, Some(project), cx);
1025
1026                            editor.read_scroll_position_from_db(item_id, workspace_id, cx);
1027                            editor
1028                        })
1029                    })
1030                }
1031            }),
1032            SerializedEditor {
1033                abs_path: Some(abs_path),
1034                contents,
1035                mtime,
1036                ..
1037            } => {
1038                let project_item = project.update(cx, |project, cx| {
1039                    let (worktree, path) = project.find_worktree(&abs_path, cx)?;
1040                    let project_path = ProjectPath {
1041                        worktree_id: worktree.read(cx).id(),
1042                        path: path.into(),
1043                    };
1044                    Some(project.open_path(project_path, cx))
1045                });
1046
1047                match project_item {
1048                    Some(project_item) => {
1049                        cx.spawn(|mut cx| async move {
1050                            let (_, project_item) = project_item.await?;
1051                            let buffer = project_item.downcast::<Buffer>().map_err(|_| {
1052                                anyhow!("Project item at stored path was not a buffer")
1053                            })?;
1054
1055                            // This is a bit wasteful: we're loading the whole buffer from
1056                            // disk and then overwrite the content.
1057                            // But for now, it keeps the implementation of the content serialization
1058                            // simple, because we don't have to persist all of the metadata that we get
1059                            // by loading the file (git diff base, ...).
1060                            if let Some(buffer_text) = contents {
1061                                buffer.update(&mut cx, |buffer, cx| {
1062                                    // If we did restore an mtime, we want to store it on the buffer
1063                                    // so that the next edit will mark the buffer as dirty/conflicted.
1064                                    if mtime.is_some() {
1065                                        buffer.did_reload(
1066                                            buffer.version(),
1067                                            buffer.line_ending(),
1068                                            mtime,
1069                                            cx,
1070                                        );
1071                                    }
1072                                    buffer.set_text(buffer_text, cx);
1073                                })?;
1074                            }
1075
1076                            cx.update(|cx| {
1077                                cx.new_view(|cx| {
1078                                    let mut editor = Editor::for_buffer(buffer, Some(project), cx);
1079
1080                                    editor.read_scroll_position_from_db(item_id, workspace_id, cx);
1081                                    editor
1082                                })
1083                            })
1084                        })
1085                    }
1086                    None => {
1087                        let open_by_abs_path = workspace.update(cx, |workspace, cx| {
1088                            workspace.open_abs_path(abs_path.clone(), false, cx)
1089                        });
1090                        cx.spawn(|mut cx| async move {
1091                            let editor = open_by_abs_path?.await?.downcast::<Editor>().with_context(|| format!("Failed to downcast to Editor after opening abs path {abs_path:?}"))?;
1092                            editor.update(&mut cx, |editor, cx| {
1093                                editor.read_scroll_position_from_db(item_id, workspace_id, cx);
1094                            })?;
1095                            Ok(editor)
1096                        })
1097                    }
1098                }
1099            }
1100            SerializedEditor {
1101                abs_path: None,
1102                contents: None,
1103                ..
1104            } => Task::ready(Err(anyhow!("No path or contents found for buffer"))),
1105        }
1106    }
1107
1108    fn serialize(
1109        &mut self,
1110        workspace: &mut Workspace,
1111        item_id: ItemId,
1112        closing: bool,
1113        cx: &mut ViewContext<Self>,
1114    ) -> Option<Task<Result<()>>> {
1115        let mut serialize_dirty_buffers = self.serialize_dirty_buffers;
1116
1117        let project = self.project.clone()?;
1118        if project.read(cx).visible_worktrees(cx).next().is_none() {
1119            // If we don't have a worktree, we don't serialize, because
1120            // projects without worktrees aren't deserialized.
1121            serialize_dirty_buffers = false;
1122        }
1123
1124        if closing && !serialize_dirty_buffers {
1125            return None;
1126        }
1127
1128        let workspace_id = workspace.database_id()?;
1129
1130        let buffer = self.buffer().read(cx).as_singleton()?;
1131
1132        let abs_path = buffer.read(cx).file().and_then(|file| {
1133            let worktree_id = file.worktree_id(cx);
1134            project
1135                .read(cx)
1136                .worktree_for_id(worktree_id, cx)
1137                .and_then(|worktree| worktree.read(cx).absolutize(&file.path()).ok())
1138                .or_else(|| {
1139                    let full_path = file.full_path(cx);
1140                    let project_path = project.read(cx).find_project_path(&full_path, cx)?;
1141                    project.read(cx).absolute_path(&project_path, cx)
1142                })
1143        });
1144
1145        let is_dirty = buffer.read(cx).is_dirty();
1146        let mtime = buffer.read(cx).saved_mtime();
1147
1148        let snapshot = buffer.read(cx).snapshot();
1149
1150        Some(cx.spawn(|_this, cx| async move {
1151            cx.background_executor()
1152                .spawn(async move {
1153                    let (contents, language) = if serialize_dirty_buffers && is_dirty {
1154                        let contents = snapshot.text();
1155                        let language = snapshot.language().map(|lang| lang.name().to_string());
1156                        (Some(contents), language)
1157                    } else {
1158                        (None, None)
1159                    };
1160
1161                    let editor = SerializedEditor {
1162                        abs_path,
1163                        contents,
1164                        language,
1165                        mtime,
1166                    };
1167
1168                    DB.save_serialized_editor(item_id, workspace_id, editor)
1169                        .await
1170                        .context("failed to save serialized editor")
1171                })
1172                .await
1173                .context("failed to save contents of buffer")?;
1174
1175            Ok(())
1176        }))
1177    }
1178
1179    fn should_serialize(&self, event: &Self::Event) -> bool {
1180        matches!(
1181            event,
1182            EditorEvent::Saved | EditorEvent::DirtyChanged | EditorEvent::BufferEdited
1183        )
1184    }
1185}
1186
1187impl ProjectItem for Editor {
1188    type Item = Buffer;
1189
1190    fn for_project_item(
1191        project: Model<Project>,
1192        buffer: Model<Buffer>,
1193        cx: &mut ViewContext<Self>,
1194    ) -> Self {
1195        Self::for_buffer(buffer, Some(project), cx)
1196    }
1197}
1198
1199impl EventEmitter<SearchEvent> for Editor {}
1200
1201pub(crate) enum BufferSearchHighlights {}
1202impl SearchableItem for Editor {
1203    type Match = Range<Anchor>;
1204
1205    fn get_matches(&self, _: &mut WindowContext) -> Vec<Range<Anchor>> {
1206        self.background_highlights
1207            .get(&TypeId::of::<BufferSearchHighlights>())
1208            .map_or(Vec::new(), |(_color, ranges)| {
1209                ranges.iter().cloned().collect()
1210            })
1211    }
1212
1213    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
1214        if self
1215            .clear_background_highlights::<BufferSearchHighlights>(cx)
1216            .is_some()
1217        {
1218            cx.emit(SearchEvent::MatchesInvalidated);
1219        }
1220    }
1221
1222    fn update_matches(&mut self, matches: &[Range<Anchor>], cx: &mut ViewContext<Self>) {
1223        let existing_range = self
1224            .background_highlights
1225            .get(&TypeId::of::<BufferSearchHighlights>())
1226            .map(|(_, range)| range.as_ref());
1227        let updated = existing_range != Some(matches);
1228        self.highlight_background::<BufferSearchHighlights>(
1229            matches,
1230            |theme| theme.search_match_background,
1231            cx,
1232        );
1233        if updated {
1234            cx.emit(SearchEvent::MatchesInvalidated);
1235        }
1236    }
1237
1238    fn has_filtered_search_ranges(&mut self) -> bool {
1239        self.has_background_highlights::<SearchWithinRange>()
1240    }
1241
1242    fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut ViewContext<Self>) {
1243        if self.has_filtered_search_ranges() {
1244            self.previous_search_ranges = self
1245                .clear_background_highlights::<SearchWithinRange>(cx)
1246                .map(|(_, ranges)| ranges)
1247        }
1248
1249        if !enabled {
1250            return;
1251        }
1252
1253        let ranges = self.selections.disjoint_anchor_ranges();
1254        if ranges.iter().any(|range| range.start != range.end) {
1255            self.set_search_within_ranges(&ranges, cx);
1256        } else if let Some(previous_search_ranges) = self.previous_search_ranges.take() {
1257            self.set_search_within_ranges(&previous_search_ranges, cx)
1258        }
1259    }
1260
1261    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
1262        let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
1263        let snapshot = &self.snapshot(cx).buffer_snapshot;
1264        let selection = self.selections.newest::<usize>(cx);
1265
1266        match setting {
1267            SeedQuerySetting::Never => String::new(),
1268            SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
1269                let text: String = snapshot
1270                    .text_for_range(selection.start..selection.end)
1271                    .collect();
1272                if text.contains('\n') {
1273                    String::new()
1274                } else {
1275                    text
1276                }
1277            }
1278            SeedQuerySetting::Selection => String::new(),
1279            SeedQuerySetting::Always => {
1280                let (range, kind) = snapshot.surrounding_word(selection.start, true);
1281                if kind == Some(CharKind::Word) {
1282                    let text: String = snapshot.text_for_range(range).collect();
1283                    if !text.trim().is_empty() {
1284                        return text;
1285                    }
1286                }
1287                String::new()
1288            }
1289        }
1290    }
1291
1292    fn activate_match(
1293        &mut self,
1294        index: usize,
1295        matches: &[Range<Anchor>],
1296        cx: &mut ViewContext<Self>,
1297    ) {
1298        self.unfold_ranges(&[matches[index].clone()], false, true, cx);
1299        let range = self.range_for_match(&matches[index]);
1300        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
1301            s.select_ranges([range]);
1302        })
1303    }
1304
1305    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
1306        self.unfold_ranges(matches, false, false, cx);
1307        let mut ranges = Vec::new();
1308        for m in matches {
1309            ranges.push(self.range_for_match(m))
1310        }
1311        self.change_selections(None, cx, |s| s.select_ranges(ranges));
1312    }
1313    fn replace(
1314        &mut self,
1315        identifier: &Self::Match,
1316        query: &SearchQuery,
1317        cx: &mut ViewContext<Self>,
1318    ) {
1319        let text = self.buffer.read(cx);
1320        let text = text.snapshot(cx);
1321        let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>();
1322        let text: Cow<_> = if text.len() == 1 {
1323            text.first().cloned().unwrap().into()
1324        } else {
1325            let joined_chunks = text.join("");
1326            joined_chunks.into()
1327        };
1328
1329        if let Some(replacement) = query.replacement_for(&text) {
1330            self.transact(cx, |this, cx| {
1331                this.edit([(identifier.clone(), Arc::from(&*replacement))], cx);
1332            });
1333        }
1334    }
1335    fn replace_all(
1336        &mut self,
1337        matches: &mut dyn Iterator<Item = &Self::Match>,
1338        query: &SearchQuery,
1339        cx: &mut ViewContext<Self>,
1340    ) {
1341        let text = self.buffer.read(cx);
1342        let text = text.snapshot(cx);
1343        let mut edits = vec![];
1344        for m in matches {
1345            let text = text.text_for_range(m.clone()).collect::<Vec<_>>();
1346            let text: Cow<_> = if text.len() == 1 {
1347                text.first().cloned().unwrap().into()
1348            } else {
1349                let joined_chunks = text.join("");
1350                joined_chunks.into()
1351            };
1352
1353            if let Some(replacement) = query.replacement_for(&text) {
1354                edits.push((m.clone(), Arc::from(&*replacement)));
1355            }
1356        }
1357
1358        if !edits.is_empty() {
1359            self.transact(cx, |this, cx| {
1360                this.edit(edits, cx);
1361            });
1362        }
1363    }
1364    fn match_index_for_direction(
1365        &mut self,
1366        matches: &[Range<Anchor>],
1367        current_index: usize,
1368        direction: Direction,
1369        count: usize,
1370        cx: &mut ViewContext<Self>,
1371    ) -> usize {
1372        let buffer = self.buffer().read(cx).snapshot(cx);
1373        let current_index_position = if self.selections.disjoint_anchors().len() == 1 {
1374            self.selections.newest_anchor().head()
1375        } else {
1376            matches[current_index].start
1377        };
1378
1379        let mut count = count % matches.len();
1380        if count == 0 {
1381            return current_index;
1382        }
1383        match direction {
1384            Direction::Next => {
1385                if matches[current_index]
1386                    .start
1387                    .cmp(&current_index_position, &buffer)
1388                    .is_gt()
1389                {
1390                    count -= 1
1391                }
1392
1393                (current_index + count) % matches.len()
1394            }
1395            Direction::Prev => {
1396                if matches[current_index]
1397                    .end
1398                    .cmp(&current_index_position, &buffer)
1399                    .is_lt()
1400                {
1401                    count -= 1;
1402                }
1403
1404                if current_index >= count {
1405                    current_index - count
1406                } else {
1407                    matches.len() - (count - current_index)
1408                }
1409            }
1410        }
1411    }
1412
1413    fn find_matches(
1414        &mut self,
1415        query: Arc<project::search::SearchQuery>,
1416        cx: &mut ViewContext<Self>,
1417    ) -> Task<Vec<Range<Anchor>>> {
1418        let buffer = self.buffer().read(cx).snapshot(cx);
1419        let search_within_ranges = self
1420            .background_highlights
1421            .get(&TypeId::of::<SearchWithinRange>())
1422            .map_or(vec![], |(_color, ranges)| {
1423                ranges.iter().cloned().collect::<Vec<_>>()
1424            });
1425
1426        cx.background_executor().spawn(async move {
1427            let mut ranges = Vec::new();
1428
1429            if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
1430                let search_within_ranges = if search_within_ranges.is_empty() {
1431                    vec![None]
1432                } else {
1433                    search_within_ranges
1434                        .into_iter()
1435                        .map(|range| Some(range.to_offset(&buffer)))
1436                        .collect::<Vec<_>>()
1437                };
1438
1439                for range in search_within_ranges {
1440                    let buffer = &buffer;
1441                    ranges.extend(
1442                        query
1443                            .search(excerpt_buffer, range.clone())
1444                            .await
1445                            .into_iter()
1446                            .map(|matched_range| {
1447                                let offset = range.clone().map(|r| r.start).unwrap_or(0);
1448                                buffer.anchor_after(matched_range.start + offset)
1449                                    ..buffer.anchor_before(matched_range.end + offset)
1450                            }),
1451                    );
1452                }
1453            } else {
1454                let search_within_ranges = if search_within_ranges.is_empty() {
1455                    vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]
1456                } else {
1457                    search_within_ranges
1458                };
1459
1460                for (excerpt_id, search_buffer, search_range) in
1461                    buffer.excerpts_in_ranges(search_within_ranges)
1462                {
1463                    if !search_range.is_empty() {
1464                        ranges.extend(
1465                            query
1466                                .search(search_buffer, Some(search_range.clone()))
1467                                .await
1468                                .into_iter()
1469                                .map(|match_range| {
1470                                    let start = search_buffer
1471                                        .anchor_after(search_range.start + match_range.start);
1472                                    let end = search_buffer
1473                                        .anchor_before(search_range.start + match_range.end);
1474                                    buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
1475                                        ..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
1476                                }),
1477                        );
1478                    }
1479                }
1480            };
1481
1482            ranges
1483        })
1484    }
1485
1486    fn active_match_index(
1487        &mut self,
1488        matches: &[Range<Anchor>],
1489        cx: &mut ViewContext<Self>,
1490    ) -> Option<usize> {
1491        active_match_index(
1492            matches,
1493            &self.selections.newest_anchor().head(),
1494            &self.buffer().read(cx).snapshot(cx),
1495        )
1496    }
1497
1498    fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext<Self>) {
1499        self.expect_bounds_change = self.last_bounds;
1500    }
1501}
1502
1503pub fn active_match_index(
1504    ranges: &[Range<Anchor>],
1505    cursor: &Anchor,
1506    buffer: &MultiBufferSnapshot,
1507) -> Option<usize> {
1508    if ranges.is_empty() {
1509        None
1510    } else {
1511        match ranges.binary_search_by(|probe| {
1512            if probe.end.cmp(cursor, buffer).is_lt() {
1513                Ordering::Less
1514            } else if probe.start.cmp(cursor, buffer).is_gt() {
1515                Ordering::Greater
1516            } else {
1517                Ordering::Equal
1518            }
1519        }) {
1520            Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
1521        }
1522    }
1523}
1524
1525pub fn entry_label_color(selected: bool) -> Color {
1526    if selected {
1527        Color::Default
1528    } else {
1529        Color::Muted
1530    }
1531}
1532
1533pub fn entry_diagnostic_aware_icon_name_and_color(
1534    diagnostic_severity: Option<DiagnosticSeverity>,
1535) -> Option<(IconName, Color)> {
1536    match diagnostic_severity {
1537        Some(DiagnosticSeverity::ERROR) => Some((IconName::X, Color::Error)),
1538        Some(DiagnosticSeverity::WARNING) => Some((IconName::Triangle, Color::Warning)),
1539        _ => None,
1540    }
1541}
1542
1543pub fn entry_diagnostic_aware_icon_decoration_and_color(
1544    diagnostic_severity: Option<DiagnosticSeverity>,
1545) -> Option<(IconDecorationKind, Color)> {
1546    match diagnostic_severity {
1547        Some(DiagnosticSeverity::ERROR) => Some((IconDecorationKind::X, Color::Error)),
1548        Some(DiagnosticSeverity::WARNING) => Some((IconDecorationKind::Triangle, Color::Warning)),
1549        _ => None,
1550    }
1551}
1552
1553pub fn entry_git_aware_label_color(
1554    git_status: Option<GitFileStatus>,
1555    ignored: bool,
1556    selected: bool,
1557) -> Color {
1558    if ignored {
1559        Color::Ignored
1560    } else {
1561        match git_status {
1562            Some(GitFileStatus::Added) => Color::Created,
1563            Some(GitFileStatus::Modified) => Color::Modified,
1564            Some(GitFileStatus::Conflict) => Color::Conflict,
1565            None => entry_label_color(selected),
1566        }
1567    }
1568}
1569
1570fn path_for_buffer<'a>(
1571    buffer: &Model<MultiBuffer>,
1572    height: usize,
1573    include_filename: bool,
1574    cx: &'a AppContext,
1575) -> Option<Cow<'a, Path>> {
1576    let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
1577    path_for_file(file.as_ref(), height, include_filename, cx)
1578}
1579
1580fn path_for_file<'a>(
1581    file: &'a dyn language::File,
1582    mut height: usize,
1583    include_filename: bool,
1584    cx: &'a AppContext,
1585) -> Option<Cow<'a, Path>> {
1586    // Ensure we always render at least the filename.
1587    height += 1;
1588
1589    let mut prefix = file.path().as_ref();
1590    while height > 0 {
1591        if let Some(parent) = prefix.parent() {
1592            prefix = parent;
1593            height -= 1;
1594        } else {
1595            break;
1596        }
1597    }
1598
1599    // Here we could have just always used `full_path`, but that is very
1600    // allocation-heavy and so we try to use a `Cow<Path>` if we haven't
1601    // traversed all the way up to the worktree's root.
1602    if height > 0 {
1603        let full_path = file.full_path(cx);
1604        if include_filename {
1605            Some(full_path.into())
1606        } else {
1607            Some(full_path.parent()?.to_path_buf().into())
1608        }
1609    } else {
1610        let mut path = file.path().strip_prefix(prefix).ok()?;
1611        if !include_filename {
1612            path = path.parent()?;
1613        }
1614        Some(path.into())
1615    }
1616}
1617
1618#[cfg(test)]
1619mod tests {
1620    use crate::editor_tests::init_test;
1621    use fs::Fs;
1622
1623    use super::*;
1624    use fs::MTime;
1625    use gpui::{AppContext, VisualTestContext};
1626    use language::{LanguageMatcher, TestFile};
1627    use project::FakeFs;
1628    use std::path::{Path, PathBuf};
1629
1630    #[gpui::test]
1631    fn test_path_for_file(cx: &mut AppContext) {
1632        let file = TestFile {
1633            path: Path::new("").into(),
1634            root_name: String::new(),
1635        };
1636        assert_eq!(path_for_file(&file, 0, false, cx), None);
1637    }
1638
1639    async fn deserialize_editor(
1640        item_id: ItemId,
1641        workspace_id: WorkspaceId,
1642        workspace: View<Workspace>,
1643        project: Model<Project>,
1644        cx: &mut VisualTestContext,
1645    ) -> View<Editor> {
1646        workspace
1647            .update(cx, |workspace, cx| {
1648                let pane = workspace.active_pane();
1649                pane.update(cx, |_, cx| {
1650                    Editor::deserialize(
1651                        project.clone(),
1652                        workspace.weak_handle(),
1653                        workspace_id,
1654                        item_id,
1655                        cx,
1656                    )
1657                })
1658            })
1659            .await
1660            .unwrap()
1661    }
1662
1663    fn rust_language() -> Arc<language::Language> {
1664        Arc::new(language::Language::new(
1665            language::LanguageConfig {
1666                name: "Rust".into(),
1667                matcher: LanguageMatcher {
1668                    path_suffixes: vec!["rs".to_string()],
1669                    ..Default::default()
1670                },
1671                ..Default::default()
1672            },
1673            Some(tree_sitter_rust::LANGUAGE.into()),
1674        ))
1675    }
1676
1677    #[gpui::test]
1678    async fn test_deserialize(cx: &mut gpui::TestAppContext) {
1679        init_test(cx, |_| {});
1680
1681        let fs = FakeFs::new(cx.executor());
1682        fs.insert_file("/file.rs", Default::default()).await;
1683
1684        // Test case 1: Deserialize with path and contents
1685        {
1686            let project = Project::test(fs.clone(), ["/file.rs".as_ref()], cx).await;
1687            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
1688            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1689            let item_id = 1234 as ItemId;
1690            let mtime = fs
1691                .metadata(Path::new("/file.rs"))
1692                .await
1693                .unwrap()
1694                .unwrap()
1695                .mtime;
1696
1697            let serialized_editor = SerializedEditor {
1698                abs_path: Some(PathBuf::from("/file.rs")),
1699                contents: Some("fn main() {}".to_string()),
1700                language: Some("Rust".to_string()),
1701                mtime: Some(mtime),
1702            };
1703
1704            DB.save_serialized_editor(item_id, workspace_id, serialized_editor.clone())
1705                .await
1706                .unwrap();
1707
1708            let deserialized =
1709                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1710
1711            deserialized.update(cx, |editor, cx| {
1712                assert_eq!(editor.text(cx), "fn main() {}");
1713                assert!(editor.is_dirty(cx));
1714                assert!(!editor.has_conflict(cx));
1715                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1716                assert!(buffer.file().is_some());
1717            });
1718        }
1719
1720        // Test case 2: Deserialize with only path
1721        {
1722            let project = Project::test(fs.clone(), ["/file.rs".as_ref()], cx).await;
1723            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
1724
1725            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1726
1727            let item_id = 5678 as ItemId;
1728            let serialized_editor = SerializedEditor {
1729                abs_path: Some(PathBuf::from("/file.rs")),
1730                contents: None,
1731                language: None,
1732                mtime: None,
1733            };
1734
1735            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1736                .await
1737                .unwrap();
1738
1739            let deserialized =
1740                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1741
1742            deserialized.update(cx, |editor, cx| {
1743                assert_eq!(editor.text(cx), ""); // The file should be empty as per our initial setup
1744                assert!(!editor.is_dirty(cx));
1745                assert!(!editor.has_conflict(cx));
1746
1747                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1748                assert!(buffer.file().is_some());
1749            });
1750        }
1751
1752        // Test case 3: Deserialize with no path (untitled buffer, with content and language)
1753        {
1754            let project = Project::test(fs.clone(), ["/file.rs".as_ref()], cx).await;
1755            // Add Rust to the language, so that we can restore the language of the buffer
1756            project.update(cx, |project, _| project.languages().add(rust_language()));
1757
1758            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
1759
1760            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1761
1762            let item_id = 9012 as ItemId;
1763            let serialized_editor = SerializedEditor {
1764                abs_path: None,
1765                contents: Some("hello".to_string()),
1766                language: Some("Rust".to_string()),
1767                mtime: None,
1768            };
1769
1770            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1771                .await
1772                .unwrap();
1773
1774            let deserialized =
1775                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1776
1777            deserialized.update(cx, |editor, cx| {
1778                assert_eq!(editor.text(cx), "hello");
1779                assert!(editor.is_dirty(cx)); // The editor should be dirty for an untitled buffer
1780
1781                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1782                assert_eq!(
1783                    buffer.language().map(|lang| lang.name()),
1784                    Some("Rust".into())
1785                ); // Language should be set to Rust
1786                assert!(buffer.file().is_none()); // The buffer should not have an associated file
1787            });
1788        }
1789
1790        // Test case 4: Deserialize with path, content, and old mtime
1791        {
1792            let project = Project::test(fs.clone(), ["/file.rs".as_ref()], cx).await;
1793            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
1794
1795            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1796
1797            let item_id = 9345 as ItemId;
1798            let old_mtime = MTime::from_seconds_and_nanos(0, 50);
1799            let serialized_editor = SerializedEditor {
1800                abs_path: Some(PathBuf::from("/file.rs")),
1801                contents: Some("fn main() {}".to_string()),
1802                language: Some("Rust".to_string()),
1803                mtime: Some(old_mtime),
1804            };
1805
1806            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1807                .await
1808                .unwrap();
1809
1810            let deserialized =
1811                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1812
1813            deserialized.update(cx, |editor, cx| {
1814                assert_eq!(editor.text(cx), "fn main() {}");
1815                assert!(editor.has_conflict(cx)); // The editor should have a conflict
1816            });
1817        }
1818    }
1819}