Add ChannelList to AppState

Max Brunsfeld created

Change summary

gpui/src/elements/list.rs |  2 +
server/src/rpc.rs         | 18 +++++++-----
zed/src/channel.rs        | 43 ++++++++++++++++++------------
zed/src/chat_panel.rs     | 58 ++++++++++++++++++++++++----------------
zed/src/editor.rs         | 11 ++++---
zed/src/editor/buffer.rs  | 13 ++++----
zed/src/file_finder.rs    |  8 ++--
zed/src/lib.rs            |  4 ++
zed/src/main.rs           | 24 +++++++++-------
zed/src/test.rs           | 16 ++++++++--
zed/src/workspace.rs      | 24 ++++++++++------
zed/src/worktree.rs       |  6 +--
12 files changed, 134 insertions(+), 93 deletions(-)

Detailed changes

gpui/src/elements/list.rs 🔗

@@ -106,6 +106,7 @@ impl Element for List {
     }
 
     fn paint(&mut self, bounds: RectF, _: &mut (), cx: &mut PaintContext) {
+        cx.scene.push_layer(Some(bounds));
         let state = &mut *self.state.0.lock();
         let visible_range = state.visible_range(bounds.height());
 
@@ -119,6 +120,7 @@ impl Element for List {
             element.paint(origin, cx);
             item_top += element.size().y();
         }
+        cx.scene.pop_layer();
     }
 
     fn dispatch_event(

server/src/rpc.rs 🔗

@@ -1425,12 +1425,13 @@ mod tests {
         .await
         .unwrap();
 
-        let channels_a = ChannelList::new(client_a, &mut cx_a.to_async())
-            .await
-            .unwrap();
+        let channels_a = cx_a.add_model(|cx| ChannelList::new(client_a, cx));
+        channels_a
+            .condition(&mut cx_a, |list, _| list.available_channels().is_some())
+            .await;
         channels_a.read_with(&cx_a, |list, _| {
             assert_eq!(
-                list.available_channels(),
+                list.available_channels().unwrap(),
                 &[ChannelDetails {
                     id: channel_id.to_proto(),
                     name: "test-channel".to_string()
@@ -1448,12 +1449,13 @@ mod tests {
             })
             .await;
 
-        let channels_b = ChannelList::new(client_b, &mut cx_b.to_async())
-            .await
-            .unwrap();
+        let channels_b = cx_b.add_model(|cx| ChannelList::new(client_b, cx));
+        channels_b
+            .condition(&mut cx_b, |list, _| list.available_channels().is_some())
+            .await;
         channels_b.read_with(&cx_b, |list, _| {
             assert_eq!(
-                list.available_channels(),
+                list.available_channels().unwrap(),
                 &[ChannelDetails {
                     id: channel_id.to_proto(),
                     name: "test-channel".to_string()

zed/src/channel.rs 🔗

@@ -3,9 +3,7 @@ use crate::{
     util::log_async_errors,
 };
 use anyhow::{anyhow, Context, Result};
-use gpui::{
-    AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, WeakModelHandle,
-};
+use gpui::{Entity, ModelContext, ModelHandle, MutableAppContext, WeakModelHandle};
 use std::{
     cmp::Ordering,
     collections::{hash_map, BTreeSet, HashMap},
@@ -17,7 +15,7 @@ use zrpc::{
 };
 
 pub struct ChannelList {
-    available_channels: Vec<ChannelDetails>,
+    available_channels: Option<Vec<ChannelDetails>>,
     channels: HashMap<u64, WeakModelHandle<Channel>>,
     rpc: Arc<Client>,
 }
@@ -55,21 +53,32 @@ impl Entity for ChannelList {
 }
 
 impl ChannelList {
-    pub async fn new(rpc: Arc<rpc::Client>, cx: &mut AsyncAppContext) -> Result<ModelHandle<Self>> {
-        let response = rpc
-            .request(proto::GetChannels {})
-            .await
-            .context("failed to fetch available channels")?;
-
-        Ok(cx.add_model(|_| Self {
-            available_channels: response.channels.into_iter().map(Into::into).collect(),
+    pub fn new(rpc: Arc<rpc::Client>, cx: &mut ModelContext<Self>) -> Self {
+        cx.spawn(|this, mut cx| {
+            let rpc = rpc.clone();
+            log_async_errors(async move {
+                let response = rpc
+                    .request(proto::GetChannels {})
+                    .await
+                    .context("failed to fetch available channels")?;
+                this.update(&mut cx, |this, cx| {
+                    this.available_channels =
+                        Some(response.channels.into_iter().map(Into::into).collect());
+                    cx.notify();
+                });
+                Ok(())
+            })
+        })
+        .detach();
+        Self {
+            available_channels: None,
             channels: Default::default(),
             rpc,
-        }))
+        }
     }
 
-    pub fn available_channels(&self) -> &[ChannelDetails] {
-        &self.available_channels
+    pub fn available_channels(&self) -> Option<&[ChannelDetails]> {
+        self.available_channels.as_ref().map(Vec::as_slice)
     }
 
     pub fn get_channel(
@@ -82,8 +91,8 @@ impl ChannelList {
             hash_map::Entry::Vacant(entry) => {
                 if let Some(details) = self
                     .available_channels
-                    .iter()
-                    .find(|details| details.id == id)
+                    .as_ref()
+                    .and_then(|channels| channels.iter().find(|details| details.id == id))
                 {
                     let rpc = self.rpc.clone();
                     let channel = cx.add_model(|cx| Channel::new(details.clone(), rpc, cx));

zed/src/chat_panel.rs 🔗

@@ -1,38 +1,48 @@
-use crate::Settings;
-
-use super::channel::{Channel, ChannelList};
+use super::{
+    channel::{Channel, ChannelList},
+    Settings,
+};
 use gpui::{elements::*, Entity, ModelHandle, RenderContext, View, ViewContext};
 use postage::watch;
 
 pub struct ChatPanel {
-    // channel_list: ModelHandle<ChannelList>,
-    // active_channel: Option<ModelHandle<Channel>>,
+    channel_list: ModelHandle<ChannelList>,
+    active_channel: Option<ModelHandle<Channel>>,
+    // active_channel_subscription: Subscription,
     messages: ListState,
 }
 
 pub enum Event {}
 
 impl ChatPanel {
-    pub fn new(settings: watch::Receiver<Settings>) -> Self {
-        let settings = settings.borrow();
-        let mut messages = Vec::new();
-        for i in 0..1000 {
-            messages.push(
-                Container::new(
-                    Label::new(
-                        format!("This is message {}", i),
-                        settings.ui_font_family,
-                        settings.ui_font_size,
-                    )
-                    .with_style(&settings.theme.selector.label)
-                    .boxed(),
-                )
-                .boxed(),
-            );
-        }
-        Self {
-            messages: ListState::new(messages),
+    pub fn new(
+        channel_list: ModelHandle<ChannelList>,
+        settings: watch::Receiver<Settings>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        let mut this = Self {
+            channel_list,
+            messages: ListState::new(Vec::new()),
+            active_channel: None,
+        };
+        let channel = this.channel_list.update(cx, |list, cx| {
+            if let Some(channel_id) = list
+                .available_channels()
+                .and_then(|channels| channels.first())
+                .map(|details| details.id)
+            {
+                return list.get_channel(channel_id, cx);
+            }
+            None
+        });
+        if let Some(channel) = channel {
+            this.set_active_channel(channel);
         }
+        this
+    }
+
+    pub fn set_active_channel(&mut self, channel: ModelHandle<Channel>) {
+        //
     }
 }
 

zed/src/editor.rs 🔗

@@ -2596,8 +2596,9 @@ mod tests {
     use super::*;
     use crate::{
         editor::Point,
+        language::LanguageRegistry,
         settings,
-        test::{build_app_state, sample_text},
+        test::{build_settings, sample_text},
     };
     use buffer::History;
     use unindent::Unindent;
@@ -4120,8 +4121,9 @@ mod tests {
 
     #[gpui::test]
     async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) {
-        let app_state = cx.read(build_app_state);
-        let lang = app_state.languages.select_language("z.rs");
+        let settings = cx.read(build_settings);
+        let languages = LanguageRegistry::new();
+        let lang = languages.select_language("z.rs");
         let text = r#"
             use mod1::mod2::{mod3, mod4};
 
@@ -4134,8 +4136,7 @@ mod tests {
             let history = History::new(text.into());
             Buffer::from_history(0, history, None, lang.cloned(), cx)
         });
-        let (_, view) =
-            cx.add_window(|cx| Editor::for_buffer(buffer, app_state.settings.clone(), cx));
+        let (_, view) = cx.add_window(|cx| Editor::for_buffer(buffer, settings.clone(), cx));
         view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())
             .await;
 

zed/src/editor/buffer.rs 🔗

@@ -2641,7 +2641,7 @@ impl<'a> Into<proto::Operation> for &'a Operation {
                     },
                 ),
                 #[cfg(test)]
-                Operation::Test(_) => unimplemented!()
+                Operation::Test(_) => unimplemented!(),
             }),
         }
     }
@@ -2895,7 +2895,8 @@ mod tests {
     use super::*;
     use crate::{
         fs::RealFs,
-        test::{build_app_state, temp_tree},
+        language::LanguageRegistry,
+        test::temp_tree,
         util::RandomCharIter,
         worktree::{Worktree, WorktreeHandle as _},
     };
@@ -3825,8 +3826,8 @@ mod tests {
 
     #[gpui::test]
     async fn test_reparse(mut cx: gpui::TestAppContext) {
-        let app_state = cx.read(build_app_state);
-        let rust_lang = app_state.languages.select_language("test.rs");
+        let languages = LanguageRegistry::new();
+        let rust_lang = languages.select_language("test.rs");
         assert!(rust_lang.is_some());
 
         let buffer = cx.add_model(|cx| {
@@ -3966,8 +3967,8 @@ mod tests {
     async fn test_enclosing_bracket_ranges(mut cx: gpui::TestAppContext) {
         use unindent::Unindent as _;
 
-        let app_state = cx.read(build_app_state);
-        let rust_lang = app_state.languages.select_language("test.rs");
+        let languages = LanguageRegistry::new();
+        let rust_lang = languages.select_language("test.rs");
         assert!(rust_lang.is_some());
 
         let buffer = cx.add_model(|cx| {

zed/src/file_finder.rs 🔗

@@ -455,7 +455,7 @@ mod tests {
             editor::init(cx);
         });
 
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
         let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
         workspace
             .update(&mut cx, |workspace, cx| {
@@ -515,7 +515,7 @@ mod tests {
         )
         .await;
 
-        let mut app_state = cx.read(build_app_state);
+        let mut app_state = cx.update(build_app_state);
         Arc::get_mut(&mut app_state).unwrap().fs = fs;
 
         let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
@@ -577,7 +577,7 @@ mod tests {
         fs::create_dir(&dir_path).unwrap();
         fs::write(&file_path, "").unwrap();
 
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
         let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
         workspace
             .update(&mut cx, |workspace, cx| {
@@ -624,7 +624,7 @@ mod tests {
             "dir2": { "a.txt": "" }
         }));
 
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
         let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
 
         workspace

zed/src/lib.rs 🔗

@@ -19,7 +19,8 @@ mod util;
 pub mod workspace;
 pub mod worktree;
 
-use gpui::action;
+use channel::ChannelList;
+use gpui::{action, ModelHandle};
 pub use settings::Settings;
 
 use parking_lot::Mutex;
@@ -36,6 +37,7 @@ pub struct AppState {
     pub themes: Arc<settings::ThemeRegistry>,
     pub rpc: Arc<rpc::Client>,
     pub fs: Arc<dyn fs::Fs>,
+    pub channel_list: ModelHandle<ChannelList>,
 }
 
 pub fn init(cx: &mut gpui::MutableAppContext) {

zed/src/main.rs 🔗

@@ -7,7 +7,9 @@ use parking_lot::Mutex;
 use simplelog::SimpleLogger;
 use std::{fs, path::PathBuf, sync::Arc};
 use zed::{
-    self, assets, editor, file_finder,
+    self, assets,
+    channel::ChannelList,
+    editor, file_finder,
     fs::RealFs,
     language, menus, rpc, settings, theme_selector,
     workspace::{self, OpenParams, OpenPaths},
@@ -25,17 +27,17 @@ fn main() {
     let languages = Arc::new(language::LanguageRegistry::new());
     languages.set_theme(&settings.borrow().theme);
 
-    let app_state = AppState {
-        languages: languages.clone(),
-        settings_tx: Arc::new(Mutex::new(settings_tx)),
-        settings,
-        themes,
-        rpc: rpc::Client::new(),
-        fs: Arc::new(RealFs),
-    };
-
     app.run(move |cx| {
-        let app_state = Arc::new(app_state);
+        let rpc = rpc::Client::new();
+        let app_state = Arc::new(AppState {
+            languages: languages.clone(),
+            settings_tx: Arc::new(Mutex::new(settings_tx)),
+            settings,
+            themes,
+            channel_list: cx.add_model(|cx| ChannelList::new(rpc.clone(), cx)),
+            rpc,
+            fs: Arc::new(RealFs),
+        });
 
         zed::init(cx);
         workspace::init(cx);

zed/src/test.rs 🔗

@@ -1,13 +1,15 @@
 use crate::{
+    channel::ChannelList,
     fs::RealFs,
     language::LanguageRegistry,
     rpc,
     settings::{self, ThemeRegistry},
     time::ReplicaId,
-    AppState,
+    AppState, Settings,
 };
-use gpui::{AppContext, Entity, ModelHandle};
+use gpui::{AppContext, Entity, ModelHandle, MutableAppContext};
 use parking_lot::Mutex;
+use postage::watch;
 use smol::channel;
 use std::{
     marker::PhantomData,
@@ -153,16 +155,22 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
     }
 }
 
-pub fn build_app_state(cx: &AppContext) -> Arc<AppState> {
+pub fn build_settings(cx: &AppContext) -> watch::Receiver<Settings> {
+    settings::channel(&cx.font_cache()).unwrap().1
+}
+
+pub fn build_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
     let (settings_tx, settings) = settings::channel(&cx.font_cache()).unwrap();
     let languages = Arc::new(LanguageRegistry::new());
     let themes = ThemeRegistry::new(());
+    let rpc = rpc::Client::new();
     Arc::new(AppState {
         settings_tx: Arc::new(Mutex::new(settings_tx)),
         settings,
         themes,
         languages: languages.clone(),
-        rpc: rpc::Client::new(),
+        channel_list: cx.add_model(|cx| ChannelList::new(rpc.clone(), cx)),
+        rpc,
         fs: Arc::new(RealFs),
     })
 }

zed/src/workspace.rs 🔗

@@ -372,8 +372,14 @@ impl Workspace {
         let mut right_sidebar = Sidebar::new(Side::Right);
         right_sidebar.add_item(
             "icons/comment-16.svg",
-            cx.add_view(|_| ChatPanel::new(app_state.settings.clone()))
-                .into(),
+            cx.add_view(|cx| {
+                ChatPanel::new(
+                    app_state.channel_list.clone(),
+                    app_state.settings.clone(),
+                    cx,
+                )
+            })
+            .into(),
         );
         right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into());
 
@@ -1018,7 +1024,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_open_paths_action(mut cx: gpui::TestAppContext) {
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
         let dir = temp_tree(json!({
             "a": {
                 "aa": null,
@@ -1091,7 +1097,7 @@ mod tests {
             },
         }));
 
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
 
         let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
         workspace
@@ -1195,7 +1201,7 @@ mod tests {
         fs.insert_file("/dir1/a.txt", "".into()).await.unwrap();
         fs.insert_file("/dir2/b.txt", "".into()).await.unwrap();
 
-        let mut app_state = cx.read(build_app_state);
+        let mut app_state = cx.update(build_app_state);
         Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(fs);
 
         let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
@@ -1264,7 +1270,7 @@ mod tests {
             "a.txt": "",
         }));
 
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
         let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
         workspace
             .update(&mut cx, |workspace, cx| {
@@ -1309,7 +1315,7 @@ mod tests {
     #[gpui::test]
     async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) {
         let dir = TempDir::new("test-new-file").unwrap();
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
         let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
         workspace
             .update(&mut cx, |workspace, cx| {
@@ -1408,7 +1414,7 @@ mod tests {
     async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) {
         cx.update(init);
 
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
         cx.dispatch_global_action(OpenNew(app_state));
         let window_id = *cx.window_ids().first().unwrap();
         let workspace = cx.root_view::<Workspace>(window_id).unwrap();
@@ -1454,7 +1460,7 @@ mod tests {
             },
         }));
 
-        let app_state = cx.read(build_app_state);
+        let app_state = cx.update(build_app_state);
         let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
         workspace
             .update(&mut cx, |workspace, cx| {

zed/src/worktree.rs 🔗

@@ -2640,13 +2640,12 @@ mod tests {
 
     #[gpui::test]
     async fn test_save_file(mut cx: gpui::TestAppContext) {
-        let app_state = cx.read(build_app_state);
         let dir = temp_tree(json!({
             "file1": "the old contents",
         }));
         let tree = Worktree::open_local(
             dir.path(),
-            app_state.languages.clone(),
+            Arc::new(LanguageRegistry::new()),
             Arc::new(RealFs),
             &mut cx.to_async(),
         )
@@ -2668,7 +2667,6 @@ mod tests {
 
     #[gpui::test]
     async fn test_save_in_single_file_worktree(mut cx: gpui::TestAppContext) {
-        let app_state = cx.read(build_app_state);
         let dir = temp_tree(json!({
             "file1": "the old contents",
         }));
@@ -2676,7 +2674,7 @@ mod tests {
 
         let tree = Worktree::open_local(
             file_path.clone(),
-            app_state.languages.clone(),
+            Arc::new(LanguageRegistry::new()),
             Arc::new(RealFs),
             &mut cx.to_async(),
         )