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