diff --git a/Cargo.lock b/Cargo.lock index 8f9580e319..197dc7cf95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,6 +1829,47 @@ dependencies = [ "zed-actions", ] +[[package]] +name = "collab_ui2" +version = "0.1.0" +dependencies = [ + "anyhow", + "call2", + "channel2", + "client2", + "clock", + "collections", + "db2", + "editor2", + "feature_flags2", + "futures 0.3.28", + "fuzzy", + "gpui2", + "language2", + "lazy_static", + "log", + "menu2", + "notifications2", + "picker2", + "postage", + "pretty_assertions", + "project2", + "rich_text2", + "rpc2", + "schemars", + "serde", + "serde_derive", + "settings2", + "smallvec", + "theme2", + "time", + "tree-sitter-markdown", + "ui2", + "util", + "workspace2", + "zed_actions2", +] + [[package]] name = "collections" version = "0.1.0" @@ -9163,6 +9204,39 @@ dependencies = [ "workspace", ] +[[package]] +name = "terminal_view2" +version = "0.1.0" +dependencies = [ + "anyhow", + "client2", + "db2", + "dirs 4.0.0", + "editor2", + "futures 0.3.28", + "gpui2", + "itertools 0.10.5", + "language2", + "lazy_static", + "libc", + "mio-extras", + "ordered-float 2.10.0", + "procinfo", + "project2", + "rand 0.8.5", + "serde", + "serde_derive", + "settings2", + "shellexpand", + "smallvec", + "smol", + "terminal2", + "theme2", + "thiserror", + "util", + "workspace2", +] + [[package]] name = "text" version = "0.1.0" @@ -10087,6 +10161,7 @@ dependencies = [ "chrono", "gpui2", "itertools 0.11.0", + "menu2", "rand 0.8.5", "serde", "settings2", @@ -11317,7 +11392,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.113.0" +version = "0.114.0" dependencies = [ "activity_indicator", "ai", @@ -11470,6 +11545,7 @@ dependencies = [ "chrono", "cli", "client2", + "collab_ui2", "collections", "command_palette2", "copilot2", @@ -11522,6 +11598,7 @@ dependencies = [ "smol", "sum_tree", "tempdir", + "terminal_view2", "text2", "theme2", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index d4d47a5fba..8d2c420b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "crates/collab", "crates/collab2", "crates/collab_ui", + "crates/collab_ui2", "crates/collections", "crates/command_palette", "crates/command_palette2", @@ -98,6 +99,7 @@ members = [ "crates/sum_tree", "crates/terminal", "crates/terminal2", + "crates/terminal_view2", "crates/text", "crates/theme", "crates/theme2", @@ -204,6 +206,9 @@ core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "07 [profile.dev] split-debuginfo = "unpacked" +[profile.dev.package.taffy] +opt-level = 3 + [profile.release] debug = true lto = "thin" diff --git a/crates/collab2/src/tests/test_server.rs b/crates/collab2/src/tests/test_server.rs index 1b4d8945ae..de6f3e92a1 100644 --- a/crates/collab2/src/tests/test_server.rs +++ b/crates/collab2/src/tests/test_server.rs @@ -220,7 +220,6 @@ impl TestServer { languages: Arc::new(language_registry), fs: fs.clone(), build_window_options: |_, _, _| Default::default(), - initialize_workspace: |_, _, _, _| gpui::Task::ready(Ok(())), node_runtime: FakeNodeRuntime::new(), }); diff --git a/crates/collab_ui2/Cargo.toml b/crates/collab_ui2/Cargo.toml new file mode 100644 index 0000000000..4660880ecd --- /dev/null +++ b/crates/collab_ui2/Cargo.toml @@ -0,0 +1,81 @@ +[package] +name = "collab_ui2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/collab_ui.rs" +doctest = false + +[features] +test-support = [ + "call/test-support", + "client/test-support", + "collections/test-support", + "editor/test-support", + "gpui/test-support", + "project/test-support", + "settings/test-support", + "util/test-support", + "workspace/test-support", +] + +[dependencies] +# auto_update = { path = "../auto_update" } +db = { package = "db2", path = "../db2" } +call = { package = "call2", path = "../call2" } +client = { package = "client2", path = "../client2" } +channel = { package = "channel2", path = "../channel2" } +clock = { path = "../clock" } +collections = { path = "../collections" } +# context_menu = { path = "../context_menu" } +# drag_and_drop = { path = "../drag_and_drop" } +editor = { package="editor2", path = "../editor2" } +#feedback = { path = "../feedback" } +fuzzy = { path = "../fuzzy" } +gpui = { package = "gpui2", path = "../gpui2" } +language = { package = "language2", path = "../language2" } +menu = { package = "menu2", path = "../menu2" } +notifications = { package = "notifications2", path = "../notifications2" } +rich_text = { package = "rich_text2", path = "../rich_text2" } +picker = { package = "picker2", path = "../picker2" } +project = { package = "project2", path = "../project2" } +# recent_projects = { path = "../recent_projects" } +rpc = { package ="rpc2", path = "../rpc2" } +settings = { package = "settings2", path = "../settings2" } +feature_flags = { package = "feature_flags2", path = "../feature_flags2"} +theme = { package = "theme2", path = "../theme2" } +# theme_selector = { path = "../theme_selector" } +# vcs_menu = { path = "../vcs_menu" } +ui = { package = "ui2", path = "../ui2" } +util = { path = "../util" } +workspace = { package = "workspace2", path = "../workspace2" } +zed-actions = { package="zed_actions2", path = "../zed_actions2"} + +anyhow.workspace = true +futures.workspace = true +lazy_static.workspace = true +log.workspace = true +schemars.workspace = true +postage.workspace = true +serde.workspace = true +serde_derive.workspace = true +time.workspace = true +smallvec.workspace = true + +[dev-dependencies] +call = { package = "call2", path = "../call2", features = ["test-support"] } +client = { package = "client2", path = "../client2", features = ["test-support"] } +collections = { path = "../collections", features = ["test-support"] } +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +notifications = { package = "notifications2", path = "../notifications2", features = ["test-support"] } +project = { package = "project2", path = "../project2", features = ["test-support"] } +rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] } +settings = { package = "settings2", path = "../settings2", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } +workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] } + +pretty_assertions.workspace = true +tree-sitter-markdown.workspace = true diff --git a/crates/collab_ui2/src/channel_view.rs b/crates/collab_ui2/src/channel_view.rs new file mode 100644 index 0000000000..d2ffc0de57 --- /dev/null +++ b/crates/collab_ui2/src/channel_view.rs @@ -0,0 +1,454 @@ +// use anyhow::{anyhow, Result}; +// use call::report_call_event_for_channel; +// use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId, ChannelStore}; +// use client::{ +// proto::{self, PeerId}, +// Collaborator, ParticipantIndex, +// }; +// use collections::HashMap; +// use editor::{CollaborationHub, Editor}; +// use gpui::{ +// actions, +// elements::{ChildView, Label}, +// geometry::vector::Vector2F, +// AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Subscription, Task, View, +// ViewContext, ViewHandle, +// }; +// use project::Project; +// use smallvec::SmallVec; +// use std::{ +// any::{Any, TypeId}, +// sync::Arc, +// }; +// use util::ResultExt; +// use workspace::{ +// item::{FollowableItem, Item, ItemEvent, ItemHandle}, +// register_followable_item, +// searchable::SearchableItemHandle, +// ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId, +// }; + +// actions!(channel_view, [Deploy]); + +// pub fn init(cx: &mut AppContext) { +// register_followable_item::(cx) +// } + +// pub struct ChannelView { +// pub editor: ViewHandle, +// project: ModelHandle, +// channel_store: ModelHandle, +// channel_buffer: ModelHandle, +// remote_id: Option, +// _editor_event_subscription: Subscription, +// } + +// impl ChannelView { +// pub fn open( +// channel_id: ChannelId, +// workspace: ViewHandle, +// cx: &mut AppContext, +// ) -> Task>> { +// let pane = workspace.read(cx).active_pane().clone(); +// let channel_view = Self::open_in_pane(channel_id, pane.clone(), workspace.clone(), cx); +// cx.spawn(|mut cx| async move { +// let channel_view = channel_view.await?; +// pane.update(&mut cx, |pane, cx| { +// report_call_event_for_channel( +// "open channel notes", +// channel_id, +// &workspace.read(cx).app_state().client, +// cx, +// ); +// pane.add_item(Box::new(channel_view.clone()), true, true, None, cx); +// }); +// anyhow::Ok(channel_view) +// }) +// } + +// pub fn open_in_pane( +// channel_id: ChannelId, +// pane: ViewHandle, +// workspace: ViewHandle, +// cx: &mut AppContext, +// ) -> Task>> { +// 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)); + +// cx.spawn(|mut cx| async move { +// let channel_buffer = channel_buffer.await?; +// let markdown = markdown.await.log_err(); + +// channel_buffer.update(&mut cx, |buffer, cx| { +// buffer.buffer().update(cx, |buffer, cx| { +// buffer.set_language_registry(language_registry); +// if let Some(markdown) = markdown { +// buffer.set_language(Some(markdown), cx); +// } +// }) +// }); + +// pane.update(&mut cx, |pane, cx| { +// let buffer_id = channel_buffer.read(cx).remote_id(cx); + +// let existing_view = pane +// .items_of_type::() +// .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_buffer { +// return existing_view; +// } +// } + +// let view = cx.add_view(|cx| { +// let mut this = Self::new(project, channel_store, channel_buffer, cx); +// this.acknowledge_buffer_version(cx); +// this +// }); + +// // 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.id(), SaveIntent::Skip, cx) +// .detach(); +// pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx); +// } +// } + +// view +// }) +// .ok_or_else(|| anyhow!("pane was dropped")) +// }) +// } + +// pub fn new( +// project: ModelHandle, +// channel_store: ModelHandle, +// channel_buffer: ModelHandle, +// cx: &mut ViewContext, +// ) -> Self { +// let buffer = channel_buffer.read(cx).buffer(); +// let editor = cx.add_view(|cx| { +// let mut editor = Editor::for_buffer(buffer, None, cx); +// editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub( +// channel_buffer.clone(), +// ))); +// editor.set_read_only( +// !channel_buffer +// .read(cx) +// .channel(cx) +// .is_some_and(|c| c.can_edit_notes()), +// ); +// editor +// }); +// let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone())); + +// cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event) +// .detach(); + +// Self { +// editor, +// project, +// channel_store, +// channel_buffer, +// remote_id: None, +// _editor_event_subscription, +// } +// } + +// pub fn channel(&self, cx: &AppContext) -> Option> { +// self.channel_buffer.read(cx).channel(cx) +// } + +// fn handle_channel_buffer_event( +// &mut self, +// _: ModelHandle, +// event: &ChannelBufferEvent, +// cx: &mut ViewContext, +// ) { +// match event { +// ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| { +// editor.set_read_only(true); +// cx.notify(); +// }), +// ChannelBufferEvent::ChannelChanged => { +// self.editor.update(cx, |editor, cx| { +// editor.set_read_only(!self.channel(cx).is_some_and(|c| c.can_edit_notes())); +// cx.emit(editor::Event::TitleChanged); +// cx.notify() +// }); +// } +// ChannelBufferEvent::BufferEdited => { +// if cx.is_self_focused() || self.editor.is_focused(cx) { +// self.acknowledge_buffer_version(cx); +// } else { +// self.channel_store.update(cx, |store, cx| { +// let channel_buffer = self.channel_buffer.read(cx); +// store.notes_changed( +// channel_buffer.channel_id, +// channel_buffer.epoch(), +// &channel_buffer.buffer().read(cx).version(), +// cx, +// ) +// }); +// } +// } +// ChannelBufferEvent::CollaboratorsChanged => {} +// } +// } + +// fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<'_, '_, 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 Entity for ChannelView { +// type Event = editor::Event; +// } + +// impl View for ChannelView { +// fn ui_name() -> &'static str { +// "ChannelView" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// ChildView::new(self.editor.as_any(), cx).into_any() +// } + +// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { +// if cx.is_self_focused() { +// self.acknowledge_buffer_version(cx); +// cx.focus(self.editor.as_any()) +// } +// } +// } + +// impl Item for ChannelView { +// fn act_as_type<'a>( +// &'a self, +// type_id: TypeId, +// self_handle: &'a ViewHandle, +// _: &'a AppContext, +// ) -> Option<&'a AnyViewHandle> { +// if type_id == TypeId::of::() { +// Some(self_handle) +// } else if type_id == TypeId::of::() { +// Some(&self.editor) +// } else { +// None +// } +// } + +// fn tab_content( +// &self, +// _: Option, +// style: &theme::Tab, +// cx: &gpui::AppContext, +// ) -> AnyElement { +// let label = if let Some(channel) = self.channel(cx) { +// match ( +// channel.can_edit_notes(), +// self.channel_buffer.read(cx).is_connected(), +// ) { +// (true, true) => format!("#{}", channel.name), +// (false, true) => format!("#{} (read-only)", channel.name), +// (_, false) => format!("#{} (disconnected)", channel.name), +// } +// } else { +// format!("channel notes (disconnected)") +// }; +// Label::new(label, style.label.to_owned()).into_any() +// } + +// fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext) -> Option { +// Some(Self::new( +// self.project.clone(), +// self.channel_store.clone(), +// self.channel_buffer.clone(), +// cx, +// )) +// } + +// fn is_singleton(&self, _cx: &AppContext) -> bool { +// false +// } + +// fn navigate(&mut self, data: Box, cx: &mut ViewContext) -> bool { +// self.editor +// .update(cx, |editor, cx| editor.navigate(data, cx)) +// } + +// fn deactivated(&mut self, cx: &mut ViewContext) { +// self.editor +// .update(cx, |editor, cx| Item::deactivated(editor, cx)) +// } + +// fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext) { +// self.editor +// .update(cx, |editor, cx| Item::set_nav_history(editor, history, cx)) +// } + +// fn as_searchable(&self, _: &ViewHandle) -> Option> { +// Some(Box::new(self.editor.clone())) +// } + +// fn show_toolbar(&self) -> bool { +// true +// } + +// fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { +// self.editor.read(cx).pixel_position_of_cursor(cx) +// } + +// fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { +// editor::Editor::to_item_events(event) +// } +// } + +// impl FollowableItem for ChannelView { +// fn remote_id(&self) -> Option { +// self.remote_id +// } + +// fn to_state_proto(&self, cx: &AppContext) -> Option { +// let channel_buffer = self.channel_buffer.read(cx); +// if !channel_buffer.is_connected() { +// return None; +// } + +// Some(proto::view::Variant::ChannelView( +// proto::view::ChannelView { +// channel_id: channel_buffer.channel_id, +// editor: if let Some(proto::view::Variant::Editor(proto)) = +// self.editor.read(cx).to_state_proto(cx) +// { +// Some(proto) +// } else { +// None +// }, +// }, +// )) +// } + +// fn from_state_proto( +// pane: ViewHandle, +// workspace: ViewHandle, +// remote_id: workspace::ViewId, +// state: &mut Option, +// cx: &mut AppContext, +// ) -> Option>>> { +// let Some(proto::view::Variant::ChannelView(_)) = state else { +// return None; +// }; +// let Some(proto::view::Variant::ChannelView(state)) = state.take() else { +// unreachable!() +// }; + +// let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx); + +// Some(cx.spawn(|mut cx| async move { +// let this = open.await?; + +// let task = this +// .update(&mut cx, |this, cx| { +// this.remote_id = Some(remote_id); + +// if let Some(state) = state.editor { +// Some(this.editor.update(cx, |editor, cx| { +// editor.apply_update_proto( +// &this.project, +// proto::update_view::Variant::Editor(proto::update_view::Editor { +// selections: state.selections, +// pending_selection: state.pending_selection, +// scroll_top_anchor: state.scroll_top_anchor, +// scroll_x: state.scroll_x, +// scroll_y: state.scroll_y, +// ..Default::default() +// }), +// cx, +// ) +// })) +// } else { +// None +// } +// }) +// .ok_or_else(|| anyhow!("window was closed"))?; + +// if let Some(task) = task { +// task.await?; +// } + +// Ok(this) +// })) +// } + +// fn add_event_to_update_proto( +// &self, +// event: &Self::Event, +// update: &mut Option, +// cx: &AppContext, +// ) -> bool { +// self.editor +// .read(cx) +// .add_event_to_update_proto(event, update, cx) +// } + +// fn apply_update_proto( +// &mut self, +// project: &ModelHandle, +// message: proto::update_view::Variant, +// cx: &mut ViewContext, +// ) -> gpui::Task> { +// self.editor.update(cx, |editor, cx| { +// editor.apply_update_proto(project, message, cx) +// }) +// } + +// fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext) { +// self.editor.update(cx, |editor, cx| { +// editor.set_leader_peer_id(leader_peer_id, cx) +// }) +// } + +// fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool { +// Editor::should_unfollow_on_event(event, cx) +// } + +// fn is_project_item(&self, _cx: &AppContext) -> bool { +// false +// } +// } + +// struct ChannelBufferCollaborationHub(ModelHandle); + +// impl CollaborationHub for ChannelBufferCollaborationHub { +// fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap { +// self.0.read(cx).collaborators() +// } + +// fn user_participant_indices<'a>( +// &self, +// cx: &'a AppContext, +// ) -> &'a HashMap { +// self.0.read(cx).user_store().read(cx).participant_indices() +// } +// } diff --git a/crates/collab_ui2/src/chat_panel.rs b/crates/collab_ui2/src/chat_panel.rs new file mode 100644 index 0000000000..e9e2610a86 --- /dev/null +++ b/crates/collab_ui2/src/chat_panel.rs @@ -0,0 +1,983 @@ +// use crate::{ +// channel_view::ChannelView, is_channels_feature_enabled, render_avatar, ChatPanelSettings, +// }; +// use anyhow::Result; +// use call::ActiveCall; +// use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; +// use client::Client; +// use collections::HashMap; +// use db::kvp::KEY_VALUE_STORE; +// use editor::Editor; +// use gpui::{ +// actions, +// elements::*, +// platform::{CursorStyle, MouseButton}, +// serde_json, +// views::{ItemType, Select, SelectStyle}, +// AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View, +// ViewContext, ViewHandle, WeakViewHandle, +// }; +// use language::LanguageRegistry; +// use menu::Confirm; +// use message_editor::MessageEditor; +// use project::Fs; +// use rich_text::RichText; +// use serde::{Deserialize, Serialize}; +// use settings::SettingsStore; +// use std::sync::Arc; +// use theme::{IconButton, Theme}; +// use time::{OffsetDateTime, UtcOffset}; +// use util::{ResultExt, TryFutureExt}; +// use workspace::{ +// dock::{DockPosition, Panel}, +// Workspace, +// }; + +// mod message_editor; + +// const MESSAGE_LOADING_THRESHOLD: usize = 50; +// const CHAT_PANEL_KEY: &'static str = "ChatPanel"; + +// pub struct ChatPanel { +// client: Arc, +// channel_store: ModelHandle, +// languages: Arc, +// active_chat: Option<(ModelHandle, Subscription)>, +// message_list: ListState, +// input_editor: ViewHandle, +// channel_select: ViewHandle, +// ) -> AnyElement