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::{h_flex, prelude::*, IconDecorationKind, Label};
  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                self.buffer
 622                    .read(cx)
 623                    .as_singleton()
 624                    .and_then(|buffer| buffer.read(cx).project_path(cx))
 625                    .and_then(|path| FileIcons::get_icon(path.path.as_ref(), cx))
 626            })
 627            .flatten()
 628            .map(Icon::from_path)
 629    }
 630
 631    fn tab_content(&self, params: TabContentParams, _: &Window, cx: &App) -> AnyElement {
 632        let label_color = if ItemSettings::get_global(cx).git_status {
 633            self.buffer()
 634                .read(cx)
 635                .as_singleton()
 636                .and_then(|buffer| buffer.read(cx).project_path(cx))
 637                .and_then(|path| {
 638                    let project = self.project.as_ref()?.read(cx);
 639                    let entry = project.entry_for_path(&path, cx)?;
 640                    let git_status = project
 641                        .worktree_for_id(path.worktree_id, cx)?
 642                        .read(cx)
 643                        .snapshot()
 644                        .status_for_file(path.path)?;
 645
 646                    Some(entry_git_aware_label_color(
 647                        git_status.summary(),
 648                        entry.is_ignored,
 649                        params.selected,
 650                    ))
 651                })
 652                .unwrap_or_else(|| entry_label_color(params.selected))
 653        } else {
 654            entry_label_color(params.selected)
 655        };
 656
 657        let description = params.detail.and_then(|detail| {
 658            let path = path_for_buffer(&self.buffer, detail, false, cx)?;
 659            let description = path.to_string_lossy();
 660            let description = description.trim();
 661
 662            if description.is_empty() {
 663                return None;
 664            }
 665
 666            Some(util::truncate_and_trailoff(description, MAX_TAB_TITLE_LEN))
 667        });
 668
 669        // Whether the file was saved in the past but is now deleted.
 670        let was_deleted: bool = self
 671            .buffer()
 672            .read(cx)
 673            .as_singleton()
 674            .and_then(|buffer| buffer.read(cx).file())
 675            .map_or(false, |file| file.disk_state() == DiskState::Deleted);
 676
 677        h_flex()
 678            .gap_2()
 679            .child(
 680                Label::new(self.title(cx).to_string())
 681                    .color(label_color)
 682                    .italic(params.preview)
 683                    .strikethrough(was_deleted),
 684            )
 685            .when_some(description, |this, description| {
 686                this.child(
 687                    Label::new(description)
 688                        .size(LabelSize::XSmall)
 689                        .color(Color::Muted),
 690                )
 691            })
 692            .into_any_element()
 693    }
 694
 695    fn for_each_project_item(
 696        &self,
 697        cx: &App,
 698        f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
 699    ) {
 700        self.buffer
 701            .read(cx)
 702            .for_each_buffer(|buffer| f(buffer.entity_id(), buffer.read(cx)));
 703    }
 704
 705    fn is_singleton(&self, cx: &App) -> bool {
 706        self.buffer.read(cx).is_singleton()
 707    }
 708
 709    fn clone_on_split(
 710        &self,
 711        _workspace_id: Option<WorkspaceId>,
 712        window: &mut Window,
 713        cx: &mut Context<Self>,
 714    ) -> Option<Entity<Editor>>
 715    where
 716        Self: Sized,
 717    {
 718        Some(cx.new(|cx| self.clone(window, cx)))
 719    }
 720
 721    fn set_nav_history(
 722        &mut self,
 723        history: ItemNavHistory,
 724        _window: &mut Window,
 725        _: &mut Context<Self>,
 726    ) {
 727        self.nav_history = Some(history);
 728    }
 729
 730    fn discarded(&self, _project: Entity<Project>, _: &mut Window, cx: &mut Context<Self>) {
 731        for buffer in self.buffer().clone().read(cx).all_buffers() {
 732            buffer.update(cx, |buffer, cx| buffer.discarded(cx))
 733        }
 734    }
 735
 736    fn deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
 737        let selection = self.selections.newest_anchor();
 738        self.push_to_nav_history(selection.head(), None, cx);
 739    }
 740
 741    fn workspace_deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
 742        self.hide_hovered_link(cx);
 743    }
 744
 745    fn is_dirty(&self, cx: &App) -> bool {
 746        self.buffer().read(cx).read(cx).is_dirty()
 747    }
 748
 749    fn has_deleted_file(&self, cx: &App) -> bool {
 750        self.buffer().read(cx).read(cx).has_deleted_file()
 751    }
 752
 753    fn has_conflict(&self, cx: &App) -> bool {
 754        self.buffer().read(cx).read(cx).has_conflict()
 755    }
 756
 757    fn can_save(&self, cx: &App) -> bool {
 758        let buffer = &self.buffer().read(cx);
 759        if let Some(buffer) = buffer.as_singleton() {
 760            buffer.read(cx).project_path(cx).is_some()
 761        } else {
 762            true
 763        }
 764    }
 765
 766    fn save(
 767        &mut self,
 768        format: bool,
 769        project: Entity<Project>,
 770        window: &mut Window,
 771        cx: &mut Context<Self>,
 772    ) -> Task<Result<()>> {
 773        self.report_editor_event("Editor Saved", None, cx);
 774        let buffers = self.buffer().clone().read(cx).all_buffers();
 775        let buffers = buffers
 776            .into_iter()
 777            .map(|handle| handle.read(cx).base_buffer().unwrap_or(handle.clone()))
 778            .collect::<HashSet<_>>();
 779        cx.spawn_in(window, |this, mut cx| async move {
 780            if format {
 781                this.update_in(&mut cx, |editor, window, cx| {
 782                    editor.perform_format(
 783                        project.clone(),
 784                        FormatTrigger::Save,
 785                        FormatTarget::Buffers,
 786                        window,
 787                        cx,
 788                    )
 789                })?
 790                .await?;
 791            }
 792
 793            if buffers.len() == 1 {
 794                // Apply full save routine for singleton buffers, to allow to `touch` the file via the editor.
 795                project
 796                    .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))?
 797                    .await?;
 798            } else {
 799                // For multi-buffers, only format and save the buffers with changes.
 800                // For clean buffers, we simulate saving by calling `Buffer::did_save`,
 801                // so that language servers or other downstream listeners of save events get notified.
 802                let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
 803                    buffer
 804                        .update(&mut cx, |buffer, _| {
 805                            buffer.is_dirty() || buffer.has_conflict()
 806                        })
 807                        .unwrap_or(false)
 808                });
 809
 810                project
 811                    .update(&mut cx, |project, cx| {
 812                        project.save_buffers(dirty_buffers, cx)
 813                    })?
 814                    .await?;
 815                for buffer in clean_buffers {
 816                    buffer
 817                        .update(&mut cx, |buffer, cx| {
 818                            let version = buffer.saved_version().clone();
 819                            let mtime = buffer.saved_mtime();
 820                            buffer.did_save(version, mtime, cx);
 821                        })
 822                        .ok();
 823                }
 824            }
 825
 826            Ok(())
 827        })
 828    }
 829
 830    fn save_as(
 831        &mut self,
 832        project: Entity<Project>,
 833        path: ProjectPath,
 834        _: &mut Window,
 835        cx: &mut Context<Self>,
 836    ) -> Task<Result<()>> {
 837        let buffer = self
 838            .buffer()
 839            .read(cx)
 840            .as_singleton()
 841            .expect("cannot call save_as on an excerpt list");
 842
 843        let file_extension = path
 844            .path
 845            .extension()
 846            .map(|a| a.to_string_lossy().to_string());
 847        self.report_editor_event("Editor Saved", file_extension, cx);
 848
 849        project.update(cx, |project, cx| project.save_buffer_as(buffer, path, cx))
 850    }
 851
 852    fn reload(
 853        &mut self,
 854        project: Entity<Project>,
 855        window: &mut Window,
 856        cx: &mut Context<Self>,
 857    ) -> Task<Result<()>> {
 858        let buffer = self.buffer().clone();
 859        let buffers = self.buffer.read(cx).all_buffers();
 860        let reload_buffers =
 861            project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx));
 862        cx.spawn_in(window, |this, mut cx| async move {
 863            let transaction = reload_buffers.log_err().await;
 864            this.update(&mut cx, |editor, cx| {
 865                editor.request_autoscroll(Autoscroll::fit(), cx)
 866            })?;
 867            buffer
 868                .update(&mut cx, |buffer, cx| {
 869                    if let Some(transaction) = transaction {
 870                        if !buffer.is_singleton() {
 871                            buffer.push_transaction(&transaction.0, cx);
 872                        }
 873                    }
 874                })
 875                .ok();
 876            Ok(())
 877        })
 878    }
 879
 880    fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 881        Some(Box::new(handle.clone()))
 882    }
 883
 884    fn pixel_position_of_cursor(&self, _: &App) -> Option<gpui::Point<Pixels>> {
 885        self.pixel_position_of_newest_cursor
 886    }
 887
 888    fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation {
 889        if self.show_breadcrumbs {
 890            ToolbarItemLocation::PrimaryLeft
 891        } else {
 892            ToolbarItemLocation::Hidden
 893        }
 894    }
 895
 896    fn breadcrumbs(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
 897        let cursor = self.selections.newest_anchor().head();
 898        let multibuffer = &self.buffer().read(cx);
 899        let (buffer_id, symbols) =
 900            multibuffer.symbols_containing(cursor, Some(variant.syntax()), cx)?;
 901        let buffer = multibuffer.buffer(buffer_id)?;
 902
 903        let buffer = buffer.read(cx);
 904        let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
 905            buffer
 906                .snapshot()
 907                .resolve_file_path(
 908                    cx,
 909                    self.project
 910                        .as_ref()
 911                        .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
 912                        .unwrap_or_default(),
 913                )
 914                .map(|path| path.to_string_lossy().to_string())
 915                .unwrap_or_else(|| {
 916                    if multibuffer.is_singleton() {
 917                        multibuffer.title(cx).to_string()
 918                    } else {
 919                        "untitled".to_string()
 920                    }
 921                })
 922        });
 923
 924        let settings = ThemeSettings::get_global(cx);
 925
 926        let mut breadcrumbs = vec![BreadcrumbText {
 927            text,
 928            highlights: None,
 929            font: Some(settings.buffer_font.clone()),
 930        }];
 931
 932        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
 933            text: symbol.text,
 934            highlights: Some(symbol.highlight_ranges),
 935            font: Some(settings.buffer_font.clone()),
 936        }));
 937        Some(breadcrumbs)
 938    }
 939
 940    fn added_to_workspace(
 941        &mut self,
 942        workspace: &mut Workspace,
 943        _window: &mut Window,
 944        _: &mut Context<Self>,
 945    ) {
 946        self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
 947    }
 948
 949    fn to_item_events(event: &EditorEvent, mut f: impl FnMut(ItemEvent)) {
 950        match event {
 951            EditorEvent::Closed => f(ItemEvent::CloseItem),
 952
 953            EditorEvent::Saved | EditorEvent::TitleChanged => {
 954                f(ItemEvent::UpdateTab);
 955                f(ItemEvent::UpdateBreadcrumbs);
 956            }
 957
 958            EditorEvent::Reparsed(_) => {
 959                f(ItemEvent::UpdateBreadcrumbs);
 960            }
 961
 962            EditorEvent::SelectionsChanged { local } if *local => {
 963                f(ItemEvent::UpdateBreadcrumbs);
 964            }
 965
 966            EditorEvent::DirtyChanged => {
 967                f(ItemEvent::UpdateTab);
 968            }
 969
 970            EditorEvent::BufferEdited => {
 971                f(ItemEvent::Edit);
 972                f(ItemEvent::UpdateBreadcrumbs);
 973            }
 974
 975            EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
 976                f(ItemEvent::Edit);
 977            }
 978
 979            _ => {}
 980        }
 981    }
 982
 983    fn preserve_preview(&self, cx: &App) -> bool {
 984        self.buffer.read(cx).preserve_preview(cx)
 985    }
 986}
 987
 988impl SerializableItem for Editor {
 989    fn serialized_item_kind() -> &'static str {
 990        "Editor"
 991    }
 992
 993    fn cleanup(
 994        workspace_id: WorkspaceId,
 995        alive_items: Vec<ItemId>,
 996        window: &mut Window,
 997        cx: &mut App,
 998    ) -> Task<Result<()>> {
 999        window.spawn(cx, |_| DB.delete_unloaded_items(workspace_id, alive_items))
1000    }
1001
1002    fn deserialize(
1003        project: Entity<Project>,
1004        workspace: WeakEntity<Workspace>,
1005        workspace_id: workspace::WorkspaceId,
1006        item_id: ItemId,
1007        window: &mut Window,
1008        cx: &mut App,
1009    ) -> Task<Result<Entity<Self>>> {
1010        let serialized_editor = match DB
1011            .get_serialized_editor(item_id, workspace_id)
1012            .context("Failed to query editor state")
1013        {
1014            Ok(Some(serialized_editor)) => {
1015                if ProjectSettings::get_global(cx)
1016                    .session
1017                    .restore_unsaved_buffers
1018                {
1019                    serialized_editor
1020                } else {
1021                    SerializedEditor {
1022                        abs_path: serialized_editor.abs_path,
1023                        contents: None,
1024                        language: None,
1025                        mtime: None,
1026                    }
1027                }
1028            }
1029            Ok(None) => {
1030                return Task::ready(Err(anyhow!("No path or contents found for buffer")));
1031            }
1032            Err(error) => {
1033                return Task::ready(Err(error));
1034            }
1035        };
1036
1037        match serialized_editor {
1038            SerializedEditor {
1039                abs_path: None,
1040                contents: Some(contents),
1041                language,
1042                ..
1043            } => window.spawn(cx, |mut cx| {
1044                let project = project.clone();
1045                async move {
1046                    let language_registry =
1047                        project.update(&mut cx, |project, _| project.languages().clone())?;
1048
1049                    let language = if let Some(language_name) = language {
1050                        // We don't fail here, because we'd rather not set the language if the name changed
1051                        // than fail to restore the buffer.
1052                        language_registry
1053                            .language_for_name(&language_name)
1054                            .await
1055                            .ok()
1056                    } else {
1057                        None
1058                    };
1059
1060                    // First create the empty buffer
1061                    let buffer = project
1062                        .update(&mut cx, |project, cx| project.create_buffer(cx))?
1063                        .await?;
1064
1065                    // Then set the text so that the dirty bit is set correctly
1066                    buffer.update(&mut cx, |buffer, cx| {
1067                        buffer.set_language_registry(language_registry);
1068                        if let Some(language) = language {
1069                            buffer.set_language(Some(language), cx);
1070                        }
1071                        buffer.set_text(contents, cx);
1072                    })?;
1073
1074                    cx.update(|window, cx| {
1075                        cx.new(|cx| {
1076                            let mut editor = Editor::for_buffer(buffer, Some(project), window, cx);
1077
1078                            editor.read_scroll_position_from_db(item_id, workspace_id, window, cx);
1079                            editor
1080                        })
1081                    })
1082                }
1083            }),
1084            SerializedEditor {
1085                abs_path: Some(abs_path),
1086                contents,
1087                mtime,
1088                ..
1089            } => {
1090                let project_item = project.update(cx, |project, cx| {
1091                    let (worktree, path) = project.find_worktree(&abs_path, cx)?;
1092                    let project_path = ProjectPath {
1093                        worktree_id: worktree.read(cx).id(),
1094                        path: path.into(),
1095                    };
1096                    Some(project.open_path(project_path, cx))
1097                });
1098
1099                match project_item {
1100                    Some(project_item) => {
1101                        window.spawn(cx, |mut cx| async move {
1102                            let (_, project_item) = project_item.await?;
1103                            let buffer = project_item.downcast::<Buffer>().map_err(|_| {
1104                                anyhow!("Project item at stored path was not a buffer")
1105                            })?;
1106
1107                            // This is a bit wasteful: we're loading the whole buffer from
1108                            // disk and then overwrite the content.
1109                            // But for now, it keeps the implementation of the content serialization
1110                            // simple, because we don't have to persist all of the metadata that we get
1111                            // by loading the file (git diff base, ...).
1112                            if let Some(buffer_text) = contents {
1113                                buffer.update(&mut cx, |buffer, cx| {
1114                                    // If we did restore an mtime, we want to store it on the buffer
1115                                    // so that the next edit will mark the buffer as dirty/conflicted.
1116                                    if mtime.is_some() {
1117                                        buffer.did_reload(
1118                                            buffer.version(),
1119                                            buffer.line_ending(),
1120                                            mtime,
1121                                            cx,
1122                                        );
1123                                    }
1124                                    buffer.set_text(buffer_text, cx);
1125                                })?;
1126                            }
1127
1128                            cx.update(|window, cx| {
1129                                cx.new(|cx| {
1130                                    let mut editor =
1131                                        Editor::for_buffer(buffer, Some(project), window, cx);
1132
1133                                    editor.read_scroll_position_from_db(
1134                                        item_id,
1135                                        workspace_id,
1136                                        window,
1137                                        cx,
1138                                    );
1139                                    editor
1140                                })
1141                            })
1142                        })
1143                    }
1144                    None => {
1145                        let open_by_abs_path = workspace.update(cx, |workspace, cx| {
1146                            workspace.open_abs_path(abs_path.clone(), false, window, cx)
1147                        });
1148                        window.spawn(cx, |mut cx| async move {
1149                            let editor = open_by_abs_path?.await?.downcast::<Editor>().with_context(|| format!("Failed to downcast to Editor after opening abs path {abs_path:?}"))?;
1150                            editor.update_in(&mut cx, |editor, window, cx| {
1151                                editor.read_scroll_position_from_db(item_id, workspace_id, window, cx);
1152                            })?;
1153                            Ok(editor)
1154                        })
1155                    }
1156                }
1157            }
1158            SerializedEditor {
1159                abs_path: None,
1160                contents: None,
1161                ..
1162            } => Task::ready(Err(anyhow!("No path or contents found for buffer"))),
1163        }
1164    }
1165
1166    fn serialize(
1167        &mut self,
1168        workspace: &mut Workspace,
1169        item_id: ItemId,
1170        closing: bool,
1171        window: &mut Window,
1172        cx: &mut Context<Self>,
1173    ) -> Option<Task<Result<()>>> {
1174        let mut serialize_dirty_buffers = self.serialize_dirty_buffers;
1175
1176        let project = self.project.clone()?;
1177        if project.read(cx).visible_worktrees(cx).next().is_none() {
1178            // If we don't have a worktree, we don't serialize, because
1179            // projects without worktrees aren't deserialized.
1180            serialize_dirty_buffers = false;
1181        }
1182
1183        if closing && !serialize_dirty_buffers {
1184            return None;
1185        }
1186
1187        let workspace_id = workspace.database_id()?;
1188
1189        let buffer = self.buffer().read(cx).as_singleton()?;
1190
1191        let abs_path = buffer.read(cx).file().and_then(|file| {
1192            let worktree_id = file.worktree_id(cx);
1193            project
1194                .read(cx)
1195                .worktree_for_id(worktree_id, cx)
1196                .and_then(|worktree| worktree.read(cx).absolutize(&file.path()).ok())
1197                .or_else(|| {
1198                    let full_path = file.full_path(cx);
1199                    let project_path = project.read(cx).find_project_path(&full_path, cx)?;
1200                    project.read(cx).absolute_path(&project_path, cx)
1201                })
1202        });
1203
1204        let is_dirty = buffer.read(cx).is_dirty();
1205        let mtime = buffer.read(cx).saved_mtime();
1206
1207        let snapshot = buffer.read(cx).snapshot();
1208
1209        Some(cx.spawn_in(window, |_this, cx| async move {
1210            cx.background_executor()
1211                .spawn(async move {
1212                    let (contents, language) = if serialize_dirty_buffers && is_dirty {
1213                        let contents = snapshot.text();
1214                        let language = snapshot.language().map(|lang| lang.name().to_string());
1215                        (Some(contents), language)
1216                    } else {
1217                        (None, None)
1218                    };
1219
1220                    let editor = SerializedEditor {
1221                        abs_path,
1222                        contents,
1223                        language,
1224                        mtime,
1225                    };
1226                    DB.save_serialized_editor(item_id, workspace_id, editor)
1227                        .await
1228                        .context("failed to save serialized editor")
1229                })
1230                .await
1231                .context("failed to save contents of buffer")?;
1232
1233            Ok(())
1234        }))
1235    }
1236
1237    fn should_serialize(&self, event: &Self::Event) -> bool {
1238        matches!(
1239            event,
1240            EditorEvent::Saved | EditorEvent::DirtyChanged | EditorEvent::BufferEdited
1241        )
1242    }
1243}
1244
1245impl ProjectItem for Editor {
1246    type Item = Buffer;
1247
1248    fn for_project_item(
1249        project: Entity<Project>,
1250        buffer: Entity<Buffer>,
1251        window: &mut Window,
1252        cx: &mut Context<Self>,
1253    ) -> Self {
1254        Self::for_buffer(buffer, Some(project), window, cx)
1255    }
1256}
1257
1258impl EventEmitter<SearchEvent> for Editor {}
1259
1260pub(crate) enum BufferSearchHighlights {}
1261impl SearchableItem for Editor {
1262    type Match = Range<Anchor>;
1263
1264    fn get_matches(&self, _window: &mut Window, _: &mut App) -> Vec<Range<Anchor>> {
1265        self.background_highlights
1266            .get(&TypeId::of::<BufferSearchHighlights>())
1267            .map_or(Vec::new(), |(_color, ranges)| {
1268                ranges.iter().cloned().collect()
1269            })
1270    }
1271
1272    fn clear_matches(&mut self, _: &mut Window, cx: &mut Context<Self>) {
1273        if self
1274            .clear_background_highlights::<BufferSearchHighlights>(cx)
1275            .is_some()
1276        {
1277            cx.emit(SearchEvent::MatchesInvalidated);
1278        }
1279    }
1280
1281    fn update_matches(
1282        &mut self,
1283        matches: &[Range<Anchor>],
1284        _: &mut Window,
1285        cx: &mut Context<Self>,
1286    ) {
1287        let existing_range = self
1288            .background_highlights
1289            .get(&TypeId::of::<BufferSearchHighlights>())
1290            .map(|(_, range)| range.as_ref());
1291        let updated = existing_range != Some(matches);
1292        self.highlight_background::<BufferSearchHighlights>(
1293            matches,
1294            |theme| theme.search_match_background,
1295            cx,
1296        );
1297        if updated {
1298            cx.emit(SearchEvent::MatchesInvalidated);
1299        }
1300    }
1301
1302    fn has_filtered_search_ranges(&mut self) -> bool {
1303        self.has_background_highlights::<SearchWithinRange>()
1304    }
1305
1306    fn toggle_filtered_search_ranges(
1307        &mut self,
1308        enabled: bool,
1309        _: &mut Window,
1310        cx: &mut Context<Self>,
1311    ) {
1312        if self.has_filtered_search_ranges() {
1313            self.previous_search_ranges = self
1314                .clear_background_highlights::<SearchWithinRange>(cx)
1315                .map(|(_, ranges)| ranges)
1316        }
1317
1318        if !enabled {
1319            return;
1320        }
1321
1322        let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
1323        if ranges.iter().any(|s| s.start != s.end) {
1324            self.set_search_within_ranges(&ranges, cx);
1325        } else if let Some(previous_search_ranges) = self.previous_search_ranges.take() {
1326            self.set_search_within_ranges(&previous_search_ranges, cx)
1327        }
1328    }
1329
1330    fn supported_options(&self) -> SearchOptions {
1331        if self.in_project_search {
1332            SearchOptions {
1333                case: true,
1334                word: true,
1335                regex: true,
1336                replacement: false,
1337                selection: false,
1338                find_in_results: true,
1339            }
1340        } else {
1341            SearchOptions {
1342                case: true,
1343                word: true,
1344                regex: true,
1345                replacement: true,
1346                selection: true,
1347                find_in_results: false,
1348            }
1349        }
1350    }
1351
1352    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
1353        let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
1354        let snapshot = &self.snapshot(window, cx).buffer_snapshot;
1355        let selection = self.selections.newest::<usize>(cx);
1356
1357        match setting {
1358            SeedQuerySetting::Never => String::new(),
1359            SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
1360                let text: String = snapshot
1361                    .text_for_range(selection.start..selection.end)
1362                    .collect();
1363                if text.contains('\n') {
1364                    String::new()
1365                } else {
1366                    text
1367                }
1368            }
1369            SeedQuerySetting::Selection => String::new(),
1370            SeedQuerySetting::Always => {
1371                let (range, kind) = snapshot.surrounding_word(selection.start, true);
1372                if kind == Some(CharKind::Word) {
1373                    let text: String = snapshot.text_for_range(range).collect();
1374                    if !text.trim().is_empty() {
1375                        return text;
1376                    }
1377                }
1378                String::new()
1379            }
1380        }
1381    }
1382
1383    fn activate_match(
1384        &mut self,
1385        index: usize,
1386        matches: &[Range<Anchor>],
1387        window: &mut Window,
1388        cx: &mut Context<Self>,
1389    ) {
1390        self.unfold_ranges(&[matches[index].clone()], false, true, cx);
1391        let range = self.range_for_match(&matches[index]);
1392        self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
1393            s.select_ranges([range]);
1394        })
1395    }
1396
1397    fn select_matches(
1398        &mut self,
1399        matches: &[Self::Match],
1400        window: &mut Window,
1401        cx: &mut Context<Self>,
1402    ) {
1403        self.unfold_ranges(matches, false, false, cx);
1404        let mut ranges = Vec::new();
1405        for m in matches {
1406            ranges.push(self.range_for_match(m))
1407        }
1408        self.change_selections(None, window, cx, |s| s.select_ranges(ranges));
1409    }
1410    fn replace(
1411        &mut self,
1412        identifier: &Self::Match,
1413        query: &SearchQuery,
1414        window: &mut Window,
1415        cx: &mut Context<Self>,
1416    ) {
1417        let text = self.buffer.read(cx);
1418        let text = text.snapshot(cx);
1419        let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>();
1420        let text: Cow<_> = if text.len() == 1 {
1421            text.first().cloned().unwrap().into()
1422        } else {
1423            let joined_chunks = text.join("");
1424            joined_chunks.into()
1425        };
1426
1427        if let Some(replacement) = query.replacement_for(&text) {
1428            self.transact(window, cx, |this, _, cx| {
1429                this.edit([(identifier.clone(), Arc::from(&*replacement))], cx);
1430            });
1431        }
1432    }
1433    fn replace_all(
1434        &mut self,
1435        matches: &mut dyn Iterator<Item = &Self::Match>,
1436        query: &SearchQuery,
1437        window: &mut Window,
1438        cx: &mut Context<Self>,
1439    ) {
1440        let text = self.buffer.read(cx);
1441        let text = text.snapshot(cx);
1442        let mut edits = vec![];
1443        for m in matches {
1444            let text = text.text_for_range(m.clone()).collect::<Vec<_>>();
1445            let text: Cow<_> = if text.len() == 1 {
1446                text.first().cloned().unwrap().into()
1447            } else {
1448                let joined_chunks = text.join("");
1449                joined_chunks.into()
1450            };
1451
1452            if let Some(replacement) = query.replacement_for(&text) {
1453                edits.push((m.clone(), Arc::from(&*replacement)));
1454            }
1455        }
1456
1457        if !edits.is_empty() {
1458            self.transact(window, cx, |this, _, cx| {
1459                this.edit(edits, cx);
1460            });
1461        }
1462    }
1463    fn match_index_for_direction(
1464        &mut self,
1465        matches: &[Range<Anchor>],
1466        current_index: usize,
1467        direction: Direction,
1468        count: usize,
1469        _: &mut Window,
1470        cx: &mut Context<Self>,
1471    ) -> usize {
1472        let buffer = self.buffer().read(cx).snapshot(cx);
1473        let current_index_position = if self.selections.disjoint_anchors().len() == 1 {
1474            self.selections.newest_anchor().head()
1475        } else {
1476            matches[current_index].start
1477        };
1478
1479        let mut count = count % matches.len();
1480        if count == 0 {
1481            return current_index;
1482        }
1483        match direction {
1484            Direction::Next => {
1485                if matches[current_index]
1486                    .start
1487                    .cmp(&current_index_position, &buffer)
1488                    .is_gt()
1489                {
1490                    count -= 1
1491                }
1492
1493                (current_index + count) % matches.len()
1494            }
1495            Direction::Prev => {
1496                if matches[current_index]
1497                    .end
1498                    .cmp(&current_index_position, &buffer)
1499                    .is_lt()
1500                {
1501                    count -= 1;
1502                }
1503
1504                if current_index >= count {
1505                    current_index - count
1506                } else {
1507                    matches.len() - (count - current_index)
1508                }
1509            }
1510        }
1511    }
1512
1513    fn find_matches(
1514        &mut self,
1515        query: Arc<project::search::SearchQuery>,
1516        _: &mut Window,
1517        cx: &mut Context<Self>,
1518    ) -> Task<Vec<Range<Anchor>>> {
1519        let buffer = self.buffer().read(cx).snapshot(cx);
1520        let search_within_ranges = self
1521            .background_highlights
1522            .get(&TypeId::of::<SearchWithinRange>())
1523            .map_or(vec![], |(_color, ranges)| {
1524                ranges.iter().cloned().collect::<Vec<_>>()
1525            });
1526
1527        cx.background_executor().spawn(async move {
1528            let mut ranges = Vec::new();
1529
1530            let search_within_ranges = if search_within_ranges.is_empty() {
1531                vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]
1532            } else {
1533                search_within_ranges
1534            };
1535
1536            for range in search_within_ranges {
1537                for (search_buffer, search_range, excerpt_id, deleted_hunk_anchor) in
1538                    buffer.range_to_buffer_ranges_with_deleted_hunks(range)
1539                {
1540                    ranges.extend(
1541                        query
1542                            .search(search_buffer, Some(search_range.clone()))
1543                            .await
1544                            .into_iter()
1545                            .map(|match_range| {
1546                                if let Some(deleted_hunk_anchor) = deleted_hunk_anchor {
1547                                    let start = search_buffer
1548                                        .anchor_after(search_range.start + match_range.start);
1549                                    let end = search_buffer
1550                                        .anchor_before(search_range.start + match_range.end);
1551                                    Anchor {
1552                                        diff_base_anchor: Some(start),
1553                                        ..deleted_hunk_anchor
1554                                    }..Anchor {
1555                                        diff_base_anchor: Some(end),
1556                                        ..deleted_hunk_anchor
1557                                    }
1558                                } else {
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::range_in_buffer(
1564                                        excerpt_id,
1565                                        search_buffer.remote_id(),
1566                                        start..end,
1567                                    )
1568                                }
1569                            }),
1570                    );
1571                }
1572            }
1573
1574            ranges
1575        })
1576    }
1577
1578    fn active_match_index(
1579        &mut self,
1580        matches: &[Range<Anchor>],
1581        _: &mut Window,
1582        cx: &mut Context<Self>,
1583    ) -> Option<usize> {
1584        active_match_index(
1585            matches,
1586            &self.selections.newest_anchor().head(),
1587            &self.buffer().read(cx).snapshot(cx),
1588        )
1589    }
1590
1591    fn search_bar_visibility_changed(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {
1592        self.expect_bounds_change = self.last_bounds;
1593    }
1594}
1595
1596pub fn active_match_index(
1597    ranges: &[Range<Anchor>],
1598    cursor: &Anchor,
1599    buffer: &MultiBufferSnapshot,
1600) -> Option<usize> {
1601    if ranges.is_empty() {
1602        None
1603    } else {
1604        match ranges.binary_search_by(|probe| {
1605            if probe.end.cmp(cursor, buffer).is_lt() {
1606                Ordering::Less
1607            } else if probe.start.cmp(cursor, buffer).is_gt() {
1608                Ordering::Greater
1609            } else {
1610                Ordering::Equal
1611            }
1612        }) {
1613            Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
1614        }
1615    }
1616}
1617
1618pub fn entry_label_color(selected: bool) -> Color {
1619    if selected {
1620        Color::Default
1621    } else {
1622        Color::Muted
1623    }
1624}
1625
1626pub fn entry_diagnostic_aware_icon_name_and_color(
1627    diagnostic_severity: Option<DiagnosticSeverity>,
1628) -> Option<(IconName, Color)> {
1629    match diagnostic_severity {
1630        Some(DiagnosticSeverity::ERROR) => Some((IconName::X, Color::Error)),
1631        Some(DiagnosticSeverity::WARNING) => Some((IconName::Triangle, Color::Warning)),
1632        _ => None,
1633    }
1634}
1635
1636pub fn entry_diagnostic_aware_icon_decoration_and_color(
1637    diagnostic_severity: Option<DiagnosticSeverity>,
1638) -> Option<(IconDecorationKind, Color)> {
1639    match diagnostic_severity {
1640        Some(DiagnosticSeverity::ERROR) => Some((IconDecorationKind::X, Color::Error)),
1641        Some(DiagnosticSeverity::WARNING) => Some((IconDecorationKind::Triangle, Color::Warning)),
1642        _ => None,
1643    }
1644}
1645
1646pub fn entry_git_aware_label_color(git_status: GitSummary, ignored: bool, selected: bool) -> Color {
1647    let tracked = git_status.index + git_status.worktree;
1648    if ignored {
1649        Color::Ignored
1650    } else if git_status.conflict > 0 {
1651        Color::Conflict
1652    } else if tracked.modified > 0 {
1653        Color::Modified
1654    } else if tracked.added > 0 || git_status.untracked > 0 {
1655        Color::Created
1656    } else {
1657        entry_label_color(selected)
1658    }
1659}
1660
1661fn path_for_buffer<'a>(
1662    buffer: &Entity<MultiBuffer>,
1663    height: usize,
1664    include_filename: bool,
1665    cx: &'a App,
1666) -> Option<Cow<'a, Path>> {
1667    let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
1668    path_for_file(file.as_ref(), height, include_filename, cx)
1669}
1670
1671fn path_for_file<'a>(
1672    file: &'a dyn language::File,
1673    mut height: usize,
1674    include_filename: bool,
1675    cx: &'a App,
1676) -> Option<Cow<'a, Path>> {
1677    // Ensure we always render at least the filename.
1678    height += 1;
1679
1680    let mut prefix = file.path().as_ref();
1681    while height > 0 {
1682        if let Some(parent) = prefix.parent() {
1683            prefix = parent;
1684            height -= 1;
1685        } else {
1686            break;
1687        }
1688    }
1689
1690    // Here we could have just always used `full_path`, but that is very
1691    // allocation-heavy and so we try to use a `Cow<Path>` if we haven't
1692    // traversed all the way up to the worktree's root.
1693    if height > 0 {
1694        let full_path = file.full_path(cx);
1695        if include_filename {
1696            Some(full_path.into())
1697        } else {
1698            Some(full_path.parent()?.to_path_buf().into())
1699        }
1700    } else {
1701        let mut path = file.path().strip_prefix(prefix).ok()?;
1702        if !include_filename {
1703            path = path.parent()?;
1704        }
1705        Some(path.into())
1706    }
1707}
1708
1709#[cfg(test)]
1710mod tests {
1711    use crate::editor_tests::init_test;
1712    use fs::Fs;
1713
1714    use super::*;
1715    use fs::MTime;
1716    use gpui::{App, VisualTestContext};
1717    use language::{LanguageMatcher, TestFile};
1718    use project::FakeFs;
1719    use std::path::{Path, PathBuf};
1720    use util::path;
1721
1722    #[gpui::test]
1723    fn test_path_for_file(cx: &mut App) {
1724        let file = TestFile {
1725            path: Path::new("").into(),
1726            root_name: String::new(),
1727        };
1728        assert_eq!(path_for_file(&file, 0, false, cx), None);
1729    }
1730
1731    async fn deserialize_editor(
1732        item_id: ItemId,
1733        workspace_id: WorkspaceId,
1734        workspace: Entity<Workspace>,
1735        project: Entity<Project>,
1736        cx: &mut VisualTestContext,
1737    ) -> Entity<Editor> {
1738        workspace
1739            .update_in(cx, |workspace, window, cx| {
1740                let pane = workspace.active_pane();
1741                pane.update(cx, |_, cx| {
1742                    Editor::deserialize(
1743                        project.clone(),
1744                        workspace.weak_handle(),
1745                        workspace_id,
1746                        item_id,
1747                        window,
1748                        cx,
1749                    )
1750                })
1751            })
1752            .await
1753            .unwrap()
1754    }
1755
1756    fn rust_language() -> Arc<language::Language> {
1757        Arc::new(language::Language::new(
1758            language::LanguageConfig {
1759                name: "Rust".into(),
1760                matcher: LanguageMatcher {
1761                    path_suffixes: vec!["rs".to_string()],
1762                    ..Default::default()
1763                },
1764                ..Default::default()
1765            },
1766            Some(tree_sitter_rust::LANGUAGE.into()),
1767        ))
1768    }
1769
1770    #[gpui::test]
1771    async fn test_deserialize(cx: &mut gpui::TestAppContext) {
1772        init_test(cx, |_| {});
1773
1774        let fs = FakeFs::new(cx.executor());
1775        fs.insert_file(path!("/file.rs"), Default::default()).await;
1776
1777        // Test case 1: Deserialize with path and contents
1778        {
1779            let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
1780            let (workspace, cx) =
1781                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
1782            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1783            let item_id = 1234 as ItemId;
1784            let mtime = fs
1785                .metadata(Path::new(path!("/file.rs")))
1786                .await
1787                .unwrap()
1788                .unwrap()
1789                .mtime;
1790
1791            let serialized_editor = SerializedEditor {
1792                abs_path: Some(PathBuf::from(path!("/file.rs"))),
1793                contents: Some("fn main() {}".to_string()),
1794                language: Some("Rust".to_string()),
1795                mtime: Some(mtime),
1796            };
1797
1798            DB.save_serialized_editor(item_id, workspace_id, serialized_editor.clone())
1799                .await
1800                .unwrap();
1801
1802            let deserialized =
1803                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1804
1805            deserialized.update(cx, |editor, cx| {
1806                assert_eq!(editor.text(cx), "fn main() {}");
1807                assert!(editor.is_dirty(cx));
1808                assert!(!editor.has_conflict(cx));
1809                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1810                assert!(buffer.file().is_some());
1811            });
1812        }
1813
1814        // Test case 2: Deserialize with only path
1815        {
1816            let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
1817            let (workspace, cx) =
1818                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
1819
1820            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1821
1822            let item_id = 5678 as ItemId;
1823            let serialized_editor = SerializedEditor {
1824                abs_path: Some(PathBuf::from(path!("/file.rs"))),
1825                contents: None,
1826                language: None,
1827                mtime: None,
1828            };
1829
1830            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1831                .await
1832                .unwrap();
1833
1834            let deserialized =
1835                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1836
1837            deserialized.update(cx, |editor, cx| {
1838                assert_eq!(editor.text(cx), ""); // The file should be empty as per our initial setup
1839                assert!(!editor.is_dirty(cx));
1840                assert!(!editor.has_conflict(cx));
1841
1842                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1843                assert!(buffer.file().is_some());
1844            });
1845        }
1846
1847        // Test case 3: Deserialize with no path (untitled buffer, with content and language)
1848        {
1849            let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
1850            // Add Rust to the language, so that we can restore the language of the buffer
1851            project.update(cx, |project, _| project.languages().add(rust_language()));
1852
1853            let (workspace, cx) =
1854                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
1855
1856            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1857
1858            let item_id = 9012 as ItemId;
1859            let serialized_editor = SerializedEditor {
1860                abs_path: None,
1861                contents: Some("hello".to_string()),
1862                language: Some("Rust".to_string()),
1863                mtime: None,
1864            };
1865
1866            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1867                .await
1868                .unwrap();
1869
1870            let deserialized =
1871                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1872
1873            deserialized.update(cx, |editor, cx| {
1874                assert_eq!(editor.text(cx), "hello");
1875                assert!(editor.is_dirty(cx)); // The editor should be dirty for an untitled buffer
1876
1877                let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
1878                assert_eq!(
1879                    buffer.language().map(|lang| lang.name()),
1880                    Some("Rust".into())
1881                ); // Language should be set to Rust
1882                assert!(buffer.file().is_none()); // The buffer should not have an associated file
1883            });
1884        }
1885
1886        // Test case 4: Deserialize with path, content, and old mtime
1887        {
1888            let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
1889            let (workspace, cx) =
1890                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
1891
1892            let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
1893
1894            let item_id = 9345 as ItemId;
1895            let old_mtime = MTime::from_seconds_and_nanos(0, 50);
1896            let serialized_editor = SerializedEditor {
1897                abs_path: Some(PathBuf::from(path!("/file.rs"))),
1898                contents: Some("fn main() {}".to_string()),
1899                language: Some("Rust".to_string()),
1900                mtime: Some(old_mtime),
1901            };
1902
1903            DB.save_serialized_editor(item_id, workspace_id, serialized_editor)
1904                .await
1905                .unwrap();
1906
1907            let deserialized =
1908                deserialize_editor(item_id, workspace_id, workspace, project, cx).await;
1909
1910            deserialized.update(cx, |editor, cx| {
1911                assert_eq!(editor.text(cx), "fn main() {}");
1912                assert!(editor.has_conflict(cx)); // The editor should have a conflict
1913            });
1914        }
1915    }
1916}