items.rs

   1use crate::{
   2    editor_settings::SeedQuerySetting,
   3    persistence::{SerializedEditor, DB},
   4    scroll::ScrollAnchor,
   5    Anchor, Autoscroll, Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, FormatTarget,
   6    MultiBuffer, 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::status::GitSummary;
  13use gpui::{
  14    point, AnyElement, App, AsyncWindowContext, Context, Entity, EntityId, EventEmitter,
  15    IntoElement, ParentElement, Pixels, SharedString, Styled, Task, WeakEntity, Window,
  16};
  17use language::{
  18    proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, DiskState, Point,
  19    SelectionGoal,
  20};
  21use lsp::DiagnosticSeverity;
  22use project::{
  23    lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Project,
  24    ProjectItem as _, ProjectPath,
  25};
  26use rpc::proto::{self, update_view, PeerId};
  27use settings::Settings;
  28use std::{
  29    any::TypeId,
  30    borrow::Cow,
  31    cmp::{self, Ordering},
  32    iter,
  33    ops::Range,
  34    path::Path,
  35    sync::Arc,
  36};
  37use text::{BufferId, Selection};
  38use theme::{Theme, ThemeSettings};
  39use ui::{prelude::*, IconDecorationKind};
  40use util::{paths::PathExt, ResultExt, TryFutureExt};
  41use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
  42use workspace::{
  43    item::{BreadcrumbText, FollowEvent},
  44    searchable::SearchOptions,
  45};
  46use workspace::{
  47    item::{FollowableItem, Item, ItemEvent, ProjectItem},
  48    searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
  49    ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
  50};
  51
  52pub const MAX_TAB_TITLE_LEN: usize = 24;
  53
  54impl FollowableItem for Editor {
  55    fn remote_id(&self) -> Option<ViewId> {
  56        self.remote_id
  57    }
  58
  59    fn from_state_proto(
  60        workspace: Entity<Workspace>,
  61        remote_id: ViewId,
  62        state: &mut Option<proto::view::Variant>,
  63        window: &mut Window,
  64        cx: &mut App,
  65    ) -> Option<Task<Result<Entity<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(window.spawn(cx, |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(|window, cx| {
  92                let multibuffer = cx.new(|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(|cx| {
 128                    let mut editor = Editor::for_multibuffer(
 129                        multibuffer,
 130                        Some(project.clone()),
 131                        true,
 132                        window,
 133                        cx,
 134                    );
 135                    editor.remote_id = Some(remote_id);
 136                    editor
 137                })
 138            })?;
 139
 140            update_editor_from_message(
 141                editor.downgrade(),
 142                project,
 143                proto::update_view::Editor {
 144                    selections: state.selections,
 145                    pending_selection: state.pending_selection,
 146                    scroll_top_anchor: state.scroll_top_anchor,
 147                    scroll_x: state.scroll_x,
 148                    scroll_y: state.scroll_y,
 149                    ..Default::default()
 150                },
 151                &mut cx,
 152            )
 153            .await?;
 154
 155            Ok(editor)
 156        }))
 157    }
 158
 159    fn set_leader_peer_id(
 160        &mut self,
 161        leader_peer_id: Option<PeerId>,
 162        window: &mut Window,
 163        cx: &mut Context<Self>,
 164    ) {
 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(window) {
 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, _: &Window, cx: &App) -> 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        _: &Window,
 254        cx: &App,
 255    ) -> bool {
 256        let update =
 257            update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
 258
 259        match update {
 260            proto::update_view::Variant::Editor(update) => match event {
 261                EditorEvent::ExcerptsAdded {
 262                    buffer,
 263                    predecessor,
 264                    excerpts,
 265                } => {
 266                    let buffer_id = buffer.read(cx).remote_id();
 267                    let mut excerpts = excerpts.iter();
 268                    if let Some((id, range)) = excerpts.next() {
 269                        update.inserted_excerpts.push(proto::ExcerptInsertion {
 270                            previous_excerpt_id: Some(predecessor.to_proto()),
 271                            excerpt: serialize_excerpt(buffer_id, id, range),
 272                        });
 273                        update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
 274                            proto::ExcerptInsertion {
 275                                previous_excerpt_id: None,
 276                                excerpt: serialize_excerpt(buffer_id, id, range),
 277                            }
 278                        }))
 279                    }
 280                    true
 281                }
 282                EditorEvent::ExcerptsRemoved { ids } => {
 283                    update
 284                        .deleted_excerpts
 285                        .extend(ids.iter().map(ExcerptId::to_proto));
 286                    true
 287                }
 288                EditorEvent::ScrollPositionChanged { autoscroll, .. } if !autoscroll => {
 289                    let scroll_anchor = self.scroll_manager.anchor();
 290                    update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor));
 291                    update.scroll_x = scroll_anchor.offset.x;
 292                    update.scroll_y = scroll_anchor.offset.y;
 293                    true
 294                }
 295                EditorEvent::SelectionsChanged { .. } => {
 296                    update.selections = self
 297                        .selections
 298                        .disjoint_anchors()
 299                        .iter()
 300                        .map(serialize_selection)
 301                        .collect();
 302                    update.pending_selection = self
 303                        .selections
 304                        .pending_anchor()
 305                        .as_ref()
 306                        .map(serialize_selection);
 307                    true
 308                }
 309                _ => false,
 310            },
 311        }
 312    }
 313
 314    fn apply_update_proto(
 315        &mut self,
 316        project: &Entity<Project>,
 317        message: update_view::Variant,
 318        window: &mut Window,
 319        cx: &mut Context<Self>,
 320    ) -> Task<Result<()>> {
 321        let update_view::Variant::Editor(message) = message;
 322        let project = project.clone();
 323        cx.spawn_in(window, |this, mut cx| async move {
 324            update_editor_from_message(this, project, message, &mut cx).await
 325        })
 326    }
 327
 328    fn is_project_item(&self, _window: &Window, _cx: &App) -> bool {
 329        true
 330    }
 331
 332    fn dedup(&self, existing: &Self, _: &Window, cx: &App) -> Option<Dedup> {
 333        let self_singleton = self.buffer.read(cx).as_singleton()?;
 334        let other_singleton = existing.buffer.read(cx).as_singleton()?;
 335        if self_singleton == other_singleton {
 336            Some(Dedup::KeepExisting)
 337        } else {
 338            None
 339        }
 340    }
 341}
 342
 343async fn update_editor_from_message(
 344    this: WeakEntity<Editor>,
 345    project: Entity<Project>,
 346    message: proto::update_view::Editor,
 347    cx: &mut AsyncWindowContext,
 348) -> Result<()> {
 349    // Open all of the buffers of which excerpts were added to the editor.
 350    let inserted_excerpt_buffer_ids = message
 351        .inserted_excerpts
 352        .iter()
 353        .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
 354        .collect::<HashSet<_>>();
 355    let inserted_excerpt_buffers = project.update(cx, |project, cx| {
 356        inserted_excerpt_buffer_ids
 357            .into_iter()
 358            .map(|id| BufferId::new(id).map(|id| project.open_buffer_by_id(id, cx)))
 359            .collect::<Result<Vec<_>>>()
 360    })??;
 361    let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
 362
 363    // Update the editor's excerpts.
 364    this.update(cx, |editor, cx| {
 365        editor.buffer.update(cx, |multibuffer, cx| {
 366            let mut removed_excerpt_ids = message
 367                .deleted_excerpts
 368                .into_iter()
 369                .map(ExcerptId::from_proto)
 370                .collect::<Vec<_>>();
 371            removed_excerpt_ids.sort_by({
 372                let multibuffer = multibuffer.read(cx);
 373                move |a, b| a.cmp(b, &multibuffer)
 374            });
 375
 376            let mut insertions = message.inserted_excerpts.into_iter().peekable();
 377            while let Some(insertion) = insertions.next() {
 378                let Some(excerpt) = insertion.excerpt else {
 379                    continue;
 380                };
 381                let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
 382                    continue;
 383                };
 384                let buffer_id = BufferId::new(excerpt.buffer_id)?;
 385                let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
 386                    continue;
 387                };
 388
 389                let adjacent_excerpts = iter::from_fn(|| {
 390                    let insertion = insertions.peek()?;
 391                    if insertion.previous_excerpt_id.is_none()
 392                        && insertion.excerpt.as_ref()?.buffer_id == u64::from(buffer_id)
 393                    {
 394                        insertions.next()?.excerpt
 395                    } else {
 396                        None
 397                    }
 398                });
 399
 400                multibuffer.insert_excerpts_with_ids_after(
 401                    ExcerptId::from_proto(previous_excerpt_id),
 402                    buffer,
 403                    [excerpt]
 404                        .into_iter()
 405                        .chain(adjacent_excerpts)
 406                        .filter_map(|excerpt| {
 407                            Some((
 408                                ExcerptId::from_proto(excerpt.id),
 409                                deserialize_excerpt_range(excerpt)?,
 410                            ))
 411                        }),
 412                    cx,
 413                );
 414            }
 415
 416            multibuffer.remove_excerpts(removed_excerpt_ids, cx);
 417            Result::<(), anyhow::Error>::Ok(())
 418        })
 419    })??;
 420
 421    // Deserialize the editor state.
 422    let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
 423        let buffer = editor.buffer.read(cx).read(cx);
 424        let selections = message
 425            .selections
 426            .into_iter()
 427            .filter_map(|selection| deserialize_selection(&buffer, selection))
 428            .collect::<Vec<_>>();
 429        let pending_selection = message
 430            .pending_selection
 431            .and_then(|selection| deserialize_selection(&buffer, selection));
 432        let scroll_top_anchor = message
 433            .scroll_top_anchor
 434            .and_then(|anchor| deserialize_anchor(&buffer, anchor));
 435        anyhow::Ok((selections, pending_selection, scroll_top_anchor))
 436    })??;
 437
 438    // Wait until the buffer has received all of the operations referenced by
 439    // the editor's new state.
 440    this.update(cx, |editor, cx| {
 441        editor.buffer.update(cx, |buffer, cx| {
 442            buffer.wait_for_anchors(
 443                selections
 444                    .iter()
 445                    .chain(pending_selection.as_ref())
 446                    .flat_map(|selection| [selection.start, selection.end])
 447                    .chain(scroll_top_anchor),
 448                cx,
 449            )
 450        })
 451    })?
 452    .await?;
 453
 454    // Update the editor's state.
 455    this.update_in(cx, |editor, window, cx| {
 456        if !selections.is_empty() || pending_selection.is_some() {
 457            editor.set_selections_from_remote(selections, pending_selection, window, cx);
 458            editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
 459        } else if let Some(scroll_top_anchor) = scroll_top_anchor {
 460            editor.set_scroll_anchor_remote(
 461                ScrollAnchor {
 462                    anchor: scroll_top_anchor,
 463                    offset: point(message.scroll_x, message.scroll_y),
 464                },
 465                window,
 466                cx,
 467            );
 468        }
 469    })?;
 470    Ok(())
 471}
 472
 473fn serialize_excerpt(
 474    buffer_id: BufferId,
 475    id: &ExcerptId,
 476    range: &ExcerptRange<language::Anchor>,
 477) -> Option<proto::Excerpt> {
 478    Some(proto::Excerpt {
 479        id: id.to_proto(),
 480        buffer_id: buffer_id.into(),
 481        context_start: Some(serialize_text_anchor(&range.context.start)),
 482        context_end: Some(serialize_text_anchor(&range.context.end)),
 483        primary_start: range
 484            .primary
 485            .as_ref()
 486            .map(|r| serialize_text_anchor(&r.start)),
 487        primary_end: range
 488            .primary
 489            .as_ref()
 490            .map(|r| serialize_text_anchor(&r.end)),
 491    })
 492}
 493
 494fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
 495    proto::Selection {
 496        id: selection.id as u64,
 497        start: Some(serialize_anchor(&selection.start)),
 498        end: Some(serialize_anchor(&selection.end)),
 499        reversed: selection.reversed,
 500    }
 501}
 502
 503fn serialize_anchor(anchor: &Anchor) -> proto::EditorAnchor {
 504    proto::EditorAnchor {
 505        excerpt_id: anchor.excerpt_id.to_proto(),
 506        anchor: Some(serialize_text_anchor(&anchor.text_anchor)),
 507    }
 508}
 509
 510fn deserialize_excerpt_range(excerpt: proto::Excerpt) -> Option<ExcerptRange<language::Anchor>> {
 511    let context = {
 512        let start = language::proto::deserialize_anchor(excerpt.context_start?)?;
 513        let end = language::proto::deserialize_anchor(excerpt.context_end?)?;
 514        start..end
 515    };
 516    let primary = excerpt
 517        .primary_start
 518        .zip(excerpt.primary_end)
 519        .and_then(|(start, end)| {
 520            let start = language::proto::deserialize_anchor(start)?;
 521            let end = language::proto::deserialize_anchor(end)?;
 522            Some(start..end)
 523        });
 524    Some(ExcerptRange { context, primary })
 525}
 526
 527fn deserialize_selection(
 528    buffer: &MultiBufferSnapshot,
 529    selection: proto::Selection,
 530) -> Option<Selection<Anchor>> {
 531    Some(Selection {
 532        id: selection.id as usize,
 533        start: deserialize_anchor(buffer, selection.start?)?,
 534        end: deserialize_anchor(buffer, selection.end?)?,
 535        reversed: selection.reversed,
 536        goal: SelectionGoal::None,
 537    })
 538}
 539
 540fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor) -> Option<Anchor> {
 541    let excerpt_id = ExcerptId::from_proto(anchor.excerpt_id);
 542    Some(Anchor {
 543        excerpt_id,
 544        text_anchor: language::proto::deserialize_anchor(anchor.anchor?)?,
 545        buffer_id: buffer.buffer_id_for_excerpt(excerpt_id),
 546        diff_base_anchor: None,
 547    })
 548}
 549
 550impl Item for Editor {
 551    type Event = EditorEvent;
 552
 553    fn navigate(
 554        &mut self,
 555        data: Box<dyn std::any::Any>,
 556        window: &mut Window,
 557        cx: &mut Context<Self>,
 558    ) -> bool {
 559        if let Ok(data) = data.downcast::<NavigationData>() {
 560            let newest_selection = self.selections.newest::<Point>(cx);
 561            let buffer = self.buffer.read(cx).read(cx);
 562            let offset = if buffer.can_resolve(&data.cursor_anchor) {
 563                data.cursor_anchor.to_point(&buffer)
 564            } else {
 565                buffer.clip_point(data.cursor_position, Bias::Left)
 566            };
 567
 568            let mut scroll_anchor = data.scroll_anchor;
 569            if !buffer.can_resolve(&scroll_anchor.anchor) {
 570                scroll_anchor.anchor = buffer.anchor_before(
 571                    buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
 572                );
 573            }
 574
 575            drop(buffer);
 576
 577            if newest_selection.head() == offset {
 578                false
 579            } else {
 580                let nav_history = self.nav_history.take();
 581                self.set_scroll_anchor(scroll_anchor, window, cx);
 582                self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 583                    s.select_ranges([offset..offset])
 584                });
 585                self.nav_history = nav_history;
 586                true
 587            }
 588        } else {
 589            false
 590        }
 591    }
 592
 593    fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString> {
 594        let file_path = self
 595            .buffer()
 596            .read(cx)
 597            .as_singleton()?
 598            .read(cx)
 599            .file()
 600            .and_then(|f| f.as_local())?
 601            .abs_path(cx);
 602
 603        let file_path = file_path.compact().to_string_lossy().to_string();
 604
 605        Some(file_path.into())
 606    }
 607
 608    fn telemetry_event_text(&self) -> Option<&'static str> {
 609        None
 610    }
 611
 612    fn tab_description(&self, detail: usize, cx: &App) -> Option<SharedString> {
 613        let path = path_for_buffer(&self.buffer, detail, true, cx)?;
 614        Some(path.to_string_lossy().to_string().into())
 615    }
 616
 617    fn tab_icon(&self, _: &Window, cx: &App) -> Option<Icon> {
 618        ItemSettings::get_global(cx)
 619            .file_icons
 620            .then(|| {
 621                path_for_buffer(&self.buffer, 0, true, cx)
 622                    .and_then(|path| FileIcons::get_icon(path.as_ref(), cx))
 623            })
 624            .flatten()
 625            .map(Icon::from_path)
 626    }
 627
 628    fn tab_content(&self, params: TabContentParams, _: &Window, cx: &App) -> AnyElement {
 629        let label_color = if ItemSettings::get_global(cx).git_status {
 630            self.buffer()
 631                .read(cx)
 632                .as_singleton()
 633                .and_then(|buffer| buffer.read(cx).project_path(cx))
 634                .and_then(|path| {
 635                    let project = self.project.as_ref()?.read(cx);
 636                    let entry = project.entry_for_path(&path, cx)?;
 637                    let git_status = project
 638                        .worktree_for_id(path.worktree_id, cx)?
 639                        .read(cx)
 640                        .snapshot()
 641                        .status_for_file(path.path)?;
 642
 643                    Some(entry_git_aware_label_color(
 644                        git_status.summary(),
 645                        entry.is_ignored,
 646                        params.selected,
 647                    ))
 648                })
 649                .unwrap_or_else(|| entry_label_color(params.selected))
 650        } else {
 651            entry_label_color(params.selected)
 652        };
 653
 654        let description = params.detail.and_then(|detail| {
 655            let path = path_for_buffer(&self.buffer, detail, false, cx)?;
 656            let description = path.to_string_lossy();
 657            let description = description.trim();
 658
 659            if description.is_empty() {
 660                return None;
 661            }
 662
 663            Some(util::truncate_and_trailoff(description, MAX_TAB_TITLE_LEN))
 664        });
 665
 666        // Whether the file was saved in the past but is now deleted.
 667        let was_deleted: bool = self
 668            .buffer()
 669            .read(cx)
 670            .as_singleton()
 671            .and_then(|buffer| buffer.read(cx).file())
 672            .map_or(false, |file| file.disk_state() == DiskState::Deleted);
 673
 674        h_flex()
 675            .gap_2()
 676            .child(
 677                Label::new(self.title(cx).to_string())
 678                    .color(label_color)
 679                    .when(params.preview, |this| this.italic())
 680                    .when(was_deleted, |this| this.strikethrough()),
 681            )
 682            .when_some(description, |this, description| {
 683                this.child(
 684                    Label::new(description)
 685                        .size(LabelSize::XSmall)
 686                        .color(Color::Muted),
 687                )
 688            })
 689            .into_any_element()
 690    }
 691
 692    fn for_each_project_item(
 693        &self,
 694        cx: &App,
 695        f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
 696    ) {
 697        self.buffer
 698            .read(cx)
 699            .for_each_buffer(|buffer| f(buffer.entity_id(), buffer.read(cx)));
 700    }
 701
 702    fn is_singleton(&self, cx: &App) -> bool {
 703        self.buffer.read(cx).is_singleton()
 704    }
 705
 706    fn can_save_as(&self, cx: &App) -> bool {
 707        self.buffer.read(cx).is_singleton()
 708    }
 709
 710    fn clone_on_split(
 711        &self,
 712        _workspace_id: Option<WorkspaceId>,
 713        window: &mut Window,
 714        cx: &mut Context<Self>,
 715    ) -> Option<Entity<Editor>>
 716    where
 717        Self: Sized,
 718    {
 719        Some(cx.new(|cx| self.clone(window, cx)))
 720    }
 721
 722    fn set_nav_history(
 723        &mut self,
 724        history: ItemNavHistory,
 725        _window: &mut Window,
 726        _: &mut Context<Self>,
 727    ) {
 728        self.nav_history = Some(history);
 729    }
 730
 731    fn discarded(&self, _project: Entity<Project>, _: &mut Window, cx: &mut Context<Self>) {
 732        for buffer in self.buffer().clone().read(cx).all_buffers() {
 733            buffer.update(cx, |buffer, cx| buffer.discarded(cx))
 734        }
 735    }
 736
 737    fn deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
 738        let selection = self.selections.newest_anchor();
 739        self.push_to_nav_history(selection.head(), None, cx);
 740    }
 741
 742    fn workspace_deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
 743        self.hide_hovered_link(cx);
 744    }
 745
 746    fn is_dirty(&self, cx: &App) -> bool {
 747        self.buffer().read(cx).read(cx).is_dirty()
 748    }
 749
 750    fn has_deleted_file(&self, cx: &App) -> bool {
 751        self.buffer().read(cx).read(cx).has_deleted_file()
 752    }
 753
 754    fn has_conflict(&self, cx: &App) -> bool {
 755        self.buffer().read(cx).read(cx).has_conflict()
 756    }
 757
 758    fn can_save(&self, cx: &App) -> bool {
 759        let buffer = &self.buffer().read(cx);
 760        if let Some(buffer) = buffer.as_singleton() {
 761            buffer.read(cx).project_path(cx).is_some()
 762        } else {
 763            true
 764        }
 765    }
 766
 767    fn save(
 768        &mut self,
 769        format: bool,
 770        project: Entity<Project>,
 771        window: &mut Window,
 772        cx: &mut Context<Self>,
 773    ) -> Task<Result<()>> {
 774        self.report_editor_event("Editor Saved", None, cx);
 775        let buffers = self.buffer().clone().read(cx).all_buffers();
 776        let buffers = buffers
 777            .into_iter()
 778            .map(|handle| handle.read(cx).base_buffer().unwrap_or(handle.clone()))
 779            .collect::<HashSet<_>>();
 780        cx.spawn_in(window, |this, mut cx| async move {
 781            if format {
 782                this.update_in(&mut cx, |editor, window, cx| {
 783                    editor.perform_format(
 784                        project.clone(),
 785                        FormatTrigger::Save,
 786                        FormatTarget::Buffers,
 787                        window,
 788                        cx,
 789                    )
 790                })?
 791                .await?;
 792            }
 793
 794            if buffers.len() == 1 {
 795                // Apply full save routine for singleton buffers, to allow to `touch` the file via the editor.
 796                project
 797                    .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))?
 798                    .await?;
 799            } else {
 800                // For multi-buffers, only format and save the buffers with changes.
 801                // For clean buffers, we simulate saving by calling `Buffer::did_save`,
 802                // so that language servers or other downstream listeners of save events get notified.
 803                let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
 804                    buffer
 805                        .update(&mut cx, |buffer, _| {
 806                            buffer.is_dirty() || buffer.has_conflict()
 807                        })
 808                        .unwrap_or(false)
 809                });
 810
 811                project
 812                    .update(&mut cx, |project, cx| {
 813                        project.save_buffers(dirty_buffers, cx)
 814                    })?
 815                    .await?;
 816                for buffer in clean_buffers {
 817                    buffer
 818                        .update(&mut cx, |buffer, cx| {
 819                            let version = buffer.saved_version().clone();
 820                            let mtime = buffer.saved_mtime();
 821                            buffer.did_save(version, mtime, cx);
 822                        })
 823                        .ok();
 824                }
 825            }
 826
 827            Ok(())
 828        })
 829    }
 830
 831    fn save_as(
 832        &mut self,
 833        project: Entity<Project>,
 834        path: ProjectPath,
 835        _: &mut Window,
 836        cx: &mut Context<Self>,
 837    ) -> Task<Result<()>> {
 838        let buffer = self
 839            .buffer()
 840            .read(cx)
 841            .as_singleton()
 842            .expect("cannot call save_as on an excerpt list");
 843
 844        let file_extension = path
 845            .path
 846            .extension()
 847            .map(|a| a.to_string_lossy().to_string());
 848        self.report_editor_event("Editor Saved", file_extension, cx);
 849
 850        project.update(cx, |project, cx| project.save_buffer_as(buffer, path, cx))
 851    }
 852
 853    fn reload(
 854        &mut self,
 855        project: Entity<Project>,
 856        window: &mut Window,
 857        cx: &mut Context<Self>,
 858    ) -> Task<Result<()>> {
 859        let buffer = self.buffer().clone();
 860        let buffers = self.buffer.read(cx).all_buffers();
 861        let reload_buffers =
 862            project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx));
 863        cx.spawn_in(window, |this, mut cx| async move {
 864            let transaction = reload_buffers.log_err().await;
 865            this.update(&mut cx, |editor, cx| {
 866                editor.request_autoscroll(Autoscroll::fit(), cx)
 867            })?;
 868            buffer
 869                .update(&mut cx, |buffer, cx| {
 870                    if let Some(transaction) = transaction {
 871                        if !buffer.is_singleton() {
 872                            buffer.push_transaction(&transaction.0, cx);
 873                        }
 874                    }
 875                })
 876                .ok();
 877            Ok(())
 878        })
 879    }
 880
 881    fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 882        Some(Box::new(handle.clone()))
 883    }
 884
 885    fn pixel_position_of_cursor(&self, _: &App) -> Option<gpui::Point<Pixels>> {
 886        self.pixel_position_of_newest_cursor
 887    }
 888
 889    fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation {
 890        if self.show_breadcrumbs {
 891            ToolbarItemLocation::PrimaryLeft
 892        } else {
 893            ToolbarItemLocation::Hidden
 894        }
 895    }
 896
 897    fn breadcrumbs(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
 898        let cursor = self.selections.newest_anchor().head();
 899        let multibuffer = &self.buffer().read(cx);
 900        let (buffer_id, symbols) =
 901            multibuffer.symbols_containing(cursor, Some(variant.syntax()), cx)?;
 902        let buffer = multibuffer.buffer(buffer_id)?;
 903
 904        let buffer = buffer.read(cx);
 905        let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
 906            buffer
 907                .snapshot()
 908                .resolve_file_path(
 909                    cx,
 910                    self.project
 911                        .as_ref()
 912                        .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
 913                        .unwrap_or_default(),
 914                )
 915                .map(|path| path.to_string_lossy().to_string())
 916                .unwrap_or_else(|| {
 917                    if multibuffer.is_singleton() {
 918                        multibuffer.title(cx).to_string()
 919                    } else {
 920                        "untitled".to_string()
 921                    }
 922                })
 923        });
 924
 925        let settings = ThemeSettings::get_global(cx);
 926
 927        let mut breadcrumbs = vec![BreadcrumbText {
 928            text,
 929            highlights: None,
 930            font: Some(settings.buffer_font.clone()),
 931        }];
 932
 933        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
 934            text: symbol.text,
 935            highlights: Some(symbol.highlight_ranges),
 936            font: Some(settings.buffer_font.clone()),
 937        }));
 938        Some(breadcrumbs)
 939    }
 940
 941    fn added_to_workspace(
 942        &mut self,
 943        workspace: &mut Workspace,
 944        _window: &mut Window,
 945        _: &mut Context<Self>,
 946    ) {
 947        self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
 948    }
 949
 950    fn to_item_events(event: &EditorEvent, mut f: impl FnMut(ItemEvent)) {
 951        match event {
 952            EditorEvent::Closed => f(ItemEvent::CloseItem),
 953
 954            EditorEvent::Saved | EditorEvent::TitleChanged => {
 955                f(ItemEvent::UpdateTab);
 956                f(ItemEvent::UpdateBreadcrumbs);
 957            }
 958
 959            EditorEvent::Reparsed(_) => {
 960                f(ItemEvent::UpdateBreadcrumbs);
 961            }
 962
 963            EditorEvent::SelectionsChanged { local } if *local => {
 964                f(ItemEvent::UpdateBreadcrumbs);
 965            }
 966
 967            EditorEvent::DirtyChanged => {
 968                f(ItemEvent::UpdateTab);
 969            }
 970
 971            EditorEvent::BufferEdited => {
 972                f(ItemEvent::Edit);
 973                f(ItemEvent::UpdateBreadcrumbs);
 974            }
 975
 976            EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
 977                f(ItemEvent::Edit);
 978            }
 979
 980            _ => {}
 981        }
 982    }
 983
 984    fn preserve_preview(&self, cx: &App) -> bool {
 985        self.buffer.read(cx).preserve_preview(cx)
 986    }
 987}
 988
 989impl SerializableItem for Editor {
 990    fn serialized_item_kind() -> &'static str {
 991        "Editor"
 992    }
 993
 994    fn cleanup(
 995        workspace_id: WorkspaceId,
 996        alive_items: Vec<ItemId>,
 997        window: &mut Window,
 998        cx: &mut App,
 999    ) -> Task<Result<()>> {
1000        window.spawn(cx, |_| DB.delete_unloaded_items(workspace_id, alive_items))
1001    }
1002
1003    fn deserialize(
1004        project: Entity<Project>,
1005        workspace: WeakEntity<Workspace>,
1006        workspace_id: workspace::WorkspaceId,
1007        item_id: ItemId,
1008        window: &mut Window,
1009        cx: &mut App,
1010    ) -> Task<Result<Entity<Self>>> {
1011        let serialized_editor = match DB
1012            .get_serialized_editor(item_id, workspace_id)
1013            .context("Failed to query editor state")
1014        {
1015            Ok(Some(serialized_editor)) => {
1016                if ProjectSettings::get_global(cx)
1017                    .session
1018                    .restore_unsaved_buffers
1019                {
1020                    serialized_editor
1021                } else {
1022                    SerializedEditor {
1023                        abs_path: serialized_editor.abs_path,
1024                        contents: None,
1025                        language: None,
1026                        mtime: None,
1027                    }
1028                }
1029            }
1030            Ok(None) => {
1031                return Task::ready(Err(anyhow!("No path or contents found for buffer")));
1032            }
1033            Err(error) => {
1034                return Task::ready(Err(error));
1035            }
1036        };
1037
1038        match serialized_editor {
1039            SerializedEditor {
1040                abs_path: None,
1041                contents: Some(contents),
1042                language,
1043                ..
1044            } => window.spawn(cx, |mut cx| {
1045                let project = project.clone();
1046                async move {
1047                    let language_registry =
1048                        project.update(&mut cx, |project, _| project.languages().clone())?;
1049
1050                    let language = if let Some(language_name) = language {
1051                        // We don't fail here, because we'd rather not set the language if the name changed
1052                        // than fail to restore the buffer.
1053                        language_registry
1054                            .language_for_name(&language_name)
1055                            .await
1056                            .ok()
1057                    } else {
1058                        None
1059                    };
1060
1061                    // First create the empty buffer
1062                    let buffer = project
1063                        .update(&mut cx, |project, cx| project.create_buffer(cx))?
1064                        .await?;
1065
1066                    // Then set the text so that the dirty bit is set correctly
1067                    buffer.update(&mut cx, |buffer, cx| {
1068                        buffer.set_language_registry(language_registry);
1069                        if let Some(language) = language {
1070                            buffer.set_language(Some(language), cx);
1071                        }
1072                        buffer.set_text(contents, cx);
1073                        if let Some(entry) = buffer.peek_undo_stack() {
1074                            buffer.forget_transaction(entry.transaction_id());
1075                        }
1076                    })?;
1077
1078                    cx.update(|window, cx| {
1079                        cx.new(|cx| {
1080                            let mut editor = Editor::for_buffer(buffer, Some(project), window, cx);
1081
1082                            editor.read_selections_from_db(item_id, workspace_id, window, cx);
1083                            editor.read_scroll_position_from_db(item_id, workspace_id, window, cx);
1084                            editor
1085                        })
1086                    })
1087                }
1088            }),
1089            SerializedEditor {
1090                abs_path: Some(abs_path),
1091                contents,
1092                mtime,
1093                ..
1094            } => {
1095                let project_item = project.update(cx, |project, cx| {
1096                    let (worktree, path) = project.find_worktree(&abs_path, cx)?;
1097                    let project_path = ProjectPath {
1098                        worktree_id: worktree.read(cx).id(),
1099                        path: path.into(),
1100                    };
1101                    Some(project.open_path(project_path, cx))
1102                });
1103
1104                match project_item {
1105                    Some(project_item) => {
1106                        window.spawn(cx, |mut cx| async move {
1107                            let (_, project_item) = project_item.await?;
1108                            let buffer = project_item.downcast::<Buffer>().map_err(|_| {
1109                                anyhow!("Project item at stored path was not a buffer")
1110                            })?;
1111
1112                            // This is a bit wasteful: we're loading the whole buffer from
1113                            // disk and then overwrite the content.
1114                            // But for now, it keeps the implementation of the content serialization
1115                            // simple, because we don't have to persist all of the metadata that we get
1116                            // by loading the file (git diff base, ...).
1117                            if let Some(buffer_text) = contents {
1118                                buffer.update(&mut cx, |buffer, cx| {
1119                                    // If we did restore an mtime, we want to store it on the buffer
1120                                    // so that the next edit will mark the buffer as dirty/conflicted.
1121                                    if mtime.is_some() {
1122                                        buffer.did_reload(
1123                                            buffer.version(),
1124                                            buffer.line_ending(),
1125                                            mtime,
1126                                            cx,
1127                                        );
1128                                    }
1129                                    buffer.set_text(buffer_text, cx);
1130                                    if let Some(entry) = buffer.peek_undo_stack() {
1131                                        buffer.forget_transaction(entry.transaction_id());
1132                                    }
1133                                })?;
1134                            }
1135
1136                            cx.update(|window, cx| {
1137                                cx.new(|cx| {
1138                                    let mut editor =
1139                                        Editor::for_buffer(buffer, Some(project), window, cx);
1140
1141                                    editor.read_selections_from_db(
1142                                        item_id,
1143                                        workspace_id,
1144                                        window,
1145                                        cx,
1146                                    );
1147                                    editor.read_scroll_position_from_db(
1148                                        item_id,
1149                                        workspace_id,
1150                                        window,
1151                                        cx,
1152                                    );
1153                                    editor
1154                                })
1155                            })
1156                        })
1157                    }
1158                    None => {
1159                        let open_by_abs_path = workspace.update(cx, |workspace, cx| {
1160                            workspace.open_abs_path(abs_path.clone(), false, window, cx)
1161                        });
1162                        window.spawn(cx, |mut cx| async move {
1163                            let editor = open_by_abs_path?.await?.downcast::<Editor>().with_context(|| format!("Failed to downcast to Editor after opening abs path {abs_path:?}"))?;
1164                            editor.update_in(&mut cx, |editor, window, cx| {
1165                                editor.read_selections_from_db(item_id, workspace_id, window, cx);
1166                                editor.read_scroll_position_from_db(item_id, workspace_id, window, cx);
1167                            })?;
1168                            Ok(editor)
1169                        })
1170                    }
1171                }
1172            }
1173            SerializedEditor {
1174                abs_path: None,
1175                contents: None,
1176                ..
1177            } => Task::ready(Err(anyhow!("No path or contents found for buffer"))),
1178        }
1179    }
1180
1181    fn serialize(
1182        &mut self,
1183        workspace: &mut Workspace,
1184        item_id: ItemId,
1185        closing: bool,
1186        window: &mut Window,
1187        cx: &mut Context<Self>,
1188    ) -> Option<Task<Result<()>>> {
1189        let mut serialize_dirty_buffers = self.serialize_dirty_buffers;
1190
1191        let project = self.project.clone()?;
1192        if project.read(cx).visible_worktrees(cx).next().is_none() {
1193            // If we don't have a worktree, we don't serialize, because
1194            // projects without worktrees aren't deserialized.
1195            serialize_dirty_buffers = false;
1196        }
1197
1198        if closing && !serialize_dirty_buffers {
1199            return None;
1200        }
1201
1202        let workspace_id = workspace.database_id()?;
1203
1204        let buffer = self.buffer().read(cx).as_singleton()?;
1205
1206        let abs_path = buffer.read(cx).file().and_then(|file| {
1207            let worktree_id = file.worktree_id(cx);
1208            project
1209                .read(cx)
1210                .worktree_for_id(worktree_id, cx)
1211                .and_then(|worktree| worktree.read(cx).absolutize(&file.path()).ok())
1212                .or_else(|| {
1213                    let full_path = file.full_path(cx);
1214                    let project_path = project.read(cx).find_project_path(&full_path, cx)?;
1215                    project.read(cx).absolute_path(&project_path, cx)
1216                })
1217        });
1218
1219        let is_dirty = buffer.read(cx).is_dirty();
1220        let mtime = buffer.read(cx).saved_mtime();
1221
1222        let snapshot = buffer.read(cx).snapshot();
1223
1224        Some(cx.spawn_in(window, |_this, cx| async move {
1225            cx.background_spawn(async move {
1226                let (contents, language) = if serialize_dirty_buffers && is_dirty {
1227                    let contents = snapshot.text();
1228                    let language = snapshot.language().map(|lang| lang.name().to_string());
1229                    (Some(contents), language)
1230                } else {
1231                    (None, None)
1232                };
1233
1234                let editor = SerializedEditor {
1235                    abs_path,
1236                    contents,
1237                    language,
1238                    mtime,
1239                };
1240                DB.save_serialized_editor(item_id, workspace_id, editor)
1241                    .await
1242                    .context("failed to save serialized editor")
1243            })
1244            .await
1245            .context("failed to save contents of buffer")?;
1246
1247            Ok(())
1248        }))
1249    }
1250
1251    fn should_serialize(&self, event: &Self::Event) -> bool {
1252        matches!(
1253            event,
1254            EditorEvent::Saved | EditorEvent::DirtyChanged | EditorEvent::BufferEdited
1255        )
1256    }
1257}
1258
1259impl ProjectItem for Editor {
1260    type Item = Buffer;
1261
1262    fn for_project_item(
1263        project: Entity<Project>,
1264        buffer: Entity<Buffer>,
1265        window: &mut Window,
1266        cx: &mut Context<Self>,
1267    ) -> Self {
1268        Self::for_buffer(buffer, Some(project), window, cx)
1269    }
1270}
1271
1272impl EventEmitter<SearchEvent> for Editor {}
1273
1274pub(crate) enum BufferSearchHighlights {}
1275impl SearchableItem for Editor {
1276    type Match = Range<Anchor>;
1277
1278    fn get_matches(&self, _window: &mut Window, _: &mut App) -> Vec<Range<Anchor>> {
1279        self.background_highlights
1280            .get(&TypeId::of::<BufferSearchHighlights>())
1281            .map_or(Vec::new(), |(_color, ranges)| {
1282                ranges.iter().cloned().collect()
1283            })
1284    }
1285
1286    fn clear_matches(&mut self, _: &mut Window, cx: &mut Context<Self>) {
1287        if self
1288            .clear_background_highlights::<BufferSearchHighlights>(cx)
1289            .is_some()
1290        {
1291            cx.emit(SearchEvent::MatchesInvalidated);
1292        }
1293    }
1294
1295    fn update_matches(
1296        &mut self,
1297        matches: &[Range<Anchor>],
1298        _: &mut Window,
1299        cx: &mut Context<Self>,
1300    ) {
1301        let existing_range = self
1302            .background_highlights
1303            .get(&TypeId::of::<BufferSearchHighlights>())
1304            .map(|(_, range)| range.as_ref());
1305        let updated = existing_range != Some(matches);
1306        self.highlight_background::<BufferSearchHighlights>(
1307            matches,
1308            |theme| theme.search_match_background,
1309            cx,
1310        );
1311        if updated {
1312            cx.emit(SearchEvent::MatchesInvalidated);
1313        }
1314    }
1315
1316    fn has_filtered_search_ranges(&mut self) -> bool {
1317        self.has_background_highlights::<SearchWithinRange>()
1318    }
1319
1320    fn toggle_filtered_search_ranges(
1321        &mut self,
1322        enabled: bool,
1323        _: &mut Window,
1324        cx: &mut Context<Self>,
1325    ) {
1326        if self.has_filtered_search_ranges() {
1327            self.previous_search_ranges = self
1328                .clear_background_highlights::<SearchWithinRange>(cx)
1329                .map(|(_, ranges)| ranges)
1330        }
1331
1332        if !enabled {
1333            return;
1334        }
1335
1336        let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
1337        if ranges.iter().any(|s| s.start != s.end) {
1338            self.set_search_within_ranges(&ranges, cx);
1339        } else if let Some(previous_search_ranges) = self.previous_search_ranges.take() {
1340            self.set_search_within_ranges(&previous_search_ranges, cx)
1341        }
1342    }
1343
1344    fn supported_options(&self) -> SearchOptions {
1345        if self.in_project_search {
1346            SearchOptions {
1347                case: true,
1348                word: true,
1349                regex: true,
1350                replacement: false,
1351                selection: false,
1352                find_in_results: true,
1353            }
1354        } else {
1355            SearchOptions {
1356                case: true,
1357                word: true,
1358                regex: true,
1359                replacement: true,
1360                selection: true,
1361                find_in_results: false,
1362            }
1363        }
1364    }
1365
1366    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
1367        let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
1368        let snapshot = &self.snapshot(window, cx).buffer_snapshot;
1369        let selection = self.selections.newest::<usize>(cx);
1370
1371        match setting {
1372            SeedQuerySetting::Never => String::new(),
1373            SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
1374                let text: String = snapshot
1375                    .text_for_range(selection.start..selection.end)
1376                    .collect();
1377                if text.contains('\n') {
1378                    String::new()
1379                } else {
1380                    text
1381                }
1382            }
1383            SeedQuerySetting::Selection => String::new(),
1384            SeedQuerySetting::Always => {
1385                let (range, kind) = snapshot.surrounding_word(selection.start, true);
1386                if kind == Some(CharKind::Word) {
1387                    let text: String = snapshot.text_for_range(range).collect();
1388                    if !text.trim().is_empty() {
1389                        return text;
1390                    }
1391                }
1392                String::new()
1393            }
1394        }
1395    }
1396
1397    fn activate_match(
1398        &mut self,
1399        index: usize,
1400        matches: &[Range<Anchor>],
1401        window: &mut Window,
1402        cx: &mut Context<Self>,
1403    ) {
1404        self.unfold_ranges(&[matches[index].clone()], false, true, cx);
1405        let range = self.range_for_match(&matches[index]);
1406        self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
1407            s.select_ranges([range]);
1408        })
1409    }
1410
1411    fn select_matches(
1412        &mut self,
1413        matches: &[Self::Match],
1414        window: &mut Window,
1415        cx: &mut Context<Self>,
1416    ) {
1417        self.unfold_ranges(matches, false, false, cx);
1418        self.change_selections(None, window, cx, |s| {
1419            s.select_ranges(matches.iter().cloned())
1420        });
1421    }
1422    fn replace(
1423        &mut self,
1424        identifier: &Self::Match,
1425        query: &SearchQuery,
1426        window: &mut Window,
1427        cx: &mut Context<Self>,
1428    ) {
1429        let text = self.buffer.read(cx);
1430        let text = text.snapshot(cx);
1431        let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>();
1432        let text: Cow<_> = if text.len() == 1 {
1433            text.first().cloned().unwrap().into()
1434        } else {
1435            let joined_chunks = text.join("");
1436            joined_chunks.into()
1437        };
1438
1439        if let Some(replacement) = query.replacement_for(&text) {
1440            self.transact(window, cx, |this, _, cx| {
1441                this.edit([(identifier.clone(), Arc::from(&*replacement))], cx);
1442            });
1443        }
1444    }
1445    fn replace_all(
1446        &mut self,
1447        matches: &mut dyn Iterator<Item = &Self::Match>,
1448        query: &SearchQuery,
1449        window: &mut Window,
1450        cx: &mut Context<Self>,
1451    ) {
1452        let text = self.buffer.read(cx);
1453        let text = text.snapshot(cx);
1454        let mut edits = vec![];
1455        for m in matches {
1456            let text = text.text_for_range(m.clone()).collect::<Vec<_>>();
1457            let text: Cow<_> = if text.len() == 1 {
1458                text.first().cloned().unwrap().into()
1459            } else {
1460                let joined_chunks = text.join("");
1461                joined_chunks.into()
1462            };
1463
1464            if let Some(replacement) = query.replacement_for(&text) {
1465                edits.push((m.clone(), Arc::from(&*replacement)));
1466            }
1467        }
1468
1469        if !edits.is_empty() {
1470            self.transact(window, cx, |this, _, cx| {
1471                this.edit(edits, cx);
1472            });
1473        }
1474    }
1475    fn match_index_for_direction(
1476        &mut self,
1477        matches: &[Range<Anchor>],
1478        current_index: usize,
1479        direction: Direction,
1480        count: usize,
1481        _: &mut Window,
1482        cx: &mut Context<Self>,
1483    ) -> usize {
1484        let buffer = self.buffer().read(cx).snapshot(cx);
1485        let current_index_position = if self.selections.disjoint_anchors().len() == 1 {
1486            self.selections.newest_anchor().head()
1487        } else {
1488            matches[current_index].start
1489        };
1490
1491        let mut count = count % matches.len();
1492        if count == 0 {
1493            return current_index;
1494        }
1495        match direction {
1496            Direction::Next => {
1497                if matches[current_index]
1498                    .start
1499                    .cmp(&current_index_position, &buffer)
1500                    .is_gt()
1501                {
1502                    count -= 1
1503                }
1504
1505                (current_index + count) % matches.len()
1506            }
1507            Direction::Prev => {
1508                if matches[current_index]
1509                    .end
1510                    .cmp(&current_index_position, &buffer)
1511                    .is_lt()
1512                {
1513                    count -= 1;
1514                }
1515
1516                if current_index >= count {
1517                    current_index - count
1518                } else {
1519                    matches.len() - (count - current_index)
1520                }
1521            }
1522        }
1523    }
1524
1525    fn find_matches(
1526        &mut self,
1527        query: Arc<project::search::SearchQuery>,
1528        _: &mut Window,
1529        cx: &mut Context<Self>,
1530    ) -> Task<Vec<Range<Anchor>>> {
1531        let buffer = self.buffer().read(cx).snapshot(cx);
1532        let search_within_ranges = self
1533            .background_highlights
1534            .get(&TypeId::of::<SearchWithinRange>())
1535            .map_or(vec![], |(_color, ranges)| {
1536                ranges.iter().cloned().collect::<Vec<_>>()
1537            });
1538
1539        cx.background_spawn(async move {
1540            let mut ranges = Vec::new();
1541
1542            let search_within_ranges = if search_within_ranges.is_empty() {
1543                vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]
1544            } else {
1545                search_within_ranges
1546            };
1547
1548            for range in search_within_ranges {
1549                for (search_buffer, search_range, excerpt_id, deleted_hunk_anchor) in
1550                    buffer.range_to_buffer_ranges_with_deleted_hunks(range)
1551                {
1552                    ranges.extend(
1553                        query
1554                            .search(search_buffer, Some(search_range.clone()))
1555                            .await
1556                            .into_iter()
1557                            .map(|match_range| {
1558                                if let Some(deleted_hunk_anchor) = deleted_hunk_anchor {
1559                                    let start = search_buffer
1560                                        .anchor_after(search_range.start + match_range.start);
1561                                    let end = search_buffer
1562                                        .anchor_before(search_range.start + match_range.end);
1563                                    Anchor {
1564                                        diff_base_anchor: Some(start),
1565                                        ..deleted_hunk_anchor
1566                                    }..Anchor {
1567                                        diff_base_anchor: Some(end),
1568                                        ..deleted_hunk_anchor
1569                                    }
1570                                } else {
1571                                    let start = search_buffer
1572                                        .anchor_after(search_range.start + match_range.start);
1573                                    let end = search_buffer
1574                                        .anchor_before(search_range.start + match_range.end);
1575                                    Anchor::range_in_buffer(
1576                                        excerpt_id,
1577                                        search_buffer.remote_id(),
1578                                        start..end,
1579                                    )
1580                                }
1581                            }),
1582                    );
1583                }
1584            }
1585
1586            ranges
1587        })
1588    }
1589
1590    fn active_match_index(
1591        &mut self,
1592        direction: Direction,
1593        matches: &[Range<Anchor>],
1594        _: &mut Window,
1595        cx: &mut Context<Self>,
1596    ) -> Option<usize> {
1597        active_match_index(
1598            direction,
1599            matches,
1600            &self.selections.newest_anchor().head(),
1601            &self.buffer().read(cx).snapshot(cx),
1602        )
1603    }
1604
1605    fn search_bar_visibility_changed(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {
1606        self.expect_bounds_change = self.last_bounds;
1607    }
1608}
1609
1610pub fn active_match_index(
1611    direction: Direction,
1612    ranges: &[Range<Anchor>],
1613    cursor: &Anchor,
1614    buffer: &MultiBufferSnapshot,
1615) -> Option<usize> {
1616    if ranges.is_empty() {
1617        None
1618    } else {
1619        let r = ranges.binary_search_by(|probe| {
1620            if probe.end.cmp(cursor, buffer).is_lt() {
1621                Ordering::Less
1622            } else if probe.start.cmp(cursor, buffer).is_gt() {
1623                Ordering::Greater
1624            } else {
1625                Ordering::Equal
1626            }
1627        });
1628        match direction {
1629            Direction::Prev => match r {
1630                Ok(i) => Some(i),
1631                Err(i) => Some(i.saturating_sub(1)),
1632            },
1633            Direction::Next => match r {
1634                Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
1635            },
1636        }
1637    }
1638}
1639
1640pub fn entry_label_color(selected: bool) -> Color {
1641    if selected {
1642        Color::Default
1643    } else {
1644        Color::Muted
1645    }
1646}
1647
1648pub fn entry_diagnostic_aware_icon_name_and_color(
1649    diagnostic_severity: Option<DiagnosticSeverity>,
1650) -> Option<(IconName, Color)> {
1651    match diagnostic_severity {
1652        Some(DiagnosticSeverity::ERROR) => Some((IconName::X, Color::Error)),
1653        Some(DiagnosticSeverity::WARNING) => Some((IconName::Triangle, Color::Warning)),
1654        _ => None,
1655    }
1656}
1657
1658pub fn entry_diagnostic_aware_icon_decoration_and_color(
1659    diagnostic_severity: Option<DiagnosticSeverity>,
1660) -> Option<(IconDecorationKind, Color)> {
1661    match diagnostic_severity {
1662        Some(DiagnosticSeverity::ERROR) => Some((IconDecorationKind::X, Color::Error)),
1663        Some(DiagnosticSeverity::WARNING) => Some((IconDecorationKind::Triangle, Color::Warning)),
1664        _ => None,
1665    }
1666}
1667
1668pub fn entry_git_aware_label_color(git_status: GitSummary, ignored: bool, selected: bool) -> Color {
1669    let tracked = git_status.index + git_status.worktree;
1670    if ignored {
1671        Color::Ignored
1672    } else if git_status.conflict > 0 {
1673        Color::Conflict
1674    } else if tracked.modified > 0 {
1675        Color::Modified
1676    } else if tracked.added > 0 || git_status.untracked > 0 {
1677        Color::Created
1678    } else {
1679        entry_label_color(selected)
1680    }
1681}
1682
1683fn path_for_buffer<'a>(
1684    buffer: &Entity<MultiBuffer>,
1685    height: usize,
1686    include_filename: bool,
1687    cx: &'a App,
1688) -> Option<Cow<'a, Path>> {
1689    let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
1690    path_for_file(file.as_ref(), height, include_filename, cx)
1691}
1692
1693fn path_for_file<'a>(
1694    file: &'a dyn language::File,
1695    mut height: usize,
1696    include_filename: bool,
1697    cx: &'a App,
1698) -> Option<Cow<'a, Path>> {
1699    // Ensure we always render at least the filename.
1700    height += 1;
1701
1702    let mut prefix = file.path().as_ref();
1703    while height > 0 {
1704        if let Some(parent) = prefix.parent() {
1705            prefix = parent;
1706            height -= 1;
1707        } else {
1708            break;
1709        }
1710    }
1711
1712    // Here we could have just always used `full_path`, but that is very
1713    // allocation-heavy and so we try to use a `Cow<Path>` if we haven't
1714    // traversed all the way up to the worktree's root.
1715    if height > 0 {
1716        let full_path = file.full_path(cx);
1717        if include_filename {
1718            Some(full_path.into())
1719        } else {
1720            Some(full_path.parent()?.to_path_buf().into())
1721        }
1722    } else {
1723        let mut path = file.path().strip_prefix(prefix).ok()?;
1724        if !include_filename {
1725            path = path.parent()?;
1726        }
1727        Some(path.into())
1728    }
1729}
1730
1731#[cfg(test)]
1732mod tests {
1733    use crate::editor_tests::init_test;
1734    use fs::Fs;
1735
1736    use super::*;
1737    use fs::MTime;
1738    use gpui::{App, VisualTestContext};
1739    use language::{LanguageMatcher, TestFile};
1740    use project::FakeFs;
1741    use std::path::{Path, PathBuf};
1742    use util::path;
1743
1744    #[gpui::test]
1745    fn test_path_for_file(cx: &mut App) {
1746        let file = TestFile {
1747            path: Path::new("").into(),
1748            root_name: String::new(),
1749            local_root: None,
1750        };
1751        assert_eq!(path_for_file(&file, 0, false, cx), None);
1752    }
1753
1754    async fn deserialize_editor(
1755        item_id: ItemId,
1756        workspace_id: WorkspaceId,
1757        workspace: Entity<Workspace>,
1758        project: Entity<Project>,
1759        cx: &mut VisualTestContext,
1760    ) -> Entity<Editor> {
1761        workspace
1762            .update_in(cx, |workspace, window, cx| {
1763                let pane = workspace.active_pane();
1764                pane.update(cx, |_, cx| {
1765                    Editor::deserialize(
1766                        project.clone(),
1767                        workspace.weak_handle(),
1768                        workspace_id,
1769                        item_id,
1770                        window,
1771                        cx,
1772                    )
1773                })
1774            })
1775            .await
1776            .unwrap()
1777    }
1778
1779    fn rust_language() -> Arc<language::Language> {
1780        Arc::new(language::Language::new(
1781            language::LanguageConfig {
1782                name: "Rust".into(),
1783                matcher: LanguageMatcher {
1784                    path_suffixes: vec!["rs".to_string()],
1785                    ..Default::default()
1786                },
1787                ..Default::default()
1788            },
1789            Some(tree_sitter_rust::LANGUAGE.into()),
1790        ))
1791    }
1792
1793    #[gpui::test]
1794    async fn test_deserialize(cx: &mut gpui::TestAppContext) {
1795        init_test(cx, |_| {});
1796
1797        let fs = FakeFs::new(cx.executor());
1798        fs.insert_file(path!("/file.rs"), Default::default()).await;
1799
1800        // Test case 1: Deserialize with path and contents
1801        {
1802            let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
1803            let (workspace, cx) =
1804                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
1805            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1806            let item_id = 1234 as ItemId;
1807            let mtime = fs
1808                .metadata(Path::new(path!("/file.rs")))
1809                .await
1810                .unwrap()
1811                .unwrap()
1812                .mtime;
1813
1814            let serialized_editor = SerializedEditor {
1815                abs_path: Some(PathBuf::from(path!("/file.rs"))),
1816                contents: Some("fn main() {}".to_string()),
1817                language: Some("Rust".to_string()),
1818                mtime: Some(mtime),
1819            };
1820
1821            DB.save_serialized_editor(item_id, workspace_id, serialized_editor.clone())
1822                .await
1823                .unwrap();
1824
1825            let deserialized =
1826                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1827
1828            deserialized.update(cx, |editor, cx| {
1829                assert_eq!(editor.text(cx), "fn main() {}");
1830                assert!(editor.is_dirty(cx));
1831                assert!(!editor.has_conflict(cx));
1832                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1833                assert!(buffer.file().is_some());
1834            });
1835        }
1836
1837        // Test case 2: Deserialize with only path
1838        {
1839            let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
1840            let (workspace, cx) =
1841                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
1842
1843            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1844
1845            let item_id = 5678 as ItemId;
1846            let serialized_editor = SerializedEditor {
1847                abs_path: Some(PathBuf::from(path!("/file.rs"))),
1848                contents: None,
1849                language: None,
1850                mtime: None,
1851            };
1852
1853            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1854                .await
1855                .unwrap();
1856
1857            let deserialized =
1858                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1859
1860            deserialized.update(cx, |editor, cx| {
1861                assert_eq!(editor.text(cx), ""); // The file should be empty as per our initial setup
1862                assert!(!editor.is_dirty(cx));
1863                assert!(!editor.has_conflict(cx));
1864
1865                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1866                assert!(buffer.file().is_some());
1867            });
1868        }
1869
1870        // Test case 3: Deserialize with no path (untitled buffer, with content and language)
1871        {
1872            let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
1873            // Add Rust to the language, so that we can restore the language of the buffer
1874            project.update(cx, |project, _| project.languages().add(rust_language()));
1875
1876            let (workspace, cx) =
1877                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
1878
1879            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1880
1881            let item_id = 9012 as ItemId;
1882            let serialized_editor = SerializedEditor {
1883                abs_path: None,
1884                contents: Some("hello".to_string()),
1885                language: Some("Rust".to_string()),
1886                mtime: None,
1887            };
1888
1889            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1890                .await
1891                .unwrap();
1892
1893            let deserialized =
1894                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1895
1896            deserialized.update(cx, |editor, cx| {
1897                assert_eq!(editor.text(cx), "hello");
1898                assert!(editor.is_dirty(cx)); // The editor should be dirty for an untitled buffer
1899
1900                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1901                assert_eq!(
1902                    buffer.language().map(|lang| lang.name()),
1903                    Some("Rust".into())
1904                ); // Language should be set to Rust
1905                assert!(buffer.file().is_none()); // The buffer should not have an associated file
1906            });
1907        }
1908
1909        // Test case 4: Deserialize with path, content, and old mtime
1910        {
1911            let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
1912            let (workspace, cx) =
1913                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
1914
1915            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1916
1917            let item_id = 9345 as ItemId;
1918            let old_mtime = MTime::from_seconds_and_nanos(0, 50);
1919            let serialized_editor = SerializedEditor {
1920                abs_path: Some(PathBuf::from(path!("/file.rs"))),
1921                contents: Some("fn main() {}".to_string()),
1922                language: Some("Rust".to_string()),
1923                mtime: Some(old_mtime),
1924            };
1925
1926            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1927                .await
1928                .unwrap();
1929
1930            let deserialized =
1931                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1932
1933            deserialized.update(cx, |editor, cx| {
1934                assert_eq!(editor.text(cx), "fn main() {}");
1935                assert!(editor.has_conflict(cx)); // The editor should have a conflict
1936            });
1937        }
1938    }
1939}