items.rs

   1use crate::{
   2    display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition,
   3    movement::surrounding_word, Anchor, Autoscroll, Editor, Event, ExcerptId, ExcerptRange,
   4    MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, FORMAT_TIMEOUT,
   5};
   6use anyhow::{anyhow, Result};
   7use collections::HashSet;
   8use futures::FutureExt;
   9use gpui::{
  10    elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
  11    RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
  12};
  13use language::proto::serialize_anchor as serialize_text_anchor;
  14use language::{Bias, Buffer, File as _, OffsetRangeExt, Point, SelectionGoal};
  15use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath};
  16use rpc::proto::{self, update_view};
  17use settings::Settings;
  18use smallvec::SmallVec;
  19use std::{
  20    borrow::Cow,
  21    cmp::{self, Ordering},
  22    fmt::Write,
  23    iter,
  24    ops::Range,
  25    path::{Path, PathBuf},
  26};
  27use text::Selection;
  28use util::TryFutureExt;
  29use workspace::{
  30    searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
  31    FollowableItem, Item, ItemEvent, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView,
  32    ToolbarItemLocation,
  33};
  34
  35pub const MAX_TAB_TITLE_LEN: usize = 24;
  36
  37impl FollowableItem for Editor {
  38    fn from_state_proto(
  39        pane: ViewHandle<workspace::Pane>,
  40        project: ModelHandle<Project>,
  41        state: &mut Option<proto::view::Variant>,
  42        cx: &mut MutableAppContext,
  43    ) -> Option<Task<Result<ViewHandle<Self>>>> {
  44        let state = if matches!(state, Some(proto::view::Variant::Editor(_))) {
  45            if let Some(proto::view::Variant::Editor(state)) = state.take() {
  46                state
  47            } else {
  48                unreachable!()
  49            }
  50        } else {
  51            return None;
  52        };
  53
  54        let replica_id = project.read(cx).replica_id();
  55        let buffer_ids = state
  56            .excerpts
  57            .iter()
  58            .map(|excerpt| excerpt.buffer_id)
  59            .collect::<HashSet<_>>();
  60        let buffers = project.update(cx, |project, cx| {
  61            buffer_ids
  62                .iter()
  63                .map(|id| project.open_buffer_by_id(*id, cx))
  64                .collect::<Vec<_>>()
  65        });
  66
  67        Some(cx.spawn(|mut cx| async move {
  68            let mut buffers = futures::future::try_join_all(buffers).await?;
  69            let editor = pane.read_with(&cx, |pane, cx| {
  70                let mut editors = pane.items_of_type::<Self>();
  71                if state.singleton && buffers.len() == 1 {
  72                    editors.find(|editor| {
  73                        editor.read(cx).buffer.read(cx).as_singleton().as_ref() == Some(&buffers[0])
  74                    })
  75                } else if let Some(title) = &state.title {
  76                    editors.find(|editor| {
  77                        editor.read(cx).buffer().read(cx).title(cx).as_ref() == title
  78                    })
  79                } else {
  80                    None
  81                }
  82            });
  83
  84            let editor = editor.unwrap_or_else(|| {
  85                pane.update(&mut cx, |_, cx| {
  86                    let multibuffer = cx.add_model(|cx| {
  87                        let mut multibuffer;
  88                        if state.singleton && buffers.len() == 1 {
  89                            multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
  90                        } else {
  91                            multibuffer = MultiBuffer::new(replica_id);
  92                            let mut excerpts = state.excerpts.into_iter().peekable();
  93                            while let Some(excerpt) = excerpts.peek() {
  94                                let buffer_id = excerpt.buffer_id;
  95                                let buffer_excerpts = iter::from_fn(|| {
  96                                    let excerpt = excerpts.peek()?;
  97                                    (excerpt.buffer_id == buffer_id)
  98                                        .then(|| excerpts.next().unwrap())
  99                                });
 100                                let buffer =
 101                                    buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
 102                                if let Some(buffer) = buffer {
 103                                    multibuffer.push_excerpts(
 104                                        buffer.clone(),
 105                                        buffer_excerpts.filter_map(deserialize_excerpt_range),
 106                                        cx,
 107                                    );
 108                                }
 109                            }
 110                        };
 111
 112                        if let Some(title) = &state.title {
 113                            multibuffer = multibuffer.with_title(title.clone())
 114                        }
 115
 116                        multibuffer
 117                    });
 118
 119                    cx.add_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), cx))
 120                })
 121            });
 122
 123            editor.update(&mut cx, |editor, cx| {
 124                let buffer = editor.buffer.read(cx).read(cx);
 125                let selections = state
 126                    .selections
 127                    .into_iter()
 128                    .map(|selection| {
 129                        deserialize_selection(&buffer, selection)
 130                            .ok_or_else(|| anyhow!("invalid selection"))
 131                    })
 132                    .collect::<Result<Vec<_>>>()?;
 133                let scroll_top_anchor = state
 134                    .scroll_top_anchor
 135                    .and_then(|anchor| deserialize_anchor(&buffer, anchor));
 136                drop(buffer);
 137
 138                if !selections.is_empty() {
 139                    editor.set_selections_from_remote(selections, cx);
 140                }
 141                if let Some(scroll_top_anchor) = scroll_top_anchor {
 142                    editor.set_scroll_top_anchor(
 143                        scroll_top_anchor,
 144                        vec2f(state.scroll_x, state.scroll_y),
 145                        cx,
 146                    );
 147                }
 148
 149                anyhow::Ok(())
 150            })?;
 151
 152            Ok(editor)
 153        }))
 154    }
 155
 156    fn set_leader_replica_id(
 157        &mut self,
 158        leader_replica_id: Option<u16>,
 159        cx: &mut ViewContext<Self>,
 160    ) {
 161        self.leader_replica_id = leader_replica_id;
 162        if self.leader_replica_id.is_some() {
 163            self.buffer.update(cx, |buffer, cx| {
 164                buffer.remove_active_selections(cx);
 165            });
 166        } else {
 167            self.buffer.update(cx, |buffer, cx| {
 168                if self.focused {
 169                    buffer.set_active_selections(
 170                        &self.selections.disjoint_anchors(),
 171                        self.selections.line_mode,
 172                        self.cursor_shape,
 173                        cx,
 174                    );
 175                }
 176            });
 177        }
 178        cx.notify();
 179    }
 180
 181    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
 182        let buffer = self.buffer.read(cx);
 183        let excerpts = buffer
 184            .read(cx)
 185            .excerpts()
 186            .map(|(id, buffer, range)| proto::Excerpt {
 187                id: id.to_proto(),
 188                buffer_id: buffer.remote_id(),
 189                context_start: Some(serialize_text_anchor(&range.context.start)),
 190                context_end: Some(serialize_text_anchor(&range.context.end)),
 191                primary_start: range
 192                    .primary
 193                    .as_ref()
 194                    .map(|range| serialize_text_anchor(&range.start)),
 195                primary_end: range
 196                    .primary
 197                    .as_ref()
 198                    .map(|range| serialize_text_anchor(&range.end)),
 199            })
 200            .collect();
 201
 202        Some(proto::view::Variant::Editor(proto::view::Editor {
 203            singleton: buffer.is_singleton(),
 204            title: (!buffer.is_singleton()).then(|| buffer.title(cx).into()),
 205            excerpts,
 206            scroll_top_anchor: Some(serialize_anchor(&self.scroll_top_anchor)),
 207            scroll_x: self.scroll_position.x(),
 208            scroll_y: self.scroll_position.y(),
 209            selections: self
 210                .selections
 211                .disjoint_anchors()
 212                .iter()
 213                .map(serialize_selection)
 214                .collect(),
 215        }))
 216    }
 217
 218    fn add_event_to_update_proto(
 219        &self,
 220        event: &Self::Event,
 221        update: &mut Option<proto::update_view::Variant>,
 222        cx: &AppContext,
 223    ) -> bool {
 224        let update =
 225            update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
 226
 227        match update {
 228            proto::update_view::Variant::Editor(update) => match event {
 229                Event::ExcerptsAdded {
 230                    buffer,
 231                    predecessor,
 232                    excerpts,
 233                } => {
 234                    let mut excerpts = excerpts.iter();
 235                    if let Some((id, range)) = excerpts.next() {
 236                        update.inserted_excerpts.push(proto::ExcerptInsertion {
 237                            previous_excerpt_id: Some(predecessor.to_proto()),
 238                            excerpt: serialize_excerpt(buffer, id, range, cx),
 239                        });
 240                        update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
 241                            proto::ExcerptInsertion {
 242                                previous_excerpt_id: None,
 243                                excerpt: serialize_excerpt(buffer, id, range, cx),
 244                            }
 245                        }))
 246                    }
 247                    true
 248                }
 249                Event::ExcerptsRemoved { ids } => {
 250                    update
 251                        .deleted_excerpts
 252                        .extend(ids.iter().map(ExcerptId::to_proto));
 253                    true
 254                }
 255                Event::ScrollPositionChanged { .. } => {
 256                    update.scroll_top_anchor = Some(serialize_anchor(&self.scroll_top_anchor));
 257                    update.scroll_x = self.scroll_position.x();
 258                    update.scroll_y = self.scroll_position.y();
 259                    true
 260                }
 261                Event::SelectionsChanged { .. } => {
 262                    update.selections = self
 263                        .selections
 264                        .disjoint_anchors()
 265                        .iter()
 266                        .chain(self.selections.pending_anchor().as_ref())
 267                        .map(serialize_selection)
 268                        .collect();
 269                    true
 270                }
 271                _ => false,
 272            },
 273        }
 274    }
 275
 276    fn apply_update_proto(
 277        &mut self,
 278        message: update_view::Variant,
 279        cx: &mut ViewContext<Self>,
 280    ) -> Result<()> {
 281        match message {
 282            update_view::Variant::Editor(message) => {
 283                let buffer = self.buffer.read(cx);
 284                let buffer = buffer.read(cx);
 285                let selections = message
 286                    .selections
 287                    .into_iter()
 288                    .filter_map(|selection| deserialize_selection(&buffer, selection))
 289                    .collect::<Vec<_>>();
 290                let scroll_top_anchor = message
 291                    .scroll_top_anchor
 292                    .and_then(|anchor| deserialize_anchor(&buffer, anchor));
 293                drop(buffer);
 294
 295                if !selections.is_empty() {
 296                    self.set_selections_from_remote(selections, cx);
 297                    self.request_autoscroll_remotely(Autoscroll::newest(), cx);
 298                } else if let Some(anchor) = scroll_top_anchor {
 299                    self.set_scroll_top_anchor(
 300                        anchor,
 301                        vec2f(message.scroll_x, message.scroll_y),
 302                        cx,
 303                    );
 304                }
 305            }
 306        }
 307        Ok(())
 308    }
 309
 310    fn should_unfollow_on_event(event: &Self::Event, _: &AppContext) -> bool {
 311        match event {
 312            Event::Edited => true,
 313            Event::SelectionsChanged { local } => *local,
 314            Event::ScrollPositionChanged { local } => *local,
 315            _ => false,
 316        }
 317    }
 318}
 319
 320fn serialize_excerpt(
 321    buffer: &ModelHandle<Buffer>,
 322    id: &ExcerptId,
 323    range: &ExcerptRange<language::Anchor>,
 324    cx: &AppContext,
 325) -> Option<proto::Excerpt> {
 326    Some(proto::Excerpt {
 327        id: id.to_proto(),
 328        buffer_id: buffer.read(cx).remote_id(),
 329        context_start: Some(serialize_text_anchor(&range.context.start)),
 330        context_end: Some(serialize_text_anchor(&range.context.end)),
 331        primary_start: range
 332            .primary
 333            .as_ref()
 334            .map(|r| serialize_text_anchor(&r.start)),
 335        primary_end: range
 336            .primary
 337            .as_ref()
 338            .map(|r| serialize_text_anchor(&r.end)),
 339    })
 340}
 341
 342fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
 343    proto::Selection {
 344        id: selection.id as u64,
 345        start: Some(serialize_anchor(&selection.start)),
 346        end: Some(serialize_anchor(&selection.end)),
 347        reversed: selection.reversed,
 348    }
 349}
 350
 351fn serialize_anchor(anchor: &Anchor) -> proto::EditorAnchor {
 352    proto::EditorAnchor {
 353        excerpt_id: anchor.excerpt_id.to_proto(),
 354        anchor: Some(serialize_text_anchor(&anchor.text_anchor)),
 355    }
 356}
 357
 358fn deserialize_excerpt_range(excerpt: proto::Excerpt) -> Option<ExcerptRange<language::Anchor>> {
 359    let context = {
 360        let start = language::proto::deserialize_anchor(excerpt.context_start?)?;
 361        let end = language::proto::deserialize_anchor(excerpt.context_end?)?;
 362        start..end
 363    };
 364    let primary = excerpt
 365        .primary_start
 366        .zip(excerpt.primary_end)
 367        .and_then(|(start, end)| {
 368            let start = language::proto::deserialize_anchor(start)?;
 369            let end = language::proto::deserialize_anchor(end)?;
 370            Some(start..end)
 371        });
 372    Some(ExcerptRange { context, primary })
 373}
 374
 375fn deserialize_selection(
 376    buffer: &MultiBufferSnapshot,
 377    selection: proto::Selection,
 378) -> Option<Selection<Anchor>> {
 379    Some(Selection {
 380        id: selection.id as usize,
 381        start: deserialize_anchor(buffer, selection.start?)?,
 382        end: deserialize_anchor(buffer, selection.end?)?,
 383        reversed: selection.reversed,
 384        goal: SelectionGoal::None,
 385    })
 386}
 387
 388fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor) -> Option<Anchor> {
 389    let excerpt_id = ExcerptId::from_proto(anchor.excerpt_id);
 390    Some(Anchor {
 391        excerpt_id,
 392        text_anchor: language::proto::deserialize_anchor(anchor.anchor?)?,
 393        buffer_id: Some(buffer.buffer_id_for_excerpt(excerpt_id)?),
 394    })
 395}
 396
 397impl Item for Editor {
 398    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
 399        if let Ok(data) = data.downcast::<NavigationData>() {
 400            let newest_selection = self.selections.newest::<Point>(cx);
 401            let buffer = self.buffer.read(cx).read(cx);
 402            let offset = if buffer.can_resolve(&data.cursor_anchor) {
 403                data.cursor_anchor.to_point(&buffer)
 404            } else {
 405                buffer.clip_point(data.cursor_position, Bias::Left)
 406            };
 407
 408            let scroll_top_anchor = if buffer.can_resolve(&data.scroll_top_anchor) {
 409                data.scroll_top_anchor
 410            } else {
 411                buffer.anchor_before(
 412                    buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
 413                )
 414            };
 415
 416            drop(buffer);
 417
 418            if newest_selection.head() == offset {
 419                false
 420            } else {
 421                let nav_history = self.nav_history.take();
 422                self.scroll_position = data.scroll_position;
 423                self.scroll_top_anchor = scroll_top_anchor;
 424                self.change_selections(Some(Autoscroll::fit()), cx, |s| {
 425                    s.select_ranges([offset..offset])
 426                });
 427                self.nav_history = nav_history;
 428                true
 429            }
 430        } else {
 431            false
 432        }
 433    }
 434
 435    fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
 436        match path_for_buffer(&self.buffer, detail, true, cx)? {
 437            Cow::Borrowed(path) => Some(path.to_string_lossy()),
 438            Cow::Owned(path) => Some(path.to_string_lossy().to_string().into()),
 439        }
 440    }
 441
 442    fn tab_content(
 443        &self,
 444        detail: Option<usize>,
 445        style: &theme::Tab,
 446        cx: &AppContext,
 447    ) -> ElementBox {
 448        Flex::row()
 449            .with_child(
 450                Label::new(self.title(cx).into(), style.label.clone())
 451                    .aligned()
 452                    .boxed(),
 453            )
 454            .with_children(detail.and_then(|detail| {
 455                let path = path_for_buffer(&self.buffer, detail, false, cx)?;
 456                let description = path.to_string_lossy();
 457                Some(
 458                    Label::new(
 459                        if description.len() > MAX_TAB_TITLE_LEN {
 460                            description[..MAX_TAB_TITLE_LEN].to_string() + ""
 461                        } else {
 462                            description.into()
 463                        },
 464                        style.description.text.clone(),
 465                    )
 466                    .contained()
 467                    .with_style(style.description.container)
 468                    .aligned()
 469                    .boxed(),
 470                )
 471            }))
 472            .boxed()
 473    }
 474
 475    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 476        let buffer = self.buffer.read(cx).as_singleton()?;
 477        let file = buffer.read(cx).file();
 478        File::from_dyn(file).map(|file| ProjectPath {
 479            worktree_id: file.worktree_id(cx),
 480            path: file.path().clone(),
 481        })
 482    }
 483
 484    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
 485        self.buffer
 486            .read(cx)
 487            .files(cx)
 488            .into_iter()
 489            .filter_map(|file| File::from_dyn(Some(file))?.project_entry_id(cx))
 490            .collect()
 491    }
 492
 493    fn is_singleton(&self, cx: &AppContext) -> bool {
 494        self.buffer.read(cx).is_singleton()
 495    }
 496
 497    fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
 498    where
 499        Self: Sized,
 500    {
 501        Some(self.clone(cx))
 502    }
 503
 504    fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
 505        self.nav_history = Some(history);
 506    }
 507
 508    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
 509        let selection = self.selections.newest_anchor();
 510        self.push_to_nav_history(selection.head(), None, cx);
 511    }
 512
 513    fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
 514        hide_link_definition(self, cx);
 515        self.link_go_to_definition_state.last_mouse_location = None;
 516    }
 517
 518    fn is_dirty(&self, cx: &AppContext) -> bool {
 519        self.buffer().read(cx).read(cx).is_dirty()
 520    }
 521
 522    fn has_conflict(&self, cx: &AppContext) -> bool {
 523        self.buffer().read(cx).read(cx).has_conflict()
 524    }
 525
 526    fn can_save(&self, cx: &AppContext) -> bool {
 527        !self.buffer().read(cx).is_singleton() || self.project_path(cx).is_some()
 528    }
 529
 530    fn save(
 531        &mut self,
 532        project: ModelHandle<Project>,
 533        cx: &mut ViewContext<Self>,
 534    ) -> Task<Result<()>> {
 535        self.report_event("save editor", cx);
 536
 537        let buffer = self.buffer().clone();
 538        let buffers = buffer.read(cx).all_buffers();
 539        let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse();
 540        let format = project.update(cx, |project, cx| {
 541            project.format(buffers, true, FormatTrigger::Save, cx)
 542        });
 543        cx.spawn(|_, mut cx| async move {
 544            let transaction = futures::select_biased! {
 545                _ = timeout => {
 546                    log::warn!("timed out waiting for formatting");
 547                    None
 548                }
 549                transaction = format.log_err().fuse() => transaction,
 550            };
 551
 552            buffer
 553                .update(&mut cx, |buffer, cx| {
 554                    if let Some(transaction) = transaction {
 555                        if !buffer.is_singleton() {
 556                            buffer.push_transaction(&transaction.0);
 557                        }
 558                    }
 559
 560                    buffer.save(cx)
 561                })
 562                .await?;
 563            Ok(())
 564        })
 565    }
 566
 567    fn save_as(
 568        &mut self,
 569        project: ModelHandle<Project>,
 570        abs_path: PathBuf,
 571        cx: &mut ViewContext<Self>,
 572    ) -> Task<Result<()>> {
 573        let buffer = self
 574            .buffer()
 575            .read(cx)
 576            .as_singleton()
 577            .expect("cannot call save_as on an excerpt list");
 578
 579        project.update(cx, |project, cx| {
 580            project.save_buffer_as(buffer, abs_path, cx)
 581        })
 582    }
 583
 584    fn reload(
 585        &mut self,
 586        project: ModelHandle<Project>,
 587        cx: &mut ViewContext<Self>,
 588    ) -> Task<Result<()>> {
 589        let buffer = self.buffer().clone();
 590        let buffers = self.buffer.read(cx).all_buffers();
 591        let reload_buffers =
 592            project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx));
 593        cx.spawn(|this, mut cx| async move {
 594            let transaction = reload_buffers.log_err().await;
 595            this.update(&mut cx, |editor, cx| {
 596                editor.request_autoscroll(Autoscroll::fit(), cx)
 597            });
 598            buffer.update(&mut cx, |buffer, _| {
 599                if let Some(transaction) = transaction {
 600                    if !buffer.is_singleton() {
 601                        buffer.push_transaction(&transaction.0);
 602                    }
 603                }
 604            });
 605            Ok(())
 606        })
 607    }
 608
 609    fn git_diff_recalc(
 610        &mut self,
 611        _project: ModelHandle<Project>,
 612        cx: &mut ViewContext<Self>,
 613    ) -> Task<Result<()>> {
 614        self.buffer().update(cx, |multibuffer, cx| {
 615            multibuffer.git_diff_recalc(cx);
 616        });
 617        Task::ready(Ok(()))
 618    }
 619
 620    fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
 621        let mut result = Vec::new();
 622        match event {
 623            Event::Closed => result.push(ItemEvent::CloseItem),
 624            Event::Saved | Event::TitleChanged => {
 625                result.push(ItemEvent::UpdateTab);
 626                result.push(ItemEvent::UpdateBreadcrumbs);
 627            }
 628            Event::Reparsed => {
 629                result.push(ItemEvent::UpdateBreadcrumbs);
 630            }
 631            Event::SelectionsChanged { local } if *local => {
 632                result.push(ItemEvent::UpdateBreadcrumbs);
 633            }
 634            Event::DirtyChanged => {
 635                result.push(ItemEvent::UpdateTab);
 636            }
 637            Event::BufferEdited => {
 638                result.push(ItemEvent::Edit);
 639                result.push(ItemEvent::UpdateBreadcrumbs);
 640            }
 641            _ => {}
 642        }
 643        result
 644    }
 645
 646    fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 647        Some(Box::new(handle.clone()))
 648    }
 649
 650    fn breadcrumb_location(&self) -> ToolbarItemLocation {
 651        ToolbarItemLocation::PrimaryLeft { flex: None }
 652    }
 653
 654    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
 655        let cursor = self.selections.newest_anchor().head();
 656        let multibuffer = &self.buffer().read(cx);
 657        let (buffer_id, symbols) =
 658            multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
 659        let buffer = multibuffer.buffer(buffer_id)?;
 660
 661        let buffer = buffer.read(cx);
 662        let filename = buffer
 663            .snapshot()
 664            .resolve_file_path(
 665                cx,
 666                self.project
 667                    .as_ref()
 668                    .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
 669                    .unwrap_or_default(),
 670            )
 671            .map(|path| path.to_string_lossy().to_string())
 672            .unwrap_or_else(|| "untitled".to_string());
 673
 674        let mut breadcrumbs = vec![Label::new(filename, theme.breadcrumbs.text.clone()).boxed()];
 675        breadcrumbs.extend(symbols.into_iter().map(|symbol| {
 676            Text::new(symbol.text, theme.breadcrumbs.text.clone())
 677                .with_highlights(symbol.highlight_ranges)
 678                .boxed()
 679        }));
 680        Some(breadcrumbs)
 681    }
 682}
 683
 684impl ProjectItem for Editor {
 685    type Item = Buffer;
 686
 687    fn for_project_item(
 688        project: ModelHandle<Project>,
 689        buffer: ModelHandle<Buffer>,
 690        cx: &mut ViewContext<Self>,
 691    ) -> Self {
 692        Self::for_buffer(buffer, Some(project), cx)
 693    }
 694}
 695
 696enum BufferSearchHighlights {}
 697impl SearchableItem for Editor {
 698    type Match = Range<Anchor>;
 699
 700    fn to_search_event(event: &Self::Event) -> Option<SearchEvent> {
 701        match event {
 702            Event::BufferEdited => Some(SearchEvent::MatchesInvalidated),
 703            Event::SelectionsChanged { .. } => Some(SearchEvent::ActiveMatchChanged),
 704            _ => None,
 705        }
 706    }
 707
 708    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
 709        self.clear_background_highlights::<BufferSearchHighlights>(cx);
 710    }
 711
 712    fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
 713        self.highlight_background::<BufferSearchHighlights>(
 714            matches,
 715            |theme| theme.search.match_background,
 716            cx,
 717        );
 718    }
 719
 720    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
 721        let display_map = self.snapshot(cx).display_snapshot;
 722        let selection = self.selections.newest::<usize>(cx);
 723        if selection.start == selection.end {
 724            let point = selection.start.to_display_point(&display_map);
 725            let range = surrounding_word(&display_map, point);
 726            let range = range.start.to_offset(&display_map, Bias::Left)
 727                ..range.end.to_offset(&display_map, Bias::Right);
 728            let text: String = display_map.buffer_snapshot.text_for_range(range).collect();
 729            if text.trim().is_empty() {
 730                String::new()
 731            } else {
 732                text
 733            }
 734        } else {
 735            display_map
 736                .buffer_snapshot
 737                .text_for_range(selection.start..selection.end)
 738                .collect()
 739        }
 740    }
 741
 742    fn activate_match(
 743        &mut self,
 744        index: usize,
 745        matches: Vec<Range<Anchor>>,
 746        cx: &mut ViewContext<Self>,
 747    ) {
 748        self.unfold_ranges([matches[index].clone()], false, cx);
 749        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
 750            s.select_ranges([matches[index].clone()])
 751        });
 752    }
 753
 754    fn match_index_for_direction(
 755        &mut self,
 756        matches: &Vec<Range<Anchor>>,
 757        mut current_index: usize,
 758        direction: Direction,
 759        cx: &mut ViewContext<Self>,
 760    ) -> usize {
 761        let buffer = self.buffer().read(cx).snapshot(cx);
 762        let cursor = self.selections.newest_anchor().head();
 763        if matches[current_index].start.cmp(&cursor, &buffer).is_gt() {
 764            if direction == Direction::Prev {
 765                if current_index == 0 {
 766                    current_index = matches.len() - 1;
 767                } else {
 768                    current_index -= 1;
 769                }
 770            }
 771        } else if matches[current_index].end.cmp(&cursor, &buffer).is_lt() {
 772            if direction == Direction::Next {
 773                current_index = 0;
 774            }
 775        } else if direction == Direction::Prev {
 776            if current_index == 0 {
 777                current_index = matches.len() - 1;
 778            } else {
 779                current_index -= 1;
 780            }
 781        } else if direction == Direction::Next {
 782            if current_index == matches.len() - 1 {
 783                current_index = 0
 784            } else {
 785                current_index += 1;
 786            }
 787        };
 788        current_index
 789    }
 790
 791    fn find_matches(
 792        &mut self,
 793        query: project::search::SearchQuery,
 794        cx: &mut ViewContext<Self>,
 795    ) -> Task<Vec<Range<Anchor>>> {
 796        let buffer = self.buffer().read(cx).snapshot(cx);
 797        cx.background().spawn(async move {
 798            let mut ranges = Vec::new();
 799            if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
 800                ranges.extend(
 801                    query
 802                        .search(excerpt_buffer.as_rope())
 803                        .await
 804                        .into_iter()
 805                        .map(|range| {
 806                            buffer.anchor_after(range.start)..buffer.anchor_before(range.end)
 807                        }),
 808                );
 809            } else {
 810                for excerpt in buffer.excerpt_boundaries_in_range(0..buffer.len()) {
 811                    let excerpt_range = excerpt.range.context.to_offset(&excerpt.buffer);
 812                    let rope = excerpt.buffer.as_rope().slice(excerpt_range.clone());
 813                    ranges.extend(query.search(&rope).await.into_iter().map(|range| {
 814                        let start = excerpt
 815                            .buffer
 816                            .anchor_after(excerpt_range.start + range.start);
 817                        let end = excerpt
 818                            .buffer
 819                            .anchor_before(excerpt_range.start + range.end);
 820                        buffer.anchor_in_excerpt(excerpt.id.clone(), start)
 821                            ..buffer.anchor_in_excerpt(excerpt.id.clone(), end)
 822                    }));
 823                }
 824            }
 825            ranges
 826        })
 827    }
 828
 829    fn active_match_index(
 830        &mut self,
 831        matches: Vec<Range<Anchor>>,
 832        cx: &mut ViewContext<Self>,
 833    ) -> Option<usize> {
 834        active_match_index(
 835            &matches,
 836            &self.selections.newest_anchor().head(),
 837            &self.buffer().read(cx).snapshot(cx),
 838        )
 839    }
 840}
 841
 842pub fn active_match_index(
 843    ranges: &[Range<Anchor>],
 844    cursor: &Anchor,
 845    buffer: &MultiBufferSnapshot,
 846) -> Option<usize> {
 847    if ranges.is_empty() {
 848        None
 849    } else {
 850        match ranges.binary_search_by(|probe| {
 851            if probe.end.cmp(cursor, &*buffer).is_lt() {
 852                Ordering::Less
 853            } else if probe.start.cmp(cursor, &*buffer).is_gt() {
 854                Ordering::Greater
 855            } else {
 856                Ordering::Equal
 857            }
 858        }) {
 859            Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
 860        }
 861    }
 862}
 863
 864pub struct CursorPosition {
 865    position: Option<Point>,
 866    selected_count: usize,
 867    _observe_active_editor: Option<Subscription>,
 868}
 869
 870impl Default for CursorPosition {
 871    fn default() -> Self {
 872        Self::new()
 873    }
 874}
 875
 876impl CursorPosition {
 877    pub fn new() -> Self {
 878        Self {
 879            position: None,
 880            selected_count: 0,
 881            _observe_active_editor: None,
 882        }
 883    }
 884
 885    fn update_position(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
 886        let editor = editor.read(cx);
 887        let buffer = editor.buffer().read(cx).snapshot(cx);
 888
 889        self.selected_count = 0;
 890        let mut last_selection: Option<Selection<usize>> = None;
 891        for selection in editor.selections.all::<usize>(cx) {
 892            self.selected_count += selection.end - selection.start;
 893            if last_selection
 894                .as_ref()
 895                .map_or(true, |last_selection| selection.id > last_selection.id)
 896            {
 897                last_selection = Some(selection);
 898            }
 899        }
 900        self.position = last_selection.map(|s| s.head().to_point(&buffer));
 901
 902        cx.notify();
 903    }
 904}
 905
 906impl Entity for CursorPosition {
 907    type Event = ();
 908}
 909
 910impl View for CursorPosition {
 911    fn ui_name() -> &'static str {
 912        "CursorPosition"
 913    }
 914
 915    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
 916        if let Some(position) = self.position {
 917            let theme = &cx.global::<Settings>().theme.workspace.status_bar;
 918            let mut text = format!("{},{}", position.row + 1, position.column + 1);
 919            if self.selected_count > 0 {
 920                write!(text, " ({} selected)", self.selected_count).unwrap();
 921            }
 922            Label::new(text, theme.cursor_position.clone()).boxed()
 923        } else {
 924            Empty::new().boxed()
 925        }
 926    }
 927}
 928
 929impl StatusItemView for CursorPosition {
 930    fn set_active_pane_item(
 931        &mut self,
 932        active_pane_item: Option<&dyn ItemHandle>,
 933        cx: &mut ViewContext<Self>,
 934    ) {
 935        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
 936            self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
 937            self.update_position(editor, cx);
 938        } else {
 939            self.position = None;
 940            self._observe_active_editor = None;
 941        }
 942
 943        cx.notify();
 944    }
 945}
 946
 947fn path_for_buffer<'a>(
 948    buffer: &ModelHandle<MultiBuffer>,
 949    height: usize,
 950    include_filename: bool,
 951    cx: &'a AppContext,
 952) -> Option<Cow<'a, Path>> {
 953    let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
 954    path_for_file(file, height, include_filename, cx)
 955}
 956
 957fn path_for_file<'a>(
 958    file: &'a dyn language::File,
 959    mut height: usize,
 960    include_filename: bool,
 961    cx: &'a AppContext,
 962) -> Option<Cow<'a, Path>> {
 963    // Ensure we always render at least the filename.
 964    height += 1;
 965
 966    let mut prefix = file.path().as_ref();
 967    while height > 0 {
 968        if let Some(parent) = prefix.parent() {
 969            prefix = parent;
 970            height -= 1;
 971        } else {
 972            break;
 973        }
 974    }
 975
 976    // Here we could have just always used `full_path`, but that is very
 977    // allocation-heavy and so we try to use a `Cow<Path>` if we haven't
 978    // traversed all the way up to the worktree's root.
 979    if height > 0 {
 980        let full_path = file.full_path(cx);
 981        if include_filename {
 982            Some(full_path.into())
 983        } else {
 984            Some(full_path.parent()?.to_path_buf().into())
 985        }
 986    } else {
 987        let mut path = file.path().strip_prefix(prefix).ok()?;
 988        if !include_filename {
 989            path = path.parent()?;
 990        }
 991        Some(path.into())
 992    }
 993}
 994
 995#[cfg(test)]
 996mod tests {
 997    use super::*;
 998    use gpui::MutableAppContext;
 999    use std::{
1000        path::{Path, PathBuf},
1001        sync::Arc,
1002    };
1003
1004    #[gpui::test]
1005    fn test_path_for_file(cx: &mut MutableAppContext) {
1006        let file = TestFile {
1007            path: Path::new("").into(),
1008            full_path: PathBuf::from(""),
1009        };
1010        assert_eq!(path_for_file(&file, 0, false, cx), None);
1011    }
1012
1013    struct TestFile {
1014        path: Arc<Path>,
1015        full_path: PathBuf,
1016    }
1017
1018    impl language::File for TestFile {
1019        fn path(&self) -> &Arc<Path> {
1020            &self.path
1021        }
1022
1023        fn full_path(&self, _: &gpui::AppContext) -> PathBuf {
1024            self.full_path.clone()
1025        }
1026
1027        fn as_local(&self) -> Option<&dyn language::LocalFile> {
1028            todo!()
1029        }
1030
1031        fn mtime(&self) -> std::time::SystemTime {
1032            todo!()
1033        }
1034
1035        fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr {
1036            todo!()
1037        }
1038
1039        fn is_deleted(&self) -> bool {
1040            todo!()
1041        }
1042
1043        fn save(
1044            &self,
1045            _: u64,
1046            _: language::Rope,
1047            _: clock::Global,
1048            _: project::LineEnding,
1049            _: &mut MutableAppContext,
1050        ) -> gpui::Task<anyhow::Result<(clock::Global, String, std::time::SystemTime)>> {
1051            todo!()
1052        }
1053
1054        fn as_any(&self) -> &dyn std::any::Any {
1055            todo!()
1056        }
1057
1058        fn to_proto(&self) -> rpc::proto::File {
1059            todo!()
1060        }
1061    }
1062}