Mainline GPUI2 UI work (#3079)

Marshall Bowers , Nate Butler , Piotr Osiewicz , and Nate created

This PR mainlines the current state of new GPUI2-based UI from the
`gpui2-ui` branch.

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Nate <nate@zed.dev>

Change summary

Cargo.lock                                               |   4 
crates/fs/Cargo.toml                                     |   6 
crates/fs/src/fs.rs                                      |  27 
crates/gpui/Cargo.toml                                   |   2 
crates/project/src/project.rs                            |  27 
crates/storybook/Cargo.toml                              |   2 
crates/storybook/src/stories/components/breadcrumb.rs    |  33 
crates/storybook/src/stories/components/buffer.rs        |   8 
crates/storybook/src/stories/components/chat_panel.rs    |  48 
crates/storybook/src/stories/components/facepile.rs      |   2 
crates/storybook/src/stories/components/panel.rs         |   7 
crates/storybook/src/stories/components/project_panel.rs |   8 
crates/storybook/src/stories/components/tab_bar.rs       |  34 
crates/storybook/src/stories/components/toolbar.rs       |  60 +
crates/storybook/src/stories/elements/avatar.rs          |   2 
crates/storybook/src/stories/elements/icon.rs            |   2 
crates/storybook/src/stories/kitchen_sink.rs             |   3 
crates/storybook/src/storybook.rs                        |  39 
crates/ui/Cargo.toml                                     |   1 
crates/ui/src/components.rs                              |   4 
crates/ui/src/components/breadcrumb.rs                   |  60 +
crates/ui/src/components/buffer.rs                       |  40 
crates/ui/src/components/chat_panel.rs                   |  75 
crates/ui/src/components/editor.rs                       |  25 
crates/ui/src/components/editor_pane.rs                  |  60 +
crates/ui/src/components/panel.rs                        |  16 
crates/ui/src/components/player_stack.rs                 |   9 
crates/ui/src/components/project_panel.rs                |  87 -
crates/ui/src/components/tab.rs                          |   2 
crates/ui/src/components/tab_bar.rs                      |  57 
crates/ui/src/components/terminal.rs                     |  11 
crates/ui/src/components/title_bar.rs                    |  29 
crates/ui/src/components/toolbar.rs                      |  40 
crates/ui/src/components/workspace.rs                    | 101 +
crates/ui/src/elements/icon.rs                           |   2 
crates/ui/src/elements/input.rs                          |   1 
crates/ui/src/elements/player.rs                         |   3 
crates/ui/src/prelude.rs                                 |  19 
crates/ui/src/static_data.rs                             | 482 +++++++++
39 files changed, 1,043 insertions(+), 395 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2790,7 +2790,6 @@ dependencies = [
  "lazy_static",
  "libc",
  "log",
- "lsp",
  "parking_lot 0.11.2",
  "regex",
  "rope",
@@ -7403,6 +7402,8 @@ dependencies = [
  "anyhow",
  "chrono",
  "clap 4.4.4",
+ "fs",
+ "futures 0.3.28",
  "gpui2",
  "itertools 0.11.0",
  "log",
@@ -8638,6 +8639,7 @@ dependencies = [
  "anyhow",
  "chrono",
  "gpui2",
+ "rand 0.8.5",
  "serde",
  "settings",
  "smallvec",

crates/fs/Cargo.toml 🔗

@@ -9,8 +9,6 @@ path = "src/fs.rs"
 
 [dependencies]
 collections = { path = "../collections" }
-gpui = { path = "../gpui" }
-lsp = { path = "../lsp" }
 rope = { path = "../rope" }
 text = { path = "../text" }
 util = { path = "../util" }
@@ -34,8 +32,10 @@ log.workspace = true
 libc = "0.2"
 time.workspace = true
 
+gpui = { path = "../gpui", optional = true}
+
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
 
 [features]
-test-support = []
+test-support = ["gpui/test-support"]

crates/fs/src/fs.rs 🔗

@@ -93,33 +93,6 @@ pub struct Metadata {
     pub is_dir: bool,
 }
 
-impl From<lsp::CreateFileOptions> for CreateOptions {
-    fn from(options: lsp::CreateFileOptions) -> Self {
-        Self {
-            overwrite: options.overwrite.unwrap_or(false),
-            ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
-        }
-    }
-}
-
-impl From<lsp::RenameFileOptions> for RenameOptions {
-    fn from(options: lsp::RenameFileOptions) -> Self {
-        Self {
-            overwrite: options.overwrite.unwrap_or(false),
-            ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
-        }
-    }
-}
-
-impl From<lsp::DeleteFileOptions> for RemoveOptions {
-    fn from(options: lsp::DeleteFileOptions) -> Self {
-        Self {
-            recursive: options.recursive.unwrap_or(false),
-            ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
-        }
-    }
-}
-
 pub struct RealFs;
 
 #[async_trait::async_trait]

crates/gpui/Cargo.toml 🔗

@@ -11,7 +11,7 @@ path = "src/gpui.rs"
 doctest = false
 
 [features]
-test-support = ["backtrace", "dhat", "env_logger", "collections/test-support"]
+test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
 
 [dependencies]
 collections = { path = "../collections" }

crates/project/src/project.rs 🔗

@@ -4957,8 +4957,16 @@ impl Project {
                     if abs_path.ends_with("/") {
                         fs.create_dir(&abs_path).await?;
                     } else {
-                        fs.create_file(&abs_path, op.options.map(Into::into).unwrap_or_default())
-                            .await?;
+                        fs.create_file(
+                            &abs_path,
+                            op.options
+                                .map(|options| fs::CreateOptions {
+                                    overwrite: options.overwrite.unwrap_or(false),
+                                    ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
+                                })
+                                .unwrap_or_default(),
+                        )
+                        .await?;
                     }
                 }
 
@@ -4974,7 +4982,12 @@ impl Project {
                     fs.rename(
                         &source_abs_path,
                         &target_abs_path,
-                        op.options.map(Into::into).unwrap_or_default(),
+                        op.options
+                            .map(|options| fs::RenameOptions {
+                                overwrite: options.overwrite.unwrap_or(false),
+                                ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
+                            })
+                            .unwrap_or_default(),
                     )
                     .await?;
                 }
@@ -4984,7 +4997,13 @@ impl Project {
                         .uri
                         .to_file_path()
                         .map_err(|_| anyhow!("can't convert URI to path"))?;
-                    let options = op.options.map(Into::into).unwrap_or_default();
+                    let options = op
+                        .options
+                        .map(|options| fs::RemoveOptions {
+                            recursive: options.recursive.unwrap_or(false),
+                            ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
+                        })
+                        .unwrap_or_default();
                     if abs_path.ends_with("/") {
                         fs.remove_dir(&abs_path, options).await?;
                     } else {

crates/storybook/Cargo.toml 🔗

@@ -12,6 +12,8 @@ path = "src/storybook.rs"
 anyhow.workspace = true
 clap = { version = "4.4", features = ["derive", "string"] }
 chrono = "0.4"
+fs = { path = "../fs" }
+futures.workspace = true
 gpui2 = { path = "../gpui2" }
 itertools = "0.11.0"
 log.workspace = true

crates/storybook/src/stories/components/breadcrumb.rs 🔗

@@ -1,5 +1,8 @@
+use std::path::PathBuf;
+use std::str::FromStr;
+
 use ui::prelude::*;
-use ui::Breadcrumb;
+use ui::{Breadcrumb, HighlightedText, Symbol};
 
 use crate::story::Story;
 
@@ -8,9 +11,35 @@ pub struct BreadcrumbStory {}
 
 impl BreadcrumbStory {
     fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+
         Story::container(cx)
             .child(Story::title_for::<_, Breadcrumb>(cx))
             .child(Story::label(cx, "Default"))
-            .child(Breadcrumb::new())
+            .child(Breadcrumb::new(
+                PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
+                vec![
+                    Symbol(vec![
+                        HighlightedText {
+                            text: "impl ".to_string(),
+                            color: HighlightColor::Keyword.hsla(&theme),
+                        },
+                        HighlightedText {
+                            text: "BreadcrumbStory".to_string(),
+                            color: HighlightColor::Function.hsla(&theme),
+                        },
+                    ]),
+                    Symbol(vec![
+                        HighlightedText {
+                            text: "fn ".to_string(),
+                            color: HighlightColor::Keyword.hsla(&theme),
+                        },
+                        HighlightedText {
+                            text: "render".to_string(),
+                            color: HighlightColor::Function.hsla(&theme),
+                        },
+                    ]),
+                ],
+            ))
     }
 }

crates/storybook/src/stories/components/buffer.rs 🔗

@@ -12,8 +12,10 @@ pub struct BufferStory {}
 
 impl BufferStory {
     fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+
         Story::container(cx)
-            .child(Story::title_for::<_, Buffer<V>>(cx))
+            .child(Story::title_for::<_, Buffer>(cx))
             .child(Story::label(cx, "Default"))
             .child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
             .child(Story::label(cx, "Hello World (Rust)"))
@@ -21,14 +23,14 @@ impl BufferStory {
                 div()
                     .w(rems(64.))
                     .h_96()
-                    .child(hello_world_rust_buffer_example(cx)),
+                    .child(hello_world_rust_buffer_example(&theme)),
             )
             .child(Story::label(cx, "Hello World (Rust) with Status"))
             .child(
                 div()
                     .w(rems(64.))
                     .h_96()
-                    .child(hello_world_rust_buffer_with_status_example(cx)),
+                    .child(hello_world_rust_buffer_with_status_example(&theme)),
             )
     }
 }

crates/storybook/src/stories/components/chat_panel.rs 🔗

@@ -1,6 +1,6 @@
 use chrono::DateTime;
 use ui::prelude::*;
-use ui::{ChatMessage, ChatPanel};
+use ui::{ChatMessage, ChatPanel, Panel};
 
 use crate::story::Story;
 
@@ -12,23 +12,35 @@ impl ChatPanelStory {
         Story::container(cx)
             .child(Story::title_for::<_, ChatPanel<V>>(cx))
             .child(Story::label(cx, "Default"))
-            .child(ChatPanel::new(ScrollState::default()))
+            .child(Panel::new(
+                ScrollState::default(),
+                |_, _| vec![ChatPanel::new(ScrollState::default()).into_any()],
+                Box::new(()),
+            ))
             .child(Story::label(cx, "With Mesages"))
-            .child(ChatPanel::new(ScrollState::default()).with_messages(vec![
-                    ChatMessage::new(
-                        "osiewicz".to_string(),
-                        "is this thing on?".to_string(),
-                        DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
-                            .unwrap()
-                            .naive_local(),
-                    ),
-                    ChatMessage::new(
-                        "maxdeviant".to_string(),
-                        "Reading you loud and clear!".to_string(),
-                        DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
-                            .unwrap()
-                            .naive_local(),
-                    ),
-                ]))
+            .child(Panel::new(
+                ScrollState::default(),
+                |_, _| {
+                    vec![ChatPanel::new(ScrollState::default())
+                        .with_messages(vec![
+                            ChatMessage::new(
+                                "osiewicz".to_string(),
+                                "is this thing on?".to_string(),
+                                DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
+                                    .unwrap()
+                                    .naive_local(),
+                            ),
+                            ChatMessage::new(
+                                "maxdeviant".to_string(),
+                                "Reading you loud and clear!".to_string(),
+                                DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
+                                    .unwrap()
+                                    .naive_local(),
+                            ),
+                        ])
+                        .into_any()]
+                },
+                Box::new(()),
+            ))
     }
 }

crates/storybook/src/stories/components/facepile.rs 🔗

@@ -11,7 +11,7 @@ impl FacepileStory {
         let players = static_players();
 
         Story::container(cx)
-            .child(Story::title_for::<_, ui::Facepile>(cx))
+            .child(Story::title_for::<_, Facepile>(cx))
             .child(Story::label(cx, "Default"))
             .child(
                 div()

crates/storybook/src/stories/components/panel.rs 🔗

@@ -14,9 +14,10 @@ impl PanelStory {
             .child(Panel::new(
                 ScrollState::default(),
                 |_, _| {
-                    (0..100)
-                        .map(|ix| Label::new(format!("Item {}", ix + 1)).into_any())
-                        .collect()
+                    vec![div()
+                        .overflow_y_scroll(ScrollState::default())
+                        .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1))))
+                        .into_any()]
                 },
                 Box::new(()),
             ))

crates/storybook/src/stories/components/project_panel.rs 🔗

@@ -1,5 +1,5 @@
 use ui::prelude::*;
-use ui::ProjectPanel;
+use ui::{Panel, ProjectPanel};
 
 use crate::story::Story;
 
@@ -11,6 +11,10 @@ impl ProjectPanelStory {
         Story::container(cx)
             .child(Story::title_for::<_, ProjectPanel<V>>(cx))
             .child(Story::label(cx, "Default"))
-            .child(ProjectPanel::new(ScrollState::default()))
+            .child(Panel::new(
+                ScrollState::default(),
+                |_, _| vec![ProjectPanel::new(ScrollState::default()).into_any()],
+                Box::new(()),
+            ))
     }
 }

crates/storybook/src/stories/components/tab_bar.rs 🔗

@@ -1,5 +1,5 @@
 use ui::prelude::*;
-use ui::TabBar;
+use ui::{Tab, TabBar};
 
 use crate::story::Story;
 
@@ -11,6 +11,36 @@ impl TabBarStory {
         Story::container(cx)
             .child(Story::title_for::<_, TabBar<V>>(cx))
             .child(Story::label(cx, "Default"))
-            .child(TabBar::new(ScrollState::default()))
+            .child(TabBar::new(vec![
+                Tab::new()
+                    .title("Cargo.toml".to_string())
+                    .current(false)
+                    .git_status(GitStatus::Modified),
+                Tab::new()
+                    .title("Channels Panel".to_string())
+                    .current(false),
+                Tab::new()
+                    .title("channels_panel.rs".to_string())
+                    .current(true)
+                    .git_status(GitStatus::Modified),
+                Tab::new()
+                    .title("workspace.rs".to_string())
+                    .current(false)
+                    .git_status(GitStatus::Modified),
+                Tab::new()
+                    .title("icon_button.rs".to_string())
+                    .current(false),
+                Tab::new()
+                    .title("storybook.rs".to_string())
+                    .current(false)
+                    .git_status(GitStatus::Created),
+                Tab::new().title("theme.rs".to_string()).current(false),
+                Tab::new()
+                    .title("theme_registry.rs".to_string())
+                    .current(false),
+                Tab::new()
+                    .title("styleable_helpers.rs".to_string())
+                    .current(false),
+            ]))
     }
 }

crates/storybook/src/stories/components/toolbar.rs 🔗

@@ -1,5 +1,9 @@
+use std::path::PathBuf;
+use std::str::FromStr;
+use std::sync::Arc;
+
 use ui::prelude::*;
-use ui::Toolbar;
+use ui::{theme, Breadcrumb, HighlightColor, HighlightedText, Icon, IconButton, Symbol, Toolbar};
 
 use crate::story::Story;
 
@@ -8,9 +12,59 @@ pub struct ToolbarStory {}
 
 impl ToolbarStory {
     fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+
+        struct LeftItemsPayload {
+            pub theme: Arc<Theme>,
+        }
+
         Story::container(cx)
-            .child(Story::title_for::<_, Toolbar>(cx))
+            .child(Story::title_for::<_, Toolbar<V>>(cx))
             .child(Story::label(cx, "Default"))
-            .child(Toolbar::new())
+            .child(Toolbar::new(
+                |_, payload| {
+                    let payload = payload.downcast_ref::<LeftItemsPayload>().unwrap();
+
+                    let theme = payload.theme.clone();
+
+                    vec![Breadcrumb::new(
+                        PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
+                        vec![
+                            Symbol(vec![
+                                HighlightedText {
+                                    text: "impl ".to_string(),
+                                    color: HighlightColor::Keyword.hsla(&theme),
+                                },
+                                HighlightedText {
+                                    text: "ToolbarStory".to_string(),
+                                    color: HighlightColor::Function.hsla(&theme),
+                                },
+                            ]),
+                            Symbol(vec![
+                                HighlightedText {
+                                    text: "fn ".to_string(),
+                                    color: HighlightColor::Keyword.hsla(&theme),
+                                },
+                                HighlightedText {
+                                    text: "render".to_string(),
+                                    color: HighlightColor::Function.hsla(&theme),
+                                },
+                            ]),
+                        ],
+                    )
+                    .into_any()]
+                },
+                Box::new(LeftItemsPayload {
+                    theme: theme.clone(),
+                }),
+                |_, _| {
+                    vec![
+                        IconButton::new(Icon::InlayHint).into_any(),
+                        IconButton::new(Icon::MagnifyingGlass).into_any(),
+                        IconButton::new(Icon::MagicWand).into_any(),
+                    ]
+                },
+                Box::new(()),
+            ))
     }
 }

crates/storybook/src/stories/elements/avatar.rs 🔗

@@ -9,7 +9,7 @@ pub struct AvatarStory {}
 impl AvatarStory {
     fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         Story::container(cx)
-            .child(Story::title_for::<_, ui::Avatar>(cx))
+            .child(Story::title_for::<_, Avatar>(cx))
             .child(Story::label(cx, "Default"))
             .child(Avatar::new(
                 "https://avatars.githubusercontent.com/u/1714999?v=4",

crates/storybook/src/stories/elements/icon.rs 🔗

@@ -12,7 +12,7 @@ impl IconStory {
         let icons = Icon::iter();
 
         Story::container(cx)
-            .child(Story::title_for::<_, ui::IconElement>(cx))
+            .child(Story::title_for::<_, IconElement>(cx))
             .child(Story::label(cx, "All Icons"))
             .child(div().flex().gap_3().children(icons.map(IconElement::new)))
     }

crates/storybook/src/stories/kitchen_sink.rs 🔗

@@ -19,5 +19,8 @@ impl KitchenSinkStory {
             .child(div().flex().flex_col().children_any(element_stories))
             .child(Story::label(cx, "Components"))
             .child(div().flex().flex_col().children_any(component_stories))
+            // Add a bit of space at the bottom of the kitchen sink so elements
+            // don't end up squished right up against the bottom of the screen.
+            .child(div().p_4())
     }
 }

crates/storybook/src/storybook.rs 🔗

@@ -4,7 +4,7 @@ mod stories;
 mod story;
 mod story_selector;
 
-use std::sync::Arc;
+use std::{process::Command, sync::Arc};
 
 use ::theme as legacy_theme;
 use clap::Parser;
@@ -38,11 +38,44 @@ struct Args {
     theme: Option<String>,
 }
 
+async fn watch_zed_changes(fs: Arc<dyn fs::Fs>) -> Option<()> {
+    if std::env::var("ZED_HOT_RELOAD").is_err() {
+        return None;
+    }
+    use futures::StreamExt;
+    let mut events = fs
+        .watch(".".as_ref(), std::time::Duration::from_millis(100))
+        .await;
+    let mut current_child: Option<std::process::Child> = None;
+    while let Some(events) = events.next().await {
+        if !events.iter().any(|event| {
+            event
+                .path
+                .to_str()
+                .map(|path| path.contains("/crates/"))
+                .unwrap_or_default()
+        }) {
+            continue;
+        }
+        let child = current_child.take().map(|mut child| child.kill());
+        log::info!("Storybook changed, rebuilding...");
+        current_child = Some(
+            Command::new("cargo")
+                .args(["run", "-p", "storybook"])
+                .spawn()
+                .ok()?,
+        );
+    }
+    Some(())
+}
+
 fn main() {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
     let args = Args::parse();
 
+    let fs = Arc::new(fs::RealFs);
+
     gpui2::App::new(Assets).unwrap().run(move |cx| {
         let mut store = SettingsStore::default();
         store
@@ -63,6 +96,10 @@ fn main() {
             })
             .and_then(|theme_name| theme_registry.get(&theme_name).ok());
 
+        cx.spawn(|_| async move {
+            watch_zed_changes(fs).await;
+        })
+        .detach();
         cx.add_window(
             gpui2::WindowOptions {
                 bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1700., 980.))),

crates/ui/Cargo.toml 🔗

@@ -13,3 +13,4 @@ settings = { path = "../settings" }
 smallvec.workspace = true
 strum = { version = "0.25.0", features = ["derive"] }
 theme = { path = "../theme" }
+rand = "0.8"

crates/ui/src/components.rs 🔗

@@ -5,7 +5,7 @@ mod chat_panel;
 mod collab_panel;
 mod command_palette;
 mod context_menu;
-mod editor;
+mod editor_pane;
 mod facepile;
 mod icon_button;
 mod keybinding;
@@ -31,7 +31,7 @@ pub use chat_panel::*;
 pub use collab_panel::*;
 pub use command_palette::*;
 pub use context_menu::*;
-pub use editor::*;
+pub use editor_pane::*;
 pub use facepile::*;
 pub use icon_button::*;
 pub use keybinding::*;

crates/ui/src/components/breadcrumb.rs 🔗

@@ -1,17 +1,35 @@
-use crate::prelude::*;
+use std::path::PathBuf;
+
+use gpui2::elements::div::Div;
+
 use crate::{h_stack, theme};
+use crate::{prelude::*, HighlightedText};
+
+#[derive(Clone)]
+pub struct Symbol(pub Vec<HighlightedText>);
 
 #[derive(Element)]
-pub struct Breadcrumb {}
+pub struct Breadcrumb {
+    path: PathBuf,
+    symbols: Vec<Symbol>,
+}
 
 impl Breadcrumb {
-    pub fn new() -> Self {
-        Self {}
+    pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
+        Self { path, symbols }
+    }
+
+    fn render_separator<V: 'static>(&self, theme: &Theme) -> Div<V> {
+        div()
+            .child(" › ")
+            .text_color(HighlightColor::Default.hsla(theme))
     }
 
     fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         let theme = theme(cx);
 
+        let symbols_len = self.symbols.len();
+
         h_stack()
             .px_1()
             // TODO: Read font from theme (or settings?).
@@ -21,11 +39,33 @@ impl Breadcrumb {
             .rounded_md()
             .hover()
             .fill(theme.highest.base.hovered.background)
-            // TODO: Replace hardcoded breadcrumbs.
-            .child("crates/ui/src/components/toolbar.rs")
-            .child(" › ")
-            .child("impl Breadcrumb")
-            .child(" › ")
-            .child("fn render")
+            .child(self.path.clone().to_str().unwrap().to_string())
+            .child(if !self.symbols.is_empty() {
+                self.render_separator(&theme)
+            } else {
+                div()
+            })
+            .child(
+                div().flex().children(
+                    self.symbols
+                        .iter()
+                        .enumerate()
+                        // TODO: Could use something like `intersperse` here instead.
+                        .flat_map(|(ix, symbol)| {
+                            let mut items =
+                                vec![div().flex().children(symbol.0.iter().map(|segment| {
+                                    div().child(segment.text.clone()).text_color(segment.color)
+                                }))];
+
+                            let is_last_segment = ix == symbols_len - 1;
+                            if !is_last_segment {
+                                items.push(self.render_separator(&theme));
+                            }
+
+                            items
+                        })
+                        .collect::<Vec<_>>(),
+                ),
+            )
     }
 }

crates/ui/src/components/buffer.rs 🔗

@@ -1,5 +1,3 @@
-use std::marker::PhantomData;
-
 use gpui2::{Hsla, WindowContext};
 
 use crate::prelude::*;
@@ -33,6 +31,7 @@ pub struct BufferRow {
     pub show_line_number: bool,
 }
 
+#[derive(Clone)]
 pub struct BufferRows {
     pub show_line_numbers: bool,
     pub rows: Vec<BufferRow>,
@@ -108,9 +107,8 @@ impl BufferRow {
     }
 }
 
-#[derive(Element)]
-pub struct Buffer<V: 'static> {
-    view_type: PhantomData<V>,
+#[derive(Element, Clone)]
+pub struct Buffer {
     scroll_state: ScrollState,
     rows: Option<BufferRows>,
     readonly: bool,
@@ -119,10 +117,9 @@ pub struct Buffer<V: 'static> {
     path: Option<String>,
 }
 
-impl<V: 'static> Buffer<V> {
+impl Buffer {
     pub fn new() -> Self {
         Self {
-            view_type: PhantomData,
             scroll_state: ScrollState::default(),
             rows: Some(BufferRows::default()),
             readonly: false,
@@ -161,7 +158,7 @@ impl<V: 'static> Buffer<V> {
         self
     }
 
-    fn render_row(row: BufferRow, cx: &WindowContext) -> impl IntoElement<V> {
+    fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl IntoElement<V> {
         let theme = theme(cx);
         let system_color = SystemColor::new();
 
@@ -172,28 +169,35 @@ impl<V: 'static> Buffer<V> {
         };
 
         let line_number_color = if row.current {
-            HighlightColor::Default.hsla(cx)
+            HighlightColor::Default.hsla(&theme)
         } else {
-            HighlightColor::Comment.hsla(cx)
+            HighlightColor::Comment.hsla(&theme)
         };
 
         h_stack()
             .fill(line_background)
+            .w_full()
             .gap_2()
-            .px_2()
-            .child(h_stack().w_4().h_full().px_1().when(row.code_action, |c| {
-                div().child(IconElement::new(Icon::Bolt))
-            }))
+            .px_1()
+            .child(
+                h_stack()
+                    .w_4()
+                    .h_full()
+                    .px_0p5()
+                    .when(row.code_action, |c| {
+                        div().child(IconElement::new(Icon::Bolt))
+                    }),
+            )
             .when(row.show_line_number, |this| {
                 this.child(
-                    h_stack().justify_end().px_1().w_4().child(
+                    h_stack().justify_end().px_0p5().w_3().child(
                         div()
                             .text_color(line_number_color)
                             .child(row.line_number.to_string()),
                     ),
                 )
             })
-            .child(div().mx_1().w_1().h_full().fill(row.status.hsla(cx)))
+            .child(div().mx_0p5().w_1().h_full().fill(row.status.hsla(cx)))
             .children(row.line.map(|line| {
                 div()
                     .flex()
@@ -205,7 +209,7 @@ impl<V: 'static> Buffer<V> {
             }))
     }
 
-    fn render_rows(&self, cx: &WindowContext) -> Vec<impl IntoElement<V>> {
+    fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl IntoElement<V>> {
         match &self.rows {
             Some(rows) => rows
                 .rows
@@ -216,7 +220,7 @@ impl<V: 'static> Buffer<V> {
         }
     }
 
-    fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+    fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         let theme = theme(cx);
         let rows = self.render_rows(cx);
         v_stack()

crates/ui/src/components/chat_panel.rs 🔗

@@ -4,13 +4,12 @@ use chrono::NaiveDateTime;
 
 use crate::prelude::*;
 use crate::theme::theme;
-use crate::{Icon, IconButton, Input, Label, LabelColor, Panel, PanelSide};
+use crate::{Icon, IconButton, Input, Label, LabelColor};
 
 #[derive(Element)]
 pub struct ChatPanel<V: 'static> {
     view_type: PhantomData<V>,
     scroll_state: ScrollState,
-    current_side: PanelSide,
     messages: Vec<ChatMessage>,
 }
 
@@ -19,16 +18,10 @@ impl<V: 'static> ChatPanel<V> {
         Self {
             view_type: PhantomData,
             scroll_state,
-            current_side: PanelSide::default(),
             messages: Vec::new(),
         }
     }
 
-    pub fn side(mut self, side: PanelSide) -> Self {
-        self.current_side = side;
-        self
-    }
-
     pub fn with_messages(mut self, messages: Vec<ChatMessage>) -> Self {
         self.messages = messages;
         self
@@ -37,38 +30,33 @@ impl<V: 'static> ChatPanel<V> {
     fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         let theme = theme(cx);
 
-        struct PanelPayload {
-            pub scroll_state: ScrollState,
-            pub messages: Vec<ChatMessage>,
-        }
-
-        Panel::new(
-            self.scroll_state.clone(),
-            |_, payload| {
-                let payload = payload.downcast_ref::<PanelPayload>().unwrap();
-
-                vec![div()
+        div()
+            .flex()
+            .flex_col()
+            .justify_between()
+            .h_full()
+            .px_2()
+            .gap_2()
+            // Header
+            .child(
+                div()
                     .flex()
-                    .flex_col()
-                    .h_full()
-                    .px_2()
-                    .gap_2()
-                    // Header
+                    .justify_between()
+                    .py_2()
+                    .child(div().flex().child(Label::new("#design")))
                     .child(
                         div()
                             .flex()
-                            .justify_between()
-                            .gap_2()
-                            .child(div().flex().child(Label::new("#design")))
-                            .child(
-                                div()
-                                    .flex()
-                                    .items_center()
-                                    .gap_px()
-                                    .child(IconButton::new(Icon::File))
-                                    .child(IconButton::new(Icon::AudioOn)),
-                            ),
-                    )
+                            .items_center()
+                            .gap_px()
+                            .child(IconButton::new(Icon::File))
+                            .child(IconButton::new(Icon::AudioOn)),
+                    ),
+            )
+            .child(
+                div()
+                    .flex()
+                    .flex_col()
                     // Chat Body
                     .child(
                         div()
@@ -76,19 +64,12 @@ impl<V: 'static> ChatPanel<V> {
                             .flex()
                             .flex_col()
                             .gap_3()
-                            .overflow_y_scroll(payload.scroll_state.clone())
-                            .children(payload.messages.clone()),
+                            .overflow_y_scroll(self.scroll_state.clone())
+                            .children(self.messages.clone()),
                     )
                     // Composer
-                    .child(div().flex().gap_2().child(Input::new("Message #design")))
-                    .into_any()]
-            },
-            Box::new(PanelPayload {
-                scroll_state: self.scroll_state.clone(),
-                messages: self.messages.clone(),
-            }),
-        )
-        .side(self.current_side)
+                    .child(div().flex().my_2().child(Input::new("Message #design"))),
+            )
     }
 }
 

crates/ui/src/components/editor.rs 🔗

@@ -1,25 +0,0 @@
-use std::marker::PhantomData;
-
-use crate::prelude::*;
-use crate::{Buffer, Toolbar};
-
-#[derive(Element)]
-struct Editor<V: 'static> {
-    view_type: PhantomData<V>,
-    toolbar: Toolbar,
-    buffer: Buffer<V>,
-}
-
-impl<V: 'static> Editor<V> {
-    pub fn new(toolbar: Toolbar, buffer: Buffer<V>) -> Self {
-        Self {
-            view_type: PhantomData,
-            toolbar,
-            buffer,
-        }
-    }
-
-    fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
-        div().child(self.toolbar.clone())
-    }
-}

crates/ui/src/components/editor_pane.rs 🔗

@@ -0,0 +1,60 @@
+use std::marker::PhantomData;
+use std::path::PathBuf;
+
+use crate::prelude::*;
+use crate::{v_stack, Breadcrumb, Buffer, Icon, IconButton, Symbol, Tab, TabBar, Toolbar};
+
+pub struct Editor {
+    pub tabs: Vec<Tab>,
+    pub path: PathBuf,
+    pub symbols: Vec<Symbol>,
+    pub buffer: Buffer,
+}
+
+#[derive(Element)]
+pub struct EditorPane<V: 'static> {
+    view_type: PhantomData<V>,
+    editor: Editor,
+}
+
+impl<V: 'static> EditorPane<V> {
+    pub fn new(editor: Editor) -> Self {
+        Self {
+            view_type: PhantomData,
+            editor,
+        }
+    }
+
+    fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        struct LeftItemsPayload {
+            path: PathBuf,
+            symbols: Vec<Symbol>,
+        }
+
+        v_stack()
+            .w_full()
+            .h_full()
+            .flex_1()
+            .child(TabBar::new(self.editor.tabs.clone()))
+            .child(Toolbar::new(
+                |_, payload| {
+                    let payload = payload.downcast_ref::<LeftItemsPayload>().unwrap();
+
+                    vec![Breadcrumb::new(payload.path.clone(), payload.symbols.clone()).into_any()]
+                },
+                Box::new(LeftItemsPayload {
+                    path: self.editor.path.clone(),
+                    symbols: self.editor.symbols.clone(),
+                }),
+                |_, _| {
+                    vec![
+                        IconButton::new(Icon::InlayHint).into_any(),
+                        IconButton::new(Icon::MagnifyingGlass).into_any(),
+                        IconButton::new(Icon::MagicWand).into_any(),
+                    ]
+                },
+                Box::new(()),
+            ))
+            .child(self.editor.buffer.clone())
+    }
+}

crates/ui/src/components/panel.rs 🔗

@@ -105,16 +105,12 @@ impl<V: 'static> Panel<V> {
         let theme = theme(cx);
 
         let panel_base;
-        let current_width = if let Some(width) = self.width {
-            width
-        } else {
-            self.initial_width
-        };
+        let current_width = self.width.unwrap_or(self.initial_width);
 
         match self.current_side {
             PanelSide::Left => {
                 panel_base = v_stack()
-                    .overflow_y_scroll(self.scroll_state.clone())
+                    .flex_initial()
                     .h_full()
                     .w(current_width)
                     .fill(theme.middle.base.default.background)
@@ -123,20 +119,20 @@ impl<V: 'static> Panel<V> {
             }
             PanelSide::Right => {
                 panel_base = v_stack()
-                    .overflow_y_scroll(self.scroll_state.clone())
+                    .flex_initial()
                     .h_full()
                     .w(current_width)
                     .fill(theme.middle.base.default.background)
-                    .border_r()
+                    .border_l()
                     .border_color(theme.middle.base.default.border);
             }
             PanelSide::Bottom => {
                 panel_base = v_stack()
-                    .overflow_y_scroll(self.scroll_state.clone())
+                    .flex_initial()
                     .w_full()
                     .h(current_width)
                     .fill(theme.middle.base.default.background)
-                    .border_r()
+                    .border_t()
                     .border_color(theme.middle.base.default.border);
             }
         }

crates/ui/src/components/player_stack.rs 🔗

@@ -38,9 +38,8 @@ impl PlayerStack {
                 div().flex().justify_center().w_full().child(
                     div()
                         .w_4()
-                        .h_1()
-                        .rounded_bl_sm()
-                        .rounded_br_sm()
+                        .h_0p5()
+                        .rounded_sm()
                         .fill(player.cursor_color(cx)),
                 ),
             )
@@ -50,7 +49,7 @@ impl PlayerStack {
                     .items_center()
                     .justify_center()
                     .h_6()
-                    .px_1()
+                    .pl_1()
                     .rounded_lg()
                     .fill(if followers.is_none() {
                         system_color.transparent
@@ -59,7 +58,7 @@ impl PlayerStack {
                     })
                     .child(Avatar::new(player.avatar_src().to_string()))
                     .children(followers.map(|followers| {
-                        div().neg_mr_1().child(Facepile::new(followers.into_iter()))
+                        div().neg_ml_2().child(Facepile::new(followers.into_iter()))
                     })),
             )
     }

crates/ui/src/components/project_panel.rs 🔗

@@ -1,17 +1,15 @@
 use std::marker::PhantomData;
-use std::sync::Arc;
 
 use crate::prelude::*;
 use crate::{
     static_project_panel_project_items, static_project_panel_single_items, theme, Input, List,
-    ListHeader, Panel, PanelSide, Theme,
+    ListHeader,
 };
 
 #[derive(Element)]
 pub struct ProjectPanel<V: 'static> {
     view_type: PhantomData<V>,
     scroll_state: ScrollState,
-    current_side: PanelSide,
 }
 
 impl<V: 'static> ProjectPanel<V> {
@@ -19,69 +17,42 @@ impl<V: 'static> ProjectPanel<V> {
         Self {
             view_type: PhantomData,
             scroll_state,
-            current_side: PanelSide::default(),
         }
     }
 
-    pub fn side(mut self, side: PanelSide) -> Self {
-        self.current_side = side;
-        self
-    }
-
     fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
-        struct PanelPayload {
-            pub theme: Arc<Theme>,
-            pub scroll_state: ScrollState,
-        }
-
-        Panel::new(
-            self.scroll_state.clone(),
-            |_, payload| {
-                let payload = payload.downcast_ref::<PanelPayload>().unwrap();
-
-                let theme = payload.theme.clone();
-
-                vec![div()
+        let theme = theme(cx);
+
+        div()
+            .flex()
+            .flex_col()
+            .w_full()
+            .h_full()
+            .px_2()
+            .fill(theme.middle.base.default.background)
+            .child(
+                div()
+                    .w_56()
                     .flex()
                     .flex_col()
-                    .w_56()
-                    .h_full()
-                    .px_2()
-                    .fill(theme.middle.base.default.background)
+                    .overflow_y_scroll(ScrollState::default())
                     .child(
-                        div()
-                            .w_56()
-                            .flex()
-                            .flex_col()
-                            .overflow_y_scroll(payload.scroll_state.clone())
-                            .child(
-                                List::new(static_project_panel_single_items())
-                                    .header(
-                                        ListHeader::new("FILES").set_toggle(ToggleState::Toggled),
-                                    )
-                                    .empty_message("No files in directory")
-                                    .set_toggle(ToggleState::Toggled),
-                            )
-                            .child(
-                                List::new(static_project_panel_project_items())
-                                    .header(
-                                        ListHeader::new("PROJECT").set_toggle(ToggleState::Toggled),
-                                    )
-                                    .empty_message("No folders in directory")
-                                    .set_toggle(ToggleState::Toggled),
-                            ),
+                        List::new(static_project_panel_single_items())
+                            .header(ListHeader::new("FILES").set_toggle(ToggleState::Toggled))
+                            .empty_message("No files in directory")
+                            .set_toggle(ToggleState::Toggled),
                     )
                     .child(
-                        Input::new("Find something...")
-                            .value("buffe".to_string())
-                            .state(InteractionState::Focused),
-                    )
-                    .into_any()]
-            },
-            Box::new(PanelPayload {
-                theme: theme(cx),
-                scroll_state: self.scroll_state.clone(),
-            }),
-        )
+                        List::new(static_project_panel_project_items())
+                            .header(ListHeader::new("PROJECT").set_toggle(ToggleState::Toggled))
+                            .empty_message("No folders in directory")
+                            .set_toggle(ToggleState::Toggled),
+                    ),
+            )
+            .child(
+                Input::new("Find something...")
+                    .value("buffe".to_string())
+                    .state(InteractionState::Focused),
+            )
     }
 }

crates/ui/src/components/tab.rs 🔗

@@ -1,7 +1,7 @@
 use crate::prelude::*;
 use crate::{theme, Icon, IconColor, IconElement, Label, LabelColor};
 
-#[derive(Element)]
+#[derive(Element, Clone)]
 pub struct Tab {
     title: String,
     icon: Option<Icon>,

crates/ui/src/components/tab_bar.rs 🔗

@@ -7,20 +7,27 @@ use crate::{theme, Icon, IconButton, Tab};
 pub struct TabBar<V: 'static> {
     view_type: PhantomData<V>,
     scroll_state: ScrollState,
+    tabs: Vec<Tab>,
 }
 
 impl<V: 'static> TabBar<V> {
-    pub fn new(scroll_state: ScrollState) -> Self {
+    pub fn new(tabs: Vec<Tab>) -> Self {
         Self {
             view_type: PhantomData,
-            scroll_state,
+            scroll_state: ScrollState::default(),
+            tabs,
         }
     }
 
+    pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) {
+        self.scroll_state = scroll_state;
+    }
+
     fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         let theme = theme(cx);
         let can_navigate_back = true;
         let can_navigate_forward = false;
+
         div()
             .w_full()
             .flex()
@@ -54,51 +61,7 @@ impl<V: 'static> TabBar<V> {
                     div()
                         .flex()
                         .overflow_x_scroll(self.scroll_state.clone())
-                        .child(
-                            Tab::new()
-                                .title("Cargo.toml".to_string())
-                                .current(false)
-                                .git_status(GitStatus::Modified),
-                        )
-                        .child(
-                            Tab::new()
-                                .title("Channels Panel".to_string())
-                                .current(false),
-                        )
-                        .child(
-                            Tab::new()
-                                .title("channels_panel.rs".to_string())
-                                .current(true)
-                                .git_status(GitStatus::Modified),
-                        )
-                        .child(
-                            Tab::new()
-                                .title("workspace.rs".to_string())
-                                .current(false)
-                                .git_status(GitStatus::Modified),
-                        )
-                        .child(
-                            Tab::new()
-                                .title("icon_button.rs".to_string())
-                                .current(false),
-                        )
-                        .child(
-                            Tab::new()
-                                .title("storybook.rs".to_string())
-                                .current(false)
-                                .git_status(GitStatus::Created),
-                        )
-                        .child(Tab::new().title("theme.rs".to_string()).current(false))
-                        .child(
-                            Tab::new()
-                                .title("theme_registry.rs".to_string())
-                                .current(false),
-                        )
-                        .child(
-                            Tab::new()
-                                .title("styleable_helpers.rs".to_string())
-                                .current(false),
-                        ),
+                        .children(self.tabs.clone()),
                 ),
             )
             // Right Side

crates/ui/src/components/terminal.rs 🔗

@@ -1,3 +1,5 @@
+use std::sync::Arc;
+
 use gpui2::geometry::{relative, rems, Size};
 
 use crate::prelude::*;
@@ -20,6 +22,7 @@ impl Terminal {
         div()
             .flex()
             .flex_col()
+            .w_full()
             .child(
                 // Terminal Tabs.
                 div()
@@ -70,8 +73,12 @@ impl Terminal {
                     width: relative(1.).into(),
                     height: rems(36.).into(),
                 },
-                |_, _| vec![],
-                Box::new(()),
+                |_, payload| {
+                    let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
+
+                    vec![crate::static_data::terminal_buffer(&theme).into_any()]
+                },
+                Box::new(theme),
             ))
     }
 }

crates/ui/src/components/title_bar.rs 🔗

@@ -2,16 +2,24 @@ use std::marker::PhantomData;
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 
-use crate::prelude::*;
+use crate::{prelude::*, PlayerWithCallStatus};
 use crate::{
-    static_players_with_call_status, theme, Avatar, Button, Icon, IconButton, IconColor,
-    PlayerStack, ToolDivider, TrafficLights,
+    theme, Avatar, Button, Icon, IconButton, IconColor, PlayerStack, ToolDivider, TrafficLights,
 };
 
+#[derive(Clone)]
+pub struct Livestream {
+    pub players: Vec<PlayerWithCallStatus>,
+    pub channel: Option<String>, // projects
+                                 // windows
+}
+
 #[derive(Element)]
 pub struct TitleBar<V: 'static> {
     view_type: PhantomData<V>,
+    /// If the window is active from the OS's perspective.
     is_active: Arc<AtomicBool>,
+    livestream: Option<Livestream>,
 }
 
 impl<V: 'static> TitleBar<V> {
@@ -28,14 +36,24 @@ impl<V: 'static> TitleBar<V> {
         Self {
             view_type: PhantomData,
             is_active,
+            livestream: None,
         }
     }
 
+    pub fn set_livestream(mut self, livestream: Option<Livestream>) -> Self {
+        self.livestream = livestream;
+        self
+    }
+
     fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         let theme = theme(cx);
         let has_focus = cx.window_is_active();
 
-        let player_list = static_players_with_call_status().into_iter();
+        let player_list = if let Some(livestream) = &self.livestream {
+            livestream.players.clone().into_iter()
+        } else {
+            vec![].into_iter()
+        };
 
         div()
             .flex()
@@ -61,7 +79,8 @@ impl<V: 'static> TitleBar<V> {
                             .child(Button::new("zed"))
                             .child(Button::new("nate/gpui2-ui-components")),
                     )
-                    .children(player_list.map(|p| PlayerStack::new(p))),
+                    .children(player_list.map(|p| PlayerStack::new(p)))
+                    .child(IconButton::new(Icon::Plus)),
             )
             .child(
                 div()

crates/ui/src/components/toolbar.rs 🔗

@@ -1,33 +1,49 @@
 use crate::prelude::*;
-use crate::{theme, Breadcrumb, Icon, IconButton};
+use crate::theme;
 
 #[derive(Clone)]
 pub struct ToolbarItem {}
 
-#[derive(Element, Clone)]
-pub struct Toolbar {
-    items: Vec<ToolbarItem>,
+#[derive(Element)]
+pub struct Toolbar<V: 'static> {
+    left_items: HackyChildren<V>,
+    left_items_payload: HackyChildrenPayload,
+    right_items: HackyChildren<V>,
+    right_items_payload: HackyChildrenPayload,
 }
 
-impl Toolbar {
-    pub fn new() -> Self {
-        Self { items: Vec::new() }
+impl<V: 'static> Toolbar<V> {
+    pub fn new(
+        left_items: HackyChildren<V>,
+        left_items_payload: HackyChildrenPayload,
+        right_items: HackyChildren<V>,
+        right_items_payload: HackyChildrenPayload,
+    ) -> Self {
+        Self {
+            left_items,
+            left_items_payload,
+            right_items,
+            right_items_payload,
+        }
     }
 
-    fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+    fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         let theme = theme(cx);
 
         div()
+            .fill(theme.highest.base.default.background)
             .p_2()
             .flex()
             .justify_between()
-            .child(Breadcrumb::new())
             .child(
                 div()
                     .flex()
-                    .child(IconButton::new(Icon::InlayHint))
-                    .child(IconButton::new(Icon::MagnifyingGlass))
-                    .child(IconButton::new(Icon::MagicWand)),
+                    .children_any((self.left_items)(cx, self.left_items_payload.as_ref())),
+            )
+            .child(
+                div()
+                    .flex()
+                    .children_any((self.right_items)(cx, self.right_items_payload.as_ref())),
             )
     }
 }

crates/ui/src/components/workspace.rs 🔗

@@ -1,10 +1,15 @@
+use std::sync::Arc;
+
 use chrono::DateTime;
 use gpui2::geometry::{relative, rems, Size};
 
-use crate::prelude::*;
 use crate::{
-    theme, v_stack, ChatMessage, ChatPanel, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide,
-    ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
+    hello_world_rust_editor_with_status_example, prelude::*, random_players_with_call_status,
+    Livestream,
+};
+use crate::{
+    theme, v_stack, ChatMessage, ChatPanel, EditorPane, Pane, PaneGroup, Panel, PanelAllowedSides,
+    PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
 };
 
 #[derive(Element, Default)]
@@ -17,6 +22,8 @@ pub struct WorkspaceElement {
 
 impl WorkspaceElement {
     fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx).clone();
+
         let temp_size = rems(36.).into();
 
         let root_group = PaneGroup::new_groups(
@@ -29,8 +36,15 @@ impl WorkspaceElement {
                                 width: relative(1.).into(),
                                 height: temp_size,
                             },
-                            |_, _| vec![Terminal::new().into_any()],
-                            Box::new(()),
+                            |_, payload| {
+                                let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
+
+                                vec![EditorPane::new(hello_world_rust_editor_with_status_example(
+                                    &theme,
+                                ))
+                                .into_any()]
+                            },
+                            Box::new(theme.clone()),
                         ),
                         Pane::new(
                             ScrollState::default(),
@@ -51,8 +65,15 @@ impl WorkspaceElement {
                             width: relative(1.).into(),
                             height: relative(1.).into(),
                         },
-                        |_, _| vec![Terminal::new().into_any()],
-                        Box::new(()),
+                        |_, payload| {
+                            let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
+
+                            vec![EditorPane::new(hello_world_rust_editor_with_status_example(
+                                &theme,
+                            ))
+                            .into_any()]
+                        },
+                        Box::new(theme.clone()),
                     )],
                     SplitDirection::Vertical,
                 ),
@@ -60,8 +81,6 @@ impl WorkspaceElement {
             SplitDirection::Horizontal,
         );
 
-        let theme = theme(cx).clone();
-
         div()
             .size_full()
             .flex()
@@ -72,7 +91,10 @@ impl WorkspaceElement {
             .items_start()
             .text_color(theme.lowest.base.default.foreground)
             .fill(theme.lowest.base.default.background)
-            .child(TitleBar::new(cx))
+            .child(TitleBar::new(cx).set_livestream(Some(Livestream {
+                players: random_players_with_call_status(7),
+                channel: Some("gpui2-ui".to_string()),
+            })))
             .child(
                 div()
                     .flex_1()
@@ -84,8 +106,12 @@ impl WorkspaceElement {
                     .border_b()
                     .border_color(theme.lowest.base.default.border)
                     .child(
-                        ProjectPanel::new(self.left_panel_scroll_state.clone())
-                            .side(PanelSide::Left),
+                        Panel::new(
+                            self.left_panel_scroll_state.clone(),
+                            |_, payload| vec![ProjectPanel::new(ScrollState::default()).into_any()],
+                            Box::new(()),
+                        )
+                        .side(PanelSide::Left),
                     )
                     .child(
                         v_stack()
@@ -110,26 +136,37 @@ impl WorkspaceElement {
                                 .side(PanelSide::Bottom),
                             ),
                     )
-                    .child(ChatPanel::new(ScrollState::default()).with_messages(vec![
-                                ChatMessage::new(
-                                    "osiewicz".to_string(),
-                                    "is this thing on?".to_string(),
-                                    DateTime::parse_from_rfc3339(
-                                        "2023-09-27T15:40:52.707Z",
-                                    )
-                                    .unwrap()
-                                    .naive_local(),
-                                ),
-                                ChatMessage::new(
-                                    "maxdeviant".to_string(),
-                                    "Reading you loud and clear!".to_string(),
-                                    DateTime::parse_from_rfc3339(
-                                        "2023-09-28T15:40:52.707Z",
-                                    )
-                                    .unwrap()
-                                    .naive_local(),
-                                ),
-                            ])),
+                    .child(
+                        Panel::new(
+                            self.right_panel_scroll_state.clone(),
+                            |_, payload| {
+                                vec![ChatPanel::new(ScrollState::default())
+                                    .with_messages(vec![
+                                        ChatMessage::new(
+                                            "osiewicz".to_string(),
+                                            "is this thing on?".to_string(),
+                                            DateTime::parse_from_rfc3339(
+                                                "2023-09-27T15:40:52.707Z",
+                                            )
+                                            .unwrap()
+                                            .naive_local(),
+                                        ),
+                                        ChatMessage::new(
+                                            "maxdeviant".to_string(),
+                                            "Reading you loud and clear!".to_string(),
+                                            DateTime::parse_from_rfc3339(
+                                                "2023-09-28T15:40:52.707Z",
+                                            )
+                                            .unwrap()
+                                            .naive_local(),
+                                        ),
+                                    ])
+                                    .into_any()]
+                            },
+                            Box::new(()),
+                        )
+                        .side(PanelSide::Right),
+                    ),
             )
             .child(StatusBar::new())
     }

crates/ui/src/elements/icon.rs 🔗

@@ -84,6 +84,7 @@ pub enum Icon {
     Plus,
     Quote,
     Screen,
+    SelectAll,
     Split,
     SplitMessage,
     Terminal,
@@ -131,6 +132,7 @@ impl Icon {
             Icon::Plus => "icons/plus.svg",
             Icon::Quote => "icons/quote.svg",
             Icon::Screen => "icons/desktop.svg",
+            Icon::SelectAll => "icons/select-all.svg",
             Icon::Split => "icons/split.svg",
             Icon::SplitMessage => "icons/split_message.svg",
             Icon::Terminal => "icons/terminal.svg",

crates/ui/src/elements/player.rs 🔗

@@ -65,7 +65,7 @@ impl PlayerCallStatus {
     }
 }
 
-#[derive(Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Player {
     index: usize,
     avatar_src: String,
@@ -73,6 +73,7 @@ pub struct Player {
     status: PlayerStatus,
 }
 
+#[derive(Clone)]
 pub struct PlayerWithCallStatus {
     player: Player,
     call_status: PlayerCallStatus,

crates/ui/src/prelude.rs 🔗

@@ -2,7 +2,7 @@ pub use gpui2::elements::div::{div, ScrollState};
 pub use gpui2::style::{StyleHelpers, Styleable};
 pub use gpui2::{Element, IntoElement, ParentElement, ViewContext};
 
-pub use crate::{theme, ButtonVariant, HackyChildren, HackyChildrenPayload, InputVariant};
+pub use crate::{theme, ButtonVariant, HackyChildren, HackyChildrenPayload, InputVariant, Theme};
 
 use gpui2::{hsla, rgb, Hsla, WindowContext};
 use strum::EnumIter;
@@ -40,8 +40,7 @@ pub enum HighlightColor {
 }
 
 impl HighlightColor {
-    pub fn hsla(&self, cx: &WindowContext) -> Hsla {
-        let theme = theme(cx);
+    pub fn hsla(&self, theme: &Theme) -> Hsla {
         let system_color = SystemColor::new();
 
         match self {
@@ -74,7 +73,7 @@ impl HighlightColor {
     }
 }
 
-#[derive(Default, PartialEq, EnumIter)]
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum FileSystemStatus {
     #[default]
     None,
@@ -92,7 +91,7 @@ impl FileSystemStatus {
     }
 }
 
-#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum GitStatus {
     #[default]
     None,
@@ -130,7 +129,7 @@ impl GitStatus {
     }
 }
 
-#[derive(Default, PartialEq)]
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum DiagnosticStatus {
     #[default]
     None,
@@ -139,14 +138,14 @@ pub enum DiagnosticStatus {
     Info,
 }
 
-#[derive(Default, PartialEq)]
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum IconSide {
     #[default]
     Left,
     Right,
 }
 
-#[derive(Default, PartialEq)]
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum OrderMethod {
     #[default]
     Ascending,
@@ -154,14 +153,14 @@ pub enum OrderMethod {
     MostRecent,
 }
 
-#[derive(Default, PartialEq, Clone, Copy)]
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum Shape {
     #[default]
     Circle,
     RoundedRectangle,
 }
 
-#[derive(Default, PartialEq, Clone, Copy)]
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum DisclosureControlVisibility {
     #[default]
     OnHover,

crates/ui/src/static_data.rs 🔗

@@ -1,12 +1,109 @@
-use gpui2::WindowContext;
+use std::path::PathBuf;
+use std::str::FromStr;
+
+use rand::Rng;
 
 use crate::{
-    Buffer, BufferRow, BufferRows, GitStatus, HighlightColor, HighlightedLine, HighlightedText,
-    Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListItem, MicStatus,
-    ModifierKeys, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus,
-    ToggleState,
+    Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor,
+    HighlightedLine, HighlightedText, Icon, Keybinding, Label, LabelColor, ListEntry,
+    ListEntrySize, ListItem, Livestream, MicStatus, ModifierKeys, PaletteItem, Player,
+    PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, Theme, ToggleState,
+    VideoStatus,
 };
 
+pub fn static_tabs_example() -> Vec<Tab> {
+    vec![
+        Tab::new()
+            .title("wip.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false)
+            .fs_status(FileSystemStatus::Deleted),
+        Tab::new()
+            .title("Cargo.toml".to_string())
+            .icon(Icon::FileToml)
+            .current(false)
+            .git_status(GitStatus::Modified),
+        Tab::new()
+            .title("Channels Panel".to_string())
+            .icon(Icon::Hash)
+            .current(false),
+        Tab::new()
+            .title("channels_panel.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(true)
+            .git_status(GitStatus::Modified),
+        Tab::new()
+            .title("workspace.rs".to_string())
+            .current(false)
+            .icon(Icon::FileRust)
+            .git_status(GitStatus::Modified),
+        Tab::new()
+            .title("icon_button.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false),
+        Tab::new()
+            .title("storybook.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false)
+            .git_status(GitStatus::Created),
+        Tab::new()
+            .title("theme.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false),
+        Tab::new()
+            .title("theme_registry.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false),
+        Tab::new()
+            .title("styleable_helpers.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false),
+    ]
+}
+
+pub fn static_tabs_1() -> Vec<Tab> {
+    vec![
+        Tab::new()
+            .title("project_panel.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false)
+            .fs_status(FileSystemStatus::Deleted),
+        Tab::new()
+            .title("tab_bar.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false)
+            .git_status(GitStatus::Modified),
+        Tab::new()
+            .title("workspace.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false),
+        Tab::new()
+            .title("tab.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(true)
+            .git_status(GitStatus::Modified),
+    ]
+}
+
+pub fn static_tabs_2() -> Vec<Tab> {
+    vec![
+        Tab::new()
+            .title("tab_bar.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(false)
+            .fs_status(FileSystemStatus::Deleted),
+        Tab::new()
+            .title("static_data.rs".to_string())
+            .icon(Icon::FileRust)
+            .current(true)
+            .git_status(GitStatus::Modified),
+    ]
+}
+
+pub fn static_tabs_3() -> Vec<Tab> {
+    vec![Tab::new().git_status(GitStatus::Created).current(true)]
+}
+
 pub fn static_players() -> Vec<Player> {
     vec![
         Player::new(
@@ -37,6 +134,154 @@ pub fn static_players() -> Vec<Player> {
     ]
 }
 
+#[derive(Debug)]
+pub struct PlayerData {
+    pub url: String,
+    pub name: String,
+}
+pub fn static_player_data() -> Vec<PlayerData> {
+    vec![
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/1714999?v=4".into(),
+            name: "iamnbutler".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/326587?v=4".into(),
+            name: "maxbrunsfeld".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/482957?v=4".into(),
+            name: "as-cii".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/1789?v=4".into(),
+            name: "nathansobo".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(),
+            name: "ForLoveOfCats".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/2690773?v=4".into(),
+            name: "SomeoneToIgnore".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/19867440?v=4".into(),
+            name: "JosephTLyons".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/24362066?v=4".into(),
+            name: "osiewicz".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/22121886?v=4".into(),
+            name: "KCaverly".into(),
+        },
+        PlayerData {
+            url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(),
+            name: "maxdeviant".into(),
+        },
+    ]
+}
+pub fn create_static_players(player_data: Vec<PlayerData>) -> Vec<Player> {
+    let mut players = Vec::new();
+    for data in player_data {
+        players.push(Player::new(players.len(), data.url, data.name));
+    }
+    players
+}
+pub fn static_player_1(data: &Vec<PlayerData>) -> Player {
+    Player::new(1, data[0].url.clone(), data[0].name.clone())
+}
+pub fn static_player_2(data: &Vec<PlayerData>) -> Player {
+    Player::new(2, data[1].url.clone(), data[1].name.clone())
+}
+pub fn static_player_3(data: &Vec<PlayerData>) -> Player {
+    Player::new(3, data[2].url.clone(), data[2].name.clone())
+}
+pub fn static_player_4(data: &Vec<PlayerData>) -> Player {
+    Player::new(4, data[3].url.clone(), data[3].name.clone())
+}
+pub fn static_player_5(data: &Vec<PlayerData>) -> Player {
+    Player::new(5, data[4].url.clone(), data[4].name.clone())
+}
+pub fn static_player_6(data: &Vec<PlayerData>) -> Player {
+    Player::new(6, data[5].url.clone(), data[5].name.clone())
+}
+pub fn static_player_7(data: &Vec<PlayerData>) -> Player {
+    Player::new(7, data[6].url.clone(), data[6].name.clone())
+}
+pub fn static_player_8(data: &Vec<PlayerData>) -> Player {
+    Player::new(8, data[7].url.clone(), data[7].name.clone())
+}
+pub fn static_player_9(data: &Vec<PlayerData>) -> Player {
+    Player::new(9, data[8].url.clone(), data[8].name.clone())
+}
+pub fn static_player_10(data: &Vec<PlayerData>) -> Player {
+    Player::new(10, data[9].url.clone(), data[9].name.clone())
+}
+pub fn static_livestream() -> Livestream {
+    Livestream {
+        players: random_players_with_call_status(7),
+        channel: Some("gpui2-ui".to_string()),
+    }
+}
+pub fn populate_player_call_status(
+    player: Player,
+    followers: Option<Vec<Player>>,
+) -> PlayerCallStatus {
+    let mut rng = rand::thread_rng();
+    let in_current_project: bool = rng.gen();
+    let disconnected: bool = rng.gen();
+    let voice_activity: f32 = rng.gen();
+    let mic_status = if rng.gen_bool(0.5) {
+        MicStatus::Muted
+    } else {
+        MicStatus::Unmuted
+    };
+    let video_status = if rng.gen_bool(0.5) {
+        VideoStatus::On
+    } else {
+        VideoStatus::Off
+    };
+    let screen_share_status = if rng.gen_bool(0.5) {
+        ScreenShareStatus::Shared
+    } else {
+        ScreenShareStatus::NotShared
+    };
+    PlayerCallStatus {
+        mic_status,
+        voice_activity,
+        video_status,
+        screen_share_status,
+        in_current_project,
+        disconnected,
+        following: None,
+        followers,
+    }
+}
+pub fn random_players_with_call_status(number_of_players: usize) -> Vec<PlayerWithCallStatus> {
+    let players = create_static_players(static_player_data());
+    let mut player_status = vec![];
+    for i in 0..number_of_players {
+        let followers = if i == 0 {
+            Some(vec![
+                players[1].clone(),
+                players[3].clone(),
+                players[5].clone(),
+                players[6].clone(),
+            ])
+        } else if i == 1 {
+            Some(vec![players[2].clone(), players[6].clone()])
+        } else {
+            None
+        };
+        let call_status = populate_player_call_status(players[i].clone(), followers);
+        player_status.push(PlayerWithCallStatus::new(players[i].clone(), call_status));
+    }
+    player_status
+}
+
 pub fn static_players_with_call_status() -> Vec<PlayerWithCallStatus> {
     let players = static_players();
     let mut player_0_status = PlayerCallStatus::new();
@@ -123,7 +368,7 @@ pub fn static_project_panel_project_items() -> Vec<ListItem> {
             .left_icon(Icon::FolderOpen.into())
             .indent_level(3)
             .set_toggle(ToggleState::Toggled),
-        ListEntry::new(Label::new("derrive_element.rs"))
+        ListEntry::new(Label::new("derive_element.rs"))
             .left_icon(Icon::FileRust.into())
             .indent_level(4),
         ListEntry::new(Label::new("storybook").color(LabelColor::Modified))
@@ -337,33 +582,49 @@ pub fn example_editor_actions() -> Vec<PaletteItem> {
     ]
 }
 
-pub fn empty_buffer_example<V: 'static>() -> Buffer<V> {
+pub fn empty_editor_example() -> Editor {
+    Editor {
+        tabs: static_tabs_example(),
+        path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
+        symbols: vec![],
+        buffer: empty_buffer_example(),
+    }
+}
+
+pub fn empty_buffer_example() -> Buffer {
     Buffer::new().set_rows(Some(BufferRows::default()))
 }
 
-pub fn hello_world_rust_buffer_example<V: 'static>(cx: &WindowContext) -> Buffer<V> {
-    Buffer::new()
-        .set_title("hello_world.rs".to_string())
-        .set_path("src/hello_world.rs".to_string())
-        .set_language("rust".to_string())
-        .set_rows(Some(BufferRows {
-            show_line_numbers: true,
-            rows: hello_world_rust_buffer_rows(cx),
-        }))
+pub fn hello_world_rust_editor_example(theme: &Theme) -> Editor {
+    Editor {
+        tabs: static_tabs_example(),
+        path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
+        symbols: vec![Symbol(vec![
+            HighlightedText {
+                text: "fn ".to_string(),
+                color: HighlightColor::Keyword.hsla(&theme),
+            },
+            HighlightedText {
+                text: "main".to_string(),
+                color: HighlightColor::Function.hsla(&theme),
+            },
+        ])],
+        buffer: hello_world_rust_buffer_example(theme),
+    }
 }
 
-pub fn hello_world_rust_buffer_with_status_example<V: 'static>(cx: &WindowContext) -> Buffer<V> {
+pub fn hello_world_rust_buffer_example(theme: &Theme) -> Buffer {
     Buffer::new()
         .set_title("hello_world.rs".to_string())
         .set_path("src/hello_world.rs".to_string())
         .set_language("rust".to_string())
         .set_rows(Some(BufferRows {
             show_line_numbers: true,
-            rows: hello_world_rust_with_status_buffer_rows(cx),
+            rows: hello_world_rust_buffer_rows(theme),
         }))
 }
 
-pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
+pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
     let show_line_number = true;
 
     vec![
@@ -375,15 +636,15 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
                 highlighted_texts: vec![
                     HighlightedText {
                         text: "fn ".to_string(),
-                        color: HighlightColor::Keyword.hsla(cx),
+                        color: HighlightColor::Keyword.hsla(&theme),
                     },
                     HighlightedText {
                         text: "main".to_string(),
-                        color: HighlightColor::Function.hsla(cx),
+                        color: HighlightColor::Function.hsla(&theme),
                     },
                     HighlightedText {
                         text: "() {".to_string(),
-                        color: HighlightColor::Default.hsla(cx),
+                        color: HighlightColor::Default.hsla(&theme),
                     },
                 ],
             }),
@@ -399,7 +660,7 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
                 highlighted_texts: vec![HighlightedText {
                     text: "    // Statements here are executed when the compiled binary is called."
                         .to_string(),
-                    color: HighlightColor::Comment.hsla(cx),
+                    color: HighlightColor::Comment.hsla(&theme),
                 }],
             }),
             cursors: None,
@@ -422,7 +683,7 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "    // Print text to the console.".to_string(),
-                    color: HighlightColor::Comment.hsla(cx),
+                    color: HighlightColor::Comment.hsla(&theme),
                 }],
             }),
             cursors: None,
@@ -433,10 +694,34 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
             line_number: 5,
             code_action: false,
             current: false,
+            line: Some(HighlightedLine {
+                highlighted_texts: vec![
+                    HighlightedText {
+                        text: "    println!(".to_string(),
+                        color: HighlightColor::Default.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: "\"Hello, world!\"".to_string(),
+                        color: HighlightColor::String.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: ");".to_string(),
+                        color: HighlightColor::Default.hsla(&theme),
+                    },
+                ],
+            }),
+            cursors: None,
+            status: GitStatus::None,
+            show_line_number,
+        },
+        BufferRow {
+            line_number: 6,
+            code_action: false,
+            current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "}".to_string(),
-                    color: HighlightColor::Default.hsla(cx),
+                    color: HighlightColor::Default.hsla(&theme),
                 }],
             }),
             cursors: None,
@@ -446,7 +731,36 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
     ]
 }
 
-pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
+pub fn hello_world_rust_editor_with_status_example(theme: &Theme) -> Editor {
+    Editor {
+        tabs: static_tabs_example(),
+        path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
+        symbols: vec![Symbol(vec![
+            HighlightedText {
+                text: "fn ".to_string(),
+                color: HighlightColor::Keyword.hsla(&theme),
+            },
+            HighlightedText {
+                text: "main".to_string(),
+                color: HighlightColor::Function.hsla(&theme),
+            },
+        ])],
+        buffer: hello_world_rust_buffer_with_status_example(theme),
+    }
+}
+
+pub fn hello_world_rust_buffer_with_status_example(theme: &Theme) -> Buffer {
+    Buffer::new()
+        .set_title("hello_world.rs".to_string())
+        .set_path("src/hello_world.rs".to_string())
+        .set_language("rust".to_string())
+        .set_rows(Some(BufferRows {
+            show_line_numbers: true,
+            rows: hello_world_rust_with_status_buffer_rows(theme),
+        }))
+}
+
+pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
     let show_line_number = true;
 
     vec![
@@ -458,15 +772,15 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
                 highlighted_texts: vec![
                     HighlightedText {
                         text: "fn ".to_string(),
-                        color: HighlightColor::Keyword.hsla(cx),
+                        color: HighlightColor::Keyword.hsla(&theme),
                     },
                     HighlightedText {
                         text: "main".to_string(),
-                        color: HighlightColor::Function.hsla(cx),
+                        color: HighlightColor::Function.hsla(&theme),
                     },
                     HighlightedText {
                         text: "() {".to_string(),
-                        color: HighlightColor::Default.hsla(cx),
+                        color: HighlightColor::Default.hsla(&theme),
                     },
                 ],
             }),
@@ -482,7 +796,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
                 highlighted_texts: vec![HighlightedText {
                     text: "// Statements here are executed when the compiled binary is called."
                         .to_string(),
-                    color: HighlightColor::Comment.hsla(cx),
+                    color: HighlightColor::Comment.hsla(&theme),
                 }],
             }),
             cursors: None,
@@ -505,7 +819,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "    // Print text to the console.".to_string(),
-                    color: HighlightColor::Comment.hsla(cx),
+                    color: HighlightColor::Comment.hsla(&theme),
                 }],
             }),
             cursors: None,
@@ -516,10 +830,34 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
             line_number: 5,
             code_action: false,
             current: false,
+            line: Some(HighlightedLine {
+                highlighted_texts: vec![
+                    HighlightedText {
+                        text: "    println!(".to_string(),
+                        color: HighlightColor::Default.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: "\"Hello, world!\"".to_string(),
+                        color: HighlightColor::String.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: ");".to_string(),
+                        color: HighlightColor::Default.hsla(&theme),
+                    },
+                ],
+            }),
+            cursors: None,
+            status: GitStatus::None,
+            show_line_number,
+        },
+        BufferRow {
+            line_number: 6,
+            code_action: false,
+            current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "}".to_string(),
-                    color: HighlightColor::Default.hsla(cx),
+                    color: HighlightColor::Default.hsla(&theme),
                 }],
             }),
             cursors: None,
@@ -527,13 +865,13 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
             show_line_number,
         },
         BufferRow {
-            line_number: 6,
+            line_number: 7,
             code_action: false,
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "".to_string(),
-                    color: HighlightColor::Default.hsla(cx),
+                    color: HighlightColor::Default.hsla(&theme),
                 }],
             }),
             cursors: None,
@@ -541,13 +879,13 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
             show_line_number,
         },
         BufferRow {
-            line_number: 7,
+            line_number: 8,
             code_action: false,
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
-                    text: "Marshall and Nate were here".to_string(),
-                    color: HighlightColor::Default.hsla(cx),
+                    text: "// Marshall and Nate were here".to_string(),
+                    color: HighlightColor::Comment.hsla(&theme),
                 }],
             }),
             cursors: None,
@@ -556,3 +894,73 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
         },
     ]
 }
+
+pub fn terminal_buffer(theme: &Theme) -> Buffer {
+    Buffer::new()
+        .set_title("zed — fish".to_string())
+        .set_rows(Some(BufferRows {
+            show_line_numbers: false,
+            rows: terminal_buffer_rows(theme),
+        }))
+}
+
+pub fn terminal_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
+    let show_line_number = false;
+
+    vec![
+        BufferRow {
+            line_number: 1,
+            code_action: false,
+            current: false,
+            line: Some(HighlightedLine {
+                highlighted_texts: vec![
+                    HighlightedText {
+                        text: "maxdeviant ".to_string(),
+                        color: HighlightColor::Keyword.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: "in ".to_string(),
+                        color: HighlightColor::Default.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: "profaned-capital ".to_string(),
+                        color: HighlightColor::Function.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: "in ".to_string(),
+                        color: HighlightColor::Default.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: "~/p/zed ".to_string(),
+                        color: HighlightColor::Function.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: "on ".to_string(),
+                        color: HighlightColor::Default.hsla(&theme),
+                    },
+                    HighlightedText {
+                        text: " gpui2-ui ".to_string(),
+                        color: HighlightColor::Keyword.hsla(&theme),
+                    },
+                ],
+            }),
+            cursors: None,
+            status: GitStatus::None,
+            show_line_number,
+        },
+        BufferRow {
+            line_number: 2,
+            code_action: false,
+            current: false,
+            line: Some(HighlightedLine {
+                highlighted_texts: vec![HighlightedText {
+                    text: "λ ".to_string(),
+                    color: HighlightColor::String.hsla(&theme),
+                }],
+            }),
+            cursors: None,
+            status: GitStatus::None,
+            show_line_number,
+        },
+    ]
+}