diff --git a/Cargo.lock b/Cargo.lock index e77af3b7ac..a0088a1695 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4198,6 +4198,7 @@ dependencies = [ "anyhow", "gpui2", "log", + "serde", "smol", "util", ] @@ -11432,7 +11433,7 @@ dependencies = [ "ignore", "image", "indexmap 1.9.3", - "install_cli", + "install_cli2", "isahc", "journal2", "language2", diff --git a/crates/Cargo.toml b/crates/Cargo.toml deleted file mode 100644 index 6516e07cd4..0000000000 --- a/crates/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "ai" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -path = "src/ai.rs" -doctest = false - -[features] -test-support = [] - -[dependencies] -gpui = { path = "../gpui" } -util = { path = "../util" } -language = { path = "../language" } -async-trait.workspace = true -anyhow.workspace = true -futures.workspace = true -lazy_static.workspace = true -ordered-float.workspace = true -parking_lot.workspace = true -isahc.workspace = true -regex.workspace = true -serde.workspace = true -serde_json.workspace = true -postage.workspace = true -rand.workspace = true -log.workspace = true -parse_duration = "2.1.1" -tiktoken-rs.workspace = true -matrixmultiply = "0.3.7" -rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } -bincode = "1.3.3" - -[dev-dependencies] -gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index 4b43a63f05..09bb2f1182 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -30,7 +30,7 @@ use std::{ }; use text::Selection; use theme::{ActiveTheme, Theme}; -use ui::{Label, LabelColor}; +use ui::{Label, TextColor}; use util::{paths::PathExt, ResultExt, TryFutureExt}; use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle}; use workspace::{ @@ -607,7 +607,7 @@ impl Item for Editor { &description, MAX_TAB_TITLE_LEN, )) - .color(LabelColor::Muted), + .color(TextColor::Muted), ), ) })), diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 36bd726030..ccd6b7ada2 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -5,7 +5,7 @@ use gpui::{ }; use text::{Bias, Point}; use theme::ActiveTheme; -use ui::{h_stack, v_stack, Label, LabelColor, StyledExt}; +use ui::{h_stack, v_stack, Label, StyledExt, TextColor}; use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::{Modal, ModalEvent, Workspace}; @@ -176,7 +176,7 @@ impl Render for GoToLine { .justify_between() .px_2() .py_1() - .child(Label::new(self.current_text.clone()).color(LabelColor::Muted)), + .child(Label::new(self.current_text.clone()).color(TextColor::Muted)), ), ) } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 4b6b9bea73..b732be7455 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2112,6 +2112,10 @@ impl AppContext { AsyncAppContext(self.weak_self.as_ref().unwrap().upgrade().unwrap()) } + pub fn open_url(&self, url: &str) { + self.platform.open_url(url) + } + pub fn write_to_clipboard(&self, item: ClipboardItem) { self.platform.write_to_clipboard(item); } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 67a7139368..0040469e5f 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -431,6 +431,18 @@ impl AppContext { self.platform.activate(ignoring_other_apps); } + pub fn hide(&self) { + self.platform.hide(); + } + + pub fn hide_other_apps(&self) { + self.platform.hide_other_apps(); + } + + pub fn unhide_other_apps(&self) { + self.platform.unhide_other_apps(); + } + /// Returns the list of currently active displays. pub fn displays(&self) -> Vec> { self.platform.displays() diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 00e050f2d8..c177ffc8c2 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1365,6 +1365,14 @@ impl<'a> WindowContext<'a> { self.window.platform_window.activate(); } + pub fn minimize_window(&self) { + self.window.platform_window.minimize(); + } + + pub fn toggle_full_screen(&self) { + self.window.platform_window.toggle_full_screen(); + } + pub fn prompt( &self, level: PromptLevel, @@ -2368,6 +2376,12 @@ impl WindowHandle { { cx.read_window(self, |root_view, _cx| root_view.clone()) } + + pub fn is_active(&self, cx: &WindowContext) -> Option { + cx.windows + .get(self.id) + .and_then(|window| window.as_ref().map(|window| window.active)) + } } impl Copy for WindowHandle {} diff --git a/crates/install_cli2/Cargo.toml b/crates/install_cli2/Cargo.toml index 3310e7fbc8..26fe212fe3 100644 --- a/crates/install_cli2/Cargo.toml +++ b/crates/install_cli2/Cargo.toml @@ -14,5 +14,6 @@ test-support = [] smol.workspace = true anyhow.workspace = true log.workspace = true +serde.workspace = true gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } diff --git a/crates/install_cli2/src/install_cli2.rs b/crates/install_cli2/src/install_cli2.rs index 7938d60210..6fd1019c3f 100644 --- a/crates/install_cli2/src/install_cli2.rs +++ b/crates/install_cli2/src/install_cli2.rs @@ -1,10 +1,9 @@ use anyhow::{anyhow, Result}; -use gpui::AsyncAppContext; +use gpui::{actions, AsyncAppContext}; use std::path::Path; use util::ResultExt; -// todo!() -// actions!(cli, [Install]); +actions!(Install); pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??; diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 199941ef3b..089e6097e4 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -4,7 +4,7 @@ use gpui::{ UniformListScrollHandle, View, ViewContext, WindowContext, }; use std::{cmp, sync::Arc}; -use ui::{prelude::*, v_stack, Divider, Label, LabelColor}; +use ui::{prelude::*, v_stack, Divider, Label, TextColor}; pub struct Picker { pub delegate: D, @@ -224,7 +224,7 @@ impl Render for Picker { v_stack().p_1().grow().child( div() .px_1() - .child(Label::new("No matches").color(LabelColor::Muted)), + .child(Label::new("No matches").color(TextColor::Muted)), ), ) }) diff --git a/crates/ui2/src/components/button.rs b/crates/ui2/src/components/button.rs index e7acd9fdc1..8eff92f11a 100644 --- a/crates/ui2/src/components/button.rs +++ b/crates/ui2/src/components/button.rs @@ -2,10 +2,8 @@ use std::sync::Arc; use gpui::{div, DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext}; -use crate::{ - h_stack, prelude::*, Icon, IconButton, IconColor, IconElement, Label, LabelColor, - LineHeightStyle, -}; +use crate::prelude::*; +use crate::{h_stack, Icon, IconButton, IconElement, Label, LineHeightStyle, TextColor}; /// Provides the flexibility to use either a standard /// button or an icon button in a given context. @@ -87,7 +85,7 @@ pub struct Button { label: SharedString, variant: ButtonVariant, width: Option, - color: Option, + color: Option, } impl Button { @@ -141,14 +139,14 @@ impl Button { self } - pub fn color(mut self, color: Option) -> Self { + pub fn color(mut self, color: Option) -> Self { self.color = color; self } - pub fn label_color(&self, color: Option) -> LabelColor { + pub fn label_color(&self, color: Option) -> TextColor { if self.disabled { - LabelColor::Disabled + TextColor::Disabled } else if let Some(color) = color { color } else { @@ -156,21 +154,21 @@ impl Button { } } - fn render_label(&self, color: LabelColor) -> Label { + fn render_label(&self, color: TextColor) -> Label { Label::new(self.label.clone()) .color(color) .line_height_style(LineHeightStyle::UILabel) } - fn render_icon(&self, icon_color: IconColor) -> Option { + fn render_icon(&self, icon_color: TextColor) -> Option { self.icon.map(|i| IconElement::new(i).color(icon_color)) } pub fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let (icon_color, label_color) = match (self.disabled, self.color) { - (true, _) => (IconColor::Disabled, LabelColor::Disabled), - (_, None) => (IconColor::Default, LabelColor::Default), - (_, Some(color)) => (IconColor::from(color), color), + (true, _) => (TextColor::Disabled, TextColor::Disabled), + (_, None) => (TextColor::Default, TextColor::Default), + (_, Some(color)) => (TextColor::from(color), color), }; let mut button = h_stack() @@ -240,7 +238,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { use super::*; - use crate::{h_stack, v_stack, LabelColor, Story}; + use crate::{h_stack, v_stack, Story, TextColor}; use gpui::{rems, Div, Render}; use strum::IntoEnumIterator; @@ -265,7 +263,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label").variant(ButtonVariant::Ghost), // .state(state), @@ -276,7 +274,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label") @@ -290,7 +288,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label") @@ -307,7 +305,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label").variant(ButtonVariant::Filled), // .state(state), @@ -318,7 +316,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label") @@ -332,7 +330,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label") @@ -349,7 +347,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label") @@ -363,7 +361,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label") @@ -379,7 +377,7 @@ mod stories { v_stack() .gap_1() .child( - Label::new(state.to_string()).color(LabelColor::Muted), + Label::new(state.to_string()).color(TextColor::Muted), ) .child( Button::new("Label") diff --git a/crates/ui2/src/components/checkbox.rs b/crates/ui2/src/components/checkbox.rs index 971da8338b..5b9db17785 100644 --- a/crates/ui2/src/components/checkbox.rs +++ b/crates/ui2/src/components/checkbox.rs @@ -2,7 +2,7 @@ use gpui::{div, prelude::*, Component, ElementId, Styled, ViewContext}; use std::sync::Arc; use theme2::ActiveTheme; -use crate::{Icon, IconColor, IconElement, Selection}; +use crate::{Icon, IconElement, Selection, TextColor}; pub type CheckHandler = Arc) + Send + Sync>; @@ -54,9 +54,9 @@ impl Checkbox { .color( // If the checkbox is disabled we change the color of the icon. if self.disabled { - IconColor::Disabled + TextColor::Disabled } else { - IconColor::Selected + TextColor::Selected }, ), ) @@ -69,9 +69,9 @@ impl Checkbox { .color( // If the checkbox is disabled we change the color of the icon. if self.disabled { - IconColor::Disabled + TextColor::Disabled } else { - IconColor::Selected + TextColor::Selected }, ), ) diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index 75c8129608..5b60421205 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -1,7 +1,7 @@ -use gpui::{rems, svg, Hsla}; +use gpui::{rems, svg}; use strum::EnumIter; -use crate::{prelude::*, LabelColor}; +use crate::prelude::*; #[derive(Default, PartialEq, Copy, Clone)] pub enum IconSize { @@ -10,70 +10,6 @@ pub enum IconSize { Medium, } -#[derive(Default, PartialEq, Copy, Clone)] -pub enum IconColor { - #[default] - Default, - Accent, - Created, - Deleted, - Disabled, - Error, - Hidden, - Info, - Modified, - Muted, - Placeholder, - Player(u32), - Selected, - Success, - Warning, -} - -impl IconColor { - pub fn color(self, cx: &WindowContext) -> Hsla { - match self { - IconColor::Default => cx.theme().colors().icon, - IconColor::Muted => cx.theme().colors().icon_muted, - IconColor::Disabled => cx.theme().colors().icon_disabled, - IconColor::Placeholder => cx.theme().colors().icon_placeholder, - IconColor::Accent => cx.theme().colors().icon_accent, - IconColor::Error => cx.theme().status().error, - IconColor::Warning => cx.theme().status().warning, - IconColor::Success => cx.theme().status().success, - IconColor::Info => cx.theme().status().info, - IconColor::Selected => cx.theme().colors().icon_accent, - IconColor::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor, - IconColor::Created => cx.theme().status().created, - IconColor::Modified => cx.theme().status().modified, - IconColor::Deleted => cx.theme().status().deleted, - IconColor::Hidden => cx.theme().status().hidden, - } - } -} - -impl From for IconColor { - fn from(label: LabelColor) -> Self { - match label { - LabelColor::Default => IconColor::Default, - LabelColor::Muted => IconColor::Muted, - LabelColor::Disabled => IconColor::Disabled, - LabelColor::Placeholder => IconColor::Placeholder, - LabelColor::Accent => IconColor::Accent, - LabelColor::Error => IconColor::Error, - LabelColor::Warning => IconColor::Warning, - LabelColor::Success => IconColor::Success, - LabelColor::Info => IconColor::Info, - LabelColor::Selected => IconColor::Selected, - LabelColor::Player(i) => IconColor::Player(i), - LabelColor::Created => IconColor::Created, - LabelColor::Modified => IconColor::Modified, - LabelColor::Deleted => IconColor::Deleted, - LabelColor::Hidden => IconColor::Hidden, - } - } -} - #[derive(Debug, PartialEq, Copy, Clone, EnumIter)] pub enum Icon { Ai, @@ -194,7 +130,7 @@ impl Icon { #[derive(Component)] pub struct IconElement { icon: Icon, - color: IconColor, + color: TextColor, size: IconSize, } @@ -202,12 +138,12 @@ impl IconElement { pub fn new(icon: Icon) -> Self { Self { icon, - color: IconColor::default(), + color: TextColor::default(), size: IconSize::default(), } } - pub fn color(mut self, color: IconColor) -> Self { + pub fn color(mut self, color: TextColor) -> Self { self.color = color; self } diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index c9982f1f67..7afaa12243 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -1,5 +1,4 @@ -use crate::{h_stack, prelude::*}; -use crate::{ClickHandler, Icon, IconColor, IconElement, TextTooltip}; +use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement, TextTooltip}; use gpui::{prelude::*, MouseButton, VisualContext}; use std::sync::Arc; @@ -17,7 +16,7 @@ impl Default for IconButtonHandlers { pub struct IconButton { id: ElementId, icon: Icon, - color: IconColor, + color: TextColor, variant: ButtonVariant, state: InteractionState, tooltip: Option, @@ -29,7 +28,7 @@ impl IconButton { Self { id: id.into(), icon, - color: IconColor::default(), + color: TextColor::default(), variant: ButtonVariant::default(), state: InteractionState::default(), tooltip: None, @@ -42,7 +41,7 @@ impl IconButton { self } - pub fn color(mut self, color: IconColor) -> Self { + pub fn color(mut self, color: TextColor) -> Self { self.color = color; self } @@ -72,7 +71,7 @@ impl IconButton { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let icon_color = match (self.state, self.color) { - (InteractionState::Disabled, _) => IconColor::Disabled, + (InteractionState::Disabled, _) => TextColor::Disabled, _ => self.color, }; diff --git a/crates/ui2/src/components/input.rs b/crates/ui2/src/components/input.rs index 4db9222a0e..42de03db12 100644 --- a/crates/ui2/src/components/input.rs +++ b/crates/ui2/src/components/input.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, Label, LabelColor}; +use crate::{prelude::*, Label}; use gpui::prelude::*; #[derive(Default, PartialEq)] @@ -70,15 +70,15 @@ impl Input { }; let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled { - LabelColor::Disabled + TextColor::Disabled } else { - LabelColor::Placeholder + TextColor::Placeholder }); let label = Label::new(self.value.clone()).color(if self.disabled { - LabelColor::Disabled + TextColor::Disabled } else { - LabelColor::Default + TextColor::Default }); div() diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index a3e5a870a6..04e036f365 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -3,7 +3,7 @@ use strum::EnumIter; use crate::prelude::*; -#[derive(Component)] +#[derive(Component, Clone)] pub struct KeyBinding { /// A keybinding consists of a key and a set of modifier keys. /// More then one keybinding produces a chord. diff --git a/crates/ui2/src/components/label.rs b/crates/ui2/src/components/label.rs index 4b9cea8dc2..cbb75278c2 100644 --- a/crates/ui2/src/components/label.rs +++ b/crates/ui2/src/components/label.rs @@ -3,8 +3,15 @@ use gpui::{relative, Hsla, Text, TextRun, WindowContext}; use crate::prelude::*; use crate::styled_ext::StyledExt; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] +pub enum LabelSize { + #[default] + Default, + Small, +} + #[derive(Default, PartialEq, Copy, Clone)] -pub enum LabelColor { +pub enum TextColor { #[default] Default, Accent, @@ -23,24 +30,24 @@ pub enum LabelColor { Warning, } -impl LabelColor { - pub fn hsla(&self, cx: &WindowContext) -> Hsla { +impl TextColor { + pub fn color(&self, cx: &WindowContext) -> Hsla { match self { - LabelColor::Default => cx.theme().colors().text, - LabelColor::Muted => cx.theme().colors().text_muted, - LabelColor::Created => cx.theme().status().created, - LabelColor::Modified => cx.theme().status().modified, - LabelColor::Deleted => cx.theme().status().deleted, - LabelColor::Disabled => cx.theme().colors().text_disabled, - LabelColor::Hidden => cx.theme().status().hidden, - LabelColor::Info => cx.theme().status().info, - LabelColor::Placeholder => cx.theme().colors().text_placeholder, - LabelColor::Accent => cx.theme().colors().text_accent, - LabelColor::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor, - LabelColor::Error => cx.theme().status().error, - LabelColor::Selected => cx.theme().colors().text_accent, - LabelColor::Success => cx.theme().status().success, - LabelColor::Warning => cx.theme().status().warning, + TextColor::Default => cx.theme().colors().text, + TextColor::Muted => cx.theme().colors().text_muted, + TextColor::Created => cx.theme().status().created, + TextColor::Modified => cx.theme().status().modified, + TextColor::Deleted => cx.theme().status().deleted, + TextColor::Disabled => cx.theme().colors().text_disabled, + TextColor::Hidden => cx.theme().status().hidden, + TextColor::Info => cx.theme().status().info, + TextColor::Placeholder => cx.theme().colors().text_placeholder, + TextColor::Accent => cx.theme().colors().text_accent, + TextColor::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor, + TextColor::Error => cx.theme().status().error, + TextColor::Selected => cx.theme().colors().text_accent, + TextColor::Success => cx.theme().status().success, + TextColor::Warning => cx.theme().status().warning, } } } @@ -56,8 +63,9 @@ pub enum LineHeightStyle { #[derive(Component)] pub struct Label { label: SharedString, + size: LabelSize, line_height_style: LineHeightStyle, - color: LabelColor, + color: TextColor, strikethrough: bool, } @@ -65,13 +73,19 @@ impl Label { pub fn new(label: impl Into) -> Self { Self { label: label.into(), + size: LabelSize::Default, line_height_style: LineHeightStyle::default(), - color: LabelColor::Default, + color: TextColor::Default, strikethrough: false, } } - pub fn color(mut self, color: LabelColor) -> Self { + pub fn size(mut self, size: LabelSize) -> Self { + self.size = size; + self + } + + pub fn color(mut self, color: TextColor) -> Self { self.color = color; self } @@ -95,14 +109,17 @@ impl Label { .top_1_2() .w_full() .h_px() - .bg(LabelColor::Hidden.hsla(cx)), + .bg(TextColor::Hidden.color(cx)), ) }) - .text_ui() + .map(|this| match self.size { + LabelSize::Default => this.text_ui(), + LabelSize::Small => this.text_ui_sm(), + }) .when(self.line_height_style == LineHeightStyle::UILabel, |this| { this.line_height(relative(1.)) }) - .text_color(self.color.hsla(cx)) + .text_color(self.color.color(cx)) .child(self.label.clone()) } } @@ -110,7 +127,8 @@ impl Label { #[derive(Component)] pub struct HighlightedLabel { label: SharedString, - color: LabelColor, + size: LabelSize, + color: TextColor, highlight_indices: Vec, strikethrough: bool, } @@ -121,13 +139,19 @@ impl HighlightedLabel { pub fn new(label: impl Into, highlight_indices: Vec) -> Self { Self { label: label.into(), - color: LabelColor::Default, + size: LabelSize::Default, + color: TextColor::Default, highlight_indices, strikethrough: false, } } - pub fn color(mut self, color: LabelColor) -> Self { + pub fn size(mut self, size: LabelSize) -> Self { + self.size = size; + self + } + + pub fn color(mut self, color: TextColor) -> Self { self.color = color; self } @@ -146,7 +170,7 @@ impl HighlightedLabel { let mut runs: Vec = Vec::new(); for (char_ix, char) in self.label.char_indices() { - let mut color = self.color.hsla(cx); + let mut color = self.color.color(cx); if let Some(highlight_ix) = highlight_indices.peek() { if char_ix == *highlight_ix { @@ -183,9 +207,13 @@ impl HighlightedLabel { .my_auto() .w_full() .h_px() - .bg(LabelColor::Hidden.hsla(cx)), + .bg(TextColor::Hidden.color(cx)), ) }) + .map(|this| match self.size { + LabelSize::Default => this.text_ui(), + LabelSize::Small => this.text_ui_sm(), + }) .child(Text::styled(self.label, runs)) } } diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 5c42975b17..1ddad269dd 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,11 +1,11 @@ use gpui::div; +use crate::prelude::*; use crate::settings::user_settings; use crate::{ - disclosure_control, h_stack, v_stack, Avatar, Icon, IconColor, IconElement, IconSize, Label, - LabelColor, Toggle, + disclosure_control, h_stack, v_stack, Avatar, GraphicSlot, Icon, IconElement, IconSize, Label, + TextColor, Toggle, }; -use crate::{prelude::*, GraphicSlot}; #[derive(Clone, Copy, Default, Debug, PartialEq)] pub enum ListItemVariant { @@ -68,7 +68,7 @@ impl ListHeader { .items_center() .children(icons.into_iter().map(|i| { IconElement::new(i) - .color(IconColor::Muted) + .color(TextColor::Muted) .size(IconSize::Small) })), ), @@ -106,10 +106,10 @@ impl ListHeader { .items_center() .children(self.left_icon.map(|i| { IconElement::new(i) - .color(IconColor::Muted) + .color(TextColor::Muted) .size(IconSize::Small) })) - .child(Label::new(self.label.clone()).color(LabelColor::Muted)), + .child(Label::new(self.label.clone()).color(TextColor::Muted)), ) .child(disclosure_control), ) @@ -157,10 +157,10 @@ impl ListSubHeader { .items_center() .children(self.left_icon.map(|i| { IconElement::new(i) - .color(IconColor::Muted) + .color(TextColor::Muted) .size(IconSize::Small) })) - .child(Label::new(self.label.clone()).color(LabelColor::Muted)), + .child(Label::new(self.label.clone()).color(TextColor::Muted)), ), ) } @@ -291,7 +291,7 @@ impl ListEntry { h_stack().child( IconElement::new(i) .size(IconSize::Small) - .color(IconColor::Muted), + .color(TextColor::Muted), ), ), Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::new(src))), @@ -394,7 +394,7 @@ impl List { (false, _) => div().children(self.items), (true, Toggle::Toggled(false)) => div(), (true, _) => { - div().child(Label::new(self.empty_message.clone()).color(LabelColor::Muted)) + div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted)) } }; diff --git a/crates/ui2/src/components/palette.rs b/crates/ui2/src/components/palette.rs index c72722dc08..5adf794a5e 100644 --- a/crates/ui2/src/components/palette.rs +++ b/crates/ui2/src/components/palette.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label, LabelColor}; +use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label}; use gpui::prelude::*; #[derive(Component)] @@ -54,7 +54,7 @@ impl Palette { v_stack() .gap_px() .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child( - Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder), + Label::new(self.input_placeholder.clone()).color(TextColor::Placeholder), ))) .child( div() @@ -75,7 +75,7 @@ impl Palette { Some( h_stack().justify_between().px_2().py_1().child( Label::new(self.empty_string.clone()) - .color(LabelColor::Muted), + .color(TextColor::Muted), ), ) } else { @@ -108,7 +108,7 @@ impl Palette { pub struct PaletteItem { pub label: SharedString, pub sublabel: Option, - pub keybinding: Option, + pub key_binding: Option, } impl PaletteItem { @@ -116,7 +116,7 @@ impl PaletteItem { Self { label: label.into(), sublabel: None, - keybinding: None, + key_binding: None, } } @@ -130,11 +130,8 @@ impl PaletteItem { self } - pub fn keybinding(mut self, keybinding: K) -> Self - where - K: Into>, - { - self.keybinding = keybinding.into(); + pub fn key_binding(mut self, key_binding: impl Into>) -> Self { + self.key_binding = key_binding.into(); self } @@ -149,7 +146,7 @@ impl PaletteItem { .child(Label::new(self.label.clone())) .children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))), ) - .children(self.keybinding) + .children(self.key_binding) } } @@ -182,23 +179,23 @@ mod stories { .placeholder("Execute a command...") .items(vec![ PaletteItem::new("theme selector: toggle") - .keybinding(KeyBinding::new(binding("cmd-k cmd-t"))), + .key_binding(KeyBinding::new(binding("cmd-k cmd-t"))), PaletteItem::new("assistant: inline assist") - .keybinding(KeyBinding::new(binding("cmd-enter"))), + .key_binding(KeyBinding::new(binding("cmd-enter"))), PaletteItem::new("assistant: quote selection") - .keybinding(KeyBinding::new(binding("cmd-<"))), + .key_binding(KeyBinding::new(binding("cmd-<"))), PaletteItem::new("assistant: toggle focus") - .keybinding(KeyBinding::new(binding("cmd-?"))), + .key_binding(KeyBinding::new(binding("cmd-?"))), PaletteItem::new("auto update: check"), PaletteItem::new("auto update: view release notes"), PaletteItem::new("branches: open recent") - .keybinding(KeyBinding::new(binding("cmd-alt-b"))), + .key_binding(KeyBinding::new(binding("cmd-alt-b"))), PaletteItem::new("chat panel: toggle focus"), PaletteItem::new("cli: install"), PaletteItem::new("client: sign in"), PaletteItem::new("client: sign out"), PaletteItem::new("editor: cancel") - .keybinding(KeyBinding::new(binding("escape"))), + .key_binding(KeyBinding::new(binding("escape"))), ]), ) } diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 268098a579..820fe5b361 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::{Icon, IconColor, IconElement, Label, LabelColor}; +use crate::{Icon, IconElement, Label, TextColor}; use gpui::{prelude::*, red, Div, ElementId, Render, View}; #[derive(Component, Clone)] @@ -92,20 +92,18 @@ impl Tab { let label = match (self.git_status, is_deleted) { (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone()) - .color(LabelColor::Hidden) + .color(TextColor::Hidden) .set_strikethrough(true), (GitStatus::None, false) => Label::new(self.title.clone()), - (GitStatus::Created, false) => { - Label::new(self.title.clone()).color(LabelColor::Created) - } + (GitStatus::Created, false) => Label::new(self.title.clone()).color(TextColor::Created), (GitStatus::Modified, false) => { - Label::new(self.title.clone()).color(LabelColor::Modified) + Label::new(self.title.clone()).color(TextColor::Modified) } - (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent), + (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(TextColor::Accent), (GitStatus::Conflict, false) => Label::new(self.title.clone()), }; - let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted); + let close_icon = || IconElement::new(Icon::Close).color(TextColor::Muted); let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current { false => ( @@ -148,7 +146,7 @@ impl Tab { .children(has_fs_conflict.then(|| { IconElement::new(Icon::ExclamationTriangle) .size(crate::IconSize::Small) - .color(IconColor::Warning) + .color(TextColor::Warning) })) .children(self.icon.map(IconElement::new)) .children(if self.close_side == IconSide::Left { diff --git a/crates/ui2/src/components/toggle.rs b/crates/ui2/src/components/toggle.rs index f34f08e09e..8388e27531 100644 --- a/crates/ui2/src/components/toggle.rs +++ b/crates/ui2/src/components/toggle.rs @@ -1,6 +1,6 @@ use gpui::{div, Component, ParentComponent}; -use crate::{Icon, IconColor, IconElement, IconSize}; +use crate::{Icon, IconElement, IconSize, TextColor}; /// Whether the entry is toggleable, and if so, whether it is currently toggled. /// @@ -49,12 +49,12 @@ pub fn disclosure_control(toggle: Toggle) -> impl Component { (false, _) => div(), (_, true) => div().child( IconElement::new(Icon::ChevronDown) - .color(IconColor::Muted) + .color(TextColor::Muted) .size(IconSize::Small), ), (_, false) => div().child( IconElement::new(Icon::ChevronRight) - .color(IconColor::Muted) + .color(TextColor::Muted) .size(IconSize::Small), ), } diff --git a/crates/ui2/src/components/tooltip.rs b/crates/ui2/src/components/tooltip.rs index 231d4e856d..ca9e6d3eac 100644 --- a/crates/ui2/src/components/tooltip.rs +++ b/crates/ui2/src/components/tooltip.rs @@ -1,32 +1,59 @@ -use gpui::{div, Div, ParentComponent, Render, SharedString, Styled, ViewContext}; +use gpui::{Div, ParentComponent, Render, SharedString, Styled, ViewContext}; use theme2::ActiveTheme; -use crate::StyledExt; +use crate::prelude::*; +use crate::{h_stack, v_stack, KeyBinding, Label, LabelSize, StyledExt, TextColor}; -#[derive(Clone, Debug)] pub struct TextTooltip { title: SharedString, + meta: Option, + key_binding: Option, } impl TextTooltip { pub fn new(title: impl Into) -> Self { Self { title: title.into(), + meta: None, + key_binding: None, } } + + pub fn meta(mut self, meta: impl Into) -> Self { + self.meta = Some(meta.into()); + self + } + + pub fn key_binding(mut self, key_binding: impl Into>) -> Self { + self.key_binding = key_binding.into(); + self + } } impl Render for TextTooltip { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - div() + v_stack() .elevation_2(cx) .font("Zed Sans") - .text_ui() + .text_ui_sm() .text_color(cx.theme().colors().text) .py_1() .px_2() - .child(self.title.clone()) + .child( + h_stack() + .child(self.title.clone()) + .when_some(self.key_binding.clone(), |this, key_binding| { + this.justify_between().child(key_binding) + }), + ) + .when_some(self.meta.clone(), |this, meta| { + this.child( + Label::new(meta) + .size(LabelSize::Small) + .color(TextColor::Muted), + ) + }) } } diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index 09ce43d912..d4abb78c21 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -6,8 +6,8 @@ pub use gpui::{ }; pub use crate::elevation::*; -pub use crate::ButtonVariant; pub use crate::StyledExt; +pub use crate::{ButtonVariant, TextColor}; pub use theme2::ActiveTheme; use gpui::Hsla; diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 89aef8140a..bb81d6230f 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -10,9 +10,9 @@ use theme2::ActiveTheme; use crate::{binding, HighlightedText}; use crate::{ Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, - HighlightedLine, Icon, KeyBinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream, - MicStatus, Notification, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus, - PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus, + HighlightedLine, Icon, KeyBinding, Label, ListEntry, ListEntrySize, Livestream, MicStatus, + Notification, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus, PublicPlayer, + ScreenShareStatus, Symbol, Tab, TextColor, Toggle, VideoStatus, }; use crate::{ListItem, NotificationAction}; @@ -490,20 +490,20 @@ pub fn static_project_panel_project_items() -> Vec { ListEntry::new(Label::new(".config")) .left_icon(Icon::Folder.into()) .indent_level(1), - ListEntry::new(Label::new(".git").color(LabelColor::Hidden)) + ListEntry::new(Label::new(".git").color(TextColor::Hidden)) .left_icon(Icon::Folder.into()) .indent_level(1), ListEntry::new(Label::new(".cargo")) .left_icon(Icon::Folder.into()) .indent_level(1), - ListEntry::new(Label::new(".idea").color(LabelColor::Hidden)) + ListEntry::new(Label::new(".idea").color(TextColor::Hidden)) .left_icon(Icon::Folder.into()) .indent_level(1), ListEntry::new(Label::new("assets")) .left_icon(Icon::Folder.into()) .indent_level(1) .toggle(Toggle::Toggled(true)), - ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden)) + ListEntry::new(Label::new("cargo-target").color(TextColor::Hidden)) .left_icon(Icon::Folder.into()) .indent_level(1), ListEntry::new(Label::new("crates")) @@ -528,7 +528,7 @@ pub fn static_project_panel_project_items() -> Vec { ListEntry::new(Label::new("call")) .left_icon(Icon::Folder.into()) .indent_level(2), - ListEntry::new(Label::new("sqlez").color(LabelColor::Modified)) + ListEntry::new(Label::new("sqlez").color(TextColor::Modified)) .left_icon(Icon::Folder.into()) .indent_level(2) .toggle(Toggle::Toggled(false)), @@ -543,45 +543,45 @@ pub fn static_project_panel_project_items() -> Vec { ListEntry::new(Label::new("derive_element.rs")) .left_icon(Icon::FileRust.into()) .indent_level(4), - ListEntry::new(Label::new("storybook").color(LabelColor::Modified)) + ListEntry::new(Label::new("storybook").color(TextColor::Modified)) .left_icon(Icon::FolderOpen.into()) .indent_level(1) .toggle(Toggle::Toggled(true)), - ListEntry::new(Label::new("docs").color(LabelColor::Default)) + ListEntry::new(Label::new("docs").color(TextColor::Default)) .left_icon(Icon::Folder.into()) .indent_level(2) .toggle(Toggle::Toggled(true)), - ListEntry::new(Label::new("src").color(LabelColor::Modified)) + ListEntry::new(Label::new("src").color(TextColor::Modified)) .left_icon(Icon::FolderOpen.into()) .indent_level(3) .toggle(Toggle::Toggled(true)), - ListEntry::new(Label::new("ui").color(LabelColor::Modified)) + ListEntry::new(Label::new("ui").color(TextColor::Modified)) .left_icon(Icon::FolderOpen.into()) .indent_level(4) .toggle(Toggle::Toggled(true)), - ListEntry::new(Label::new("component").color(LabelColor::Created)) + ListEntry::new(Label::new("component").color(TextColor::Created)) .left_icon(Icon::FolderOpen.into()) .indent_level(5) .toggle(Toggle::Toggled(true)), - ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default)) + ListEntry::new(Label::new("facepile.rs").color(TextColor::Default)) .left_icon(Icon::FileRust.into()) .indent_level(6), - ListEntry::new(Label::new("follow_group.rs").color(LabelColor::Default)) + ListEntry::new(Label::new("follow_group.rs").color(TextColor::Default)) .left_icon(Icon::FileRust.into()) .indent_level(6), - ListEntry::new(Label::new("list_item.rs").color(LabelColor::Created)) + ListEntry::new(Label::new("list_item.rs").color(TextColor::Created)) .left_icon(Icon::FileRust.into()) .indent_level(6), - ListEntry::new(Label::new("tab.rs").color(LabelColor::Default)) + ListEntry::new(Label::new("tab.rs").color(TextColor::Default)) .left_icon(Icon::FileRust.into()) .indent_level(6), - ListEntry::new(Label::new("target").color(LabelColor::Hidden)) + ListEntry::new(Label::new("target").color(TextColor::Hidden)) .left_icon(Icon::Folder.into()) .indent_level(1), ListEntry::new(Label::new(".dockerignore")) .left_icon(Icon::FileGeneric.into()) .indent_level(1), - ListEntry::new(Label::new(".DS_Store").color(LabelColor::Hidden)) + ListEntry::new(Label::new(".DS_Store").color(TextColor::Hidden)) .left_icon(Icon::FileGeneric.into()) .indent_level(1), ListEntry::new(Label::new("Cargo.lock")) @@ -701,16 +701,16 @@ pub fn static_collab_panel_channels() -> Vec { pub fn example_editor_actions() -> Vec { vec![ - PaletteItem::new("New File").keybinding(KeyBinding::new(binding("cmd-n"))), - PaletteItem::new("Open File").keybinding(KeyBinding::new(binding("cmd-o"))), - PaletteItem::new("Save File").keybinding(KeyBinding::new(binding("cmd-s"))), - PaletteItem::new("Cut").keybinding(KeyBinding::new(binding("cmd-x"))), - PaletteItem::new("Copy").keybinding(KeyBinding::new(binding("cmd-c"))), - PaletteItem::new("Paste").keybinding(KeyBinding::new(binding("cmd-v"))), - PaletteItem::new("Undo").keybinding(KeyBinding::new(binding("cmd-z"))), - PaletteItem::new("Redo").keybinding(KeyBinding::new(binding("cmd-shift-z"))), - PaletteItem::new("Find").keybinding(KeyBinding::new(binding("cmd-f"))), - PaletteItem::new("Replace").keybinding(KeyBinding::new(binding("cmd-r"))), + PaletteItem::new("New File").key_binding(KeyBinding::new(binding("cmd-n"))), + PaletteItem::new("Open File").key_binding(KeyBinding::new(binding("cmd-o"))), + PaletteItem::new("Save File").key_binding(KeyBinding::new(binding("cmd-s"))), + PaletteItem::new("Cut").key_binding(KeyBinding::new(binding("cmd-x"))), + PaletteItem::new("Copy").key_binding(KeyBinding::new(binding("cmd-c"))), + PaletteItem::new("Paste").key_binding(KeyBinding::new(binding("cmd-v"))), + PaletteItem::new("Undo").key_binding(KeyBinding::new(binding("cmd-z"))), + PaletteItem::new("Redo").key_binding(KeyBinding::new(binding("cmd-shift-z"))), + PaletteItem::new("Find").key_binding(KeyBinding::new(binding("cmd-f"))), + PaletteItem::new("Replace").key_binding(KeyBinding::new(binding("cmd-r"))), PaletteItem::new("Jump to Line"), PaletteItem::new("Select All"), PaletteItem::new("Deselect All"), diff --git a/crates/ui2/src/to_extract/buffer_search.rs b/crates/ui2/src/to_extract/buffer_search.rs index 9993cd3612..996ac6d253 100644 --- a/crates/ui2/src/to_extract/buffer_search.rs +++ b/crates/ui2/src/to_extract/buffer_search.rs @@ -1,7 +1,7 @@ use gpui::{Div, Render, View, VisualContext}; use crate::prelude::*; -use crate::{h_stack, Icon, IconButton, IconColor, Input}; +use crate::{h_stack, Icon, IconButton, Input, TextColor}; #[derive(Clone)] pub struct BufferSearch { @@ -36,7 +36,7 @@ impl Render for BufferSearch { .child( h_stack().child(Input::new("Search")).child( IconButton::::new("replace", Icon::Replace) - .when(self.is_replace_open, |this| this.color(IconColor::Accent)) + .when(self.is_replace_open, |this| this.color(TextColor::Accent)) .on_click(|buffer_search, cx| { buffer_search.toggle_replace(cx); }), diff --git a/crates/ui2/src/to_extract/chat_panel.rs b/crates/ui2/src/to_extract/chat_panel.rs index fcc8e6a46e..7e2846a3f6 100644 --- a/crates/ui2/src/to_extract/chat_panel.rs +++ b/crates/ui2/src/to_extract/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, Icon, IconButton, Input, Label, LabelColor}; +use crate::{prelude::*, Icon, IconButton, Input, Label}; use chrono::NaiveDateTime; use gpui::prelude::*; @@ -94,7 +94,7 @@ impl ChatMessage { .child(Label::new(self.author.clone())) .child( Label::new(self.sent_at.format("%m/%d/%Y").to_string()) - .color(LabelColor::Muted), + .color(TextColor::Muted), ), ) .child(div().child(Label::new(self.text.clone()))) diff --git a/crates/ui2/src/to_extract/copilot.rs b/crates/ui2/src/to_extract/copilot.rs index 8750ab3c51..c5622f5be6 100644 --- a/crates/ui2/src/to_extract/copilot.rs +++ b/crates/ui2/src/to_extract/copilot.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, Button, Label, LabelColor, Modal}; +use crate::{prelude::*, Button, Label, Modal, TextColor}; #[derive(Component)] pub struct CopilotModal { @@ -14,7 +14,7 @@ impl CopilotModal { div().id(self.id.clone()).child( Modal::new("some-id") .title("Connect Copilot to Zed") - .child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(LabelColor::Muted)) + .child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(TextColor::Muted)) .primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)), ) } diff --git a/crates/ui2/src/to_extract/editor_pane.rs b/crates/ui2/src/to_extract/editor_pane.rs index fd21e81242..bd34c22805 100644 --- a/crates/ui2/src/to_extract/editor_pane.rs +++ b/crates/ui2/src/to_extract/editor_pane.rs @@ -5,7 +5,7 @@ use gpui::{Div, Render, View, VisualContext}; use crate::prelude::*; use crate::{ hello_world_rust_editor_with_status_example, v_stack, Breadcrumb, Buffer, BufferSearch, Icon, - IconButton, IconColor, Symbol, Tab, TabBar, Toolbar, + IconButton, Symbol, Tab, TabBar, TextColor, Toolbar, }; #[derive(Clone)] @@ -60,12 +60,12 @@ impl Render for EditorPane { Toolbar::new() .left_item(Breadcrumb::new(self.path.clone(), self.symbols.clone())) .right_items(vec![ - IconButton::new("toggle_inlay_hints", Icon::InlayHint), + IconButton::::new("toggle_inlay_hints", Icon::InlayHint), IconButton::::new("buffer_search", Icon::MagnifyingGlass) .when(self.is_buffer_search_open, |this| { - this.color(IconColor::Accent) + this.color(TextColor::Accent) }) - .on_click(|editor, cx| { + .on_click(|editor: &mut Self, cx| { editor.toggle_buffer_search(cx); }), IconButton::new("inline_assist", Icon::MagicWand), diff --git a/crates/ui2/src/to_extract/notifications_panel.rs b/crates/ui2/src/to_extract/notifications_panel.rs index a7854107b1..f56194fc47 100644 --- a/crates/ui2/src/to_extract/notifications_panel.rs +++ b/crates/ui2/src/to_extract/notifications_panel.rs @@ -1,7 +1,7 @@ use crate::{ h_stack, prelude::*, static_new_notification_items_2, utils::naive_format_distance_from_now, - v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LabelColor, - LineHeightStyle, ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, UnreadIndicator, + v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LineHeightStyle, + ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, TextColor, UnreadIndicator, }; use gpui::prelude::*; @@ -47,7 +47,7 @@ impl NotificationsPanel { .border_color(cx.theme().colors().border_variant) .child( Label::new("Search...") - .color(LabelColor::Placeholder) + .color(TextColor::Placeholder) .line_height_style(LineHeightStyle::UILabel), ), ) @@ -251,7 +251,7 @@ impl Notification { if let Some(icon) = icon { meta_el = meta_el.child(IconElement::new(icon.clone())); } - meta_el.child(Label::new(text.clone()).color(LabelColor::Muted)) + meta_el.child(Label::new(text.clone()).color(TextColor::Muted)) }) .collect::>(), ) @@ -310,7 +310,7 @@ impl Notification { true, true, )) - .color(LabelColor::Muted), + .color(TextColor::Muted), ) .child(self.render_meta_items(cx)), ) @@ -320,11 +320,11 @@ impl Notification { // Show the taken_message (Some(_), Some(action_taken)) => h_stack() .children(action_taken.taken_message.0.map(|icon| { - IconElement::new(icon).color(crate::IconColor::Muted) + IconElement::new(icon).color(crate::TextColor::Muted) })) .child( Label::new(action_taken.taken_message.1.clone()) - .color(LabelColor::Muted), + .color(TextColor::Muted), ), // Show the actions (Some(actions), None) => { diff --git a/crates/ui2/src/to_extract/status_bar.rs b/crates/ui2/src/to_extract/status_bar.rs index 34a5993e69..eee96ecad8 100644 --- a/crates/ui2/src/to_extract/status_bar.rs +++ b/crates/ui2/src/to_extract/status_bar.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use crate::prelude::*; -use crate::{Button, Icon, IconButton, IconColor, ToolDivider, Workspace}; +use crate::{Button, Icon, IconButton, TextColor, ToolDivider, Workspace}; #[derive(Default, PartialEq)] pub enum Tool { @@ -110,18 +110,18 @@ impl StatusBar { .child( IconButton::::new("project_panel", Icon::FileTree) .when(workspace.is_project_panel_open(), |this| { - this.color(IconColor::Accent) + this.color(TextColor::Accent) }) - .on_click(|workspace, cx| { + .on_click(|workspace: &mut Workspace, cx| { workspace.toggle_project_panel(cx); }), ) .child( IconButton::::new("collab_panel", Icon::Hash) .when(workspace.is_collab_panel_open(), |this| { - this.color(IconColor::Accent) + this.color(TextColor::Accent) }) - .on_click(|workspace, cx| { + .on_click(|workspace: &mut Workspace, cx| { workspace.toggle_collab_panel(); }), ) @@ -174,27 +174,27 @@ impl StatusBar { .child( IconButton::::new("terminal", Icon::Terminal) .when(workspace.is_terminal_open(), |this| { - this.color(IconColor::Accent) + this.color(TextColor::Accent) }) - .on_click(|workspace, cx| { + .on_click(|workspace: &mut Workspace, cx| { workspace.toggle_terminal(cx); }), ) .child( IconButton::::new("chat_panel", Icon::MessageBubbles) .when(workspace.is_chat_panel_open(), |this| { - this.color(IconColor::Accent) + this.color(TextColor::Accent) }) - .on_click(|workspace, cx| { + .on_click(|workspace: &mut Workspace, cx| { workspace.toggle_chat_panel(cx); }), ) .child( IconButton::::new("assistant_panel", Icon::Ai) .when(workspace.is_assistant_panel_open(), |this| { - this.color(IconColor::Accent) + this.color(TextColor::Accent) }) - .on_click(|workspace, cx| { + .on_click(|workspace: &mut Workspace, cx| { workspace.toggle_assistant_panel(cx); }), ), diff --git a/crates/ui2/src/to_extract/title_bar.rs b/crates/ui2/src/to_extract/title_bar.rs index 87d7dd4146..1a106cbf7a 100644 --- a/crates/ui2/src/to_extract/title_bar.rs +++ b/crates/ui2/src/to_extract/title_bar.rs @@ -6,8 +6,8 @@ use gpui::{Div, Render, View, VisualContext}; use crate::prelude::*; use crate::settings::user_settings; use crate::{ - Avatar, Button, Icon, IconButton, IconColor, MicStatus, PlayerStack, PlayerWithCallStatus, - ScreenShareStatus, ToolDivider, TrafficLights, + Avatar, Button, Icon, IconButton, MicStatus, PlayerStack, PlayerWithCallStatus, + ScreenShareStatus, TextColor, ToolDivider, TrafficLights, }; #[derive(Clone)] @@ -152,21 +152,25 @@ impl Render for TitleBar { .gap_1() .child( IconButton::::new("toggle_mic_status", Icon::Mic) - .when(self.is_mic_muted(), |this| this.color(IconColor::Error)) - .on_click(|title_bar, cx| title_bar.toggle_mic_status(cx)), + .when(self.is_mic_muted(), |this| this.color(TextColor::Error)) + .on_click(|title_bar: &mut TitleBar, cx| { + title_bar.toggle_mic_status(cx) + }), ) .child( IconButton::::new("toggle_deafened", Icon::AudioOn) - .when(self.is_deafened, |this| this.color(IconColor::Error)) - .on_click(|title_bar, cx| title_bar.toggle_deafened(cx)), + .when(self.is_deafened, |this| this.color(TextColor::Error)) + .on_click(|title_bar: &mut TitleBar, cx| { + title_bar.toggle_deafened(cx) + }), ) .child( IconButton::::new("toggle_screen_share", Icon::Screen) .when( self.screen_share_status == ScreenShareStatus::Shared, - |this| this.color(IconColor::Accent), + |this| this.color(TextColor::Accent), ) - .on_click(|title_bar, cx| { + .on_click(|title_bar: &mut TitleBar, cx| { title_bar.toggle_screen_share_status(cx) }), ), diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 5dc6b8b692..67ecc16165 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -25,7 +25,7 @@ use std::{ }, }; use ui::v_stack; -use ui::{prelude::*, Icon, IconButton, IconColor, IconElement, TextTooltip}; +use ui::{prelude::*, Icon, IconButton, IconElement, TextColor, TextTooltip}; use util::truncate_and_remove_front; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -1430,13 +1430,13 @@ impl Pane { Some( IconElement::new(Icon::ExclamationTriangle) .size(ui::IconSize::Small) - .color(IconColor::Warning), + .color(TextColor::Warning), ) } else if item.is_dirty(cx) { Some( IconElement::new(Icon::ExclamationTriangle) .size(ui::IconSize::Small) - .color(IconColor::Info), + .color(TextColor::Info), ) } else { None diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index bba86b6beb..f0b40ab883 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -44,7 +44,7 @@ use gpui::{ }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; -use language2::LanguageRegistry; +use language2::{LanguageRegistry, Rope}; use lazy_static::lazy_static; pub use modal_layer::*; use node_runtime::NodeRuntime; @@ -68,10 +68,10 @@ use std::{ }; use theme2::ActiveTheme; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; -use ui::{h_stack, Button, ButtonVariant, Label, LabelColor}; +use ui::{h_stack, Button, ButtonVariant, KeyBinding, Label, TextColor, TextTooltip}; use util::ResultExt; use uuid::Uuid; -use workspace_settings::{AutosaveSetting, WorkspaceSettings}; +pub use workspace_settings::{AutosaveSetting, WorkspaceSettings}; lazy_static! { static ref ZED_WINDOW_SIZE: Option> = env::var("ZED_WINDOW_SIZE") @@ -1044,29 +1044,29 @@ impl Workspace { // self.titlebar_item.clone() // } - // /// Call the given callback with a workspace whose project is local. - // /// - // /// If the given workspace has a local project, then it will be passed - // /// to the callback. Otherwise, a new empty window will be created. - // pub fn with_local_workspace( - // &mut self, - // cx: &mut ViewContext, - // callback: F, - // ) -> Task> - // where - // T: 'static, - // F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, - // { - // if self.project.read(cx).is_local() { - // Task::Ready(Some(Ok(callback(self, cx)))) - // } else { - // let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); - // cx.spawn(|_vh, mut cx| async move { - // let (workspace, _) = task.await; - // workspace.update(&mut cx, callback) - // }) - // } - // } + /// Call the given callback with a workspace whose project is local. + /// + /// If the given workspace has a local project, then it will be passed + /// to the callback. Otherwise, a new empty window will be created. + pub fn with_local_workspace( + &mut self, + cx: &mut ViewContext, + callback: F, + ) -> Task> + where + T: 'static, + F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, + { + if self.project.read(cx).is_local() { + Task::Ready(Some(Ok(callback(self, cx)))) + } else { + let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); + cx.spawn(|_vh, mut cx| async move { + let (workspace, _) = task.await?; + workspace.update(&mut cx, callback) + }) + } + } pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator> { self.project.read(cx).worktrees() @@ -2464,17 +2464,50 @@ impl Workspace { h_stack() // TODO - Add player menu .child( - Button::new("player") - .variant(ButtonVariant::Ghost) - .color(Some(LabelColor::Player(0))), + div() + .id("project_owner_indicator") + .child( + Button::new("player") + .variant(ButtonVariant::Ghost) + .color(Some(TextColor::Player(0))), + ) + .tooltip(move |_, cx| { + cx.build_view(|cx| TextTooltip::new("Toggle following")) + }), ) // TODO - Add project menu - .child(Button::new("project_name").variant(ButtonVariant::Ghost)) + .child( + div() + .id("titlebar_project_menu_button") + .child(Button::new("project_name").variant(ButtonVariant::Ghost)) + .tooltip(move |_, cx| { + cx.build_view(|cx| TextTooltip::new("Recent Projects")) + }), + ) // TODO - Add git menu .child( - Button::new("branch_name") - .variant(ButtonVariant::Ghost) - .color(Some(LabelColor::Muted)), + div() + .id("titlebar_git_menu_button") + .child( + Button::new("branch_name") + .variant(ButtonVariant::Ghost) + .color(Some(TextColor::Muted)), + ) + .tooltip(move |_, cx| { + // todo!() Replace with real action. + #[gpui::action] + struct NoAction {} + + cx.build_view(|cx| { + TextTooltip::new("Recent Branches") + .key_binding(KeyBinding::new(gpui::KeyBinding::new( + "cmd-b", + NoAction {}, + None, + ))) + .meta("Only local branches shown") + }) + }), ), ) // self.titlebar_item .child(h_stack().child(Label::new("Right side titlebar item"))) @@ -3426,13 +3459,14 @@ impl Workspace { pub fn register_action( &mut self, callback: impl Fn(&mut Self, &A, &mut ViewContext) + 'static, - ) { + ) -> &mut Self { let callback = Arc::new(callback); self.workspace_actions.push(Box::new(move |div| { let callback = callback.clone(); div.on_action(move |workspace, event, cx| (callback.clone())(workspace, event, cx)) })); + self } fn add_workspace_actions_listeners(&self, mut div: Div) -> Div { @@ -4371,32 +4405,32 @@ pub fn open_new( }) } -// pub fn create_and_open_local_file( -// path: &'static Path, -// cx: &mut ViewContext, -// default_content: impl 'static + Send + FnOnce() -> Rope, -// ) -> Task>> { -// cx.spawn(|workspace, mut cx| async move { -// let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?; -// if !fs.is_file(path).await { -// fs.create_file(path, Default::default()).await?; -// fs.save(path, &default_content(), Default::default()) -// .await?; -// } +pub fn create_and_open_local_file( + path: &'static Path, + cx: &mut ViewContext, + default_content: impl 'static + Send + FnOnce() -> Rope, +) -> Task>> { + cx.spawn(|workspace, mut cx| async move { + let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?; + if !fs.is_file(path).await { + fs.create_file(path, Default::default()).await?; + fs.save(path, &default_content(), Default::default()) + .await?; + } -// let mut items = workspace -// .update(&mut cx, |workspace, cx| { -// workspace.with_local_workspace(cx, |workspace, cx| { -// workspace.open_paths(vec![path.to_path_buf()], false, cx) -// }) -// })? -// .await? -// .await; + let mut items = workspace + .update(&mut cx, |workspace, cx| { + workspace.with_local_workspace(cx, |workspace, cx| { + workspace.open_paths(vec![path.to_path_buf()], false, cx) + }) + })? + .await? + .await; -// let item = items.pop().flatten(); -// item.ok_or_else(|| anyhow!("path {path:?} is not a file"))? -// }) -// } + let item = items.pop().flatten(); + item.ok_or_else(|| anyhow!("path {path:?} is not a file"))? + }) +} // pub fn join_remote_project( // project_id: u64, diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index b816b59661..a21d113cad 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -43,7 +43,7 @@ fsevent = { path = "../fsevent" } fuzzy = { path = "../fuzzy" } go_to_line = { package = "go_to_line2", path = "../go_to_line2" } gpui = { package = "gpui2", path = "../gpui2" } -install_cli = { path = "../install_cli" } +install_cli = { package = "install_cli2", path = "../install_cli2" } journal = { package = "journal2", path = "../journal2" } language = { package = "language2", path = "../language2" } # language_selector = { path = "../language_selector" } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index f32aaf7503..20fc18e6ed 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -50,14 +50,16 @@ use util::{ use uuid::Uuid; use workspace::{AppState, WorkspaceStore}; use zed2::{ - build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace, - languages, Assets, IsOnlyInstance, OpenListener, OpenRequest, + build_window_options, ensure_only_instance, handle_cli_connection, init_zed_actions, + initialize_workspace, languages, Assets, IsOnlyInstance, OpenListener, OpenRequest, }; mod open_listener; fn main() { menu::init(); + zed_actions::init(); + let http = http::client(); init_paths(); init_logger(); @@ -96,7 +98,7 @@ fn main() { let (listener, mut open_rx) = OpenListener::new(); let listener = Arc::new(listener); let open_listener = listener.clone(); - app.on_open_urls(move |urls, _| open_listener.open_urls(urls)); + app.on_open_urls(move |urls, _| open_listener.open_urls(&urls)); app.on_reopen(move |_cx| { // todo!("workspace") // if cx.has_global::>() { @@ -111,6 +113,8 @@ fn main() { app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); + cx.set_global(listener.clone()); + load_embedded_fonts(cx); let mut store = SettingsStore::default(); @@ -209,12 +213,13 @@ fn main() { // zed::init(&app_state, cx); // cx.set_menus(menus::menus()); + init_zed_actions(app_state.clone(), cx); if stdout_is_a_pty() { cx.activate(true); let urls = collect_url_args(); if !urls.is_empty() { - listener.open_urls(urls) + listener.open_urls(&urls) } } else { upload_previous_panics(http.clone(), cx); @@ -224,7 +229,7 @@ fn main() { if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() && !listener.triggered.load(Ordering::Acquire) { - listener.open_urls(collect_url_args()) + listener.open_urls(&collect_url_args()) } } diff --git a/crates/zed2/src/open_listener.rs b/crates/zed2/src/open_listener.rs index f4219f199d..4c961a2b31 100644 --- a/crates/zed2/src/open_listener.rs +++ b/crates/zed2/src/open_listener.rs @@ -54,7 +54,7 @@ impl OpenListener { ) } - pub fn open_urls(&self, urls: Vec) { + pub fn open_urls(&self, urls: &[String]) { self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) @@ -101,7 +101,7 @@ impl OpenListener { None } - fn handle_file_urls(&self, urls: Vec) -> Option { + fn handle_file_urls(&self, urls: &[String]) -> Option { let paths: Vec<_> = urls .iter() .flat_map(|url| url.strip_prefix("file://")) diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 4385d966ef..73faeaaaf4 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -1,5 +1,5 @@ -#![allow(unused_variables, dead_code, unused_mut)] -// todo!() this is to make transition easier. +#![allow(unused_variables, unused_mut)] +//todo!() mod assets; pub mod languages; @@ -7,18 +7,56 @@ mod only_instance; mod open_listener; pub use assets::*; +use collections::VecDeque; +use editor::{Editor, MultiBuffer}; use gpui::{ - point, px, AppContext, AsyncWindowContext, Task, TitlebarOptions, WeakView, WindowBounds, - WindowKind, WindowOptions, + actions, point, px, AppContext, AsyncWindowContext, Context, PromptLevel, Task, + TitlebarOptions, ViewContext, VisualContext, WeakView, WindowBounds, WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; -use anyhow::Result; +use anyhow::{anyhow, Context as _, Result}; use project_panel::ProjectPanel; -use std::sync::Arc; +use settings::{initial_local_settings_content, Settings}; +use std::{borrow::Cow, ops::Deref, sync::Arc}; +use util::{ + asset_str, + channel::ReleaseChannel, + paths::{self, LOCAL_SETTINGS_RELATIVE_PATH}, + ResultExt, +}; use uuid::Uuid; -use workspace::{dock::PanelHandle as _, AppState, Workspace}; +use workspace::{ + create_and_open_local_file, dock::PanelHandle, + notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile, + NewWindow, Workspace, WorkspaceSettings, +}; +use zed_actions::{OpenBrowser, OpenZedURL}; + +actions!( + About, + DebugElements, + DecreaseBufferFontSize, + Hide, + HideOthers, + IncreaseBufferFontSize, + Minimize, + OpenDefaultKeymap, + OpenDefaultSettings, + OpenKeymap, + OpenLicenses, + OpenLocalSettings, + OpenLog, + OpenSettings, + OpenTelemetryLog, + Quit, + ResetBufferFontSize, + ResetDatabase, + ShowAll, + ToggleFullScreen, + Zoom, +); pub fn build_window_options( bounds: Option, @@ -48,6 +86,211 @@ pub fn build_window_options( } } +pub fn init_zed_actions(app_state: Arc, cx: &mut AppContext) { + cx.observe_new_views(move |workspace: &mut Workspace, _cx| { + workspace + .register_action(about) + .register_action(|_, _: &Hide, cx| { + cx.hide(); + }) + .register_action(|_, _: &HideOthers, cx| { + cx.hide_other_apps(); + }) + .register_action(|_, _: &ShowAll, cx| { + cx.unhide_other_apps(); + }) + .register_action(|_, _: &Minimize, cx| { + cx.minimize_window(); + }) + .register_action(|_, _: &Zoom, cx| { + cx.zoom_window(); + }) + .register_action(|_, _: &ToggleFullScreen, cx| { + cx.toggle_full_screen(); + }) + .register_action(quit) + .register_action(|_, action: &OpenZedURL, cx| { + cx.global::>() + .open_urls(&[action.url.clone()]) + }) + .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url)) + //todo!(buffer font size) + // cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { + // theme::adjust_font_size(cx, |size| *size += 1.0) + // }); + // cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { + // theme::adjust_font_size(cx, |size| *size -= 1.0) + // }); + // cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx)); + .register_action(|_, _: &install_cli::Install, cx| { + cx.spawn(|_, cx| async move { + install_cli::install_cli(cx.deref()) + .await + .context("error creating CLI symlink") + }) + .detach_and_log_err(cx); + }) + .register_action(|workspace, _: &OpenLog, cx| { + open_log_file(workspace, cx); + }) + .register_action(|workspace, _: &OpenLicenses, cx| { + open_bundled_file( + workspace, + asset_str::("licenses.md"), + "Open Source License Attribution", + "Markdown", + cx, + ); + }) + .register_action( + move |workspace: &mut Workspace, + _: &OpenTelemetryLog, + cx: &mut ViewContext| { + open_telemetry_log_file(workspace, cx); + }, + ) + .register_action( + move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext| { + create_and_open_local_file(&paths::KEYMAP, cx, Default::default) + .detach_and_log_err(cx); + }, + ) + .register_action( + move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { + create_and_open_local_file(&paths::SETTINGS, cx, || { + settings::initial_user_settings_content().as_ref().into() + }) + .detach_and_log_err(cx); + }, + ) + .register_action(open_local_settings_file) + .register_action( + move |workspace: &mut Workspace, + _: &OpenDefaultKeymap, + cx: &mut ViewContext| { + open_bundled_file( + workspace, + settings::default_keymap(), + "Default Key Bindings", + "JSON", + cx, + ); + }, + ) + .register_action( + move |workspace: &mut Workspace, + _: &OpenDefaultSettings, + cx: &mut ViewContext| { + open_bundled_file( + workspace, + settings::default_settings(), + "Default Settings", + "JSON", + cx, + ); + }, + ) + //todo!() + // cx.add_action({ + // move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { + // let app_state = workspace.app_state().clone(); + // let markdown = app_state.languages.language_for_name("JSON"); + // let window = cx.window(); + // cx.spawn(|workspace, mut cx| async move { + // let markdown = markdown.await.log_err(); + // let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| { + // anyhow!("could not debug elements for window {}", window.id()) + // })?) + // .unwrap(); + // workspace + // .update(&mut cx, |workspace, cx| { + // workspace.with_local_workspace(cx, move |workspace, cx| { + // let project = workspace.project().clone(); + // let buffer = project + // .update(cx, |project, cx| { + // project.create_buffer(&content, markdown, cx) + // }) + // .expect("creating buffers on a local workspace always succeeds"); + // let buffer = cx.add_model(|cx| { + // MultiBuffer::singleton(buffer, cx) + // .with_title("Debug Elements".into()) + // }); + // workspace.add_item( + // Box::new(cx.add_view(|cx| { + // Editor::for_multibuffer(buffer, Some(project.clone()), cx) + // })), + // cx, + // ); + // }) + // })? + // .await + // }) + // .detach_and_log_err(cx); + // } + // }); + // .register_action( + // |workspace: &mut Workspace, + // _: &project_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); + // }, + // ); + // cx.add_action( + // |workspace: &mut Workspace, + // _: &collab_ui::collab_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); + // }, + // ); + // cx.add_action( + // |workspace: &mut Workspace, + // _: &collab_ui::chat_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); + // }, + // ); + // cx.add_action( + // |workspace: &mut Workspace, + // _: &collab_ui::notification_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); + // }, + // ); + // cx.add_action( + // |workspace: &mut Workspace, + // _: &terminal_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); + // }, + // ); + .register_action({ + let app_state = Arc::downgrade(&app_state); + move |_, _: &NewWindow, cx| { + if let Some(app_state) = app_state.upgrade() { + open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + } + }) + .register_action({ + let app_state = Arc::downgrade(&app_state); + move |_, _: &NewFile, cx| { + if let Some(app_state) = app_state.upgrade() { + open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + } + }); + //todo!() + // load_default_keymap(cx); + }) + .detach(); +} + pub fn initialize_workspace( workspace_handle: WeakView, was_deserialized: bool, @@ -195,3 +438,280 @@ pub fn initialize_workspace( Ok(()) }) } + +fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { + let app_name = cx.global::().display_name(); + let version = env!("CARGO_PKG_VERSION"); + let prompt = cx.prompt(PromptLevel::Info, &format!("{app_name} {version}"), &["OK"]); + cx.foreground_executor() + .spawn(async { + prompt.await.ok(); + }) + .detach(); +} + +fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext) { + let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit; + cx.spawn(|_, mut cx| async move { + let mut workspace_windows = cx.update(|_, cx| { + cx.windows() + .into_iter() + .filter_map(|window| window.downcast::()) + .collect::>() + })?; + + // If multiple windows have unsaved changes, and need a save prompt, + // prompt in the active window before switching to a different window. + cx.update(|_, cx| { + workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false)); + }) + .log_err(); + + if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) { + let answer = cx + .update(|_, cx| { + cx.prompt( + PromptLevel::Info, + "Are you sure you want to quit?", + &["Quit", "Cancel"], + ) + }) + .log_err(); + + if let Some(mut answer) = answer { + let answer = answer.await.ok(); + if answer != Some(0) { + return Ok(()); + } + } + } + + // If the user cancels any save prompt, then keep the app open. + for window in workspace_windows { + if let Some(should_close) = window + .update(&mut cx, |workspace, cx| { + workspace.prepare_to_close(true, cx) + }) + .log_err() + { + if !should_close.await? { + return Ok(()); + } + } + } + cx.update(|_, cx| { + cx.quit(); + })?; + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); +} + +fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { + const MAX_LINES: usize = 1000; + workspace + .with_local_workspace(cx, move |workspace, cx| { + let fs = workspace.app_state().fs.clone(); + cx.spawn(|workspace, mut cx| async move { + let (old_log, new_log) = + futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG)); + + let mut lines = VecDeque::with_capacity(MAX_LINES); + for line in old_log + .iter() + .flat_map(|log| log.lines()) + .chain(new_log.iter().flat_map(|log| log.lines())) + { + if lines.len() == MAX_LINES { + lines.pop_front(); + } + lines.push_back(line); + } + let log = lines + .into_iter() + .flat_map(|line| [line, "\n"]) + .collect::(); + + workspace + .update(&mut cx, |workspace, cx| { + let project = workspace.project().clone(); + let buffer = project + .update(cx, |project, cx| project.create_buffer("", None, cx)) + .expect("creating buffers on a local workspace always succeeds"); + buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx)); + + let buffer = cx.build_model(|cx| { + MultiBuffer::singleton(buffer, cx).with_title("Log".into()) + }); + workspace.add_item( + Box::new(cx.build_view(|cx| { + Editor::for_multibuffer(buffer, Some(project), cx) + })), + cx, + ); + }) + .log_err(); + }) + .detach(); + }) + .detach(); +} + +fn open_local_settings_file( + workspace: &mut Workspace, + _: &OpenLocalSettings, + cx: &mut ViewContext, +) { + let project = workspace.project().clone(); + let worktree = project + .read(cx) + .visible_worktrees(cx) + .find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree)); + if let Some(worktree) = worktree { + let tree_id = worktree.read(cx).id(); + cx.spawn(|workspace, mut cx| async move { + let file_path = &*LOCAL_SETTINGS_RELATIVE_PATH; + + if let Some(dir_path) = file_path.parent() { + if worktree.update(&mut cx, |tree, _| tree.entry_for_path(dir_path).is_none())? { + project + .update(&mut cx, |project, cx| { + project.create_entry((tree_id, dir_path), true, cx) + })? + .ok_or_else(|| anyhow!("worktree was removed"))? + .await?; + } + } + + if worktree.update(&mut cx, |tree, _| tree.entry_for_path(file_path).is_none())? { + project + .update(&mut cx, |project, cx| { + project.create_entry((tree_id, file_path), false, cx) + })? + .ok_or_else(|| anyhow!("worktree was removed"))? + .await?; + } + + let editor = workspace + .update(&mut cx, |workspace, cx| { + workspace.open_path((tree_id, file_path), None, true, cx) + })? + .await? + .downcast::() + .ok_or_else(|| anyhow!("unexpected item type"))?; + + editor + .downgrade() + .update(&mut cx, |editor, cx| { + if let Some(buffer) = editor.buffer().read(cx).as_singleton() { + if buffer.read(cx).is_empty() { + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, initial_local_settings_content())], None, cx) + }); + } + } + }) + .ok(); + + anyhow::Ok(()) + }) + .detach(); + } else { + workspace.show_notification(0, cx, |cx| { + cx.build_view(|_| MessageNotification::new("This project has no folders open.")) + }) + } +} + +fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { + workspace.with_local_workspace(cx, move |workspace, cx| { + let app_state = workspace.app_state().clone(); + cx.spawn(|workspace, mut cx| async move { + async fn fetch_log_string(app_state: &Arc) -> Option { + let path = app_state.client.telemetry().log_file_path()?; + app_state.fs.load(&path).await.log_err() + } + + let log = fetch_log_string(&app_state).await.unwrap_or_else(|| "// No data has been collected yet".to_string()); + + const MAX_TELEMETRY_LOG_LEN: usize = 5 * 1024 * 1024; + let mut start_offset = log.len().saturating_sub(MAX_TELEMETRY_LOG_LEN); + if let Some(newline_offset) = log[start_offset..].find('\n') { + start_offset += newline_offset + 1; + } + let log_suffix = &log[start_offset..]; + let json = app_state.languages.language_for_name("JSON").await.log_err(); + + workspace.update(&mut cx, |workspace, cx| { + let project = workspace.project().clone(); + let buffer = project + .update(cx, |project, cx| project.create_buffer("", None, cx)) + .expect("creating buffers on a local workspace always succeeds"); + buffer.update(cx, |buffer, cx| { + buffer.set_language(json, cx); + buffer.edit( + [( + 0..0, + concat!( + "// Zed collects anonymous usage data to help us understand how people are using the app.\n", + "// Telemetry can be disabled via the `settings.json` file.\n", + "// Here is the data that has been reported for the current session:\n", + "\n" + ), + )], + None, + cx, + ); + buffer.edit([(buffer.len()..buffer.len(), log_suffix)], None, cx); + }); + + let buffer = cx.build_model(|cx| { + MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into()) + }); + workspace.add_item( + Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))), + cx, + ); + }).log_err()?; + + Some(()) + }) + .detach(); + }).detach(); +} + +fn open_bundled_file( + workspace: &mut Workspace, + text: Cow<'static, str>, + title: &'static str, + language: &'static str, + cx: &mut ViewContext, +) { + let language = workspace.app_state().languages.language_for_name(language); + cx.spawn(|workspace, mut cx| async move { + let language = language.await.log_err(); + workspace + .update(&mut cx, |workspace, cx| { + workspace.with_local_workspace(cx, |workspace, cx| { + let project = workspace.project(); + let buffer = project.update(cx, move |project, cx| { + project + .create_buffer(text.as_ref(), language, cx) + .expect("creating buffers on a local workspace always succeeds") + }); + let buffer = cx.build_model(|cx| { + MultiBuffer::singleton(buffer, cx).with_title(title.into()) + }); + workspace.add_item( + Box::new(cx.build_view(|cx| { + Editor::for_multibuffer(buffer, Some(project.clone()), cx) + })), + cx, + ); + }) + })? + .await + }) + .detach_and_log_err(cx); +} diff --git a/crates/zed_actions2/src/lib.rs b/crates/zed_actions2/src/lib.rs index 090352b2cc..7f0c19853e 100644 --- a/crates/zed_actions2/src/lib.rs +++ b/crates/zed_actions2/src/lib.rs @@ -1,33 +1,19 @@ -use gpui::{action, actions}; +use gpui::action; -actions!( - About, - DebugElements, - DecreaseBufferFontSize, - Hide, - HideOthers, - IncreaseBufferFontSize, - Minimize, - OpenDefaultKeymap, - OpenDefaultSettings, - OpenKeymap, - OpenLicenses, - OpenLocalSettings, - OpenLog, - OpenSettings, - OpenTelemetryLog, - Quit, - ResetBufferFontSize, - ResetDatabase, - ShowAll, - ToggleFullScreen, - Zoom, -); +// If the zed binary doesn't use anything in this crate, it will be optimized away +// and the actions won't initialize. So we just provide an empty initialization function +// to be called from main. +// +// These may provide relevant context: +// https://github.com/rust-lang/rust/issues/47384 +// https://github.com/mmastrac/rust-ctor/issues/280 +pub fn init() {} #[action] pub struct OpenBrowser { pub url: String, } + #[action] pub struct OpenZedURL { pub url: String,