message_editor.rs

  1use channel::{Channel, ChannelStore};
  2use client::UserId;
  3use collections::HashMap;
  4use editor::{AnchorRangeExt, Editor};
  5use gpui::{
  6    elements::ChildView, AnyElement, AsyncAppContext, Element, Entity, ModelHandle, Task, View,
  7    ViewContext, ViewHandle, WeakViewHandle,
  8};
  9use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
 10use lazy_static::lazy_static;
 11use project::search::SearchQuery;
 12use std::{ops::Range, sync::Arc, time::Duration};
 13
 14const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
 15
 16lazy_static! {
 17    static ref MENTIONS_SEARCH: SearchQuery = SearchQuery::regex(
 18        "@[-_\\w]+",
 19        false,
 20        false,
 21        Default::default(),
 22        Default::default()
 23    )
 24    .unwrap();
 25}
 26
 27pub struct MessageEditor {
 28    pub editor: ViewHandle<Editor>,
 29    channel_store: ModelHandle<ChannelStore>,
 30    users: HashMap<String, UserId>,
 31    mentions: Vec<UserId>,
 32    mentions_task: Option<Task<()>>,
 33    channel: Option<Arc<Channel>>,
 34}
 35
 36pub struct ChatMessage {
 37    pub text: String,
 38    pub mentions: Vec<(Range<usize>, UserId)>,
 39}
 40
 41impl MessageEditor {
 42    pub fn new(
 43        language_registry: Arc<LanguageRegistry>,
 44        channel_store: ModelHandle<ChannelStore>,
 45        editor: ViewHandle<Editor>,
 46        cx: &mut ViewContext<Self>,
 47    ) -> Self {
 48        editor.update(cx, |editor, cx| {
 49            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
 50        });
 51
 52        let buffer = editor
 53            .read(cx)
 54            .buffer()
 55            .read(cx)
 56            .as_singleton()
 57            .expect("message editor must be singleton");
 58
 59        cx.subscribe(&buffer, Self::on_buffer_event).detach();
 60        cx.subscribe(&editor, |_, _, event, cx| {
 61            if let editor::Event::Focused = event {
 62                eprintln!("focused");
 63                cx.notify()
 64            }
 65        })
 66        .detach();
 67
 68        let markdown = language_registry.language_for_name("Markdown");
 69        cx.app_context()
 70            .spawn(|mut cx| async move {
 71                let markdown = markdown.await?;
 72                buffer.update(&mut cx, |buffer, cx| {
 73                    buffer.set_language(Some(markdown), cx)
 74                });
 75                anyhow::Ok(())
 76            })
 77            .detach_and_log_err(cx);
 78
 79        Self {
 80            editor,
 81            channel_store,
 82            users: HashMap::default(),
 83            channel: None,
 84            mentions: Vec::new(),
 85            mentions_task: None,
 86        }
 87    }
 88
 89    pub fn set_channel(&mut self, channel: Arc<Channel>, cx: &mut ViewContext<Self>) {
 90        self.editor.update(cx, |editor, cx| {
 91            editor.set_placeholder_text(format!("Message #{}", channel.name), cx);
 92        });
 93        self.channel = Some(channel);
 94        self.refresh_users(cx);
 95    }
 96
 97    pub fn refresh_users(&mut self, cx: &mut ViewContext<Self>) {
 98        if let Some(channel) = &self.channel {
 99            let members = self.channel_store.update(cx, |store, cx| {
100                store.get_channel_member_details(channel.id, cx)
101            });
102            cx.spawn(|this, mut cx| async move {
103                let members = members.await?;
104                this.update(&mut cx, |this, _| {
105                    this.users.clear();
106                    this.users.extend(
107                        members
108                            .into_iter()
109                            .map(|member| (member.user.github_login.clone(), member.user.id)),
110                    );
111                })?;
112                anyhow::Ok(())
113            })
114            .detach_and_log_err(cx);
115        }
116    }
117
118    pub fn take_message(&mut self, cx: &mut ViewContext<Self>) -> ChatMessage {
119        self.editor.update(cx, |editor, cx| {
120            let highlights = editor.text_highlights::<Self>(cx);
121            let text = editor.text(cx);
122            let snapshot = editor.buffer().read(cx).snapshot(cx);
123            let mentions = if let Some((_, ranges)) = highlights {
124                ranges
125                    .iter()
126                    .map(|range| range.to_offset(&snapshot))
127                    .zip(self.mentions.iter().copied())
128                    .collect()
129            } else {
130                Vec::new()
131            };
132
133            editor.clear(cx);
134            self.mentions.clear();
135
136            ChatMessage { text, mentions }
137        })
138    }
139
140    fn on_buffer_event(
141        &mut self,
142        buffer: ModelHandle<Buffer>,
143        event: &language::Event,
144        cx: &mut ViewContext<Self>,
145    ) {
146        if let language::Event::Reparsed | language::Event::Edited = event {
147            let buffer = buffer.read(cx).snapshot();
148            self.mentions_task = Some(cx.spawn(|this, cx| async move {
149                cx.background().timer(MENTIONS_DEBOUNCE_INTERVAL).await;
150                Self::find_mentions(this, buffer, cx).await;
151            }));
152        }
153    }
154
155    async fn find_mentions(
156        this: WeakViewHandle<MessageEditor>,
157        buffer: BufferSnapshot,
158        mut cx: AsyncAppContext,
159    ) {
160        let (buffer, ranges) = cx
161            .background()
162            .spawn(async move {
163                let ranges = MENTIONS_SEARCH.search(&buffer, None).await;
164                (buffer, ranges)
165            })
166            .await;
167
168        this.update(&mut cx, |this, cx| {
169            let mut anchor_ranges = Vec::new();
170            let mut mentioned_user_ids = Vec::new();
171            let mut text = String::new();
172
173            this.editor.update(cx, |editor, cx| {
174                let multi_buffer = editor.buffer().read(cx).snapshot(cx);
175                for range in ranges {
176                    text.clear();
177                    text.extend(buffer.text_for_range(range.clone()));
178                    if let Some(username) = text.strip_prefix("@") {
179                        if let Some(user_id) = this.users.get(username) {
180                            let start = multi_buffer.anchor_after(range.start);
181                            let end = multi_buffer.anchor_after(range.end);
182
183                            mentioned_user_ids.push(*user_id);
184                            anchor_ranges.push(start..end);
185                        }
186                    }
187                }
188
189                editor.clear_highlights::<Self>(cx);
190                editor.highlight_text::<Self>(
191                    anchor_ranges,
192                    theme::current(cx).chat_panel.mention_highlight,
193                    cx,
194                )
195            });
196
197            this.mentions = mentioned_user_ids;
198            this.mentions_task.take();
199        })
200        .ok();
201    }
202}
203
204impl Entity for MessageEditor {
205    type Event = ();
206}
207
208impl View for MessageEditor {
209    fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
210        ChildView::new(&self.editor, cx).into_any()
211    }
212
213    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
214        if cx.is_self_focused() {
215            cx.focus(&self.editor);
216        }
217    }
218}