items.rs

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