mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 02:47:34 +03:00
Start work on chat panel and non-uniform list
Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
parent
4353bdb9d5
commit
6955579f19
@ -12,7 +12,7 @@ use chrono::{DateTime, Local};
|
|||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{actions, AppContext};
|
use gpui::{actions, AppContext, SharedString};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
|
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
|
||||||
@ -47,7 +47,7 @@ struct MessageMetadata {
|
|||||||
enum MessageStatus {
|
enum MessageStatus {
|
||||||
Pending,
|
Pending,
|
||||||
Done,
|
Done,
|
||||||
Error(Arc<str>),
|
Error(SharedString),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -1628,8 +1628,9 @@ impl Conversation {
|
|||||||
metadata.status = MessageStatus::Done;
|
metadata.status = MessageStatus::Done;
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
metadata.status =
|
metadata.status = MessageStatus::Error(SharedString::from(
|
||||||
MessageStatus::Error(error.to_string().trim().into());
|
error.to_string().trim().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@ -2273,7 +2274,7 @@ impl ConversationEditor {
|
|||||||
Some(
|
Some(
|
||||||
div()
|
div()
|
||||||
.id("error")
|
.id("error")
|
||||||
.tooltip(move |cx| Tooltip::text(&error, cx))
|
.tooltip(move |cx| Tooltip::text(error.clone(), cx))
|
||||||
.child(IconElement::new(Icon::XCircle)),
|
.child(IconElement::new(Icon::XCircle)),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
// use crate::{
|
// use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings};
|
||||||
// channel_view::ChannelView, is_channels_feature_enabled, render_avatar, ChatPanelSettings,
|
|
||||||
// };
|
|
||||||
// use anyhow::Result;
|
// use anyhow::Result;
|
||||||
// use call::ActiveCall;
|
// use call::ActiveCall;
|
||||||
// use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore};
|
// use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore};
|
||||||
@ -9,13 +7,9 @@
|
|||||||
// use db::kvp::KEY_VALUE_STORE;
|
// use db::kvp::KEY_VALUE_STORE;
|
||||||
// use editor::Editor;
|
// use editor::Editor;
|
||||||
// use gpui::{
|
// use gpui::{
|
||||||
// actions,
|
// actions, div, list, px, serde_json, AnyElement, AnyView, AppContext, AsyncAppContext, Div,
|
||||||
// elements::*,
|
// Entity, EventEmitter, FocusableView, ListOffset, ListScrollHandle, Model, Orientation, Render,
|
||||||
// platform::{CursorStyle, MouseButton},
|
// Subscription, Task, View, ViewContext, WeakView,
|
||||||
// serde_json,
|
|
||||||
// views::{ItemType, Select, SelectStyle},
|
|
||||||
// AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View,
|
|
||||||
// ViewContext, ViewHandle, WeakViewHandle,
|
|
||||||
// };
|
// };
|
||||||
// use language::LanguageRegistry;
|
// use language::LanguageRegistry;
|
||||||
// use menu::Confirm;
|
// use menu::Confirm;
|
||||||
@ -23,10 +17,10 @@
|
|||||||
// use project::Fs;
|
// use project::Fs;
|
||||||
// use rich_text::RichText;
|
// use rich_text::RichText;
|
||||||
// use serde::{Deserialize, Serialize};
|
// use serde::{Deserialize, Serialize};
|
||||||
// use settings::SettingsStore;
|
// use settings::{Settings, SettingsStore};
|
||||||
// use std::sync::Arc;
|
// use std::sync::Arc;
|
||||||
// use theme::{IconButton, Theme};
|
|
||||||
// use time::{OffsetDateTime, UtcOffset};
|
// use time::{OffsetDateTime, UtcOffset};
|
||||||
|
// use ui::{h_stack, v_stack, Avatar, Button, Label};
|
||||||
// use util::{ResultExt, TryFutureExt};
|
// use util::{ResultExt, TryFutureExt};
|
||||||
// use workspace::{
|
// use workspace::{
|
||||||
// dock::{DockPosition, Panel},
|
// dock::{DockPosition, Panel},
|
||||||
@ -40,19 +34,18 @@
|
|||||||
|
|
||||||
// pub struct ChatPanel {
|
// pub struct ChatPanel {
|
||||||
// client: Arc<Client>,
|
// client: Arc<Client>,
|
||||||
// channel_store: ModelHandle<ChannelStore>,
|
// channel_store: Model<ChannelStore>,
|
||||||
// languages: Arc<LanguageRegistry>,
|
// languages: Arc<LanguageRegistry>,
|
||||||
// active_chat: Option<(ModelHandle<ChannelChat>, Subscription)>,
|
// list_scroll: ListScrollHandle,
|
||||||
// message_list: ListState<ChatPanel>,
|
// active_chat: Option<(Model<ChannelChat>, Subscription)>,
|
||||||
// input_editor: ViewHandle<MessageEditor>,
|
// input_editor: View<MessageEditor>,
|
||||||
// channel_select: ViewHandle<Select>,
|
|
||||||
// local_timezone: UtcOffset,
|
// local_timezone: UtcOffset,
|
||||||
// fs: Arc<dyn Fs>,
|
// fs: Arc<dyn Fs>,
|
||||||
// width: Option<f32>,
|
// width: Option<f32>,
|
||||||
// active: bool,
|
// active: bool,
|
||||||
// pending_serialization: Task<Option<()>>,
|
// pending_serialization: Task<Option<()>>,
|
||||||
// subscriptions: Vec<gpui::Subscription>,
|
// subscriptions: Vec<gpui::Subscription>,
|
||||||
// workspace: WeakViewHandle<Workspace>,
|
// workspace: WeakView<Workspace>,
|
||||||
// is_scrolled_to_bottom: bool,
|
// is_scrolled_to_bottom: bool,
|
||||||
// has_focus: bool,
|
// has_focus: bool,
|
||||||
// markdown_data: HashMap<ChannelMessageId, RichText>,
|
// markdown_data: HashMap<ChannelMessageId, RichText>,
|
||||||
@ -70,10 +63,7 @@
|
|||||||
// Dismissed,
|
// Dismissed,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// actions!(
|
// actions!(LoadMoreMessages, ToggleFocus, OpenChannelNotes, JoinCall);
|
||||||
// chat_panel,
|
|
||||||
// [LoadMoreMessages, ToggleFocus, OpenChannelNotes, JoinCall]
|
|
||||||
// );
|
|
||||||
|
|
||||||
// pub fn init(cx: &mut AppContext) {
|
// pub fn init(cx: &mut AppContext) {
|
||||||
// cx.add_action(ChatPanel::send);
|
// cx.add_action(ChatPanel::send);
|
||||||
@ -83,7 +73,7 @@
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// impl ChatPanel {
|
// impl ChatPanel {
|
||||||
// pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
|
// pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||||
// let fs = workspace.app_state().fs.clone();
|
// let fs = workspace.app_state().fs.clone();
|
||||||
// let client = workspace.app_state().client.clone();
|
// let client = workspace.app_state().client.clone();
|
||||||
// let channel_store = ChannelStore::global(cx);
|
// let channel_store = ChannelStore::global(cx);
|
||||||
@ -93,53 +83,46 @@
|
|||||||
// MessageEditor::new(
|
// MessageEditor::new(
|
||||||
// languages.clone(),
|
// languages.clone(),
|
||||||
// channel_store.clone(),
|
// channel_store.clone(),
|
||||||
// cx.add_view(|cx| {
|
// cx.add_view(|cx| Editor::auto_height(4, cx)),
|
||||||
// Editor::auto_height(
|
|
||||||
// 4,
|
|
||||||
// Some(Arc::new(|theme| theme.chat_panel.input_editor.clone())),
|
|
||||||
// cx,
|
|
||||||
// )
|
|
||||||
// }),
|
|
||||||
// cx,
|
// cx,
|
||||||
// )
|
// )
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// let workspace_handle = workspace.weak_handle();
|
// let workspace_handle = workspace.weak_handle();
|
||||||
|
|
||||||
// let channel_select = cx.add_view(|cx| {
|
// // let channel_select = cx.add_view(|cx| {
|
||||||
// let channel_store = channel_store.clone();
|
// // let channel_store = channel_store.clone();
|
||||||
// let workspace = workspace_handle.clone();
|
// // let workspace = workspace_handle.clone();
|
||||||
// Select::new(0, cx, {
|
// // Select::new(0, cx, {
|
||||||
// move |ix, item_type, is_hovered, cx| {
|
// // move |ix, item_type, is_hovered, cx| {
|
||||||
// Self::render_channel_name(
|
// // Self::render_channel_name(
|
||||||
// &channel_store,
|
// // &channel_store,
|
||||||
// ix,
|
// // ix,
|
||||||
// item_type,
|
// // item_type,
|
||||||
// is_hovered,
|
// // is_hovered,
|
||||||
// workspace,
|
// // workspace,
|
||||||
// cx,
|
// // cx,
|
||||||
// )
|
// // )
|
||||||
// }
|
// // }
|
||||||
// })
|
// // })
|
||||||
// .with_style(move |cx| {
|
// // .with_style(move |cx| {
|
||||||
// let style = &theme::current(cx).chat_panel.channel_select;
|
// // let style = &cx.theme().chat_panel.channel_select;
|
||||||
// SelectStyle {
|
// // SelectStyle {
|
||||||
// header: Default::default(),
|
// // header: Default::default(),
|
||||||
// menu: style.menu,
|
// // menu: style.menu,
|
||||||
// }
|
// // }
|
||||||
// })
|
// // })
|
||||||
// });
|
// // });
|
||||||
|
|
||||||
// let mut message_list =
|
// // let mut message_list = ListState::new(0, Orientation::Bottom, 10., move |this, ix, cx| {
|
||||||
// ListState::<Self>::new(0, Orientation::Bottom, 10., move |this, ix, cx| {
|
// // this.render_message(ix, cx)
|
||||||
// this.render_message(ix, cx)
|
// // });
|
||||||
// });
|
// // message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| {
|
||||||
// message_list.set_scroll_handler(|visible_range, count, this, cx| {
|
// // if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
|
||||||
// if visible_range.start < MESSAGE_LOADING_THRESHOLD {
|
// // this.load_more_messages(cx);
|
||||||
// this.load_more_messages(&LoadMoreMessages, cx);
|
// // }
|
||||||
// }
|
// // this.is_scrolled_to_bottom = event.visible_range.end == event.count;
|
||||||
// this.is_scrolled_to_bottom = visible_range.end == count;
|
// // }));
|
||||||
// });
|
|
||||||
|
|
||||||
// cx.add_view(|cx| {
|
// cx.add_view(|cx| {
|
||||||
// let mut this = Self {
|
// let mut this = Self {
|
||||||
@ -147,11 +130,10 @@
|
|||||||
// client,
|
// client,
|
||||||
// channel_store,
|
// channel_store,
|
||||||
// languages,
|
// languages,
|
||||||
|
// list_scroll: ListScrollHandle::new(),
|
||||||
// active_chat: Default::default(),
|
// active_chat: Default::default(),
|
||||||
// pending_serialization: Task::ready(None),
|
// pending_serialization: Task::ready(None),
|
||||||
// message_list,
|
|
||||||
// input_editor,
|
// input_editor,
|
||||||
// channel_select,
|
|
||||||
// local_timezone: cx.platform().local_timezone(),
|
// local_timezone: cx.platform().local_timezone(),
|
||||||
// has_focus: false,
|
// has_focus: false,
|
||||||
// subscriptions: Vec::new(),
|
// subscriptions: Vec::new(),
|
||||||
@ -204,14 +186,11 @@
|
|||||||
// self.is_scrolled_to_bottom
|
// self.is_scrolled_to_bottom
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// pub fn active_chat(&self) -> Option<ModelHandle<ChannelChat>> {
|
// pub fn active_chat(&self) -> Option<Model<ChannelChat>> {
|
||||||
// self.active_chat.as_ref().map(|(chat, _)| chat.clone())
|
// self.active_chat.as_ref().map(|(chat, _)| chat.clone())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// pub fn load(
|
// pub fn load(workspace: WeakView<Workspace>, cx: AsyncAppContext) -> Task<Result<View<Self>>> {
|
||||||
// workspace: WeakViewHandle<Workspace>,
|
|
||||||
// cx: AsyncAppContext,
|
|
||||||
// ) -> Task<Result<ViewHandle<Self>>> {
|
|
||||||
// cx.spawn(|mut cx| async move {
|
// cx.spawn(|mut cx| async move {
|
||||||
// let serialized_panel = if let Some(panel) = cx
|
// let serialized_panel = if let Some(panel) = cx
|
||||||
// .background()
|
// .background()
|
||||||
@ -261,7 +240,7 @@
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn set_active_chat(&mut self, chat: ModelHandle<ChannelChat>, cx: &mut ViewContext<Self>) {
|
// fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
|
||||||
// if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
|
// if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
|
||||||
// let channel_id = chat.read(cx).channel_id;
|
// let channel_id = chat.read(cx).channel_id;
|
||||||
// {
|
// {
|
||||||
@ -288,7 +267,7 @@
|
|||||||
|
|
||||||
// fn channel_did_change(
|
// fn channel_did_change(
|
||||||
// &mut self,
|
// &mut self,
|
||||||
// _: ModelHandle<ChannelChat>,
|
// _: Model<ChannelChat>,
|
||||||
// event: &ChannelChatEvent,
|
// event: &ChannelChatEvent,
|
||||||
// cx: &mut ViewContext<Self>,
|
// cx: &mut ViewContext<Self>,
|
||||||
// ) {
|
// ) {
|
||||||
@ -326,30 +305,29 @@
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn render_channel(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
// fn render_channel(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||||
// let theme = theme::current(cx);
|
// v_stack()
|
||||||
// Flex::column()
|
// .child(Label::new(
|
||||||
// .with_child(
|
// self.active_chat.map_or(Default::default(), |c| {
|
||||||
// ChildView::new(&self.channel_select, cx)
|
// c.0.read(cx).channel(cx)?.name.into()
|
||||||
// .contained()
|
// }),
|
||||||
// .with_style(theme.chat_panel.channel_select.container),
|
// ))
|
||||||
// )
|
// .child(self.render_active_channel_messages(cx))
|
||||||
// .with_child(self.render_active_channel_messages(&theme))
|
// .child(self.input_editor.to_any())
|
||||||
// .with_child(self.render_input_box(&theme, cx))
|
|
||||||
// .into_any()
|
// .into_any()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn render_active_channel_messages(&self, theme: &Arc<Theme>) -> AnyElement<Self> {
|
// fn render_active_channel_messages(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||||
// let messages = if self.active_chat.is_some() {
|
// if self.active_chat.is_some() {
|
||||||
// List::new(self.message_list.clone())
|
// list(
|
||||||
// .contained()
|
// Orientation::Bottom,
|
||||||
// .with_style(theme.chat_panel.list)
|
// 10.,
|
||||||
// .into_any()
|
// cx.listener(move |this, ix, cx| this.render_message(ix, cx)),
|
||||||
|
// )
|
||||||
|
// .into_any_element()
|
||||||
// } else {
|
// } else {
|
||||||
// Empty::new().into_any()
|
// div().into_any_element()
|
||||||
// };
|
// }
|
||||||
|
|
||||||
// messages.flex(1., true).into_any()
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
// fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
@ -388,21 +366,12 @@
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
// let is_pending = message.is_pending();
|
// let is_pending = message.is_pending();
|
||||||
// let theme = theme::current(cx);
|
|
||||||
// let text = self.markdown_data.entry(message.id).or_insert_with(|| {
|
// let text = self.markdown_data.entry(message.id).or_insert_with(|| {
|
||||||
// Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
|
// Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// let now = OffsetDateTime::now_utc();
|
// let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
// let style = if is_pending {
|
|
||||||
// &theme.chat_panel.pending_message
|
|
||||||
// } else if is_continuation {
|
|
||||||
// &theme.chat_panel.continuation_message
|
|
||||||
// } else {
|
|
||||||
// &theme.chat_panel.message
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let belongs_to_user = Some(message.sender.id) == self.client.user_id();
|
// let belongs_to_user = Some(message.sender.id) == self.client.user_id();
|
||||||
// let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
|
// let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
|
||||||
// (message.id, belongs_to_user || is_admin)
|
// (message.id, belongs_to_user || is_admin)
|
||||||
@ -412,89 +381,37 @@
|
|||||||
// None
|
// None
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// enum MessageBackgroundHighlight {}
|
// if is_continuation {
|
||||||
// MouseEventHandler::new::<MessageBackgroundHighlight, _>(ix, cx, |state, cx| {
|
// h_stack()
|
||||||
// let container = style.style_for(state);
|
// .child(text.element(cx))
|
||||||
// if is_continuation {
|
// .child(render_remove(message_id_to_remove, cx))
|
||||||
// Flex::row()
|
// .mb_1()
|
||||||
// .with_child(
|
// .into_any()
|
||||||
// text.element(
|
// } else {
|
||||||
// theme.editor.syntax.clone(),
|
// v_stack()
|
||||||
// theme.chat_panel.rich_text.clone(),
|
// .child(
|
||||||
// cx,
|
// h_stack()
|
||||||
|
// .child(Avatar::data(message.sender.avatar.clone()))
|
||||||
|
// .child(Label::new(message.sender.github_login.clone()))
|
||||||
|
// .child(
|
||||||
|
// Label::new(format_timestamp(
|
||||||
|
// message.timestamp,
|
||||||
|
// now,
|
||||||
|
// self.local_timezone,
|
||||||
|
// ))
|
||||||
|
// .flex(1., true),
|
||||||
// )
|
// )
|
||||||
// .flex(1., true),
|
// .child(render_remove(message_id_to_remove, cx))
|
||||||
// )
|
// .align_children_center(),
|
||||||
// .with_child(render_remove(message_id_to_remove, cx, &theme))
|
// )
|
||||||
// .contained()
|
// .child(
|
||||||
// .with_style(*container)
|
// h_stack()
|
||||||
// .with_margin_bottom(if is_last {
|
// .child(text.element(cx))
|
||||||
// theme.chat_panel.last_message_bottom_spacing
|
// .child(render_remove(None, cx)),
|
||||||
// } else {
|
// )
|
||||||
// 0.
|
// .mb_1()
|
||||||
// })
|
// .into_any()
|
||||||
// .into_any()
|
// }
|
||||||
// } else {
|
|
||||||
// Flex::column()
|
|
||||||
// .with_child(
|
|
||||||
// Flex::row()
|
|
||||||
// .with_child(
|
|
||||||
// Flex::row()
|
|
||||||
// .with_child(render_avatar(
|
|
||||||
// message.sender.avatar.clone(),
|
|
||||||
// &theme.chat_panel.avatar,
|
|
||||||
// theme.chat_panel.avatar_container,
|
|
||||||
// ))
|
|
||||||
// .with_child(
|
|
||||||
// Label::new(
|
|
||||||
// message.sender.github_login.clone(),
|
|
||||||
// theme.chat_panel.message_sender.text.clone(),
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.chat_panel.message_sender.container),
|
|
||||||
// )
|
|
||||||
// .with_child(
|
|
||||||
// Label::new(
|
|
||||||
// format_timestamp(
|
|
||||||
// message.timestamp,
|
|
||||||
// now,
|
|
||||||
// self.local_timezone,
|
|
||||||
// ),
|
|
||||||
// theme.chat_panel.message_timestamp.text.clone(),
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.chat_panel.message_timestamp.container),
|
|
||||||
// )
|
|
||||||
// .align_children_center()
|
|
||||||
// .flex(1., true),
|
|
||||||
// )
|
|
||||||
// .with_child(render_remove(message_id_to_remove, cx, &theme))
|
|
||||||
// .align_children_center(),
|
|
||||||
// )
|
|
||||||
// .with_child(
|
|
||||||
// Flex::row()
|
|
||||||
// .with_child(
|
|
||||||
// text.element(
|
|
||||||
// theme.editor.syntax.clone(),
|
|
||||||
// theme.chat_panel.rich_text.clone(),
|
|
||||||
// cx,
|
|
||||||
// )
|
|
||||||
// .flex(1., true),
|
|
||||||
// )
|
|
||||||
// // Add a spacer to make everything line up
|
|
||||||
// .with_child(render_remove(None, cx, &theme)),
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// .with_style(*container)
|
|
||||||
// .with_margin_bottom(if is_last {
|
|
||||||
// theme.chat_panel.last_message_bottom_spacing
|
|
||||||
// } else {
|
|
||||||
// 0.
|
|
||||||
// })
|
|
||||||
// .into_any()
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .into_any()
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn render_markdown_with_mentions(
|
// fn render_markdown_with_mentions(
|
||||||
@ -514,127 +431,106 @@
|
|||||||
// rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None)
|
// rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn render_input_box(&self, theme: &Arc<Theme>, cx: &AppContext) -> AnyElement<Self> {
|
// // fn render_channel_name(
|
||||||
// ChildView::new(&self.input_editor, cx)
|
// // channel_store: &Model<ChannelStore>,
|
||||||
// .contained()
|
// // ix: usize,
|
||||||
// .with_style(theme.chat_panel.input_editor.container)
|
// // item_type: ItemType,
|
||||||
// .into_any()
|
// // is_hovered: bool,
|
||||||
// }
|
// // workspace: WeakView<Workspace>,
|
||||||
|
// // cx: &mut ViewContext<Select>,
|
||||||
|
// // ) -> AnyElement<Select> {
|
||||||
|
// // let theme = theme::current(cx);
|
||||||
|
// // let tooltip_style = &theme.tooltip;
|
||||||
|
// // let theme = &theme.chat_panel;
|
||||||
|
// // let style = match (&item_type, is_hovered) {
|
||||||
|
// // (ItemType::Header, _) => &theme.channel_select.header,
|
||||||
|
// // (ItemType::Selected, _) => &theme.channel_select.active_item,
|
||||||
|
// // (ItemType::Unselected, false) => &theme.channel_select.item,
|
||||||
|
// // (ItemType::Unselected, true) => &theme.channel_select.hovered_item,
|
||||||
|
// // };
|
||||||
|
|
||||||
// fn render_channel_name(
|
// // let channel = &channel_store.read(cx).channel_at(ix).unwrap();
|
||||||
// channel_store: &ModelHandle<ChannelStore>,
|
// // let channel_id = channel.id;
|
||||||
// ix: usize,
|
|
||||||
// item_type: ItemType,
|
|
||||||
// is_hovered: bool,
|
|
||||||
// workspace: WeakViewHandle<Workspace>,
|
|
||||||
// cx: &mut ViewContext<Select>,
|
|
||||||
// ) -> AnyElement<Select> {
|
|
||||||
// let theme = theme::current(cx);
|
|
||||||
// let tooltip_style = &theme.tooltip;
|
|
||||||
// let theme = &theme.chat_panel;
|
|
||||||
// let style = match (&item_type, is_hovered) {
|
|
||||||
// (ItemType::Header, _) => &theme.channel_select.header,
|
|
||||||
// (ItemType::Selected, _) => &theme.channel_select.active_item,
|
|
||||||
// (ItemType::Unselected, false) => &theme.channel_select.item,
|
|
||||||
// (ItemType::Unselected, true) => &theme.channel_select.hovered_item,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let channel = &channel_store.read(cx).channel_at(ix).unwrap();
|
// // let mut row = Flex::row()
|
||||||
// let channel_id = channel.id;
|
// // .with_child(
|
||||||
|
// // Label::new("#".to_string(), style.hash.text.clone())
|
||||||
|
// // .contained()
|
||||||
|
// // .with_style(style.hash.container),
|
||||||
|
// // )
|
||||||
|
// // .with_child(Label::new(channel.name.clone(), style.name.clone()));
|
||||||
|
|
||||||
// let mut row = Flex::row()
|
// // if matches!(item_type, ItemType::Header) {
|
||||||
// .with_child(
|
// // row.add_children([
|
||||||
// Label::new("#".to_string(), style.hash.text.clone())
|
// // MouseEventHandler::new::<OpenChannelNotes, _>(0, cx, |mouse_state, _| {
|
||||||
// .contained()
|
// // render_icon_button(theme.icon_button.style_for(mouse_state), "icons/file.svg")
|
||||||
// .with_style(style.hash.container),
|
// // })
|
||||||
// )
|
// // .on_click(MouseButton::Left, move |_, _, cx| {
|
||||||
// .with_child(Label::new(channel.name.clone(), style.name.clone()));
|
// // if let Some(workspace) = workspace.upgrade(cx) {
|
||||||
|
// // ChannelView::open(channel_id, workspace, cx).detach();
|
||||||
|
// // }
|
||||||
|
// // })
|
||||||
|
// // .with_tooltip::<OpenChannelNotes>(
|
||||||
|
// // channel_id as usize,
|
||||||
|
// // "Open Notes",
|
||||||
|
// // Some(Box::new(OpenChannelNotes)),
|
||||||
|
// // tooltip_style.clone(),
|
||||||
|
// // cx,
|
||||||
|
// // )
|
||||||
|
// // .flex_float(),
|
||||||
|
// // MouseEventHandler::new::<ActiveCall, _>(0, cx, |mouse_state, _| {
|
||||||
|
// // render_icon_button(
|
||||||
|
// // theme.icon_button.style_for(mouse_state),
|
||||||
|
// // "icons/speaker-loud.svg",
|
||||||
|
// // )
|
||||||
|
// // })
|
||||||
|
// // .on_click(MouseButton::Left, move |_, _, cx| {
|
||||||
|
// // ActiveCall::global(cx)
|
||||||
|
// // .update(cx, |call, cx| call.join_channel(channel_id, cx))
|
||||||
|
// // .detach_and_log_err(cx);
|
||||||
|
// // })
|
||||||
|
// // .with_tooltip::<ActiveCall>(
|
||||||
|
// // channel_id as usize,
|
||||||
|
// // "Join Call",
|
||||||
|
// // Some(Box::new(JoinCall)),
|
||||||
|
// // tooltip_style.clone(),
|
||||||
|
// // cx,
|
||||||
|
// // )
|
||||||
|
// // .flex_float(),
|
||||||
|
// // ]);
|
||||||
|
// // }
|
||||||
|
|
||||||
// if matches!(item_type, ItemType::Header) {
|
// // row.align_children_center()
|
||||||
// row.add_children([
|
// // .contained()
|
||||||
// MouseEventHandler::new::<OpenChannelNotes, _>(0, cx, |mouse_state, _| {
|
// // .with_style(style.container)
|
||||||
// render_icon_button(theme.icon_button.style_for(mouse_state), "icons/file.svg")
|
// // .into_any()
|
||||||
// })
|
// // }
|
||||||
// .on_click(MouseButton::Left, move |_, _, cx| {
|
|
||||||
// if let Some(workspace) = workspace.upgrade(cx) {
|
|
||||||
// ChannelView::open(channel_id, workspace, cx).detach();
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .with_tooltip::<OpenChannelNotes>(
|
|
||||||
// channel_id as usize,
|
|
||||||
// "Open Notes",
|
|
||||||
// Some(Box::new(OpenChannelNotes)),
|
|
||||||
// tooltip_style.clone(),
|
|
||||||
// cx,
|
|
||||||
// )
|
|
||||||
// .flex_float(),
|
|
||||||
// MouseEventHandler::new::<ActiveCall, _>(0, cx, |mouse_state, _| {
|
|
||||||
// render_icon_button(
|
|
||||||
// theme.icon_button.style_for(mouse_state),
|
|
||||||
// "icons/speaker-loud.svg",
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// .on_click(MouseButton::Left, move |_, _, cx| {
|
|
||||||
// ActiveCall::global(cx)
|
|
||||||
// .update(cx, |call, cx| call.join_channel(channel_id, cx))
|
|
||||||
// .detach_and_log_err(cx);
|
|
||||||
// })
|
|
||||||
// .with_tooltip::<ActiveCall>(
|
|
||||||
// channel_id as usize,
|
|
||||||
// "Join Call",
|
|
||||||
// Some(Box::new(JoinCall)),
|
|
||||||
// tooltip_style.clone(),
|
|
||||||
// cx,
|
|
||||||
// )
|
|
||||||
// .flex_float(),
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// row.align_children_center()
|
// fn render_sign_in_prompt(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
// .contained()
|
|
||||||
// .with_style(style.container)
|
|
||||||
// .into_any()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn render_sign_in_prompt(
|
|
||||||
// &self,
|
|
||||||
// theme: &Arc<Theme>,
|
|
||||||
// cx: &mut ViewContext<Self>,
|
|
||||||
// ) -> AnyElement<Self> {
|
|
||||||
// enum SignInPromptLabel {}
|
// enum SignInPromptLabel {}
|
||||||
|
|
||||||
// MouseEventHandler::new::<SignInPromptLabel, _>(0, cx, |mouse_state, _| {
|
// Button::new("sign-in", "Sign in to use chat")
|
||||||
// Label::new(
|
// .on_click(move |_, this, cx| {
|
||||||
// "Sign in to use chat".to_string(),
|
// let client = this.client.clone();
|
||||||
// theme
|
// cx.spawn(|this, mut cx| async move {
|
||||||
// .chat_panel
|
// if client
|
||||||
// .sign_in_prompt
|
// .authenticate_and_connect(true, &cx)
|
||||||
// .style_for(mouse_state)
|
// .log_err()
|
||||||
// .clone(),
|
// .await
|
||||||
// )
|
// .is_some()
|
||||||
// })
|
// {
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
// this.update(&mut cx, |this, cx| {
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
// if cx.handle().is_focused(cx) {
|
||||||
// let client = this.client.clone();
|
// cx.focus(&this.input_editor);
|
||||||
// cx.spawn(|this, mut cx| async move {
|
// }
|
||||||
// if client
|
// })
|
||||||
// .authenticate_and_connect(true, &cx)
|
// .ok();
|
||||||
// .log_err()
|
// }
|
||||||
// .await
|
// })
|
||||||
// .is_some()
|
// .detach();
|
||||||
// {
|
|
||||||
// this.update(&mut cx, |this, cx| {
|
|
||||||
// if cx.handle().is_focused(cx) {
|
|
||||||
// cx.focus(&this.input_editor);
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .ok();
|
|
||||||
// }
|
|
||||||
// })
|
// })
|
||||||
// .detach();
|
// .aligned()
|
||||||
// })
|
// .into_any()
|
||||||
// .aligned()
|
|
||||||
// .into_any()
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
// fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||||
@ -700,9 +596,9 @@
|
|||||||
// {
|
// {
|
||||||
// this.update(&mut cx, |this, cx| {
|
// this.update(&mut cx, |this, cx| {
|
||||||
// if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) {
|
// if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) {
|
||||||
// this.message_list.scroll_to(ListOffset {
|
// this.list_scroll.scroll_to(ListOffset {
|
||||||
// item_ix,
|
// item_ix,
|
||||||
// offset_in_item: 0.,
|
// offset_in_item: px(0.0),
|
||||||
// });
|
// });
|
||||||
// cx.notify();
|
// cx.notify();
|
||||||
// }
|
// }
|
||||||
@ -733,11 +629,7 @@
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn render_remove(
|
// fn render_remove(message_id_to_remove: Option<u64>, cx: &mut ViewContext<ChatPanel>) -> AnyElement {
|
||||||
// message_id_to_remove: Option<u64>,
|
|
||||||
// cx: &mut ViewContext<'_, '_, ChatPanel>,
|
|
||||||
// theme: &Arc<Theme>,
|
|
||||||
// ) -> AnyElement<ChatPanel> {
|
|
||||||
// enum DeleteMessage {}
|
// enum DeleteMessage {}
|
||||||
|
|
||||||
// message_id_to_remove
|
// message_id_to_remove
|
||||||
@ -773,49 +665,31 @@
|
|||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// impl Entity for ChatPanel {
|
// impl EventEmitter<Event> for ChatPanel {}
|
||||||
// type Event = Event;
|
|
||||||
|
// impl Render for ChatPanel {
|
||||||
|
// type Element = Div;
|
||||||
|
|
||||||
|
// fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
// div()
|
||||||
|
// .child(if self.client.user_id().is_some() {
|
||||||
|
// self.render_channel(cx)
|
||||||
|
// } else {
|
||||||
|
// self.render_sign_in_prompt(cx)
|
||||||
|
// })
|
||||||
|
// .min_w(px(150.))
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// impl View for ChatPanel {
|
// impl FocusableView for ChatPanel {
|
||||||
// fn ui_name() -> &'static str {
|
// fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||||
// "ChatPanel"
|
// self.input_editor.read(cx).focus_handle(cx)
|
||||||
// }
|
|
||||||
|
|
||||||
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
|
||||||
// let theme = theme::current(cx);
|
|
||||||
// let element = if self.client.user_id().is_some() {
|
|
||||||
// self.render_channel(cx)
|
|
||||||
// } else {
|
|
||||||
// self.render_sign_in_prompt(&theme, cx)
|
|
||||||
// };
|
|
||||||
// element
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.chat_panel.container)
|
|
||||||
// .constrained()
|
|
||||||
// .with_min_width(150.)
|
|
||||||
// .into_any()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
|
||||||
// self.has_focus = true;
|
|
||||||
// if matches!(
|
|
||||||
// *self.client.status().borrow(),
|
|
||||||
// client::Status::Connected { .. }
|
|
||||||
// ) {
|
|
||||||
// let editor = self.input_editor.read(cx).editor.clone();
|
|
||||||
// cx.focus(&editor);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
|
|
||||||
// self.has_focus = false;
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// impl Panel for ChatPanel {
|
// impl Panel for ChatPanel {
|
||||||
// fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
|
// fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
|
||||||
// settings::get::<ChatPanelSettings>(cx).dock
|
// ChatPanelSettings::get_global(cx).dock
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn position_is_valid(&self, position: DockPosition) -> bool {
|
// fn position_is_valid(&self, position: DockPosition) -> bool {
|
||||||
@ -830,7 +704,7 @@
|
|||||||
|
|
||||||
// fn size(&self, cx: &gpui::WindowContext) -> f32 {
|
// fn size(&self, cx: &gpui::WindowContext) -> f32 {
|
||||||
// self.width
|
// self.width
|
||||||
// .unwrap_or_else(|| settings::get::<ChatPanelSettings>(cx).default_width)
|
// .unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
|
// fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
|
||||||
@ -849,29 +723,16 @@
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn icon_path(&self, cx: &gpui::WindowContext) -> Option<&'static str> {
|
// fn persistent_name() -> &'static str {
|
||||||
// (settings::get::<ChatPanelSettings>(cx).button && is_channels_feature_enabled(cx))
|
// todo!()
|
||||||
// .then(|| "icons/conversations.svg")
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn icon_tooltip(&self) -> (String, Option<Box<dyn gpui::Action>>) {
|
// fn icon(&self, cx: &ui::prelude::WindowContext) -> Option<ui::Icon> {
|
||||||
// ("Chat Panel".to_string(), Some(Box::new(ToggleFocus)))
|
// Some(ui::Icon::MessageBubbles)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn should_change_position_on_event(event: &Self::Event) -> bool {
|
// fn toggle_action(&self) -> Box<dyn gpui::Action> {
|
||||||
// matches!(event, Event::DockPositionChanged)
|
// todo!()
|
||||||
// }
|
|
||||||
|
|
||||||
// fn should_close_on_event(event: &Self::Event) -> bool {
|
|
||||||
// matches!(event, Event::Dismissed)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn has_focus(&self, _cx: &gpui::WindowContext) -> bool {
|
|
||||||
// self.has_focus
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn is_focus_event(event: &Self::Event) -> bool {
|
|
||||||
// matches!(event, Event::Focus)
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@ -2,14 +2,12 @@ use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams};
|
|||||||
use client::UserId;
|
use client::UserId;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{AnchorRangeExt, Editor};
|
use editor::{AnchorRangeExt, Editor};
|
||||||
use gpui::{
|
use gpui::{AnyView, AsyncAppContext, Model, Render, Task, View, ViewContext, WeakView};
|
||||||
elements::ChildView, AnyElement, AsyncAppContext, Element, Entity, ModelHandle, Task, View,
|
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
|
||||||
};
|
|
||||||
use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
|
use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use workspace::item::ItemHandle;
|
||||||
|
|
||||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
@ -19,8 +17,8 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
pub editor: ViewHandle<Editor>,
|
pub editor: View<Editor>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
users: HashMap<String, UserId>,
|
users: HashMap<String, UserId>,
|
||||||
mentions: Vec<UserId>,
|
mentions: Vec<UserId>,
|
||||||
mentions_task: Option<Task<()>>,
|
mentions_task: Option<Task<()>>,
|
||||||
@ -30,8 +28,8 @@ pub struct MessageEditor {
|
|||||||
impl MessageEditor {
|
impl MessageEditor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
editor: ViewHandle<Editor>,
|
editor: View<Editor>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
@ -132,7 +130,7 @@ impl MessageEditor {
|
|||||||
|
|
||||||
fn on_buffer_event(
|
fn on_buffer_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
event: &language::Event,
|
event: &language::Event,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
@ -146,7 +144,7 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn find_mentions(
|
async fn find_mentions(
|
||||||
this: WeakViewHandle<MessageEditor>,
|
this: WeakView<MessageEditor>,
|
||||||
buffer: BufferSnapshot,
|
buffer: BufferSnapshot,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) {
|
) {
|
||||||
@ -180,11 +178,7 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editor.clear_highlights::<Self>(cx);
|
editor.clear_highlights::<Self>(cx);
|
||||||
editor.highlight_text::<Self>(
|
editor.highlight_text::<Self>(anchor_ranges, gpui::red().into(), cx)
|
||||||
anchor_ranges,
|
|
||||||
theme::current(cx).chat_panel.rich_text.mention_highlight,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mentions = mentioned_user_ids;
|
this.mentions = mentioned_user_ids;
|
||||||
@ -194,19 +188,11 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for MessageEditor {
|
impl Render for MessageEditor {
|
||||||
type Event = ();
|
type Element = AnyView;
|
||||||
}
|
|
||||||
|
|
||||||
impl View for MessageEditor {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
|
self.editor.to_any()
|
||||||
ChildView::new(&self.editor, cx).into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
|
||||||
if cx.is_self_focused() {
|
|
||||||
cx.focus(&self.editor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +283,7 @@ mod tests {
|
|||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
language_registry,
|
language_registry,
|
||||||
ChannelStore::global(cx),
|
ChannelStore::global(cx),
|
||||||
cx.add_view(|cx| Editor::auto_height(4, None, cx)),
|
cx.add_view(|cx| Editor::auto_height(4, cx)),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
493
crates/gpui2/src/elements/list.rs
Normal file
493
crates/gpui2/src/elements/list.rs
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
use crate::{
|
||||||
|
px, AnyElement, AvailableSpace, BorrowAppContext, DispatchPhase, Element, IntoElement, Pixels,
|
||||||
|
Point, ScrollWheelEvent, Size, Style, StyleRefinement, ViewContext, WindowContext,
|
||||||
|
};
|
||||||
|
use collections::VecDeque;
|
||||||
|
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||||
|
use sum_tree::{Bias, SumTree};
|
||||||
|
|
||||||
|
pub fn list(state: ListState) -> List {
|
||||||
|
List {
|
||||||
|
state,
|
||||||
|
style: StyleRefinement::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct List {
|
||||||
|
state: ListState,
|
||||||
|
style: StyleRefinement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ListState(Rc<RefCell<StateInner>>);
|
||||||
|
|
||||||
|
struct StateInner {
|
||||||
|
last_layout_width: Option<Pixels>,
|
||||||
|
render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
|
||||||
|
items: SumTree<ListItem>,
|
||||||
|
logical_scroll_top: Option<ListOffset>,
|
||||||
|
orientation: Orientation,
|
||||||
|
overdraw: Pixels,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Orientation {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListScrollEvent {
|
||||||
|
pub visible_range: Range<usize>,
|
||||||
|
pub count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum ListItem {
|
||||||
|
Unrendered,
|
||||||
|
Rendered { height: Pixels },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
struct ListItemSummary {
|
||||||
|
count: usize,
|
||||||
|
rendered_count: usize,
|
||||||
|
unrendered_count: usize,
|
||||||
|
height: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct Count(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct RenderedCount(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct UnrenderedCount(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct Height(Pixels);
|
||||||
|
|
||||||
|
impl ListState {
|
||||||
|
pub fn new<F, V>(
|
||||||
|
element_count: usize,
|
||||||
|
orientation: Orientation,
|
||||||
|
overdraw: Pixels,
|
||||||
|
cx: &mut ViewContext<V>,
|
||||||
|
mut render_item: F,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(&mut V, usize, &mut ViewContext<V>) -> AnyElement,
|
||||||
|
V: 'static,
|
||||||
|
{
|
||||||
|
let mut items = SumTree::new();
|
||||||
|
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||||
|
let view = cx.view().clone();
|
||||||
|
Self(Rc::new(RefCell::new(StateInner {
|
||||||
|
last_layout_width: None,
|
||||||
|
render_item: Box::new(move |ix, cx| {
|
||||||
|
view.update(cx, |view, cx| render_item(view, ix, cx))
|
||||||
|
}),
|
||||||
|
items,
|
||||||
|
logical_scroll_top: None,
|
||||||
|
orientation,
|
||||||
|
overdraw,
|
||||||
|
scroll_handler: None,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&self, element_count: usize) {
|
||||||
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
state.logical_scroll_top = None;
|
||||||
|
state.items = SumTree::new();
|
||||||
|
state
|
||||||
|
.items
|
||||||
|
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item_count(&self) -> usize {
|
||||||
|
self.0.borrow().items.summary().count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn splice(&self, old_range: Range<usize>, count: usize) {
|
||||||
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
|
||||||
|
if let Some(ListOffset {
|
||||||
|
item_ix,
|
||||||
|
offset_in_item,
|
||||||
|
}) = state.logical_scroll_top.as_mut()
|
||||||
|
{
|
||||||
|
if old_range.contains(item_ix) {
|
||||||
|
*item_ix = old_range.start;
|
||||||
|
*offset_in_item = px(0.);
|
||||||
|
} else if old_range.end <= *item_ix {
|
||||||
|
*item_ix = *item_ix - (old_range.end - old_range.start) + count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut old_heights = state.items.cursor::<Count>();
|
||||||
|
let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
|
||||||
|
old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
|
||||||
|
|
||||||
|
new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
|
||||||
|
new_heights.append(old_heights.suffix(&()), &());
|
||||||
|
drop(old_heights);
|
||||||
|
state.items = new_heights;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scroll_handler(
|
||||||
|
&mut self,
|
||||||
|
handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
|
||||||
|
) {
|
||||||
|
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logical_scroll_top(&self) -> ListOffset {
|
||||||
|
self.0.borrow().logical_scroll_top()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to(&self, mut scroll_top: ListOffset) {
|
||||||
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
let item_count = state.items.summary().count;
|
||||||
|
if scroll_top.item_ix >= item_count {
|
||||||
|
scroll_top.item_ix = item_count;
|
||||||
|
scroll_top.offset_in_item = px(0.);
|
||||||
|
}
|
||||||
|
state.logical_scroll_top = Some(scroll_top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateInner {
|
||||||
|
fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
|
||||||
|
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||||
|
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||||
|
let start_y = cursor.start().height + scroll_top.offset_in_item;
|
||||||
|
cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
|
||||||
|
scroll_top.item_ix..cursor.start().count + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll(
|
||||||
|
&mut self,
|
||||||
|
scroll_top: &ListOffset,
|
||||||
|
height: Pixels,
|
||||||
|
delta: Point<Pixels>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) {
|
||||||
|
let scroll_max = (self.items.summary().height - height).max(px(0.));
|
||||||
|
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
|
||||||
|
.max(px(0.))
|
||||||
|
.min(scroll_max);
|
||||||
|
|
||||||
|
if self.orientation == Orientation::Bottom && new_scroll_top == scroll_max {
|
||||||
|
self.logical_scroll_top = None;
|
||||||
|
} else {
|
||||||
|
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||||
|
cursor.seek(&Height(new_scroll_top), Bias::Right, &());
|
||||||
|
let item_ix = cursor.start().count;
|
||||||
|
let offset_in_item = new_scroll_top - cursor.start().height;
|
||||||
|
self.logical_scroll_top = Some(ListOffset {
|
||||||
|
item_ix,
|
||||||
|
offset_in_item,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.scroll_handler.is_some() {
|
||||||
|
let visible_range = self.visible_range(height, scroll_top);
|
||||||
|
self.scroll_handler.as_mut().unwrap()(
|
||||||
|
&ListScrollEvent {
|
||||||
|
visible_range,
|
||||||
|
count: self.items.summary().count,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logical_scroll_top(&self) -> ListOffset {
|
||||||
|
self.logical_scroll_top
|
||||||
|
.unwrap_or_else(|| match self.orientation {
|
||||||
|
Orientation::Top => ListOffset {
|
||||||
|
item_ix: 0,
|
||||||
|
offset_in_item: px(0.),
|
||||||
|
},
|
||||||
|
Orientation::Bottom => ListOffset {
|
||||||
|
item_ix: self.items.summary().count,
|
||||||
|
offset_in_item: px(0.),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
|
||||||
|
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||||
|
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
|
||||||
|
cursor.start().height + logical_scroll_top.offset_in_item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ListItem {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Unrendered => write!(f, "Unrendered"),
|
||||||
|
Self::Rendered { height, .. } => {
|
||||||
|
f.debug_struct("Rendered").field("height", height).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ListOffset {
|
||||||
|
pub item_ix: usize,
|
||||||
|
pub offset_in_item: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for List {
|
||||||
|
type State = ();
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
_state: Option<Self::State>,
|
||||||
|
cx: &mut crate::WindowContext,
|
||||||
|
) -> (crate::LayoutId, Self::State) {
|
||||||
|
let style = Style::from(self.style.clone());
|
||||||
|
let layout_id = cx.with_text_style(style.text_style().cloned(), |cx| {
|
||||||
|
cx.request_layout(&style, None)
|
||||||
|
});
|
||||||
|
(layout_id, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
self,
|
||||||
|
bounds: crate::Bounds<crate::Pixels>,
|
||||||
|
_state: &mut Self::State,
|
||||||
|
cx: &mut crate::WindowContext,
|
||||||
|
) {
|
||||||
|
let state = &mut *self.state.0.borrow_mut();
|
||||||
|
|
||||||
|
// If the width of the list has changed, invalidate all cached item heights
|
||||||
|
if state.last_layout_width != Some(bounds.size.width) {
|
||||||
|
state.items = SumTree::from_iter(
|
||||||
|
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
|
||||||
|
&(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_items = state.items.clone();
|
||||||
|
let mut measured_items = VecDeque::new();
|
||||||
|
let mut item_elements = VecDeque::new();
|
||||||
|
let mut rendered_height = px(0.);
|
||||||
|
let mut scroll_top = state.logical_scroll_top();
|
||||||
|
|
||||||
|
let available_item_space = Size {
|
||||||
|
width: AvailableSpace::Definite(bounds.size.width),
|
||||||
|
height: AvailableSpace::MinContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render items after the scroll top, including those in the trailing overdraw
|
||||||
|
let mut cursor = old_items.cursor::<Count>();
|
||||||
|
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||||
|
for (ix, item) in cursor.by_ref().enumerate() {
|
||||||
|
let visible_height = rendered_height - scroll_top.offset_in_item;
|
||||||
|
if visible_height >= bounds.size.height + state.overdraw {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the previously cached height if available
|
||||||
|
let mut height = if let ListItem::Rendered { height } = item {
|
||||||
|
Some(*height)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we're within the visible area or the height wasn't cached, render and measure the item's element
|
||||||
|
if visible_height < bounds.size.height || height.is_none() {
|
||||||
|
let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
|
||||||
|
let element_size = element.measure(available_item_space, cx);
|
||||||
|
height = Some(element_size.height);
|
||||||
|
if visible_height < bounds.size.height {
|
||||||
|
item_elements.push_back(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let height = height.unwrap();
|
||||||
|
rendered_height += height;
|
||||||
|
measured_items.push_back(ListItem::Rendered { height });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare to start walking upward from the item at the scroll top.
|
||||||
|
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||||
|
|
||||||
|
// If the rendered items do not fill the visible region, then adjust
|
||||||
|
// the scroll top upward.
|
||||||
|
if rendered_height - scroll_top.offset_in_item < bounds.size.height {
|
||||||
|
while rendered_height < bounds.size.height {
|
||||||
|
cursor.prev(&());
|
||||||
|
if cursor.item().is_some() {
|
||||||
|
let mut element = (state.render_item)(cursor.start().0, cx);
|
||||||
|
let element_size = element.measure(available_item_space, cx);
|
||||||
|
|
||||||
|
rendered_height += element_size.height;
|
||||||
|
measured_items.push_front(ListItem::Rendered {
|
||||||
|
height: element_size.height,
|
||||||
|
});
|
||||||
|
item_elements.push_front(element)
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll_top = ListOffset {
|
||||||
|
item_ix: cursor.start().0,
|
||||||
|
offset_in_item: rendered_height - bounds.size.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
match state.orientation {
|
||||||
|
Orientation::Top => {
|
||||||
|
scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
|
||||||
|
state.logical_scroll_top = Some(scroll_top);
|
||||||
|
}
|
||||||
|
Orientation::Bottom => {
|
||||||
|
scroll_top = ListOffset {
|
||||||
|
item_ix: cursor.start().0,
|
||||||
|
offset_in_item: rendered_height - bounds.size.height,
|
||||||
|
};
|
||||||
|
state.logical_scroll_top = None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure items in the leading overdraw
|
||||||
|
let mut leading_overdraw = scroll_top.offset_in_item;
|
||||||
|
while leading_overdraw < state.overdraw {
|
||||||
|
cursor.prev(&());
|
||||||
|
if let Some(item) = cursor.item() {
|
||||||
|
let height = if let ListItem::Rendered { height } = item {
|
||||||
|
*height
|
||||||
|
} else {
|
||||||
|
let mut element = (state.render_item)(cursor.start().0, cx);
|
||||||
|
element.measure(available_item_space, cx).height
|
||||||
|
};
|
||||||
|
|
||||||
|
leading_overdraw += height;
|
||||||
|
measured_items.push_front(ListItem::Rendered { height });
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
|
||||||
|
let mut cursor = old_items.cursor::<Count>();
|
||||||
|
let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
|
||||||
|
new_items.extend(measured_items, &());
|
||||||
|
cursor.seek(&Count(measured_range.end), Bias::Right, &());
|
||||||
|
new_items.append(cursor.suffix(&()), &());
|
||||||
|
|
||||||
|
// Paint the visible items
|
||||||
|
let mut item_origin = bounds.origin;
|
||||||
|
item_origin.y -= scroll_top.offset_in_item;
|
||||||
|
for mut item_element in item_elements {
|
||||||
|
let item_height = item_element.measure(available_item_space, cx).height;
|
||||||
|
item_element.draw(item_origin, available_item_space, cx);
|
||||||
|
item_origin.y += item_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.items = new_items;
|
||||||
|
state.last_layout_width = Some(bounds.size.width);
|
||||||
|
|
||||||
|
let list_state = self.state.clone();
|
||||||
|
let height = bounds.size.height;
|
||||||
|
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble {
|
||||||
|
list_state.0.borrow_mut().scroll(
|
||||||
|
&scroll_top,
|
||||||
|
height,
|
||||||
|
event.delta.pixel_delta(px(20.)),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoElement for List {
|
||||||
|
type Element = Self;
|
||||||
|
|
||||||
|
fn element_id(&self) -> Option<crate::ElementId> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_element(self) -> Self::Element {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Item for ListItem {
|
||||||
|
type Summary = ListItemSummary;
|
||||||
|
|
||||||
|
fn summary(&self) -> Self::Summary {
|
||||||
|
match self {
|
||||||
|
ListItem::Unrendered => ListItemSummary {
|
||||||
|
count: 1,
|
||||||
|
rendered_count: 0,
|
||||||
|
unrendered_count: 1,
|
||||||
|
height: px(0.),
|
||||||
|
},
|
||||||
|
ListItem::Rendered { height } => ListItemSummary {
|
||||||
|
count: 1,
|
||||||
|
rendered_count: 1,
|
||||||
|
unrendered_count: 0,
|
||||||
|
height: *height,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Summary for ListItemSummary {
|
||||||
|
type Context = ();
|
||||||
|
|
||||||
|
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||||
|
self.count += summary.count;
|
||||||
|
self.rendered_count += summary.rendered_count;
|
||||||
|
self.unrendered_count += summary.unrendered_count;
|
||||||
|
self.height += summary.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
|
||||||
|
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||||
|
self.0 += summary.count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount {
|
||||||
|
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||||
|
self.0 += summary.rendered_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount {
|
||||||
|
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||||
|
self.0 += summary.unrendered_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
|
||||||
|
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||||
|
self.0 += summary.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Count {
|
||||||
|
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
|
||||||
|
self.0.partial_cmp(&other.count).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
|
||||||
|
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
|
||||||
|
self.0.partial_cmp(&other.height).unwrap()
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
mod canvas;
|
mod canvas;
|
||||||
mod div;
|
mod div;
|
||||||
mod img;
|
mod img;
|
||||||
|
mod list;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
mod svg;
|
mod svg;
|
||||||
mod text;
|
mod text;
|
||||||
@ -9,6 +10,7 @@ mod uniform_list;
|
|||||||
pub use canvas::*;
|
pub use canvas::*;
|
||||||
pub use div::*;
|
pub use div::*;
|
||||||
pub use img::*;
|
pub use img::*;
|
||||||
|
pub use list::*;
|
||||||
pub use overlay::*;
|
pub use overlay::*;
|
||||||
pub use svg::*;
|
pub use svg::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
@ -131,7 +131,7 @@ impl Element for UniformList {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let height = match available_space.height {
|
let height = match available_space.height {
|
||||||
AvailableSpace::Definite(x) => desired_height.min(x),
|
AvailableSpace::Definite(height) => desired_height.min(height),
|
||||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
||||||
desired_height
|
desired_height
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,7 @@ pub use view::*;
|
|||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
borrow::{Borrow, BorrowMut},
|
borrow::{Borrow, BorrowMut},
|
||||||
@ -248,3 +249,22 @@ impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
|||||||
Self(value.into())
|
Self(value.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for SharedString {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for SharedString {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Ok(SharedString::from(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ use std::{ops::Range, sync::Arc};
|
|||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use gpui::{AnyElement, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
|
use gpui::{AnyElement, FontStyle, FontWeight, HighlightStyle, UnderlineStyle, WindowContext};
|
||||||
use language::{HighlightId, Language, LanguageRegistry};
|
use language::{HighlightId, Language, LanguageRegistry};
|
||||||
use util::RangeExt;
|
use util::RangeExt;
|
||||||
|
|
||||||
@ -56,12 +56,7 @@ pub struct Mention {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RichText {
|
impl RichText {
|
||||||
pub fn element(
|
pub fn element(&self, cx: &mut WindowContext) -> AnyElement {
|
||||||
&self,
|
|
||||||
// syntax: Arc<SyntaxTheme>,
|
|
||||||
// style: RichTextStyle,
|
|
||||||
// cx: &mut ViewContext<V>,
|
|
||||||
) -> AnyElement {
|
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
// let mut region_id = 0;
|
// let mut region_id = 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user