697 lines · 22.5 KB · Rust
RawDownload
use anyhow::Result;use call::ActiveCall;use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelStore};use client::{    proto::{self, PeerId},    ChannelId, Collaborator, ParticipantIndex,};use collections::HashMap;use editor::{    display_map::ToDisplayPoint, scroll::Autoscroll, CollaborationHub, DisplayPoint, Editor,    EditorEvent,};use gpui::{    actions, AnyView, App, ClipboardItem, Context, Entity, EventEmitter, Focusable, Pixels, Point,    Render, Subscription, Task, VisualContext as _, WeakEntity, Window,};use project::Project;use rpc::proto::ChannelVisibility;use std::{    any::{Any, TypeId},    sync::Arc,};use ui::prelude::*;use util::ResultExt;use workspace::item::TabContentParams;use workspace::{item::Dedup, notifications::NotificationId};use workspace::{    item::{FollowableItem, Item, ItemEvent, ItemHandle},    searchable::SearchableItemHandle,    ItemNavHistory, Pane, SaveIntent, Toast, ViewId, Workspace, WorkspaceId,}; actions!(collab, [CopyLink]); pub fn init(cx: &mut App) {    workspace::FollowableViewRegistry::register::<ChannelView>(cx)} pub struct ChannelView {    pub editor: Entity<Editor>,    workspace: WeakEntity<Workspace>,    project: Entity<Project>,    channel_store: Entity<ChannelStore>,    channel_buffer: Entity<ChannelBuffer>,    remote_id: Option<ViewId>,    _editor_event_subscription: Subscription,    _reparse_subscription: Option<Subscription>,} impl ChannelView {    pub fn open(        channel_id: ChannelId,        link_position: Option<String>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut App,    ) -> Task<Result<Entity<Self>>> {        let pane = workspace.read(cx).active_pane().clone();        let channel_view = Self::open_in_pane(            channel_id,            link_position,            pane.clone(),            workspace.clone(),            window,            cx,        );        window.spawn(cx, |mut cx| async move {            let channel_view = channel_view.await?;            pane.update_in(&mut cx, |pane, window, cx| {                telemetry::event!(                    "Channel Notes Opened",                    channel_id,                    room_id = ActiveCall::global(cx)                        .read(cx)                        .room()                        .map(|r| r.read(cx).id())                );                pane.add_item(Box::new(channel_view.clone()), true, true, None, window, cx);            })?;            anyhow::Ok(channel_view)        })    }     pub fn open_in_pane(        channel_id: ChannelId,        link_position: Option<String>,        pane: Entity<Pane>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut App,    ) -> Task<Result<Entity<Self>>> {        let channel_view = Self::load(channel_id, workspace, window, cx);        window.spawn(cx, |mut cx| async move {            let channel_view = channel_view.await?;             pane.update_in(&mut cx, |pane, window, cx| {                let buffer_id = channel_view.read(cx).channel_buffer.read(cx).remote_id(cx);                 let existing_view = pane                    .items_of_type::<Self>()                    .find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);                 // If this channel buffer is already open in this pane, just return it.                if let Some(existing_view) = existing_view.clone() {                    if existing_view.read(cx).channel_buffer == channel_view.read(cx).channel_buffer                    {                        if let Some(link_position) = link_position {                            existing_view.update(cx, |channel_view, cx| {                                channel_view.focus_position_from_link(                                    link_position,                                    true,                                    window,                                    cx,                                )                            });                        }                        return existing_view;                    }                }                 // If the pane contained a disconnected view for this channel buffer,                // replace that.                if let Some(existing_item) = existing_view {                    if let Some(ix) = pane.index_for_item(&existing_item) {                        pane.close_item_by_id(                            existing_item.entity_id(),                            SaveIntent::Skip,                            window,                            cx,                        )                        .detach();                        pane.add_item(                            Box::new(channel_view.clone()),                            true,                            true,                            Some(ix),                            window,                            cx,                        );                    }                }                 if let Some(link_position) = link_position {                    channel_view.update(cx, |channel_view, cx| {                        channel_view.focus_position_from_link(link_position, true, window, cx)                    });                }                 channel_view            })        })    }     pub fn load(        channel_id: ChannelId,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut App,    ) -> Task<Result<Entity<Self>>> {        let weak_workspace = workspace.downgrade();        let workspace = workspace.read(cx);        let project = workspace.project().to_owned();        let channel_store = ChannelStore::global(cx);        let language_registry = workspace.app_state().languages.clone();        let markdown = language_registry.language_for_name("Markdown");        let channel_buffer =            channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));         window.spawn(cx, |mut cx| async move {            let channel_buffer = channel_buffer.await?;            let markdown = markdown.await.log_err();             channel_buffer.update(&mut cx, |channel_buffer, cx| {                channel_buffer.buffer().update(cx, |buffer, cx| {                    buffer.set_language_registry(language_registry);                    let Some(markdown) = markdown else {                        return;                    };                    buffer.set_language(Some(markdown), cx);                })            })?;             cx.new_window_entity(|window, cx| {                let mut this = Self::new(                    project,                    weak_workspace,                    channel_store,                    channel_buffer,                    window,                    cx,                );                this.acknowledge_buffer_version(cx);                this            })        })    }     pub fn new(        project: Entity<Project>,        workspace: WeakEntity<Workspace>,        channel_store: Entity<ChannelStore>,        channel_buffer: Entity<ChannelBuffer>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let buffer = channel_buffer.read(cx).buffer();        let this = cx.entity().downgrade();        let editor = cx.new(|cx| {            let mut editor = Editor::for_buffer(buffer, None, window, cx);            editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(                channel_buffer.clone(),            )));            editor.set_custom_context_menu(move |_, position, window, cx| {                let this = this.clone();                Some(ui::ContextMenu::build(window, cx, move |menu, _, _| {                    menu.entry("Copy link to section", None, move |window, cx| {                        this.update(cx, |this, cx| {                            this.copy_link_for_position(position, window, cx)                        })                        .ok();                    })                }))            });            editor        });        let _editor_event_subscription =            cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| cx.emit(e.clone()));         cx.subscribe_in(&channel_buffer, window, Self::handle_channel_buffer_event)            .detach();         Self {            editor,            workspace,            project,            channel_store,            channel_buffer,            remote_id: None,            _editor_event_subscription,            _reparse_subscription: None,        }    }     fn focus_position_from_link(        &mut self,        position: String,        first_attempt: bool,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let position = Channel::slug(&position).to_lowercase();        let snapshot = self            .editor            .update(cx, |editor, cx| editor.snapshot(window, cx));         if let Some(outline) = snapshot.buffer_snapshot.outline(None) {            if let Some(item) = outline                .items                .iter()                .find(|item| &Channel::slug(&item.text).to_lowercase() == &position)            {                self.editor.update(cx, |editor, cx| {                    editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {                        s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)])                    })                });                return;            }        }         if !first_attempt {            return;        }        self._reparse_subscription = Some(cx.subscribe_in(            &self.editor,            window,            move |this, _, e: &EditorEvent, window, cx| {                match e {                    EditorEvent::Reparsed(_) => {                        this.focus_position_from_link(position.clone(), false, window, cx);                        this._reparse_subscription.take();                    }                    EditorEvent::Edited { .. } | EditorEvent::SelectionsChanged { local: true } => {                        this._reparse_subscription.take();                    }                    _ => {}                };            },        ));    }     fn copy_link(&mut self, _: &CopyLink, window: &mut Window, cx: &mut Context<Self>) {        let position = self            .editor            .update(cx, |editor, cx| editor.selections.newest_display(cx).start);        self.copy_link_for_position(position, window, cx)    }     fn copy_link_for_position(        &self,        position: DisplayPoint,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let snapshot = self            .editor            .update(cx, |editor, cx| editor.snapshot(window, cx));         let mut closest_heading = None;         if let Some(outline) = snapshot.buffer_snapshot.outline(None) {            for item in outline.items {                if item.range.start.to_display_point(&snapshot) > position {                    break;                }                closest_heading = Some(item);            }        }         let Some(channel) = self.channel(cx) else {            return;        };         let link = channel.notes_link(closest_heading.map(|heading| heading.text), cx);        cx.write_to_clipboard(ClipboardItem::new_string(link));        self.workspace            .update(cx, |workspace, cx| {                struct CopyLinkForPositionToast;                 workspace.show_toast(                    Toast::new(                        NotificationId::unique::<CopyLinkForPositionToast>(),                        "Link copied to clipboard",                    ),                    cx,                );            })            .ok();    }     pub fn channel(&self, cx: &App) -> Option<Arc<Channel>> {        self.channel_buffer.read(cx).channel(cx)    }     fn handle_channel_buffer_event(        &mut self,        _: &Entity<ChannelBuffer>,        event: &ChannelBufferEvent,        window: &mut Window,        cx: &mut Context<Self>,    ) {        match event {            ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {                editor.set_read_only(true);                cx.notify();            }),            ChannelBufferEvent::ChannelChanged => {                self.editor.update(cx, |_, cx| {                    cx.emit(editor::EditorEvent::TitleChanged);                    cx.notify()                });            }            ChannelBufferEvent::BufferEdited => {                if self.editor.read(cx).is_focused(window) {                    self.acknowledge_buffer_version(cx);                } else {                    self.channel_store.update(cx, |store, cx| {                        let channel_buffer = self.channel_buffer.read(cx);                        store.update_latest_notes_version(                            channel_buffer.channel_id,                            channel_buffer.epoch(),                            &channel_buffer.buffer().read(cx).version(),                            cx,                        )                    });                }            }            ChannelBufferEvent::CollaboratorsChanged => {}        }    }     fn acknowledge_buffer_version(&mut self, cx: &mut Context<ChannelView>) {        self.channel_store.update(cx, |store, cx| {            let channel_buffer = self.channel_buffer.read(cx);            store.acknowledge_notes_version(                channel_buffer.channel_id,                channel_buffer.epoch(),                &channel_buffer.buffer().read(cx).version(),                cx,            )        });        self.channel_buffer.update(cx, |buffer, cx| {            buffer.acknowledge_buffer_version(cx);        });    }} impl EventEmitter<EditorEvent> for ChannelView {} impl Render for ChannelView {    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        div()            .size_full()            .on_action(cx.listener(Self::copy_link))            .child(self.editor.clone())    }} impl Focusable for ChannelView {    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {        self.editor.read(cx).focus_handle(cx)    }} impl Item for ChannelView {    type Event = EditorEvent;     fn act_as_type<'a>(        &'a self,        type_id: TypeId,        self_handle: &'a Entity<Self>,        _: &'a App,    ) -> Option<AnyView> {        if type_id == TypeId::of::<Self>() {            Some(self_handle.to_any())        } else if type_id == TypeId::of::<Editor>() {            Some(self.editor.to_any())        } else {            None        }    }     fn tab_icon(&self, _: &Window, cx: &App) -> Option<Icon> {        let channel = self.channel(cx)?;        let icon = match channel.visibility {            ChannelVisibility::Public => IconName::Public,            ChannelVisibility::Members => IconName::Hash,        };         Some(Icon::new(icon))    }     fn tab_content(&self, params: TabContentParams, _: &Window, cx: &App) -> gpui::AnyElement {        let (channel_name, status) = if let Some(channel) = self.channel(cx) {            let status = match (                self.channel_buffer.read(cx).buffer().read(cx).read_only(),                self.channel_buffer.read(cx).is_connected(),            ) {                (false, true) => None,                (true, true) => Some("read-only"),                (_, false) => Some("disconnected"),            };             (channel.name.clone(), status)        } else {            ("<unknown>".into(), Some("disconnected"))        };         h_flex()            .gap_2()            .child(                Label::new(channel_name)                    .color(params.text_color())                    .when(params.preview, |this| this.italic()),            )            .when_some(status, |element, status| {                element.child(                    Label::new(status)                        .size(LabelSize::XSmall)                        .color(Color::Muted),                )            })            .into_any_element()    }     fn telemetry_event_text(&self) -> Option<&'static str> {        None    }     fn clone_on_split(        &self,        _: Option<WorkspaceId>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Option<Entity<Self>> {        Some(cx.new(|cx| {            Self::new(                self.project.clone(),                self.workspace.clone(),                self.channel_store.clone(),                self.channel_buffer.clone(),                window,                cx,            )        }))    }     fn is_singleton(&self, _cx: &App) -> bool {        false    }

Lines 1–500 of 697.