From 9401ef223d97f57ce33252675ac169b353213767 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 21 Feb 2023 18:16:47 -0800 Subject: [PATCH 01/45] Add welcome crate and associated types --- Cargo.lock | 15 +++++++++++ Cargo.toml | 1 + crates/welcome/Cargo.toml | 21 +++++++++++++++ crates/welcome/src/welcome.rs | 51 +++++++++++++++++++++++++++++++++++ crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 1 + 6 files changed, 90 insertions(+) create mode 100644 crates/welcome/Cargo.toml create mode 100644 crates/welcome/src/welcome.rs diff --git a/Cargo.lock b/Cargo.lock index 2e7997458ed00185d5d1b48f2943eb0e657a1b41..607f47b5cf18944ca0c21de72c1047e6c328acde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8013,6 +8013,20 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" +[[package]] +name = "welcome" +version = "0.1.0" +dependencies = [ + "anyhow", + "gpui", + "log", + "project", + "settings", + "theme", + "util", + "workspace", +] + [[package]] name = "wepoll-ffi" version = "0.1.2" @@ -8459,6 +8473,7 @@ dependencies = [ "util", "uuid 1.2.2", "vim", + "welcome", "workspace", ] diff --git a/Cargo.toml b/Cargo.toml index c74a76cccefe6c6de610b30c264a81e74b2654df..feb80633c4f48b63562f5adda2a22cd0062e94c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "crates/util", "crates/vim", "crates/workspace", + "crates/welcome", "crates/zed", ] default-members = ["crates/zed"] diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6ac312c37ffbcdf21c9ec5583dc306b3da29ecc2 --- /dev/null +++ b/crates/welcome/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "welcome" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/welcome.rs" + +[features] +test-support = [] + +[dependencies] +anyhow = "1.0.38" +log = "0.4" +gpui = { path = "../gpui" } +project = { path = "../project" } +settings = { path = "../settings" } +theme = { path = "../theme" } +util = { path = "../util" } +workspace = { path = "../workspace" } \ No newline at end of file diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs new file mode 100644 index 0000000000000000000000000000000000000000..1deede76119bab32e193551b2c12f22455765085 --- /dev/null +++ b/crates/welcome/src/welcome.rs @@ -0,0 +1,51 @@ +use gpui::{ + actions, + elements::{Flex, Label, ParentElement}, + Element, Entity, MutableAppContext, View, +}; +use settings::Settings; +use workspace::{item::Item, Workspace}; + +actions!(welcome, [ShowWelcome]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(|workspace: &mut Workspace, _: &ShowWelcome, cx| { + let welcome_page = cx.add_view(|_cx| WelcomePage); + workspace.add_item(Box::new(welcome_page), cx) + }) +} + +struct WelcomePage; + +impl Entity for WelcomePage { + type Event = (); +} + +impl View for WelcomePage { + fn ui_name() -> &'static str { + "WelcomePage" + } + + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { + let theme = &cx.global::().theme; + Label::new("Welcome page", theme.editor.hover_popover.prose.clone()).boxed() + } +} + +impl Item for WelcomePage { + fn tab_content( + &self, + _detail: Option, + style: &theme::Tab, + _cx: &gpui::AppContext, + ) -> gpui::ElementBox { + Flex::row() + .with_child( + Label::new("Welcome to Zed!", style.label.clone()) + .aligned() + .contained() + .boxed(), + ) + .boxed() + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 19c9a3d727704176f7d36b216719a3584d8c4a8d..d3b6e4810fa497cc41394d796a718e863af6f4dc 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -58,6 +58,7 @@ theme_testbench = { path = "../theme_testbench" } util = { path = "../util" } vim = { path = "../vim" } workspace = { path = "../workspace" } +welcome = { path = "../welcome" } anyhow = "1.0.38" async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } async-tar = "0.4.2" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b9e3ed550beb434c7da92138308131f09f3c8ac2..cc930774d58bb2a7d5f8dbc5912955eb6f3c736a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -189,6 +189,7 @@ fn main() { zed::init(&app_state, cx); collab_ui::init(app_state.clone(), cx); feedback::init(app_state.clone(), cx); + welcome::init(cx); cx.set_menus(menus::menus()); From a0637a769ca0fde5af883c4004a7e164fd71e8ee Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 22 Feb 2023 12:01:59 -0800 Subject: [PATCH 02/45] WIP --- crates/welcome/src/welcome.rs | 34 ++++++++++++++++++++++++------- crates/workspace/src/workspace.rs | 7 ++++--- crates/zed/src/main.rs | 4 ++-- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 1deede76119bab32e193551b2c12f22455765085..face85c4b6eb49fe6af491eba607f7b8e304a393 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,15 +1,13 @@ use gpui::{ - actions, - elements::{Flex, Label, ParentElement}, + color::Color, + elements::{Flex, Label, ParentElement, Svg}, Element, Entity, MutableAppContext, View, }; use settings::Settings; -use workspace::{item::Item, Workspace}; - -actions!(welcome, [ShowWelcome]); +use workspace::{item::Item, Welcome, Workspace}; pub fn init(cx: &mut MutableAppContext) { - cx.add_action(|workspace: &mut Workspace, _: &ShowWelcome, cx| { + cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { let welcome_page = cx.add_view(|_cx| WelcomePage); workspace.add_item(Box::new(welcome_page), cx) }) @@ -28,7 +26,29 @@ impl View for WelcomePage { fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { let theme = &cx.global::().theme; - Label::new("Welcome page", theme.editor.hover_popover.prose.clone()).boxed() + + Flex::new(gpui::Axis::Vertical) + .with_children([ + Flex::new(gpui::Axis::Horizontal) + .with_children([ + Svg::new("icons/terminal_16.svg") + .with_color(Color::red()) + .constrained() + .with_width(100.) + .with_height(100.) + .aligned() + .contained() + .boxed(), + Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), + ]) + .boxed(), + Label::new( + "Code at the speed of thought", + theme.editor.hover_popover.prose.clone(), + ) + .boxed(), + ]) + .boxed() } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b9d80e7150fae064c2b34e80dee0507b7da22a2e..31960fbc1ec7751a147ecfcf788e26dd33ab1a0d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -118,7 +118,8 @@ actions!( NewTerminal, NewSearch, Feedback, - Restart + Restart, + Welcome ] ); @@ -198,7 +199,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { }); cx.add_global_action({ let app_state = Arc::downgrade(&app_state); - move |_: &NewFile, cx: &mut MutableAppContext| { + move |_: &Welcome, cx: &mut MutableAppContext| { if let Some(app_state) = app_state.upgrade() { open_new(&app_state, cx).detach(); } @@ -2865,7 +2866,7 @@ pub fn open_new(app_state: &Arc, cx: &mut MutableAppContext) -> Task<( workspace.update(&mut cx, |_, cx| { if opened_paths.is_empty() { - cx.dispatch_action(NewFile); + cx.dispatch_action(Welcome); } }) }) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index cc930774d58bb2a7d5f8dbc5912955eb6f3c736a..298e8ef8b9c50aff25f1b8bbf24c1b4f2559e37c 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -43,7 +43,7 @@ use theme::ThemeRegistry; use util::StaffMode; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, + self, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenPaths, Welcome, Workspace, }; use zed::{self, build_window_options, initialize_workspace, languages, menus}; @@ -260,7 +260,7 @@ async fn restore_or_create_workspace(mut cx: AsyncAppContext) { }); } else { cx.update(|cx| { - cx.dispatch_global_action(NewFile); + cx.dispatch_global_action(Welcome); }); } } From 416c79307695dc164ef2262a64f49c784a49fcc1 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 24 Feb 2023 14:31:59 -0800 Subject: [PATCH 03/45] Start on welcome experience settings --- Cargo.lock | 1 + crates/gpui/src/app.rs | 2 +- crates/settings/src/settings.rs | 180 ++++++++++++++++++++++++--- crates/settings/src/settings_file.rs | 50 ++------ crates/theme/src/theme.rs | 15 +++ crates/welcome/Cargo.toml | 1 + crates/welcome/src/welcome.rs | 107 ++++++++++++++-- styles/src/styleTree/app.ts | 2 + styles/src/styleTree/welcome.ts | 34 +++++ 9 files changed, 326 insertions(+), 66 deletions(-) create mode 100644 styles/src/styleTree/welcome.ts diff --git a/Cargo.lock b/Cargo.lock index 607f47b5cf18944ca0c21de72c1047e6c328acde..90da178c7bcb1afa8758f94bb7468983cb28d8d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8018,6 +8018,7 @@ name = "welcome" version = "0.1.0" dependencies = [ "anyhow", + "editor", "gpui", "log", "project", diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 31563010b723d88b4f834084fba91cb3ecd9a15c..0397032de89964e6a095039d315b614be2906102 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -5086,7 +5086,7 @@ impl From> for AnyWeakModelHandle { } } -#[derive(Debug)] +#[derive(Debug, Copy)] pub struct WeakViewHandle { window_id: usize, view_id: usize, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 6b51b06c9c0e4e3e8617c3bd26a58c7e902ed188..229e11ff3a22345b1be118ced67edbed8c3f3c7c 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -66,9 +66,18 @@ impl TelemetrySettings { pub fn metrics(&self) -> bool { self.metrics.unwrap() } + pub fn diagnostics(&self) -> bool { self.diagnostics.unwrap() } + + pub fn set_metrics(&mut self, value: bool) { + self.metrics = Some(value); + } + + pub fn set_diagnostics(&mut self, value: bool) { + self.diagnostics = Some(value); + } } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -679,7 +688,7 @@ pub fn settings_file_json_schema( /// Expects the key to be unquoted, and the value to be valid JSON /// (e.g. values should be unquoted for numbers and bools, quoted for strings) -pub fn write_top_level_setting( +pub fn write_settings_key( mut settings_content: String, top_level_key: &str, new_val: &str, @@ -786,11 +795,160 @@ pub fn parse_json_with_comments(content: &str) -> Result )?) } +pub fn update_settings_file( + old_text: String, + old_file_content: SettingsFileContent, + update: impl FnOnce(&mut SettingsFileContent), +) -> String { + let mut new_file_content = old_file_content.clone(); + update(&mut new_file_content); + + let old_json = to_json_object(old_file_content); + let new_json = to_json_object(new_file_content); + + // Find changed fields + let mut diffs = vec![]; + for (key, old_value) in old_json.iter() { + let new_value = new_json.get(key).unwrap(); + if old_value != new_value { + if matches!( + new_value, + &Value::Null | &Value::Object(_) | &Value::Array(_) + ) { + unimplemented!("We only support updating basic values at the top level"); + } + + let new_json = serde_json::to_string_pretty(new_value) + .expect("Could not serialize new json field to string"); + + diffs.push((key, new_json)); + } + } + + let mut new_text = old_text; + for (key, new_value) in diffs { + new_text = write_settings_key(new_text, key, &new_value) + } + new_text +} + +fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map { + let tmp = serde_json::to_value(settings_file).unwrap(); + match tmp { + Value::Object(map) => map, + _ => unreachable!("SettingsFileContent represents a JSON map"), + } +} + #[cfg(test)] mod tests { - use crate::write_top_level_setting; + use super::*; use unindent::Unindent; + fn assert_new_settings, S2: Into>( + old_json: S1, + update: fn(&mut SettingsFileContent), + expected_new_json: S2, + ) { + let old_json = old_json.into(); + let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap(); + let new_json = update_settings_file(old_json, old_content, update); + assert_eq!(new_json, expected_new_json.into()); + } + + #[test] + fn test_update_telemetry_setting_multiple_fields() { + assert_new_settings( + r#"{ + "telemetry": { + "metrics": false, + "diagnostics": false + } + }"# + .unindent(), + |settings| { + settings.telemetry.set_diagnostics(true); + settings.telemetry.set_metrics(true); + }, + r#"{ + "telemetry": { + "metrics": true, + "diagnostics": true + } + }"# + .unindent(), + ); + } + + #[test] + fn test_update_telemetry_setting_weird_formatting() { + assert_new_settings( + r#"{ + "telemetry": { "metrics": false, "diagnostics": true } + }"# + .unindent(), + |settings| settings.telemetry.set_diagnostics(false), + r#"{ + "telemetry": { "metrics": false, "diagnostics": false } + }"# + .unindent(), + ); + } + + #[test] + fn test_update_telemetry_setting_other_fields() { + assert_new_settings( + r#"{ + "telemetry": { + "metrics": false, + "diagnostics": true + } + }"# + .unindent(), + |settings| settings.telemetry.set_diagnostics(false), + r#"{ + "telemetry": { + "metrics": false, + "diagnostics": false + } + }"# + .unindent(), + ); + } + + #[test] + fn test_update_telemetry_setting_pre_existing() { + assert_new_settings( + r#"{ + "telemetry": { + "diagnostics": true + } + }"# + .unindent(), + |settings| settings.telemetry.set_diagnostics(false), + r#"{ + "telemetry": { + "diagnostics": false + } + }"# + .unindent(), + ); + } + + #[test] + fn test_update_telemetry_setting() { + assert_new_settings( + "{}", + |settings| settings.telemetry.set_diagnostics(true), + r#"{ + "telemetry": { + "diagnostics": true + } + }"# + .unindent(), + ); + } + #[test] fn test_write_theme_into_settings_with_theme() { let settings = r#" @@ -807,8 +965,7 @@ mod tests { "# .unindent(); - let settings_after_theme = - write_top_level_setting(settings, "theme", "\"summerfruit-light\""); + let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); assert_eq!(settings_after_theme, new_settings) } @@ -828,8 +985,7 @@ mod tests { "# .unindent(); - let settings_after_theme = - write_top_level_setting(settings, "theme", "\"summerfruit-light\""); + let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); assert_eq!(settings_after_theme, new_settings) } @@ -845,8 +1001,7 @@ mod tests { "# .unindent(); - let settings_after_theme = - write_top_level_setting(settings, "theme", "\"summerfruit-light\""); + let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); assert_eq!(settings_after_theme, new_settings) } @@ -856,8 +1011,7 @@ mod tests { let settings = r#"{ "a": "", "ok": true }"#.to_string(); let new_settings = r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#; - let settings_after_theme = - write_top_level_setting(settings, "theme", "\"summerfruit-light\""); + let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); assert_eq!(settings_after_theme, new_settings) } @@ -867,8 +1021,7 @@ mod tests { let settings = r#" { "a": "", "ok": true }"#.to_string(); let new_settings = r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#; - let settings_after_theme = - write_top_level_setting(settings, "theme", "\"summerfruit-light\""); + let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); assert_eq!(settings_after_theme, new_settings) } @@ -890,8 +1043,7 @@ mod tests { "# .unindent(); - let settings_after_theme = - write_top_level_setting(settings, "theme", "\"summerfruit-light\""); + let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); assert_eq!(settings_after_theme, new_settings) } diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 506ebc8c3ddea5eafa35259b77b7e000be737584..575e9499d38a26008bea65a86907bbd31306db1c 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,8 +1,7 @@ -use crate::{watched_json::WatchedJsonFile, write_top_level_setting, SettingsFileContent}; +use crate::{update_settings_file, watched_json::WatchedJsonFile, SettingsFileContent}; use anyhow::Result; use fs::Fs; use gpui::MutableAppContext; -use serde_json::Value; use std::{path::Path, sync::Arc}; // TODO: Switch SettingsFile to open a worktree and buffer for synchronization @@ -27,57 +26,24 @@ impl SettingsFile { } } - pub fn update(cx: &mut MutableAppContext, update: impl FnOnce(&mut SettingsFileContent)) { + pub fn update( + cx: &mut MutableAppContext, + update: impl 'static + Send + FnOnce(&mut SettingsFileContent), + ) { let this = cx.global::(); let current_file_content = this.settings_file_content.current(); - let mut new_file_content = current_file_content.clone(); - - update(&mut new_file_content); let fs = this.fs.clone(); let path = this.path.clone(); cx.background() .spawn(async move { - // Unwrap safety: These values are all guarnteed to be well formed, and we know - // that they will deserialize to our settings object. All of the following unwraps - // are therefore safe. - let tmp = serde_json::to_value(current_file_content).unwrap(); - let old_json = tmp.as_object().unwrap(); - - let new_tmp = serde_json::to_value(new_file_content).unwrap(); - let new_json = new_tmp.as_object().unwrap(); - - // Find changed fields - let mut diffs = vec![]; - for (key, old_value) in old_json.iter() { - let new_value = new_json.get(key).unwrap(); - if old_value != new_value { - if matches!( - new_value, - &Value::Null | &Value::Object(_) | &Value::Array(_) - ) { - unimplemented!( - "We only support updating basic values at the top level" - ); - } - - let new_json = serde_json::to_string_pretty(new_value) - .expect("Could not serialize new json field to string"); - - diffs.push((key, new_json)); - } - } + let old_text = fs.load(path).await?; - // Have diffs, rewrite the settings file now. - let mut content = fs.load(path).await?; - - for (key, new_value) in diffs { - content = write_top_level_setting(content, key, &new_value) - } + let new_text = update_settings_file(old_text, current_file_content, update); - fs.atomic_write(path.to_path_buf(), content).await?; + fs.atomic_write(path.to_path_buf(), new_text).await?; Ok(()) as Result<()> }) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 484c542edeffce8b22c8f2f29ae4f672c0693399..49f2d982ba475a60c94fa6f3e0e045db3b758cb8 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -37,6 +37,7 @@ pub struct Theme { pub tooltip: TooltipStyle, pub terminal: TerminalStyle, pub feedback: FeedbackStyle, + pub welcome: WelcomeStyle, pub color_scheme: ColorScheme, } @@ -850,6 +851,20 @@ pub struct FeedbackStyle { pub link_text_hover: ContainedText, } +#[derive(Clone, Deserialize, Default)] +pub struct WelcomeStyle { + pub checkbox: CheckboxStyle, +} + +#[derive(Clone, Deserialize, Default)] +pub struct CheckboxStyle { + pub width: f32, + pub height: f32, + pub unchecked: ContainerStyle, + pub checked: ContainerStyle, + pub hovered: ContainerStyle, +} + #[derive(Clone, Deserialize, Default)] pub struct ColorScheme { pub name: String, diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 6ac312c37ffbcdf21c9ec5583dc306b3da29ecc2..3e450e2312794188f3ba9e3401fe80affeab6ad9 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -13,6 +13,7 @@ test-support = [] [dependencies] anyhow = "1.0.38" log = "0.4" +editor = { path = "../editor" } gpui = { path = "../gpui" } project = { path = "../project" } settings = { path = "../settings" } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index face85c4b6eb49fe6af491eba607f7b8e304a393..385a8a5f00372dab695a20392b8438ce7694508d 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,19 +1,22 @@ use gpui::{ color::Color, - elements::{Flex, Label, ParentElement, Svg}, - Element, Entity, MutableAppContext, View, + elements::{Empty, Flex, Label, MouseEventHandler, ParentElement, Svg}, + Element, ElementBox, Entity, MutableAppContext, RenderContext, Subscription, View, ViewContext, }; -use settings::Settings; +use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; +use theme::CheckboxStyle; use workspace::{item::Item, Welcome, Workspace}; pub fn init(cx: &mut MutableAppContext) { cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { - let welcome_page = cx.add_view(|_cx| WelcomePage); + let welcome_page = cx.add_view(WelcomePage::new); workspace.add_item(Box::new(welcome_page), cx) }) } -struct WelcomePage; +struct WelcomePage { + _settings_subscription: Subscription, +} impl Entity for WelcomePage { type Event = (); @@ -24,12 +27,21 @@ impl View for WelcomePage { "WelcomePage" } - fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { - let theme = &cx.global::().theme; + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { + let settings = cx.global::(); + let theme = settings.theme.clone(); + + let (diagnostics, metrics) = { + let telemetry = settings.telemetry(); + (telemetry.diagnostics(), telemetry.metrics()) + }; + + enum Metrics {} + enum Diagnostics {} - Flex::new(gpui::Axis::Vertical) + Flex::column() .with_children([ - Flex::new(gpui::Axis::Horizontal) + Flex::row() .with_children([ Svg::new("icons/terminal_16.svg") .with_color(Color::red()) @@ -47,11 +59,88 @@ impl View for WelcomePage { theme.editor.hover_popover.prose.clone(), ) .boxed(), + Flex::row() + .with_children([ + self.render_settings_checkbox::( + &theme.welcome.checkbox, + metrics, + cx, + |content, checked| { + content.telemetry.set_metrics(checked); + }, + ), + Label::new( + "Do you want to send telemetry?", + theme.editor.hover_popover.prose.clone(), + ) + .boxed(), + ]) + .boxed(), + Flex::row() + .with_children([ + self.render_settings_checkbox::( + &theme.welcome.checkbox, + diagnostics, + cx, + |content, checked| content.telemetry.set_diagnostics(checked), + ), + Label::new( + "Send crash reports", + theme.editor.hover_popover.prose.clone(), + ) + .boxed(), + ]) + .boxed(), ]) + .aligned() .boxed() } } +impl WelcomePage { + fn new(cx: &mut ViewContext) -> Self { + let handle = cx.weak_handle(); + + let settings_subscription = cx.observe_global::(move |cx| { + if let Some(handle) = handle.upgrade(cx) { + handle.update(cx, |_, cx| cx.notify()) + } + }); + + WelcomePage { + _settings_subscription: settings_subscription, + } + } + + fn render_settings_checkbox( + &self, + style: &CheckboxStyle, + checked: bool, + cx: &mut RenderContext, + set_value: fn(&mut SettingsFileContent, checked: bool) -> (), + ) -> ElementBox { + MouseEventHandler::::new(0, cx, |state, _| { + Empty::new() + .constrained() + .with_width(style.width) + .with_height(style.height) + .contained() + .with_style(if checked { + style.checked + } else if state.hovered() { + style.hovered + } else { + style.unchecked + }) + .boxed() + }) + .on_click(gpui::MouseButton::Left, move |_, cx| { + SettingsFile::update(cx, move |content| set_value(content, !checked)) + }) + .boxed() + } +} + impl Item for WelcomePage { fn tab_content( &self, diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index dc57468df65f079af5eb5af7596823024b5d4df2..423ce37d481e8f47bf9118ad6134b5c123a4ed31 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -20,6 +20,7 @@ import contactList from "./contactList" import incomingCallNotification from "./incomingCallNotification" import { ColorScheme } from "../themes/common/colorScheme" import feedback from "./feedback" +import welcome from "./welcome" export default function app(colorScheme: ColorScheme): Object { return { @@ -33,6 +34,7 @@ export default function app(colorScheme: ColorScheme): Object { incomingCallNotification: incomingCallNotification(colorScheme), picker: picker(colorScheme), workspace: workspace(colorScheme), + welcome: welcome(colorScheme), contextMenu: contextMenu(colorScheme), editor: editor(colorScheme), projectDiagnostics: projectDiagnostics(colorScheme), diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1325514cdd0771b7f5d9b9570a9345d9e0f5b2e --- /dev/null +++ b/styles/src/styleTree/welcome.ts @@ -0,0 +1,34 @@ + +import { ColorScheme } from "../themes/common/colorScheme"; +import { border } from "./components"; + +export default function welcome(colorScheme: ColorScheme) { + let layer = colorScheme.highest; + + // TODO + let checkbox_base = { + background: colorScheme.ramps.red(0.5).hex(), + cornerRadius: 8, + padding: { + left: 8, + right: 8, + top: 4, + bottom: 4, + }, + shadow: colorScheme.popoverShadow, + border: border(layer), + margin: { + left: -8, + }, + }; + + return { + checkbox: { + width: 9, + height: 9, + unchecked: checkbox_base, + checked: checkbox_base, + hovered: checkbox_base + } + } +} \ No newline at end of file From 50586812ecdbca1f8fa538a708122dafd52bba9c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 24 Feb 2023 14:41:16 -0800 Subject: [PATCH 04/45] Make generate licenses quieter --- script/generate-licenses | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/generate-licenses b/script/generate-licenses index 8a41f55c025bf4909b4298180588f043a6153894..14c9d4c79f1646d4889da82d9ee24c7975003532 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -10,7 +10,7 @@ echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE echo "Generating theme licenses" cd styles -npm ci +npm --silent ci npm run --silent build-licenses >> $OUTPUT_FILE cd .. From 86e21015922487c20cacba5f93ecf588142a89c8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 24 Feb 2023 16:43:34 -0800 Subject: [PATCH 05/45] Added the ability to nested values to the settings file, while preserving user formatting co-authored-by: max --- crates/settings/src/settings.rs | 479 ++++++++++++++++++++------------ 1 file changed, 294 insertions(+), 185 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 229e11ff3a22345b1be118ced67edbed8c3f3c7c..501a88c42e14bcb3da9cadaa3f889a63580830df 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -18,7 +18,7 @@ use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use std::{collections::HashMap, fmt::Write as _, num::NonZeroU32, str, sync::Arc}; +use std::{collections::HashMap, num::NonZeroU32, str, sync::Arc}; use theme::{Theme, ThemeRegistry}; use tree_sitter::Query; use util::ResultExt as _; @@ -686,13 +686,25 @@ pub fn settings_file_json_schema( serde_json::to_value(root_schema).unwrap() } +fn merge(target: &mut T, value: Option) { + if let Some(value) = value { + *target = value; + } +} + +pub fn parse_json_with_comments(content: &str) -> Result { + Ok(serde_json::from_reader( + json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()), + )?) +} + /// Expects the key to be unquoted, and the value to be valid JSON /// (e.g. values should be unquoted for numbers and bools, quoted for strings) -pub fn write_settings_key( - mut settings_content: String, - top_level_key: &str, - new_val: &str, -) -> String { +pub fn write_settings_key( + settings_content: &mut String, + key_path: &[&str], + new_value: &T, +) { let mut parser = tree_sitter::Parser::new(); parser.set_language(tree_sitter_json::language()).unwrap(); let tree = parser.parse(&settings_content, None).unwrap(); @@ -702,56 +714,64 @@ pub fn write_settings_key( let query = Query::new( tree_sitter_json::language(), " - (document - (object - (pair - key: (string) @key - value: (_) @value))) - ", + (pair + key: (string) @key + value: (_) @value) + ", ) .unwrap(); + let mut depth = 0; let mut first_key_start = None; - let mut existing_value_range = None; + let mut existing_value_range = 0..settings_content.len(); let matches = cursor.matches(&query, tree.root_node(), settings_content.as_bytes()); for mat in matches { if mat.captures.len() != 2 { continue; } - let key = mat.captures[0]; - let value = mat.captures[1]; + let key_range = mat.captures[0].node.byte_range(); + let value_range = mat.captures[1].node.byte_range(); + + if key_range.start > existing_value_range.end { + break; + } + + first_key_start.get_or_insert_with(|| key_range.start); + + let found_key = settings_content + .get(key_range.clone()) + .map(|key_text| key_text == format!("\"{}\"", key_path[depth])) + .unwrap_or(false); - first_key_start.get_or_insert_with(|| key.node.start_byte()); + if found_key { + existing_value_range = value_range; + depth += 1; - if let Some(key_text) = settings_content.get(key.node.byte_range()) { - if key_text == format!("\"{top_level_key}\"") { - existing_value_range = Some(value.node.byte_range()); + if depth == key_path.len() { break; + } else { + first_key_start = None; } } } - match (first_key_start, existing_value_range) { - (None, None) => { - // No document, create a new object and overwrite - settings_content.clear(); - write!( - settings_content, - "{{\n \"{}\": {new_val}\n}}\n", - top_level_key - ) - .unwrap(); - } - - (_, Some(existing_value_range)) => { - // Existing theme key, overwrite - settings_content.replace_range(existing_value_range, &new_val); + // We found the exact key we want, insert the new value + if depth == key_path.len() { + let new_val = serde_json::to_string_pretty(new_value) + .expect("Could not serialize new json field to string"); + settings_content.replace_range(existing_value_range, &new_val); + } else { + // We have key paths, construct the sub objects + let new_key = key_path[depth]; + + // We don't have the key, construct the nested objects + let mut new_value = serde_json::to_value(new_value).unwrap(); + for key in key_path[(depth + 1)..].iter().rev() { + new_value = serde_json::json!({ key.to_string(): new_value }); } - (Some(first_key_start), None) => { - // No existing theme key, but other settings. Prepend new theme settings and - // match style of first key + if let Some(first_key_start) = first_key_start { let mut row = 0; let mut column = 0; for (ix, char) in settings_content.char_indices() { @@ -766,70 +786,118 @@ pub fn write_settings_key( } } - let content = format!(r#""{top_level_key}": {new_val},"#); - settings_content.insert_str(first_key_start, &content); - if row > 0 { + let new_val = to_pretty_json(&new_value, column, column); + let content = format!(r#""{new_key}": {new_val},"#); + settings_content.insert_str(first_key_start, &content); + settings_content.insert_str( first_key_start + content.len(), &format!("\n{:width$}", ' ', width = column), ) } else { - settings_content.insert_str(first_key_start + content.len(), " ") + let new_val = serde_json::to_string(&new_value).unwrap(); + let mut content = format!(r#""{new_key}": {new_val},"#); + content.push(' '); + settings_content.insert_str(first_key_start, &content); + } + } else { + new_value = serde_json::json!({ new_key.to_string(): new_value }); + let indent_prefix_len = 4 * depth; + let new_val = to_pretty_json(&new_value, 4, indent_prefix_len); + + settings_content.replace_range(existing_value_range, &new_val); + if depth == 0 { + settings_content.push('\n'); } } } - - settings_content } -fn merge(target: &mut T, value: Option) { - if let Some(value) = value { - *target = value; - } -} +fn to_pretty_json( + value: &serde_json::Value, + indent_size: usize, + indent_prefix_len: usize, +) -> String { + const SPACES: [u8; 32] = [b' '; 32]; -pub fn parse_json_with_comments(content: &str) -> Result { - Ok(serde_json::from_reader( - json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()), - )?) + debug_assert!(indent_size <= SPACES.len()); + debug_assert!(indent_prefix_len <= SPACES.len()); + + let mut output = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter( + &mut output, + serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]), + ); + + value.serialize(&mut ser).unwrap(); + let text = String::from_utf8(output).unwrap(); + + let mut adjusted_text = String::new(); + for (i, line) in text.split('\n').enumerate() { + if i > 0 { + adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap()); + } + adjusted_text.push_str(line); + adjusted_text.push('\n'); + } + adjusted_text.pop(); + adjusted_text } pub fn update_settings_file( - old_text: String, + mut text: String, old_file_content: SettingsFileContent, update: impl FnOnce(&mut SettingsFileContent), ) -> String { let mut new_file_content = old_file_content.clone(); - update(&mut new_file_content); - let old_json = to_json_object(old_file_content); - let new_json = to_json_object(new_file_content); - - // Find changed fields - let mut diffs = vec![]; - for (key, old_value) in old_json.iter() { - let new_value = new_json.get(key).unwrap(); - if old_value != new_value { - if matches!( - new_value, - &Value::Null | &Value::Object(_) | &Value::Array(_) - ) { - unimplemented!("We only support updating basic values at the top level"); - } + update(&mut new_file_content); - let new_json = serde_json::to_string_pretty(new_value) - .expect("Could not serialize new json field to string"); + let old_object = to_json_object(old_file_content); + let new_object = to_json_object(new_file_content); - diffs.push((key, new_json)); + fn apply_changes_to_json_text( + old_object: &serde_json::Map, + new_object: &serde_json::Map, + current_key_path: Vec<&str>, + json_text: &mut String, + ) { + for (key, old_value) in old_object.iter() { + // We know that these two are from the same shape of object, so we can just unwrap + let new_value = new_object.get(key).unwrap(); + if old_value != new_value { + match new_value { + Value::Bool(_) | Value::Number(_) | Value::String(_) => { + let mut key_path = current_key_path.clone(); + key_path.push(key); + write_settings_key(json_text, &key_path, &new_value); + } + Value::Object(new_sub_object) => { + let mut key_path = current_key_path.clone(); + key_path.push(key); + if let Value::Object(old_sub_object) = old_value { + apply_changes_to_json_text( + old_sub_object, + new_sub_object, + key_path, + json_text, + ); + } else { + unimplemented!("This function doesn't support changing values from simple values to objects yet"); + } + } + Value::Null | Value::Array(_) => { + unimplemented!("We only support objects and simple values"); + } + } + } } } - let mut new_text = old_text; - for (key, new_value) in diffs { - new_text = write_settings_key(new_text, key, &new_value) - } - new_text + apply_changes_to_json_text(&old_object, &new_object, vec![], &mut text); + + text } fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map { @@ -851,7 +919,7 @@ mod tests { expected_new_json: S2, ) { let old_json = old_json.into(); - let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap(); + let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default(); let new_json = update_settings_file(old_json, old_content, update); assert_eq!(new_json, expected_new_json.into()); } @@ -859,23 +927,27 @@ mod tests { #[test] fn test_update_telemetry_setting_multiple_fields() { assert_new_settings( - r#"{ - "telemetry": { - "metrics": false, - "diagnostics": false + r#" + { + "telemetry": { + "metrics": false, + "diagnostics": false + } } - }"# + "# .unindent(), |settings| { settings.telemetry.set_diagnostics(true); settings.telemetry.set_metrics(true); }, - r#"{ - "telemetry": { - "metrics": true, - "diagnostics": true + r#" + { + "telemetry": { + "metrics": true, + "diagnostics": true + } } - }"# + "# .unindent(), ); } @@ -898,20 +970,45 @@ mod tests { #[test] fn test_update_telemetry_setting_other_fields() { assert_new_settings( - r#"{ - "telemetry": { - "metrics": false, - "diagnostics": true + r#" + { + "telemetry": { + "metrics": false, + "diagnostics": true + } } - }"# + "# .unindent(), |settings| settings.telemetry.set_diagnostics(false), - r#"{ - "telemetry": { - "metrics": false, - "diagnostics": false + r#" + { + "telemetry": { + "metrics": false, + "diagnostics": false + } } - }"# + "# + .unindent(), + ); + } + + #[test] + fn test_update_telemetry_setting_empty_telemetry() { + assert_new_settings( + r#" + { + "telemetry": {} + } + "# + .unindent(), + |settings| settings.telemetry.set_diagnostics(false), + r#" + { + "telemetry": { + "diagnostics": false + } + } + "# .unindent(), ); } @@ -919,18 +1016,22 @@ mod tests { #[test] fn test_update_telemetry_setting_pre_existing() { assert_new_settings( - r#"{ - "telemetry": { - "diagnostics": true + r#" + { + "telemetry": { + "diagnostics": true + } } - }"# + "# .unindent(), |settings| settings.telemetry.set_diagnostics(false), - r#"{ - "telemetry": { - "diagnostics": false + r#" + { + "telemetry": { + "diagnostics": false + } } - }"# + "# .unindent(), ); } @@ -940,111 +1041,119 @@ mod tests { assert_new_settings( "{}", |settings| settings.telemetry.set_diagnostics(true), - r#"{ - "telemetry": { - "diagnostics": true + r#" + { + "telemetry": { + "diagnostics": true + } } - }"# + "# .unindent(), ); } #[test] - fn test_write_theme_into_settings_with_theme() { - let settings = r#" - { - "theme": "One Dark" - } - "# - .unindent(); - - let new_settings = r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(); - - let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); + fn test_update_object_empty_doc() { + assert_new_settings( + "", + |settings| settings.telemetry.set_diagnostics(true), + r#" + { + "telemetry": { + "diagnostics": true + } + } + "# + .unindent(), + ); + } - assert_eq!(settings_after_theme, new_settings) + #[test] + fn test_write_theme_into_settings_with_theme() { + assert_new_settings( + r#" + { + "theme": "One Dark" + } + "# + .unindent(), + |settings| settings.theme = Some("summerfruit-light".to_string()), + r#" + { + "theme": "summerfruit-light" + } + "# + .unindent(), + ); } #[test] fn test_write_theme_into_empty_settings() { - let settings = r#" - { - } - "# - .unindent(); - - let new_settings = r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(); - - let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); - - assert_eq!(settings_after_theme, new_settings) + assert_new_settings( + r#" + { + } + "# + .unindent(), + |settings| settings.theme = Some("summerfruit-light".to_string()), + r#" + { + "theme": "summerfruit-light" + } + "# + .unindent(), + ); } #[test] - fn test_write_theme_into_no_settings() { - let settings = "".to_string(); - - let new_settings = r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(); - - let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); - - assert_eq!(settings_after_theme, new_settings) + fn write_key_no_document() { + assert_new_settings( + "", + |settings| settings.theme = Some("summerfruit-light".to_string()), + r#" + { + "theme": "summerfruit-light" + } + "# + .unindent(), + ); } #[test] fn test_write_theme_into_single_line_settings_without_theme() { - let settings = r#"{ "a": "", "ok": true }"#.to_string(); - let new_settings = r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#; - - let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); - - assert_eq!(settings_after_theme, new_settings) + assert_new_settings( + r#"{ "a": "", "ok": true }"#, + |settings| settings.theme = Some("summerfruit-light".to_string()), + r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#, + ); } #[test] fn test_write_theme_pre_object_whitespace() { - let settings = r#" { "a": "", "ok": true }"#.to_string(); - let new_settings = r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#; - - let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); - - assert_eq!(settings_after_theme, new_settings) + assert_new_settings( + r#" { "a": "", "ok": true }"#, + |settings| settings.theme = Some("summerfruit-light".to_string()), + r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(), + ); } #[test] fn test_write_theme_into_multi_line_settings_without_theme() { - let settings = r#" - { - "a": "b" - } - "# - .unindent(); - - let new_settings = r#" - { - "theme": "summerfruit-light", - "a": "b" - } - "# - .unindent(); - - let settings_after_theme = write_settings_key(settings, "theme", "\"summerfruit-light\""); - - assert_eq!(settings_after_theme, new_settings) + assert_new_settings( + r#" + { + "a": "b" + } + "# + .unindent(), + |settings| settings.theme = Some("summerfruit-light".to_string()), + r#" + { + "theme": "summerfruit-light", + "a": "b" + } + "# + .unindent(), + ); } } From 118435a348e76ebcb43b3b0f8c6a04c5b3b66256 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 24 Feb 2023 17:04:31 -0800 Subject: [PATCH 06/45] Added basic styling for checkboxes, yay Co-authored-by: Max --- crates/theme/src/theme.rs | 3 ++- crates/welcome/src/welcome.rs | 14 ++++++++++---- styles/src/styleTree/welcome.ts | 33 +++++++++++++++++++++++++++------ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 49f2d982ba475a60c94fa6f3e0e045db3b758cb8..87b9d9845a314d6db5a63c280b417c43a6dc43cf 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -860,9 +860,10 @@ pub struct WelcomeStyle { pub struct CheckboxStyle { pub width: f32, pub height: f32, - pub unchecked: ContainerStyle, + pub default: ContainerStyle, pub checked: ContainerStyle, pub hovered: ContainerStyle, + pub hovered_and_checked: ContainerStyle, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 385a8a5f00372dab695a20392b8438ce7694508d..6985b700690a0eac970ffa7082dd0fa7e8c867df 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -126,11 +126,17 @@ impl WelcomePage { .with_height(style.height) .contained() .with_style(if checked { - style.checked - } else if state.hovered() { - style.hovered + if state.hovered() { + style.hovered_and_checked + } else { + style.checked + } } else { - style.unchecked + if state.hovered() { + style.hovered + } else { + style.default + } }) .boxed() }) diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index f1325514cdd0771b7f5d9b9570a9345d9e0f5b2e..6085782afd009722784c5f70e6a835bb3fe0f1ef 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -6,9 +6,8 @@ export default function welcome(colorScheme: ColorScheme) { let layer = colorScheme.highest; // TODO - let checkbox_base = { - background: colorScheme.ramps.red(0.5).hex(), - cornerRadius: 8, + let checkboxBase = { + cornerRadius: 4, padding: { left: 8, right: 8, @@ -26,9 +25,31 @@ export default function welcome(colorScheme: ColorScheme) { checkbox: { width: 9, height: 9, - unchecked: checkbox_base, - checked: checkbox_base, - hovered: checkbox_base + default: { + ...checkboxBase, + background: colorScheme.ramps.blue(0.5).hex(), + }, + checked: { + ...checkboxBase, + background: colorScheme.ramps.red(0.5).hex(), + }, + hovered: { + ...checkboxBase, + background: colorScheme.ramps.blue(0.5).hex(), + + border: { + color: colorScheme.ramps.green(0.5).hex(), + width: 1, + } + }, + hoveredAndChecked: { + ...checkboxBase, + background: colorScheme.ramps.red(0.5).hex(), + border: { + color: colorScheme.ramps.green(0.5).hex(), + width: 1, + } + } } } } \ No newline at end of file From 7d7053b990419edc1772c8a370bb80976455dee0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Feb 2023 19:20:21 -0800 Subject: [PATCH 07/45] Move to using stateless --- Cargo.lock | 1 + assets/settings/default.json | 2 +- crates/db/src/db.rs | 3 ++- crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 14 +++++++++++--- crates/zed/src/zed.rs | 25 ++++++++++++++++++++++++- 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90da178c7bcb1afa8758f94bb7468983cb28d8d6..c8124fcc3a180d2159a7bd8e5a690986f7dfa527 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8395,6 +8395,7 @@ dependencies = [ "command_palette", "context_menu", "ctor", + "db", "diagnostics", "easy-parallel", "editor", diff --git a/assets/settings/default.json b/assets/settings/default.json index 2a5e05b40138053862461f721231e042b2a1cb38..90c47478f3f7c3a97b50cea061907c122bf513c9 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -50,7 +50,7 @@ // "default_dock_anchor": "right" // 3. Position the dock full screen over the entire workspace" // "default_dock_anchor": "expanded" - "default_dock_anchor": "right", + "default_dock_anchor": "bottom", // Whether or not to remove any trailing whitespace from lines of a buffer // before saving it. "remove_trailing_whitespace_on_save": true, diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 20f2300d89b591230dcc41df4008e8c1fc65ec7a..f4d0dc1a469a272b0f2422e4eb0e88e33def510e 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -39,7 +39,8 @@ const FALLBACK_DB_NAME: &'static str = "FALLBACK_MEMORY_DB"; const DB_FILE_NAME: &'static str = "db.sqlite"; lazy_static::lazy_static! { - static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()); + // !!!!!!! CHANGE BACK TO DEFAULT FALSE BEFORE SHIPPING + static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(true, |v| !v.is_empty()); static ref DB_FILE_OPERATIONS: Mutex<()> = Mutex::new(()); pub static ref BACKUP_DB_PATH: RwLock> = RwLock::new(None); pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d3b6e4810fa497cc41394d796a718e863af6f4dc..68b04c7e2f846f549837f6aa7b77d1857d2243c3 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -29,6 +29,7 @@ context_menu = { path = "../context_menu" } client = { path = "../client" } clock = { path = "../clock" } diagnostics = { path = "../diagnostics" } +db = { path = "../db" } editor = { path = "../editor" } feedback = { path = "../feedback" } file_finder = { path = "../file_finder" } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 298e8ef8b9c50aff25f1b8bbf24c1b4f2559e37c..a9625bf78ee81e2d73e6a071f361e15dc12dd975 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -13,6 +13,7 @@ use client::{ http::{self, HttpClient}, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN, }; +use db::kvp::KEY_VALUE_STORE; use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, @@ -43,9 +44,12 @@ use theme::ThemeRegistry; use util::StaffMode; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - self, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenPaths, Welcome, Workspace, + self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, +}; +use zed::{ + self, build_window_options, initialize_workspace, languages, menus, WelcomeExperience, + FIRST_OPEN, }; -use zed::{self, build_window_options, initialize_workspace, languages, menus}; fn main() { let http = http::client(); @@ -258,9 +262,13 @@ async fn restore_or_create_workspace(mut cx: AsyncAppContext) { paths: location.paths().as_ref().clone(), }) }); + } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + cx.update(|cx| { + cx.dispatch_global_action(WelcomeExperience); + }); } else { cx.update(|cx| { - cx.dispatch_global_action(Welcome); + cx.dispatch_global_action(NewFile); }); } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2fbac3613ed3a6242c41db026e7b46b0d7f1aa50..75cdf0e687360e52b1e2af5e8eabf436bf0b0231 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -8,6 +8,7 @@ use breadcrumbs::Breadcrumbs; pub use client; use collab_ui::{CollabTitlebarItem, ToggleContactsMenu}; use collections::VecDeque; +use db::kvp::KEY_VALUE_STORE; pub use editor; use editor::{Editor, MultiBuffer}; @@ -34,7 +35,9 @@ use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode}; use uuid::Uuid; pub use workspace; -use workspace::{sidebar::SidebarSide, AppState, Restart, Workspace}; +use workspace::{sidebar::SidebarSide, AppState, Restart, Welcome, Workspace}; + +pub const FIRST_OPEN: &str = "first_open"; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -67,6 +70,7 @@ actions!( ResetBufferFontSize, InstallCommandLineInterface, ResetDatabase, + WelcomeExperience ] ); @@ -252,6 +256,25 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }, ); + cx.add_global_action(|_: &WelcomeExperience, cx| { + if !matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + return; //noop, in case someone fires this from the command palette + } + + // Make a workspace, set it up with an open bottom dock and the welcome page + + cx.dispatch_global_action(Welcome); + + cx.background() + .spawn(async move { + KEY_VALUE_STORE + .write_kvp(FIRST_OPEN.to_string(), "false".to_string()) + .await + .log_err(); + }) + .detach(); + }); + activity_indicator::init(cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); settings::KeymapFileContent::load_defaults(cx); From 5210be95feeb75ae801d90cbf354e2dd8c95ddd9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Feb 2023 22:11:58 -0800 Subject: [PATCH 08/45] Added welcome experience sketch Made toolbar hideable --- crates/db/src/db.rs | 11 +++++++++ crates/welcome/src/welcome.rs | 8 +++++-- crates/workspace/src/dock.rs | 40 +++++++++++++++++++------------ crates/workspace/src/item.rs | 8 +++++++ crates/workspace/src/pane.rs | 7 +++--- crates/workspace/src/toolbar.rs | 12 ++++++++++ crates/workspace/src/workspace.rs | 29 +++++++++++----------- crates/zed/src/zed.rs | 37 +++++++++++++++------------- styles/src/styleTree/workspace.ts | 2 +- 9 files changed, 103 insertions(+), 51 deletions(-) diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index f4d0dc1a469a272b0f2422e4eb0e88e33def510e..989dcf0af561999fb31e108f712b92eb9ff499d3 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -4,6 +4,7 @@ pub mod query; // Re-export pub use anyhow; use anyhow::Context; +use gpui::MutableAppContext; pub use indoc::indoc; pub use lazy_static; use parking_lot::{Mutex, RwLock}; @@ -17,6 +18,7 @@ use sqlez::domain::Migrator; use sqlez::thread_safe_connection::ThreadSafeConnection; use sqlez_macros::sql; use std::fs::create_dir_all; +use std::future::Future; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -237,6 +239,15 @@ macro_rules! define_connection { }; } +pub fn write_and_log(cx: &mut MutableAppContext, db_write: impl FnOnce() -> F + Send + 'static) +where + F: Future> + Send, +{ + cx.background() + .spawn(async move { db_write().await.log_err() }) + .detach() +} + #[cfg(test)] mod tests { use std::{fs, thread}; diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 6985b700690a0eac970ffa7082dd0fa7e8c867df..5a166218ceb6df431de323de3a07fe43eb14c32d 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -14,7 +14,7 @@ pub fn init(cx: &mut MutableAppContext) { }) } -struct WelcomePage { +pub struct WelcomePage { _settings_subscription: Subscription, } @@ -98,7 +98,7 @@ impl View for WelcomePage { } impl WelcomePage { - fn new(cx: &mut ViewContext) -> Self { + pub fn new(cx: &mut ViewContext) -> Self { let handle = cx.weak_handle(); let settings_subscription = cx.observe_global::(move |cx| { @@ -163,4 +163,8 @@ impl Item for WelcomePage { ) .boxed() } + + fn show_toolbar(&self) -> bool { + false + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 2bd880828106cbfaf6dd6fb9797876b5d53ead0d..4281c04649ee12ea7ee6e02ee89784ca4620d22c 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -39,20 +39,24 @@ impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(Dock::focus_dock); cx.add_action(Dock::hide_dock); - cx.add_action(Dock::move_dock); + cx.add_action( + |workspace: &mut Workspace, &MoveDock(dock_anchor), cx: &mut ViewContext| { + Dock::move_dock(workspace, dock_anchor, true, cx); + }, + ); cx.add_action( |workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext| { - Dock::move_dock(workspace, &MoveDock(DockAnchor::Right), cx) + Dock::move_dock(workspace, DockAnchor::Right, true, cx); }, ); cx.add_action( |workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext| { - Dock::move_dock(workspace, &MoveDock(DockAnchor::Bottom), cx) + Dock::move_dock(workspace, DockAnchor::Bottom, true, cx) }, ); cx.add_action( |workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext| { - Dock::move_dock(workspace, &MoveDock(DockAnchor::Expanded), cx) + Dock::move_dock(workspace, DockAnchor::Expanded, true, cx) }, ); cx.add_action( @@ -215,6 +219,7 @@ impl Dock { pub(crate) fn set_dock_position( workspace: &mut Workspace, new_position: DockPosition, + focus: bool, cx: &mut ViewContext, ) { workspace.dock.position = new_position; @@ -235,19 +240,23 @@ impl Dock { let pane = workspace.dock.pane.clone(); if pane.read(cx).items().next().is_none() { if let Some(item_to_add) = (workspace.dock.default_item_factory)(workspace, cx) { - Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx); + Pane::add_item(workspace, &pane, item_to_add, focus, focus, None, cx); } else { workspace.dock.position = workspace.dock.position.hide(); } } else { - cx.focus(pane); + if focus { + cx.focus(pane); + } } } else if let Some(last_active_center_pane) = workspace .last_active_center_pane .as_ref() .and_then(|pane| pane.upgrade(cx)) { - cx.focus(last_active_center_pane); + if focus { + cx.focus(last_active_center_pane); + } } cx.emit(crate::Event::DockAnchorChanged); workspace.serialize_workspace(cx); @@ -255,11 +264,11 @@ impl Dock { } pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.hide(), cx); + Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx); } - pub fn show(workspace: &mut Workspace, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.show(), cx); + pub fn show(workspace: &mut Workspace, focus: bool, cx: &mut ViewContext) { + Self::set_dock_position(workspace, workspace.dock.position.show(), focus, cx); } pub fn hide_on_sidebar_shown( @@ -275,19 +284,20 @@ impl Dock { } fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.show(), cx); + Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx); } fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext) { - Self::set_dock_position(workspace, workspace.dock.position.hide(), cx); + Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx); } - fn move_dock( + pub fn move_dock( workspace: &mut Workspace, - &MoveDock(new_anchor): &MoveDock, + new_anchor: DockAnchor, + focus: bool, cx: &mut ViewContext, ) { - Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx); + Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), focus, cx); } pub fn render( diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index a80b9f8d83d9dc4d407b337a944537143e24581e..b55c9942f8b51707c3b550545971cc57e047b2ce 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -151,6 +151,9 @@ pub trait Item: View { "deserialize() must be implemented if serialized_item_kind() returns Some(_)" ) } + fn show_toolbar(&self) -> bool { + true + } } pub trait ItemHandle: 'static + fmt::Debug { @@ -213,6 +216,7 @@ pub trait ItemHandle: 'static + fmt::Debug { fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; fn serialized_item_kind(&self) -> Option<&'static str>; + fn show_toolbar(&self, cx: &AppContext) -> bool; } pub trait WeakItemHandle { @@ -591,6 +595,10 @@ impl ItemHandle for ViewHandle { fn serialized_item_kind(&self) -> Option<&'static str> { T::serialized_item_kind() } + + fn show_toolbar(&self, cx: &AppContext) -> bool { + self.read(cx).show_toolbar() + } } impl From> for AnyViewHandle { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 98fcac664c069fa5c22c233e54a2f5b0a96a1c25..235df0202d6fc3fc6f4546596385fee28b0647bf 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1485,11 +1485,12 @@ impl View for Pane { cx, { let toolbar = self.toolbar.clone(); + let toolbar_hidden = toolbar.read(cx).hidden(); move |_, cx| { Flex::column() - .with_child( - ChildView::new(&toolbar, cx).expanded().boxed(), - ) + .with_children((!toolbar_hidden).then(|| { + ChildView::new(&toolbar, cx).expanded().boxed() + })) .with_child( ChildView::new(active_item, cx) .flex(1., true) diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index 7443f19003fabcf14771655a39d51bd07f8a6813..df10db91a05dbd24a31d6122eb3a39ee6ca4de2f 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -42,6 +42,7 @@ pub enum ToolbarItemLocation { pub struct Toolbar { active_pane_item: Option>, + hidden: bool, pane: WeakViewHandle, items: Vec<(Box, ToolbarItemLocation)>, } @@ -211,6 +212,7 @@ impl Toolbar { active_pane_item: None, pane, items: Default::default(), + hidden: false, } } @@ -243,6 +245,12 @@ impl Toolbar { cx: &mut ViewContext, ) { self.active_pane_item = pane_item.map(|item| item.boxed_clone()); + self.hidden = self + .active_pane_item + .as_ref() + .map(|item| !item.show_toolbar(cx)) + .unwrap_or(false); + for (toolbar_item, current_location) in self.items.iter_mut() { let new_location = toolbar_item.set_active_pane_item(pane_item, cx); if new_location != *current_location { @@ -257,6 +265,10 @@ impl Toolbar { .iter() .find_map(|(item, _)| item.to_any().downcast()) } + + pub fn hidden(&self) -> bool { + self.hidden + } } impl ToolbarItemViewHandle for ViewHandle { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 31960fbc1ec7751a147ecfcf788e26dd33ab1a0d..442d28579fc1be8358cf322fa3f0c264d9cbdec3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -197,20 +197,12 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { } } }); - cx.add_global_action({ - let app_state = Arc::downgrade(&app_state); - move |_: &Welcome, cx: &mut MutableAppContext| { - if let Some(app_state) = app_state.upgrade() { - open_new(&app_state, cx).detach(); - } - } - }); cx.add_global_action({ let app_state = Arc::downgrade(&app_state); move |_: &NewWindow, cx: &mut MutableAppContext| { if let Some(app_state) = app_state.upgrade() { - open_new(&app_state, cx).detach(); + open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach(); } } }); @@ -1514,7 +1506,7 @@ impl Workspace { self.active_item_path_changed(cx); if &pane == self.dock_pane() { - Dock::show(self, cx); + Dock::show(self, true, cx); } else { self.last_active_center_pane = Some(pane.downgrade()); if self.dock.is_anchored_at(DockAnchor::Expanded) { @@ -2527,7 +2519,12 @@ impl Workspace { // the focus the dock generates start generating alternating // focus due to the deferred execution each triggering each other cx.after_window_update(move |workspace, cx| { - Dock::set_dock_position(workspace, serialized_workspace.dock_position, cx); + Dock::set_dock_position( + workspace, + serialized_workspace.dock_position, + true, + cx, + ); }); cx.notify(); @@ -2859,14 +2856,18 @@ pub fn open_paths( }) } -pub fn open_new(app_state: &Arc, cx: &mut MutableAppContext) -> Task<()> { +pub fn open_new( + app_state: &Arc, + cx: &mut MutableAppContext, + init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static, +) -> Task<()> { let task = Workspace::new_local(Vec::new(), app_state.clone(), cx); cx.spawn(|mut cx| async move { let (workspace, opened_paths) = task.await; - workspace.update(&mut cx, |_, cx| { + workspace.update(&mut cx, |workspace, cx| { if opened_paths.is_empty() { - cx.dispatch_action(Welcome); + init(workspace, cx) } }) }) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 75cdf0e687360e52b1e2af5e8eabf436bf0b0231..5d13d41bba066ca041b9f5ffdc76a4a193cd19ba 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -35,7 +35,7 @@ use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode}; use uuid::Uuid; pub use workspace; -use workspace::{sidebar::SidebarSide, AppState, Restart, Welcome, Workspace}; +use workspace::{dock::Dock, open_new, sidebar::SidebarSide, AppState, Restart, Workspace}; pub const FIRST_OPEN: &str = "first_open"; @@ -256,23 +256,27 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }, ); - cx.add_global_action(|_: &WelcomeExperience, cx| { - if !matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { - return; //noop, in case someone fires this from the command palette - } - - // Make a workspace, set it up with an open bottom dock and the welcome page - - cx.dispatch_global_action(Welcome); + cx.add_global_action({ + let app_state = app_state.clone(); + move |_: &WelcomeExperience, cx| { + if !matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + return; //noop, in case someone fires this from the command palette + } - cx.background() - .spawn(async move { - KEY_VALUE_STORE - .write_kvp(FIRST_OPEN.to_string(), "false".to_string()) - .await - .log_err(); + open_new(&app_state, cx, |workspace, cx| { + workspace.toggle_sidebar(SidebarSide::Left, cx); + let welcome_page = cx.add_view(|cx| welcome::WelcomePage::new(cx)); + workspace.add_item(Box::new(welcome_page.clone()), cx); + Dock::move_dock(workspace, settings::DockAnchor::Bottom, false, cx); + cx.focus(welcome_page); + cx.notify(); }) .detach(); + + db::write_and_log(cx, || { + KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string()) + }); + } }); activity_indicator::init(cx); @@ -881,7 +885,8 @@ mod tests { #[gpui::test] async fn test_new_empty_workspace(cx: &mut TestAppContext) { let app_state = init(cx); - cx.update(|cx| open_new(&app_state, cx)).await; + cx.update(|cx| open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile))) + .await; let window_id = *cx.window_ids().first().unwrap(); let workspace = cx.root_view::(window_id).unwrap(); diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index f9f49e3c7df14b5ccf14a9322a1a461c1c153917..c758e0227e2413ed86a15a10c28a22499d59131d 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -248,7 +248,7 @@ export default function workspace(colorScheme: ColorScheme) { }, dock: { initialSizeRight: 640, - initialSizeBottom: 480, + initialSizeBottom: 300, wash_color: withOpacity(background(colorScheme.highest), 0.5), panel: { border: border(colorScheme.middle), From 62aeb6b8b390ffe5c547c544726f5813778854e7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Feb 2023 22:33:12 -0800 Subject: [PATCH 09/45] Added background to welcome page --- crates/welcome/src/welcome.rs | 115 ++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 47 deletions(-) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 5a166218ceb6df431de323de3a07fe43eb14c32d..afb38385a4a21b9bed94b10c96570f30e51000ae 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,6 +1,7 @@ use gpui::{ color::Color, - elements::{Empty, Flex, Label, MouseEventHandler, ParentElement, Svg}, + elements::{Canvas, Empty, Flex, Label, MouseEventHandler, ParentElement, Stack, Svg}, + geometry::rect::RectF, Element, ElementBox, Entity, MutableAppContext, RenderContext, Subscription, View, ViewContext, }; use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; @@ -39,60 +40,80 @@ impl View for WelcomePage { enum Metrics {} enum Diagnostics {} - Flex::column() - .with_children([ - Flex::row() - .with_children([ - Svg::new("icons/terminal_16.svg") - .with_color(Color::red()) - .constrained() - .with_width(100.) - .with_height(100.) - .aligned() - .contained() - .boxed(), - Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), - ]) - .boxed(), - Label::new( - "Code at the speed of thought", - theme.editor.hover_popover.prose.clone(), - ) + let background = theme.editor.background; + + Stack::new() + .with_child( + Canvas::new(move |bounds, visible_bounds, cx| { + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + cx.paint_layer(Some(visible_bounds), |cx| { + cx.scene.push_quad(gpui::Quad { + bounds: RectF::new(bounds.origin(), bounds.size()), + background: Some(background), + ..Default::default() + }) + }) + }) .boxed(), - Flex::row() - .with_children([ - self.render_settings_checkbox::( - &theme.welcome.checkbox, - metrics, - cx, - |content, checked| { - content.telemetry.set_metrics(checked); - }, - ), - Label::new( - "Do you want to send telemetry?", - theme.editor.hover_popover.prose.clone(), - ) - .boxed(), - ]) - .boxed(), - Flex::row() + ) + .with_child( + Flex::column() .with_children([ - self.render_settings_checkbox::( - &theme.welcome.checkbox, - diagnostics, - cx, - |content, checked| content.telemetry.set_diagnostics(checked), - ), + Flex::row() + .with_children([ + Svg::new("icons/terminal_16.svg") + .with_color(Color::red()) + .constrained() + .with_width(100.) + .with_height(100.) + .aligned() + .contained() + .boxed(), + Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), + ]) + .boxed(), Label::new( - "Send crash reports", + "Code at the speed of thought", theme.editor.hover_popover.prose.clone(), ) .boxed(), + Flex::row() + .with_children([ + self.render_settings_checkbox::( + &theme.welcome.checkbox, + metrics, + cx, + |content, checked| { + content.telemetry.set_metrics(checked); + }, + ), + Label::new( + "Do you want to send telemetry?", + theme.editor.hover_popover.prose.clone(), + ) + .boxed(), + ]) + .boxed(), + Flex::row() + .with_children([ + self.render_settings_checkbox::( + &theme.welcome.checkbox, + diagnostics, + cx, + |content, checked| content.telemetry.set_diagnostics(checked), + ), + Label::new( + "Send crash reports", + theme.editor.hover_popover.prose.clone(), + ) + .boxed(), + ]) + .boxed(), ]) + .aligned() .boxed(), - ]) - .aligned() + ) .boxed() } } From 9dee2ca2be3606f994cba67f1a097a454268bf4b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 1 Mar 2023 20:42:01 -0800 Subject: [PATCH 10/45] WIP --- crates/project_panel/src/project_panel.rs | 182 ++++++++++++++----- crates/terminal_view/src/terminal_element.rs | 2 +- crates/welcome/src/welcome.rs | 12 +- 3 files changed, 143 insertions(+), 53 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4b3c5b7bc53a452b8b6d11ec41e7e41b8228a7f3..1cefc558f00d3f5e3c4f3c73987a42f8b4755652 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5,9 +5,11 @@ use futures::stream::StreamExt; use gpui::{ actions, anyhow::{anyhow, Result}, + color::Color, elements::{ - AnchorCorner, ChildView, ConstrainedBox, ContainerStyle, Empty, Flex, Label, - MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, + AnchorCorner, Canvas, ChildView, ConstrainedBox, ContainerStyle, Empty, Flex, + KeystrokeLabel, Label, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, + UniformList, UniformListState, }, geometry::vector::Vector2F, impl_internal_actions, @@ -1262,54 +1264,134 @@ impl View for ProjectPanel { let padding = std::mem::take(&mut container_style.padding); let last_worktree_root_id = self.last_worktree_root_id; - Stack::new() - .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - UniformList::new( - self.list.clone(), - self.visible_entries - .iter() - .map(|(_, worktree_entries)| worktree_entries.len()) - .sum(), - cx, - move |this, range, items, cx| { - let theme = cx.global::().theme.clone(); - let mut dragged_entry_destination = - this.dragged_entry_destination.clone(); - this.for_each_visible_entry(range, cx, |id, details, cx| { - items.push(Self::render_entry( - id, - details, - &this.filename_editor, - &mut dragged_entry_destination, - &theme.project_panel, - cx, - )); - }); - this.dragged_entry_destination = dragged_entry_destination; - }, - ) - .with_padding_top(padding.top) - .with_padding_bottom(padding.bottom) - .contained() - .with_style(container_style) - .expanded() - .boxed() - }) - .on_down(MouseButton::Right, move |e, cx| { - // When deploying the context menu anywhere below the last project entry, - // act as if the user clicked the root of the last worktree. - if let Some(entry_id) = last_worktree_root_id { - cx.dispatch_action(DeployContextMenu { - entry_id, - position: e.position, - }) - } - }) - .boxed(), - ) - .with_child(ChildView::new(&self.context_menu, cx).boxed()) - .boxed() + let has_worktree = self.visible_entries.len() != 0; + + if has_worktree { + Stack::new() + .with_child( + MouseEventHandler::::new(0, cx, |_, cx| { + UniformList::new( + self.list.clone(), + self.visible_entries + .iter() + .map(|(_, worktree_entries)| worktree_entries.len()) + .sum(), + cx, + move |this, range, items, cx| { + let theme = cx.global::().theme.clone(); + let mut dragged_entry_destination = + this.dragged_entry_destination.clone(); + this.for_each_visible_entry(range, cx, |id, details, cx| { + items.push(Self::render_entry( + id, + details, + &this.filename_editor, + &mut dragged_entry_destination, + &theme.project_panel, + cx, + )); + }); + this.dragged_entry_destination = dragged_entry_destination; + }, + ) + .with_padding_top(padding.top) + .with_padding_bottom(padding.bottom) + .contained() + .with_style(container_style) + .expanded() + .boxed() + }) + .on_down(MouseButton::Right, move |e, cx| { + // When deploying the context menu anywhere below the last project entry, + // act as if the user clicked the root of the last worktree. + if let Some(entry_id) = last_worktree_root_id { + cx.dispatch_action(DeployContextMenu { + entry_id, + position: e.position, + }) + } + }) + .boxed(), + ) + .with_child(ChildView::new(&self.context_menu, cx).boxed()) + .boxed() + } else { + let parent_view_id = cx.handle().id(); + Stack::new() + .with_child( + MouseEventHandler::::new(1, cx, |_, cx| { + Stack::new() + .with_child( + Canvas::new(|bounds, _visible_bounds, cx| { + cx.scene.push_quad(gpui::Quad { + bounds, + background: Some(Color::red()), + ..Default::default() + }) + }) + .boxed(), + ) + .with_child( + MouseEventHandler::::new(2, cx, |state, cx| { + let style = &cx + .global::() + .theme + .search + .option_button + .style_for(state, false); + + let context_menu_item = cx + .global::() + .theme + .context_menu + .clone() + .item + .style_for(state, true) + .clone(); + + Flex::row() + .with_child( + Label::new( + "Open a new project!".to_string(), + context_menu_item.label.clone(), + ) + .contained() + .boxed(), + ) + .with_child({ + KeystrokeLabel::new( + cx.window_id(), + parent_view_id, + Box::new(workspace::Open), + context_menu_item.keystroke.container, + context_menu_item.keystroke.text.clone(), + ) + .flex_float() + .boxed() + }) + .contained() + .with_style(style.container) + .aligned() + .top() + .constrained() + .with_width(100.) + .with_height(20.) + .boxed() + }) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(workspace::Open) + }) + .with_cursor_style(CursorStyle::PointingHand) + .boxed(), + ) + .boxed() + }) + // TODO is this nescessary? + .on_click(MouseButton::Left, |_, cx| cx.focus_parent_view()) + .boxed(), + ) + .boxed() + } } fn keymap_context(&self, _: &AppContext) -> KeymapContext { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 08ed3ecc2d1da04352dac73a9438a5f3087c8b74..5d03d6304e28ed93d334c308bd5c7d11dc526b07 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -720,7 +720,7 @@ impl Element for TerminalElement { cx.paint_layer(clip_bounds, |cx| { let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.); - //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse + // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx); cx.scene.push_cursor_region(gpui::CursorRegion { diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index afb38385a4a21b9bed94b10c96570f30e51000ae..54898f92c845c06d2b29479baac015971bd6fe1a 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -2,7 +2,8 @@ use gpui::{ color::Color, elements::{Canvas, Empty, Flex, Label, MouseEventHandler, ParentElement, Stack, Svg}, geometry::rect::RectF, - Element, ElementBox, Entity, MutableAppContext, RenderContext, Subscription, View, ViewContext, + Element, ElementBox, Entity, MouseRegion, MutableAppContext, RenderContext, Subscription, View, + ViewContext, }; use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; use theme::CheckboxStyle; @@ -29,6 +30,7 @@ impl View for WelcomePage { } fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { + let self_handle = cx.handle(); let settings = cx.global::(); let theme = settings.theme.clone(); @@ -44,6 +46,7 @@ impl View for WelcomePage { Stack::new() .with_child( + // TODO: Can this be moved into the pane? Canvas::new(move |bounds, visible_bounds, cx| { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); @@ -53,7 +56,12 @@ impl View for WelcomePage { background: Some(background), ..Default::default() }) - }) + }); + + cx.scene.push_mouse_region( + MouseRegion::new::(self_handle.id(), 0, visible_bounds) + .on_down(gpui::MouseButton::Left, |_, cx| cx.focus_parent_view()), + ); }) .boxed(), ) From f89f33347de1289c0d78cf9cb1b68fe5b1199765 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 09:58:07 -0800 Subject: [PATCH 11/45] Added CTA buttons to welcome experience Co-authored-by: Nathan --- Cargo.lock | 1 + crates/theme/src/theme.rs | 1 + crates/welcome/Cargo.toml | 1 + crates/welcome/src/welcome.rs | 36 ++++++++++- styles/src/styleTree/welcome.ts | 111 +++++++++++++++++++------------- 5 files changed, 102 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8124fcc3a180d2159a7bd8e5a690986f7dfa527..c25656bfa9300a42a5a752b2e47bf4abda7f3348 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8024,6 +8024,7 @@ dependencies = [ "project", "settings", "theme", + "theme_selector", "util", "workspace", ] diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 87b9d9845a314d6db5a63c280b417c43a6dc43cf..ded5c54f832ba53cbcc9c4ab998ed38122b38631 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -854,6 +854,7 @@ pub struct FeedbackStyle { #[derive(Clone, Deserialize, Default)] pub struct WelcomeStyle { pub checkbox: CheckboxStyle, + pub button: Interactive, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 3e450e2312794188f3ba9e3401fe80affeab6ad9..e1231ad5f631a11dfdad9bd77341b14207b572b5 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -18,5 +18,6 @@ gpui = { path = "../gpui" } project = { path = "../project" } settings = { path = "../settings" } theme = { path = "../theme" } +theme_selector = { path = "../theme_selector" } util = { path = "../util" } workspace = { path = "../workspace" } \ No newline at end of file diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 54898f92c845c06d2b29479baac015971bd6fe1a..7bf6c263023470ca784f7d4f1e05283a9926cfd3 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,12 +1,14 @@ +use std::borrow::Cow; + use gpui::{ color::Color, elements::{Canvas, Empty, Flex, Label, MouseEventHandler, ParentElement, Stack, Svg}, geometry::rect::RectF, - Element, ElementBox, Entity, MouseRegion, MutableAppContext, RenderContext, Subscription, View, - ViewContext, + Action, Element, ElementBox, Entity, MouseButton, MouseRegion, MutableAppContext, + RenderContext, Subscription, View, ViewContext, }; use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; -use theme::CheckboxStyle; +use theme::{CheckboxStyle, ContainedText, Interactive}; use workspace::{item::Item, Welcome, Workspace}; pub fn init(cx: &mut MutableAppContext) { @@ -86,6 +88,8 @@ impl View for WelcomePage { theme.editor.hover_popover.prose.clone(), ) .boxed(), + self.render_cta_button(2, "Choose a theme", theme_selector::Toggle, cx), + self.render_cta_button(3, "Choose a keymap", theme_selector::Toggle, cx), Flex::row() .with_children([ self.render_settings_checkbox::( @@ -141,6 +145,32 @@ impl WelcomePage { } } + fn render_cta_button( + &self, + region_id: usize, + label: L, + action: A, + cx: &mut RenderContext, + ) -> ElementBox + where + L: Into>, + A: 'static + Action + Clone, + { + let theme = cx.global::().theme.clone(); + MouseEventHandler::::new(region_id, cx, |state, _| { + let style = theme.welcome.button.style_for(state, false); + Label::new(label, style.text.clone()) + .contained() + .with_style(style.container) + .boxed() + }) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(action.clone()) + }) + .aligned() + .boxed() + } + fn render_settings_checkbox( &self, style: &CheckboxStyle, diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 6085782afd009722784c5f70e6a835bb3fe0f1ef..114ff0b7df69e12a0229698c3232c995e1edde2f 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -1,55 +1,76 @@ import { ColorScheme } from "../themes/common/colorScheme"; -import { border } from "./components"; +import { border, background, text } from "./components"; + export default function welcome(colorScheme: ColorScheme) { - let layer = colorScheme.highest; + let layer = colorScheme.highest; - // TODO - let checkboxBase = { - cornerRadius: 4, - padding: { - left: 8, - right: 8, - top: 4, - bottom: 4, - }, - shadow: colorScheme.popoverShadow, - border: border(layer), - margin: { - left: -8, - }, - }; + // TODO + let checkboxBase = { + cornerRadius: 4, + padding: { + left: 8, + right: 8, + top: 4, + bottom: 4, + }, + shadow: colorScheme.popoverShadow, + border: border(layer), + margin: { + left: -8, + }, + }; - return { - checkbox: { - width: 9, - height: 9, - default: { - ...checkboxBase, - background: colorScheme.ramps.blue(0.5).hex(), - }, - checked: { - ...checkboxBase, - background: colorScheme.ramps.red(0.5).hex(), - }, - hovered: { - ...checkboxBase, - background: colorScheme.ramps.blue(0.5).hex(), + return { + button: { + background: background(layer), + border: border(layer), + cornerRadius: 6, + margin: { + top: 1, + }, + padding: { + top: 1, + bottom: 1, + left: 7, + right: 7, + }, + ...text(layer, "sans", "variant", { size: "xs" }), + hover: { + ...text(layer, "sans", "hovered", { size: "xs" }), + background: background(layer, "hovered"), + border: border(layer, "hovered"), + }, + }, + checkbox: { + width: 9, + height: 9, + default: { + ...checkboxBase, + background: colorScheme.ramps.blue(0.5).hex(), + }, + checked: { + ...checkboxBase, + background: colorScheme.ramps.red(0.5).hex(), + }, + hovered: { + ...checkboxBase, + background: colorScheme.ramps.blue(0.5).hex(), - border: { - color: colorScheme.ramps.green(0.5).hex(), - width: 1, - } - }, - hoveredAndChecked: { - ...checkboxBase, - background: colorScheme.ramps.red(0.5).hex(), - border: { - color: colorScheme.ramps.green(0.5).hex(), - width: 1, - } - } + border: { + color: colorScheme.ramps.green(0.5).hex(), + width: 1, + } + }, + hoveredAndChecked: { + ...checkboxBase, + background: colorScheme.ramps.red(0.5).hex(), + border: { + color: colorScheme.ramps.green(0.5).hex(), + width: 1, } + } } + } } \ No newline at end of file From 4c179875ab5b120a871bfb853aa6969fa8779478 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 10:39:35 -0800 Subject: [PATCH 12/45] Add png image loading to gpui add zed logo into welcome experience Co-authored-by: Nathan --- assets/images/zed-logo-90x90.png | Bin 0 -> 12413 bytes crates/collab_ui/src/collab_titlebar_item.rs | 2 +- crates/collab_ui/src/contact_finder.rs | 2 +- crates/collab_ui/src/contact_list.rs | 6 +- .../src/incoming_call_notification.rs | 2 +- crates/collab_ui/src/notifications.rs | 2 +- .../src/project_shared_notification.rs | 2 +- crates/gpui/src/assets.rs | 21 ++++++- crates/gpui/src/elements/image.rs | 56 +++++++++++++----- crates/project_panel/src/project_panel.rs | 2 +- crates/welcome/src/welcome.rs | 14 ++--- 11 files changed, 76 insertions(+), 33 deletions(-) create mode 100644 assets/images/zed-logo-90x90.png diff --git a/assets/images/zed-logo-90x90.png b/assets/images/zed-logo-90x90.png new file mode 100644 index 0000000000000000000000000000000000000000..17f92d2c1afbb8459fd31bfdc7ace50a412ce0a8 GIT binary patch literal 12413 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 WubU>a9)CZGQhKiXtn?`~@c#k#=|!0U literal 0 HcmV?d00001 diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index f9f5738ad26762dd9e4fc5c08c6cf48439b90e67..a16c31595fa116c7128baa618b7c7cf16604c52d 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -823,7 +823,7 @@ impl CollabTitlebarItem { avatar_style: AvatarStyle, background_color: Color, ) -> ElementBox { - Image::new(avatar) + Image::from_data(avatar) .with_style(avatar_style.image) .aligned() .contained() diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index 98f70e83f0ecf1eb3c312429a8461a35b028199e..cb55669ab0a29230c2aa06a09ac6cd1718b68221 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -128,7 +128,7 @@ impl PickerDelegate for ContactFinder { .style_for(mouse_state, selected); Flex::row() .with_children(user.avatar.clone().map(|avatar| { - Image::new(avatar) + Image::from_data(avatar) .with_style(theme.contact_finder.contact_avatar) .aligned() .left() diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index 3bb036d3366a4de8f4bb8e8253ae4470ce86edbc..203d130b5b48d481797390dd3491f3d6e9d28314 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -726,7 +726,7 @@ impl ContactList { ) -> ElementBox { Flex::row() .with_children(user.avatar.clone().map(|avatar| { - Image::new(avatar) + Image::from_data(avatar) .with_style(theme.contact_avatar) .aligned() .left() @@ -1080,7 +1080,7 @@ impl ContactList { }; Stack::new() .with_child( - Image::new(avatar) + Image::from_data(avatar) .with_style(theme.contact_avatar) .aligned() .left() @@ -1173,7 +1173,7 @@ impl ContactList { let mut row = Flex::row() .with_children(user.avatar.clone().map(|avatar| { - Image::new(avatar) + Image::from_data(avatar) .with_style(theme.contact_avatar) .aligned() .left() diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index a0f54abe386ca56ef416f051dc2474e1736cc332..6fb0278218987d0e50ea7acc8792a24258426702 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -108,7 +108,7 @@ impl IncomingCallNotification { .unwrap_or(&default_project); Flex::row() .with_children(self.call.calling_user.avatar.clone().map(|avatar| { - Image::new(avatar) + Image::from_data(avatar) .with_style(theme.caller_avatar) .aligned() .boxed() diff --git a/crates/collab_ui/src/notifications.rs b/crates/collab_ui/src/notifications.rs index 06b6cf2a90984fe57b932892f94fda83bb06b369..21c2d2c2185ed69d6c19ad85be51cff1231d5bcb 100644 --- a/crates/collab_ui/src/notifications.rs +++ b/crates/collab_ui/src/notifications.rs @@ -24,7 +24,7 @@ pub fn render_user_notification( .with_child( Flex::row() .with_children(user.avatar.clone().map(|avatar| { - Image::new(avatar) + Image::from_data(avatar) .with_style(theme.header_avatar) .aligned() .constrained() diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index edf0354eec0d53be306e613697962a72cc34a683..b24f3492da933f4a868be65cfb2f3344679fc6c6 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -108,7 +108,7 @@ impl ProjectSharedNotification { let theme = &cx.global::().theme.project_shared_notification; Flex::row() .with_children(self.owner.avatar.clone().map(|avatar| { - Image::new(avatar) + Image::from_data(avatar) .with_style(theme.owner_avatar) .aligned() .boxed() diff --git a/crates/gpui/src/assets.rs b/crates/gpui/src/assets.rs index ac0d72dee97a8d5553963c4677bc3f6051fbaf34..2170d215af078257326c258c7502f4c90c84e19e 100644 --- a/crates/gpui/src/assets.rs +++ b/crates/gpui/src/assets.rs @@ -1,5 +1,8 @@ use anyhow::{anyhow, Result}; -use std::{borrow::Cow, cell::RefCell, collections::HashMap}; +use image::ImageFormat; +use std::{borrow::Cow, cell::RefCell, collections::HashMap, sync::Arc}; + +use crate::ImageData; pub trait AssetSource: 'static + Send + Sync { fn load(&self, path: &str) -> Result>; @@ -22,6 +25,7 @@ impl AssetSource for () { pub struct AssetCache { source: Box, svgs: RefCell>, + pngs: RefCell>>, } impl AssetCache { @@ -29,6 +33,7 @@ impl AssetCache { Self { source: Box::new(source), svgs: RefCell::new(HashMap::new()), + pngs: RefCell::new(HashMap::new()), } } @@ -43,4 +48,18 @@ impl AssetCache { Ok(svg) } } + + pub fn png(&self, path: &str) -> Result> { + let mut pngs = self.pngs.borrow_mut(); + if let Some(png) = pngs.get(path) { + Ok(png.clone()) + } else { + let bytes = self.source.load(path)?; + let image = ImageData::new( + image::load_from_memory_with_format(&bytes, ImageFormat::Png)?.into_bgra8(), + ); + pngs.insert(path.to_string(), image.clone()); + Ok(image) + } + } } diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs index 37cb01ace8a6c43e0add3d366d888f06b4b0225e..cc49308e15011b317840da086170c28d1132996d 100644 --- a/crates/gpui/src/elements/image.rs +++ b/crates/gpui/src/elements/image.rs @@ -11,8 +11,13 @@ use crate::{ use serde::Deserialize; use std::{ops::Range, sync::Arc}; +enum ImageSource { + Path(&'static str), + Data(Arc), +} + pub struct Image { - data: Arc, + source: ImageSource, style: ImageStyle, } @@ -31,9 +36,16 @@ pub struct ImageStyle { } impl Image { - pub fn new(data: Arc) -> Self { + pub fn new(asset_path: &'static str) -> Self { + Self { + source: ImageSource::Path(asset_path), + style: Default::default(), + } + } + + pub fn from_data(data: Arc) -> Self { Self { - data, + source: ImageSource::Data(data), style: Default::default(), } } @@ -45,39 +57,53 @@ impl Image { } impl Element for Image { - type LayoutState = (); + type LayoutState = Option>; type PaintState = (); fn layout( &mut self, constraint: SizeConstraint, - _: &mut LayoutContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { + let data = match &self.source { + ImageSource::Path(path) => match cx.asset_cache.png(path) { + Ok(data) => data, + Err(error) => { + log::error!("could not load image: {}", error); + return (Vector2F::zero(), None); + } + }, + ImageSource::Data(data) => data.clone(), + }; + let desired_size = vec2f( self.style.width.unwrap_or_else(|| constraint.max.x()), self.style.height.unwrap_or_else(|| constraint.max.y()), ); let size = constrain_size_preserving_aspect_ratio( constraint.constrain(desired_size), - self.data.size().to_f32(), + data.size().to_f32(), ); - (size, ()) + + (size, Some(data)) } fn paint( &mut self, bounds: RectF, _: RectF, - _: &mut Self::LayoutState, + layout: &mut Self::LayoutState, cx: &mut PaintContext, ) -> Self::PaintState { - cx.scene.push_image(scene::Image { - bounds, - border: self.style.border, - corner_radius: self.style.corner_radius, - grayscale: self.style.grayscale, - data: self.data.clone(), - }); + if let Some(data) = layout { + cx.scene.push_image(scene::Image { + bounds, + border: self.style.border, + corner_radius: self.style.corner_radius, + grayscale: self.style.grayscale, + data: data.clone(), + }); + } } fn rect_for_text_range( diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 1cefc558f00d3f5e3c4f3c73987a42f8b4755652..9df6581d209c47b4e3763ca9acf12da1ce378189 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1325,7 +1325,7 @@ impl View for ProjectPanel { Canvas::new(|bounds, _visible_bounds, cx| { cx.scene.push_quad(gpui::Quad { bounds, - background: Some(Color::red()), + background: Some(Color::transparent_black()), ..Default::default() }) }) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 7bf6c263023470ca784f7d4f1e05283a9926cfd3..232fd1bcb732aeda9cdace32a48e303f2dd36e78 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,14 +1,13 @@ use std::borrow::Cow; use gpui::{ - color::Color, - elements::{Canvas, Empty, Flex, Label, MouseEventHandler, ParentElement, Stack, Svg}, + elements::{Canvas, Empty, Flex, Image, Label, MouseEventHandler, ParentElement, Stack}, geometry::rect::RectF, Action, Element, ElementBox, Entity, MouseButton, MouseRegion, MutableAppContext, RenderContext, Subscription, View, ViewContext, }; use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; -use theme::{CheckboxStyle, ContainedText, Interactive}; +use theme::CheckboxStyle; use workspace::{item::Item, Welcome, Workspace}; pub fn init(cx: &mut MutableAppContext) { @@ -72,15 +71,14 @@ impl View for WelcomePage { .with_children([ Flex::row() .with_children([ - Svg::new("icons/terminal_16.svg") - .with_color(Color::red()) + Image::new("images/zed-logo-90x90.png") .constrained() - .with_width(100.) - .with_height(100.) + .with_width(90.) + .with_height(90.) .aligned() .contained() .boxed(), - Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), + // Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), ]) .boxed(), Label::new( From 4a8527478df335c27bf1cd601676d82fb73e535f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 11:51:58 -0800 Subject: [PATCH 13/45] Add child item alignment to flex implementation Fix checkbox styling co-authored-by: Nathan --- crates/gpui/src/elements/flex.rs | 32 ++++++++++++++++++++++++- crates/theme/src/theme.rs | 2 ++ crates/welcome/src/welcome.rs | 16 +++++++++---- styles/src/styleTree/welcome.ts | 41 +++++++++++++++++++++----------- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index ce595222f3f8a3c2e95019f19f790114f05653fd..b1427269949d050c2972bd1096601c902bdbabce 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -22,6 +22,7 @@ pub struct Flex { axis: Axis, children: Vec, scroll_state: Option<(ElementStateHandle>, usize)>, + child_alignment: f32, } impl Flex { @@ -30,6 +31,7 @@ impl Flex { axis, children: Default::default(), scroll_state: None, + child_alignment: -1., } } @@ -41,6 +43,15 @@ impl Flex { Self::new(Axis::Vertical) } + /// Render children centered relative to the cross-axis of the parent flex. + /// + /// If this is a flex row, children will be centered vertically. If this is a + /// flex column, children will be centered horizontally. + pub fn align_children_center(mut self) -> Self { + self.child_alignment = 0.; + self + } + pub fn scrollable( mut self, element_id: usize, @@ -309,7 +320,26 @@ impl Element for Flex { } } - child.paint(child_origin, visible_bounds, cx); + // We use the child_alignment f32 to determine a point along the cross axis of the + // overall flex element and each child. We then align these points. So 0 would center + // each child relative to the overall height/width of the flex. -1 puts children at + // the start. 1 puts children at the end. + let cross_axis = self.axis.invert(); + let my_center = bounds.size().along(cross_axis) / 2.; + let my_target = my_center + my_center * self.child_alignment; + + let child_center = child.size().along(cross_axis) / 2.; + let child_target = child_center + child_center * self.child_alignment; + + let mut aligned_child_origin = child_origin; + match self.axis { + Axis::Horizontal => aligned_child_origin + .set_y(aligned_child_origin.y() - (child_target - my_target)), + Axis::Vertical => aligned_child_origin + .set_x(aligned_child_origin.x() - (child_target - my_target)), + } + + child.paint(aligned_child_origin, visible_bounds, cx); match self.axis { Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ded5c54f832ba53cbcc9c4ab998ed38122b38631..9442f08d24704af9d4f2309aeadd512cb114e3f5 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -859,6 +859,8 @@ pub struct WelcomeStyle { #[derive(Clone, Deserialize, Default)] pub struct CheckboxStyle { + pub icon: String, + pub icon_color: Color, pub width: f32, pub height: f32, pub default: ContainerStyle, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 232fd1bcb732aeda9cdace32a48e303f2dd36e78..f787a6cf24b038f370a6344c031f78b4d8e9664d 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use gpui::{ - elements::{Canvas, Empty, Flex, Image, Label, MouseEventHandler, ParentElement, Stack}, + elements::{Canvas, Empty, Flex, Image, Label, MouseEventHandler, ParentElement, Stack, Svg}, geometry::rect::RectF, Action, Element, ElementBox, Entity, MouseButton, MouseRegion, MutableAppContext, RenderContext, Subscription, View, ViewContext, @@ -104,6 +104,7 @@ impl View for WelcomePage { ) .boxed(), ]) + .align_children_center() .boxed(), Flex::row() .with_children([ @@ -119,9 +120,9 @@ impl View for WelcomePage { ) .boxed(), ]) + .align_children_center() .boxed(), ]) - .aligned() .boxed(), ) .boxed() @@ -177,8 +178,15 @@ impl WelcomePage { set_value: fn(&mut SettingsFileContent, checked: bool) -> (), ) -> ElementBox { MouseEventHandler::::new(0, cx, |state, _| { - Empty::new() - .constrained() + let indicator = if checked { + Svg::new(style.icon.clone()) + .with_color(style.icon_color) + .constrained() + } else { + Empty::new().constrained() + }; + + indicator .with_width(style.width) .with_height(style.height) .contained() diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 114ff0b7df69e12a0229698c3232c995e1edde2f..02e3c11f914f1b15b8cdd190589147f242217b70 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -1,6 +1,6 @@ import { ColorScheme } from "../themes/common/colorScheme"; -import { border, background, text } from "./components"; +import { border, background, foreground, text } from "./components"; export default function welcome(colorScheme: ColorScheme) { @@ -10,15 +10,18 @@ export default function welcome(colorScheme: ColorScheme) { let checkboxBase = { cornerRadius: 4, padding: { - left: 8, - right: 8, - top: 4, - bottom: 4, + left: 3, + right: 3, + top: 3, + bottom: 3, }, shadow: colorScheme.popoverShadow, border: border(layer), margin: { - left: -8, + left: 8, + right: 8, + top: 5, + bottom: 5 }, }; @@ -44,30 +47,40 @@ export default function welcome(colorScheme: ColorScheme) { }, }, checkbox: { - width: 9, - height: 9, + width: 12, + height: 12, + icon: "icons/check_12.svg", + iconColor: foreground(layer, "on"), default: { ...checkboxBase, - background: colorScheme.ramps.blue(0.5).hex(), + background: background(layer, "default"), + border: { + color: foreground(layer, "hovered"), + width: 1, + } }, checked: { ...checkboxBase, - background: colorScheme.ramps.red(0.5).hex(), + background: background(layer, "hovered"), + border: { + color: foreground(layer, "hovered"), + width: 1, + } }, hovered: { ...checkboxBase, - background: colorScheme.ramps.blue(0.5).hex(), + background: background(layer, "hovered"), border: { - color: colorScheme.ramps.green(0.5).hex(), + color: foreground(layer, "hovered"), width: 1, } }, hoveredAndChecked: { ...checkboxBase, - background: colorScheme.ramps.red(0.5).hex(), + background: background(layer, "hovered"), border: { - color: colorScheme.ramps.green(0.5).hex(), + color: foreground(layer, "hovered"), width: 1, } } From b74553455f7712e2cf3e9fc738fdc0dc882685ef Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 14:14:36 -0800 Subject: [PATCH 14/45] Add an element to pane to take care of wiring initial mouse handlers --- crates/gpui/src/elements/flex.rs | 30 +++--- crates/welcome/src/welcome.rs | 154 ++++++++++++++----------------- crates/workspace/src/pane.rs | 91 +++++++++++++++++- 3 files changed, 175 insertions(+), 100 deletions(-) diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index b1427269949d050c2972bd1096601c902bdbabce..5df283bfee2f20a7b51ec1900526764eab3ef0d4 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -324,20 +324,24 @@ impl Element for Flex { // overall flex element and each child. We then align these points. So 0 would center // each child relative to the overall height/width of the flex. -1 puts children at // the start. 1 puts children at the end. - let cross_axis = self.axis.invert(); - let my_center = bounds.size().along(cross_axis) / 2.; - let my_target = my_center + my_center * self.child_alignment; - - let child_center = child.size().along(cross_axis) / 2.; - let child_target = child_center + child_center * self.child_alignment; + let aligned_child_origin = { + let cross_axis = self.axis.invert(); + let my_center = bounds.size().along(cross_axis) / 2.; + let my_target = my_center + my_center * self.child_alignment; + + let child_center = child.size().along(cross_axis) / 2.; + let child_target = child_center + child_center * self.child_alignment; + + let mut aligned_child_origin = child_origin; + match self.axis { + Axis::Horizontal => aligned_child_origin + .set_y(aligned_child_origin.y() - (child_target - my_target)), + Axis::Vertical => aligned_child_origin + .set_x(aligned_child_origin.x() - (child_target - my_target)), + } - let mut aligned_child_origin = child_origin; - match self.axis { - Axis::Horizontal => aligned_child_origin - .set_y(aligned_child_origin.y() - (child_target - my_target)), - Axis::Vertical => aligned_child_origin - .set_x(aligned_child_origin.x() - (child_target - my_target)), - } + aligned_child_origin + }; child.paint(aligned_child_origin, visible_bounds, cx); diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index f787a6cf24b038f370a6344c031f78b4d8e9664d..f1d59ce7a148010582998363140975cef86f028d 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,14 +1,13 @@ use std::borrow::Cow; use gpui::{ - elements::{Canvas, Empty, Flex, Image, Label, MouseEventHandler, ParentElement, Stack, Svg}, - geometry::rect::RectF, - Action, Element, ElementBox, Entity, MouseButton, MouseRegion, MutableAppContext, - RenderContext, Subscription, View, ViewContext, + elements::{Empty, Flex, Image, Label, MouseEventHandler, ParentElement, Svg}, + Action, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext, + Subscription, View, ViewContext, }; use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; use theme::CheckboxStyle; -use workspace::{item::Item, Welcome, Workspace}; +use workspace::{item::Item, PaneBackdrop, Welcome, Workspace, WorkspaceId}; pub fn init(cx: &mut MutableAppContext) { cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { @@ -43,89 +42,67 @@ impl View for WelcomePage { enum Metrics {} enum Diagnostics {} - let background = theme.editor.background; - - Stack::new() - .with_child( - // TODO: Can this be moved into the pane? - Canvas::new(move |bounds, visible_bounds, cx| { - let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - - cx.paint_layer(Some(visible_bounds), |cx| { - cx.scene.push_quad(gpui::Quad { - bounds: RectF::new(bounds.origin(), bounds.size()), - background: Some(background), - ..Default::default() - }) - }); - - cx.scene.push_mouse_region( - MouseRegion::new::(self_handle.id(), 0, visible_bounds) - .on_down(gpui::MouseButton::Left, |_, cx| cx.focus_parent_view()), - ); - }) - .boxed(), - ) - .with_child( - Flex::column() - .with_children([ - Flex::row() - .with_children([ - Image::new("images/zed-logo-90x90.png") - .constrained() - .with_width(90.) - .with_height(90.) - .aligned() - .contained() - .boxed(), - // Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), - ]) - .boxed(), - Label::new( - "Code at the speed of thought", - theme.editor.hover_popover.prose.clone(), - ) - .boxed(), - self.render_cta_button(2, "Choose a theme", theme_selector::Toggle, cx), - self.render_cta_button(3, "Choose a keymap", theme_selector::Toggle, cx), - Flex::row() - .with_children([ - self.render_settings_checkbox::( - &theme.welcome.checkbox, - metrics, - cx, - |content, checked| { - content.telemetry.set_metrics(checked); - }, - ), - Label::new( - "Do you want to send telemetry?", - theme.editor.hover_popover.prose.clone(), - ) + PaneBackdrop::new( + self_handle.id(), + Flex::column() + .with_children([ + Flex::row() + .with_children([ + Image::new("images/zed-logo-90x90.png") + .constrained() + .with_width(90.) + .with_height(90.) + .aligned() + .contained() .boxed(), - ]) - .align_children_center() + // Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), + ]) + .boxed(), + Label::new( + "Code at the speed of thought", + theme.editor.hover_popover.prose.clone(), + ) + .boxed(), + self.render_cta_button(2, "Choose a theme", theme_selector::Toggle, cx), + self.render_cta_button(3, "Choose a keymap", theme_selector::Toggle, cx), + Flex::row() + .with_children([ + self.render_settings_checkbox::( + &theme.welcome.checkbox, + metrics, + cx, + |content, checked| { + content.telemetry.set_metrics(checked); + }, + ), + Label::new( + "Do you want to send telemetry?", + theme.editor.hover_popover.prose.clone(), + ) .boxed(), - Flex::row() - .with_children([ - self.render_settings_checkbox::( - &theme.welcome.checkbox, - diagnostics, - cx, - |content, checked| content.telemetry.set_diagnostics(checked), - ), - Label::new( - "Send crash reports", - theme.editor.hover_popover.prose.clone(), - ) - .boxed(), - ]) - .align_children_center() + ]) + .align_children_center() + .boxed(), + Flex::row() + .with_children([ + self.render_settings_checkbox::( + &theme.welcome.checkbox, + diagnostics, + cx, + |content, checked| content.telemetry.set_diagnostics(checked), + ), + Label::new( + "Send crash reports", + theme.editor.hover_popover.prose.clone(), + ) .boxed(), - ]) - .boxed(), - ) - .boxed() + ]) + .align_children_center() + .boxed(), + ]) + .boxed(), + ) + .boxed() } } @@ -232,4 +209,11 @@ impl Item for WelcomePage { fn show_toolbar(&self) -> bool { false } + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option { + Some(WelcomePage::new(cx)) + } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 235df0202d6fc3fc6f4546596385fee28b0647bf..fe339969617c2214606a9a1dbb6937737ca495c9 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -24,8 +24,8 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, NavigationDirection}, Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, - ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + ModelHandle, MouseButton, MouseRegion, MutableAppContext, PromptLevel, Quad, RenderContext, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; @@ -1706,6 +1706,93 @@ impl NavHistory { } } +pub struct PaneBackdrop { + child_view: usize, + child: ElementBox, +} +impl PaneBackdrop { + pub fn new(pane_item_view: usize, child: ElementBox) -> Self { + PaneBackdrop { + child, + child_view: pane_item_view, + } + } +} + +impl Element for PaneBackdrop { + type LayoutState = (); + + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + cx: &mut gpui::LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + let size = self.child.layout(constraint, cx); + (size, ()) + } + + fn paint( + &mut self, + bounds: RectF, + visible_bounds: RectF, + _: &mut Self::LayoutState, + cx: &mut gpui::PaintContext, + ) -> Self::PaintState { + let background = cx.global::().theme.editor.background; + + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + cx.scene.push_quad(gpui::Quad { + bounds: RectF::new(bounds.origin(), bounds.size()), + background: Some(background), + ..Default::default() + }); + + let child_view_id = self.child_view; + cx.scene.push_mouse_region( + MouseRegion::new::(child_view_id, 0, visible_bounds).on_down( + gpui::MouseButton::Left, + move |_, cx| { + let window_id = cx.window_id; + cx.focus(window_id, Some(child_view_id)) + }, + ), + ); + + cx.paint_layer(Some(bounds), |cx| { + self.child.paint(bounds.origin(), visible_bounds, cx) + }) + } + + fn rect_for_text_range( + &self, + range_utf16: std::ops::Range, + _bounds: RectF, + _visible_bounds: RectF, + _layout: &Self::LayoutState, + _paint: &Self::PaintState, + cx: &gpui::MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + + fn debug( + &self, + _bounds: RectF, + _layout: &Self::LayoutState, + _paint: &Self::PaintState, + cx: &gpui::DebugContext, + ) -> serde_json::Value { + gpui::json::json!({ + "type": "Pane Back Drop", + "view": self.child_view, + "child": self.child.debug(cx), + }) + } +} + #[cfg(test)] mod tests { use std::sync::Arc; From 020a0965b0cd0a1d8dcf5c89ee61a9f44320afa3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 14:16:55 -0800 Subject: [PATCH 15/45] WIP --- crates/welcome/src/welcome.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index f1d59ce7a148010582998363140975cef86f028d..dcc72357edcd158e68729311f0ca7e3947bcf546 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -100,6 +100,9 @@ impl View for WelcomePage { .align_children_center() .boxed(), ]) + .aligned() + .constrained() + .with_max_width(300.) .boxed(), ) .boxed() From ba652fc0331cf1c4cc6c161133647ffa533e5d46 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 16:28:23 -0800 Subject: [PATCH 16/45] Finish basic welcome experience --- crates/theme/src/theme.rs | 8 +- crates/welcome/src/welcome.rs | 135 +++++++++++++-------------- styles/src/styleTree/components.ts | 2 +- styles/src/styleTree/hoverPopover.ts | 78 ++++++++-------- styles/src/styleTree/welcome.ts | 59 ++++++------ 5 files changed, 143 insertions(+), 139 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 9442f08d24704af9d4f2309aeadd512cb114e3f5..43a10978d6fd1a36e03d59b5dda4523e13d30d10 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -853,14 +853,18 @@ pub struct FeedbackStyle { #[derive(Clone, Deserialize, Default)] pub struct WelcomeStyle { + pub page_width: f32, + pub logo_subheading: ContainedText, pub checkbox: CheckboxStyle, pub button: Interactive, } #[derive(Clone, Deserialize, Default)] pub struct CheckboxStyle { - pub icon: String, - pub icon_color: Color, + pub check_icon: String, + pub check_icon_color: Color, + pub label: ContainedText, + pub container: ContainerStyle, pub width: f32, pub height: f32, pub default: ContainerStyle, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index dcc72357edcd158e68729311f0ca7e3947bcf546..c24e0b5b24ccd1cf61f5bf24c26969ba0bd040a2 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -34,6 +34,8 @@ impl View for WelcomePage { let settings = cx.global::(); let theme = settings.theme.clone(); + let width = theme.welcome.page_width; + let (diagnostics, metrics) = { let telemetry = settings.telemetry(); (telemetry.diagnostics(), telemetry.metrics()) @@ -46,63 +48,42 @@ impl View for WelcomePage { self_handle.id(), Flex::column() .with_children([ - Flex::row() - .with_children([ - Image::new("images/zed-logo-90x90.png") - .constrained() - .with_width(90.) - .with_height(90.) - .aligned() - .contained() - .boxed(), - // Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), - ]) + Image::new("images/zed-logo-90x90.png") + .constrained() + .with_width(90.) + .with_height(90.) + .aligned() + .contained() + .aligned() .boxed(), Label::new( "Code at the speed of thought", - theme.editor.hover_popover.prose.clone(), + theme.welcome.logo_subheading.text.clone(), ) + .aligned() + .contained() + .with_style(theme.welcome.logo_subheading.container) .boxed(), - self.render_cta_button(2, "Choose a theme", theme_selector::Toggle, cx), - self.render_cta_button(3, "Choose a keymap", theme_selector::Toggle, cx), - Flex::row() - .with_children([ - self.render_settings_checkbox::( - &theme.welcome.checkbox, - metrics, - cx, - |content, checked| { - content.telemetry.set_metrics(checked); - }, - ), - Label::new( - "Do you want to send telemetry?", - theme.editor.hover_popover.prose.clone(), - ) - .boxed(), - ]) - .align_children_center() - .boxed(), - Flex::row() - .with_children([ - self.render_settings_checkbox::( - &theme.welcome.checkbox, - diagnostics, - cx, - |content, checked| content.telemetry.set_diagnostics(checked), - ), - Label::new( - "Send crash reports", - theme.editor.hover_popover.prose.clone(), - ) - .boxed(), - ]) - .align_children_center() - .boxed(), + self.render_cta_button(2, "Choose a theme", theme_selector::Toggle, width, cx), + self.render_cta_button(3, "Choose a keymap", theme_selector::Toggle, width, cx), + self.render_settings_checkbox::( + "Do you want to send telemetry?", + &theme.welcome.checkbox, + metrics, + cx, + |content, checked| content.telemetry.set_metrics(checked), + ), + self.render_settings_checkbox::( + "Send crash reports", + &theme.welcome.checkbox, + diagnostics, + cx, + |content, checked| content.telemetry.set_diagnostics(checked), + ), ]) - .aligned() .constrained() - .with_max_width(300.) + .with_max_width(width) + .aligned() .boxed(), ) .boxed() @@ -129,6 +110,7 @@ impl WelcomePage { region_id: usize, label: L, action: A, + width: f32, cx: &mut RenderContext, ) -> ElementBox where @@ -139,19 +121,23 @@ impl WelcomePage { MouseEventHandler::::new(region_id, cx, |state, _| { let style = theme.welcome.button.style_for(state, false); Label::new(label, style.text.clone()) + .aligned() .contained() .with_style(style.container) + .constrained() + .with_width(width) .boxed() }) .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(action.clone()) }) - .aligned() + .with_cursor_style(gpui::CursorStyle::PointingHand) .boxed() } fn render_settings_checkbox( &self, + label: &'static str, style: &CheckboxStyle, checked: bool, cx: &mut RenderContext, @@ -159,35 +145,44 @@ impl WelcomePage { ) -> ElementBox { MouseEventHandler::::new(0, cx, |state, _| { let indicator = if checked { - Svg::new(style.icon.clone()) - .with_color(style.icon_color) + Svg::new(style.check_icon.clone()) + .with_color(style.check_icon_color) .constrained() } else { Empty::new().constrained() }; - indicator - .with_width(style.width) - .with_height(style.height) - .contained() - .with_style(if checked { - if state.hovered() { - style.hovered_and_checked - } else { - style.checked - } - } else { - if state.hovered() { - style.hovered - } else { - style.default - } - }) + Flex::row() + .with_children([ + indicator + .with_width(style.width) + .with_height(style.height) + .contained() + .with_style(if checked { + if state.hovered() { + style.hovered_and_checked + } else { + style.checked + } + } else { + if state.hovered() { + style.hovered + } else { + style.default + } + }) + .boxed(), + Label::new(label, style.label.text.clone()).contained().with_style(style.label.container).boxed(), + ]) + .align_children_center() .boxed() }) .on_click(gpui::MouseButton::Left, move |_, cx| { SettingsFile::update(cx, move |content| set_value(content, !checked)) }) + .with_cursor_style(gpui::CursorStyle::PointingHand) + .contained() + .with_style(style.container) .boxed() } } diff --git a/styles/src/styleTree/components.ts b/styles/src/styleTree/components.ts index edbced8323c390499388071f498ddfa912fc0aed..3f69df981e8124a343c3b534aced6261b72bf309 100644 --- a/styles/src/styleTree/components.ts +++ b/styles/src/styleTree/components.ts @@ -93,7 +93,7 @@ interface Text { underline?: boolean } -interface TextProperties { +export interface TextProperties { size?: keyof typeof fontSizes weight?: FontWeight underline?: boolean diff --git a/styles/src/styleTree/hoverPopover.ts b/styles/src/styleTree/hoverPopover.ts index 032c53112b27f9f6f47f378a23ef59a9ea40ac53..a2a4467d7c5a9a08bc5cd589d4e9d129c7d8aa07 100644 --- a/styles/src/styleTree/hoverPopover.ts +++ b/styles/src/styleTree/hoverPopover.ts @@ -2,44 +2,44 @@ import { ColorScheme } from "../themes/common/colorScheme" import { background, border, text } from "./components" export default function HoverPopover(colorScheme: ColorScheme) { - let layer = colorScheme.middle - let baseContainer = { - background: background(layer), - cornerRadius: 8, - padding: { - left: 8, - right: 8, - top: 4, - bottom: 4, - }, - shadow: colorScheme.popoverShadow, - border: border(layer), - margin: { - left: -8, - }, - } + let layer = colorScheme.middle + let baseContainer = { + background: background(layer), + cornerRadius: 8, + padding: { + left: 8, + right: 8, + top: 4, + bottom: 4, + }, + shadow: colorScheme.popoverShadow, + border: border(layer), + margin: { + left: -8, + }, + } - return { - container: baseContainer, - infoContainer: { - ...baseContainer, - background: background(layer, "accent"), - border: border(layer, "accent"), - }, - warningContainer: { - ...baseContainer, - background: background(layer, "warning"), - border: border(layer, "warning"), - }, - errorContainer: { - ...baseContainer, - background: background(layer, "negative"), - border: border(layer, "negative"), - }, - block_style: { - padding: { top: 4 }, - }, - prose: text(layer, "sans", { size: "sm" }), - highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better - } + return { + container: baseContainer, + infoContainer: { + ...baseContainer, + background: background(layer, "accent"), + border: border(layer, "accent"), + }, + warningContainer: { + ...baseContainer, + background: background(layer, "warning"), + border: border(layer, "warning"), + }, + errorContainer: { + ...baseContainer, + background: background(layer, "negative"), + border: border(layer, "negative"), + }, + block_style: { + padding: { top: 4 }, + }, + prose: text(layer, "sans", { size: "sm" }), + highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better + } } diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 02e3c11f914f1b15b8cdd190589147f242217b70..bfd67cec8d19d29d651e71acfc573f9aa4757c1c 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -1,12 +1,11 @@ import { ColorScheme } from "../themes/common/colorScheme"; -import { border, background, foreground, text } from "./components"; +import { border, background, foreground, text, TextProperties } from "./components"; export default function welcome(colorScheme: ColorScheme) { let layer = colorScheme.highest; - // TODO let checkboxBase = { cornerRadius: 4, padding: { @@ -18,20 +17,30 @@ export default function welcome(colorScheme: ColorScheme) { shadow: colorScheme.popoverShadow, border: border(layer), margin: { - left: 8, right: 8, top: 5, bottom: 5 }, }; + + let interactive_text_size: TextProperties = { size: "md" } return { + pageWidth: 450, + logoSubheading: { + ...text(layer, "sans", { size: "lg" }), + margin: { + top: 10, + bottom: 7, + }, + }, button: { background: background(layer), - border: border(layer), - cornerRadius: 6, + border: border(layer, "active"), + cornerRadius: 4, margin: { - top: 1, + top: 8, + bottom: 7 }, padding: { top: 1, @@ -39,50 +48,46 @@ export default function welcome(colorScheme: ColorScheme) { left: 7, right: 7, }, - ...text(layer, "sans", "variant", { size: "xs" }), + ...text(layer, "sans", "hovered", interactive_text_size), hover: { - ...text(layer, "sans", "hovered", { size: "xs" }), + ...text(layer, "sans", "hovered", interactive_text_size), background: background(layer, "hovered"), border: border(layer, "hovered"), }, }, checkbox: { + label: { + ...text(layer, "sans", interactive_text_size), + // Also supports margin, container, border, etc. + }, + container: { + margin: { + top: 5, + }, + }, width: 12, height: 12, - icon: "icons/check_12.svg", - iconColor: foreground(layer, "on"), + checkIcon: "icons/check_12.svg", + checkIconColor: foreground(layer, "on"), default: { ...checkboxBase, background: background(layer, "default"), - border: { - color: foreground(layer, "hovered"), - width: 1, - } + border: border(layer, "active") }, checked: { ...checkboxBase, background: background(layer, "hovered"), - border: { - color: foreground(layer, "hovered"), - width: 1, - } + border: border(layer, "active") }, hovered: { ...checkboxBase, background: background(layer, "hovered"), - - border: { - color: foreground(layer, "hovered"), - width: 1, - } + border: border(layer, "hovered") }, hoveredAndChecked: { ...checkboxBase, background: background(layer, "hovered"), - border: { - color: foreground(layer, "hovered"), - width: 1, - } + border: border(layer, "active") } } } From 1f6bd0ea775875d64393093b112140f3c2eedb19 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 16:35:15 -0800 Subject: [PATCH 17/45] Fix edge case where the welcome page might open in the dock if the user's actions race the welcome experience action --- crates/workspace/src/workspace.rs | 17 +++++++++++++++++ crates/zed/src/zed.rs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 442d28579fc1be8358cf322fa3f0c264d9cbdec3..cd2cd6fccb3ffd1f5f7cfcab2ae8af76386d9c1a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1349,6 +1349,23 @@ impl Workspace { pane } + pub fn add_item_to_center( + &mut self, + item: Box, + cx: &mut ViewContext, + ) -> bool { + if let Some(center_pane) = self.last_active_center_pane.clone() { + if let Some(center_pane) = center_pane.upgrade(cx) { + Pane::add_item(self, ¢er_pane, item, true, true, None, cx); + true + } else { + false + } + } else { + false + } + } + pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { let active_pane = self.active_pane().clone(); Pane::add_item(self, &active_pane, item, true, true, None, cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 5d13d41bba066ca041b9f5ffdc76a4a193cd19ba..a9032a264ba8e8fbfc9e3f505ae313038b8e7053 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -266,7 +266,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { open_new(&app_state, cx, |workspace, cx| { workspace.toggle_sidebar(SidebarSide::Left, cx); let welcome_page = cx.add_view(|cx| welcome::WelcomePage::new(cx)); - workspace.add_item(Box::new(welcome_page.clone()), cx); + workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); Dock::move_dock(workspace, settings::DockAnchor::Bottom, false, cx); cx.focus(welcome_page); cx.notify(); From 8db7e17ac51266c60fdb610ecad4efc81627210f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 17:55:58 -0800 Subject: [PATCH 18/45] Move install_cli function to a seperate crate Add install cli button to welcome experience Add toast pop ups for CLI installation status --- Cargo.lock | 14 +++++++ Cargo.toml | 1 + crates/install_cli/Cargo.toml | 18 +++++++++ crates/install_cli/src/install_cli.rs | 56 ++++++++++++++++++++++++++ crates/welcome/Cargo.toml | 1 + crates/welcome/src/welcome.rs | 1 + crates/workspace/Cargo.toml | 1 + crates/workspace/src/notifications.rs | 28 +++++++++---- crates/workspace/src/workspace.rs | 26 +++++++++++- crates/zed/Cargo.toml | 1 + crates/zed/src/menus.rs | 2 +- crates/zed/src/zed.rs | 57 ++------------------------- 12 files changed, 142 insertions(+), 64 deletions(-) create mode 100644 crates/install_cli/Cargo.toml create mode 100644 crates/install_cli/src/install_cli.rs diff --git a/Cargo.lock b/Cargo.lock index c25656bfa9300a42a5a752b2e47bf4abda7f3348..2b894eff353636540473d147ecd6a864cd6d1db1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3018,6 +3018,17 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" +[[package]] +name = "install_cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "gpui", + "log", + "smol", + "util", +] + [[package]] name = "instant" version = "0.1.12" @@ -8020,6 +8031,7 @@ dependencies = [ "anyhow", "editor", "gpui", + "install_cli", "log", "project", "settings", @@ -8304,6 +8316,7 @@ dependencies = [ "futures 0.3.25", "gpui", "indoc", + "install_cli", "language", "lazy_static", "log", @@ -8412,6 +8425,7 @@ dependencies = [ "ignore", "image", "indexmap", + "install_cli", "isahc", "journal", "language", diff --git a/Cargo.toml b/Cargo.toml index feb80633c4f48b63562f5adda2a22cd0062e94c8..83618951465273a39ee59ba214d10ddf5628a382 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "crates/go_to_line", "crates/gpui", "crates/gpui_macros", + "crates/install_cli", "crates/journal", "crates/language", "crates/live_kit_client", diff --git a/crates/install_cli/Cargo.toml b/crates/install_cli/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bbbe9899209733f8abee8500139b56f10389c4a7 --- /dev/null +++ b/crates/install_cli/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "install_cli" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/install_cli.rs" + +[features] +test-support = [] + +[dependencies] +smol = "1.2.5" +anyhow = "1.0.38" +log = "0.4" +gpui = { path = "../gpui" } +util = { path = "../util" } diff --git a/crates/install_cli/src/install_cli.rs b/crates/install_cli/src/install_cli.rs new file mode 100644 index 0000000000000000000000000000000000000000..06561a28f8b6e04c13ef65f1400cbfef858d4db8 --- /dev/null +++ b/crates/install_cli/src/install_cli.rs @@ -0,0 +1,56 @@ +use std::path::Path; + +use anyhow::{Result, anyhow}; +use gpui::{AsyncAppContext, actions}; +use util::ResultExt; + +actions!(cli, [ Install ]); + + +pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { + let cli_path = cx.platform().path_for_auxiliary_executable("cli")?; + let link_path = Path::new("/usr/local/bin/zed"); + let bin_dir_path = link_path.parent().unwrap(); + + // Don't re-create symlink if it points to the same CLI binary. + if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) { + return Ok(()); + } + + // If the symlink is not there or is outdated, first try replacing it + // without escalating. + smol::fs::remove_file(link_path).await.log_err(); + if smol::fs::unix::symlink(&cli_path, link_path) + .await + .log_err() + .is_some() + { + return Ok(()); + } + + // The symlink could not be created, so use osascript with admin privileges + // to create it. + let status = smol::process::Command::new("osascript") + .args([ + "-e", + &format!( + "do shell script \" \ + mkdir -p \'{}\' && \ + ln -sf \'{}\' \'{}\' \ + \" with administrator privileges", + bin_dir_path.to_string_lossy(), + cli_path.to_string_lossy(), + link_path.to_string_lossy(), + ), + ]) + .stdout(smol::process::Stdio::inherit()) + .stderr(smol::process::Stdio::inherit()) + .output() + .await? + .status; + if status.success() { + Ok(()) + } else { + Err(anyhow!("error running osascript")) + } +} \ No newline at end of file diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index e1231ad5f631a11dfdad9bd77341b14207b572b5..e76636411bba5188084d7c585419dcd388aab6a4 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -15,6 +15,7 @@ anyhow = "1.0.38" log = "0.4" editor = { path = "../editor" } gpui = { path = "../gpui" } +install_cli = { path = "../install_cli" } project = { path = "../project" } settings = { path = "../settings" } theme = { path = "../theme" } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index c24e0b5b24ccd1cf61f5bf24c26969ba0bd040a2..c6316c61d50264c74ad4f9da6847d89c9342643a 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -66,6 +66,7 @@ impl View for WelcomePage { .boxed(), self.render_cta_button(2, "Choose a theme", theme_selector::Toggle, width, cx), self.render_cta_button(3, "Choose a keymap", theme_selector::Toggle, width, cx), + self.render_cta_button(4, "Install the CLI", install_cli::Install, width, cx), self.render_settings_checkbox::( "Do you want to send telemetry?", &theme.welcome.checkbox, diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index fc069fe6c8d7d481873a22ebc6b679b1aba85632..2ba7a6cc40b31e6fbe72862f2fbc85ef8d60a2ee 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -27,6 +27,7 @@ context_menu = { path = "../context_menu" } drag_and_drop = { path = "../drag_and_drop" } fs = { path = "../fs" } gpui = { path = "../gpui" } +install_cli = { path = "../install_cli" } language = { path = "../language" } menu = { path = "../menu" } project = { path = "../project" } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 141a345382603ea59c1699b008d2104deb4d18ae..76f46f83c5f87c88b8dd5c68e2e2d98e39798a79 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -122,6 +122,8 @@ impl Workspace { pub mod simple_message_notification { + use std::borrow::Cow; + use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, @@ -153,9 +155,9 @@ pub mod simple_message_notification { } pub struct MessageNotification { - message: String, + message: Cow<'static, str>, click_action: Option>, - click_message: Option, + click_message: Option>, } pub enum MessageNotificationEvent { @@ -167,23 +169,23 @@ pub mod simple_message_notification { } impl MessageNotification { - pub fn new_message>(message: S) -> MessageNotification { + pub fn new_message>>(message: S) -> MessageNotification { Self { - message: message.as_ref().to_string(), + message: message.into(), click_action: None, click_message: None, } } - pub fn new, A: Action, S2: AsRef>( + pub fn new>, A: Action, S2: Into>>( message: S1, click_action: A, click_message: S2, ) -> Self { Self { - message: message.as_ref().to_string(), + message: message.into(), click_action: Some(Box::new(click_action) as Box), - click_message: Some(click_message.as_ref().to_string()), + click_message: Some(click_message.into()), } } @@ -210,6 +212,8 @@ pub mod simple_message_notification { let click_message = self.click_message.as_ref().map(|message| message.clone()); let message = self.message.clone(); + let has_click_action = click_action.is_some(); + MouseEventHandler::::new(0, cx, |state, cx| { Flex::column() .with_child( @@ -243,6 +247,7 @@ pub mod simple_message_notification { .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(CancelMessageNotification) }) + .with_cursor_style(CursorStyle::PointingHand) .aligned() .constrained() .with_height( @@ -272,12 +277,19 @@ pub mod simple_message_notification { .contained() .boxed() }) - .with_cursor_style(CursorStyle::PointingHand) + // Since we're not using a proper overlay, we have to capture these extra events + .on_down(MouseButton::Left, |_, _| {}) + .on_up(MouseButton::Left, |_, _| {}) .on_click(MouseButton::Left, move |_, cx| { if let Some(click_action) = click_action.as_ref() { cx.dispatch_any_action(click_action.boxed_clone()) } }) + .with_cursor_style(if has_click_action { + CursorStyle::PointingHand + } else { + CursorStyle::Arrow + }) .boxed() } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cd2cd6fccb3ffd1f5f7cfcab2ae8af76386d9c1a..234c40c69dff55d0279479564bb4175c63af90e5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -17,7 +17,7 @@ mod toolbar; pub use smallvec; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Result, Context}; use call::ActiveCall; use client::{ proto::{self, PeerId}, @@ -65,7 +65,7 @@ use crate::{ }; use lazy_static::lazy_static; use log::{error, warn}; -use notifications::NotificationHandle; +use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; pub use pane_group::*; use persistence::{model::SerializedItem, DB}; @@ -267,6 +267,28 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { }) }, ); + + cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { + cx.spawn(|workspace, mut cx| async move { + let err = install_cli::install_cli(&cx).await.context("Failed to create CLI symlink"); + + cx.update(|cx| { + workspace.update(cx, |workspace, cx| { + if matches!(err, Err(_)) { + err.notify_err(workspace, cx); + } else { + workspace.show_notification(1, cx, |cx| { + cx.add_view(|_| MessageNotification::new_message("Successfully installed the 'zed' binary")) + }); + } + }) + }) + + }).detach(); + + + + }); let client = &app_state.client; client.add_view_request_handler(Workspace::handle_follow); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 68b04c7e2f846f549837f6aa7b77d1857d2243c3..8589af77e0dfbf8e049749ec56138dd28ac40f38 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -39,6 +39,7 @@ fsevent = { path = "../fsevent" } fuzzy = { path = "../fuzzy" } go_to_line = { path = "../go_to_line" } gpui = { path = "../gpui" } +install_cli = { path = "../install_cli" } journal = { path = "../journal" } language = { path = "../language" } lsp = { path = "../lsp" } diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index bb519c7a9505bc2de07fa60c70014be3b4fbc169..9381a909075b72364789e765600fd25885827f02 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -19,7 +19,7 @@ pub fn menus() -> Vec> { MenuItem::action("Select Theme", theme_selector::Toggle), ], }), - MenuItem::action("Install CLI", super::InstallCommandLineInterface), + MenuItem::action("Install CLI", install_cli::Install), MenuItem::separator(), MenuItem::action("Hide Zed", super::Hide), MenuItem::action("Hide Others", super::HideOthers), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a9032a264ba8e8fbfc9e3f505ae313038b8e7053..71e4b48db381112bbf6390cc78d58459ba158056 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2,7 +2,7 @@ pub mod languages; pub mod menus; #[cfg(any(test, feature = "test-support"))] pub mod test; -use anyhow::{anyhow, Context, Result}; +use anyhow::Context; use assets::Assets; use breadcrumbs::Breadcrumbs; pub use client; @@ -21,7 +21,7 @@ use gpui::{ geometry::vector::vec2f, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, + AssetSource, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; pub use lsp; @@ -68,7 +68,6 @@ actions!( IncreaseBufferFontSize, DecreaseBufferFontSize, ResetBufferFontSize, - InstallCommandLineInterface, ResetDatabase, WelcomeExperience ] @@ -144,8 +143,8 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { cx.refresh_windows(); }); }); - cx.add_global_action(move |_: &InstallCommandLineInterface, cx| { - cx.spawn(|cx| async move { install_cli(&cx).await.context("error creating CLI symlink") }) + cx.add_global_action(move |_: &install_cli::Install, cx| { + cx.spawn(|cx| async move { install_cli::install_cli(&cx).await.context("error creating CLI symlink") }) .detach_and_log_err(cx); }); cx.add_action({ @@ -505,54 +504,6 @@ fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { ); } -async fn install_cli(cx: &AsyncAppContext) -> Result<()> { - let cli_path = cx.platform().path_for_auxiliary_executable("cli")?; - let link_path = Path::new("/usr/local/bin/zed"); - let bin_dir_path = link_path.parent().unwrap(); - - // Don't re-create symlink if it points to the same CLI binary. - if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) { - return Ok(()); - } - - // If the symlink is not there or is outdated, first try replacing it - // without escalating. - smol::fs::remove_file(link_path).await.log_err(); - if smol::fs::unix::symlink(&cli_path, link_path) - .await - .log_err() - .is_some() - { - return Ok(()); - } - - // The symlink could not be created, so use osascript with admin privileges - // to create it. - let status = smol::process::Command::new("osascript") - .args([ - "-e", - &format!( - "do shell script \" \ - mkdir -p \'{}\' && \ - ln -sf \'{}\' \'{}\' \ - \" with administrator privileges", - bin_dir_path.to_string_lossy(), - cli_path.to_string_lossy(), - link_path.to_string_lossy(), - ), - ]) - .stdout(smol::process::Stdio::inherit()) - .stderr(smol::process::Stdio::inherit()) - .output() - .await? - .status; - if status.success() { - Ok(()) - } else { - Err(anyhow!("error running osascript")) - } -} - fn open_config_file( path: &'static Path, app_state: Arc, From 3b31f10c6f5c636a23dd172813be64f632298739 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Mar 2023 18:36:18 -0800 Subject: [PATCH 19/45] Made the theme picker sort from dark to light Added a layer into 'ConstrainedBox' to clip it 's children Made the welcome experience responsive to small and large sizes --- crates/gpui/src/elements/constrained_box.rs | 4 +++- crates/picker/src/picker.rs | 5 ++++- crates/theme_selector/src/theme_selector.rs | 1 - crates/welcome/src/welcome.rs | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/elements/constrained_box.rs b/crates/gpui/src/elements/constrained_box.rs index 2e232c6197774861fd14a453e62a8efb5772ee53..e4d51f5730be1bfa190ac7f2a7aa9e5e49b630e9 100644 --- a/crates/gpui/src/elements/constrained_box.rs +++ b/crates/gpui/src/elements/constrained_box.rs @@ -153,7 +153,9 @@ impl Element for ConstrainedBox { _: &mut Self::LayoutState, cx: &mut PaintContext, ) -> Self::PaintState { - self.child.paint(bounds.origin(), visible_bounds, cx); + cx.paint_layer(Some(visible_bounds), |cx| { + self.child.paint(bounds.origin(), visible_bounds, cx); + }) } fn rect_for_text_range( diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index e4d062d575aa6a44909d0a757c766470987abb87..fe4b75dbefae3abfe21db7bc1c48ad1bda6f9e6c 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -102,7 +102,10 @@ impl View for Picker { .read(cx) .render_match(ix, state, ix == selected_ix, cx) }) - .on_down(MouseButton::Left, move |_, cx| { + // Capture mouse events + .on_down(MouseButton::Left, |_, _| {}) + .on_up(MouseButton::Left, |_, _| {}) + .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(SelectIndex(ix)) }) .with_cursor_style(CursorStyle::PointingHand) diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index d999730a0d8b7abe21c0d9b39bfc3a9fb4ca49ca..0930bf0484e9a28880035c058ff2238c53324c5f 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -50,7 +50,6 @@ impl ThemeSelector { theme_names.sort_unstable_by(|a, b| { a.is_light .cmp(&b.is_light) - .reverse() .then(a.name.cmp(&b.name)) }); let matches = theme_names diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index c6316c61d50264c74ad4f9da6847d89c9342643a..dc864a3ad17db2e7856eb8fa0174ccce724eb836 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -84,6 +84,7 @@ impl View for WelcomePage { ]) .constrained() .with_max_width(width) + .contained().with_uniform_padding(10.) .aligned() .boxed(), ) @@ -126,7 +127,7 @@ impl WelcomePage { .contained() .with_style(style.container) .constrained() - .with_width(width) + .with_max_width(width) .boxed() }) .on_click(MouseButton::Left, move |_, cx| { From 19fc14320997a29ad3af2f5d89fe0b339dffebb1 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 7 Mar 2023 12:19:51 -0800 Subject: [PATCH 20/45] Add base keymap setting Format all files Co-Authored-by: Nathan --- assets/keymaps/atom.json | 68 ++++++++++++++++++ assets/keymaps/jetbrains.json | 78 +++++++++++++++++++++ assets/keymaps/sublime_text.json | 60 ++++++++++++++++ crates/install_cli/src/install_cli.rs | 9 ++- crates/settings/src/keymap_file.rs | 6 +- crates/settings/src/settings.rs | 24 +++++++ crates/settings/src/watched_json.rs | 31 ++++++-- crates/theme_selector/src/theme_selector.rs | 6 +- crates/welcome/src/welcome.rs | 8 ++- crates/workspace/src/workspace.rs | 23 +++--- crates/zed/src/main.rs | 13 ++-- crates/zed/src/zed.rs | 10 ++- 12 files changed, 301 insertions(+), 35 deletions(-) create mode 100644 assets/keymaps/atom.json create mode 100644 assets/keymaps/jetbrains.json create mode 100644 assets/keymaps/sublime_text.json diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json new file mode 100644 index 0000000000000000000000000000000000000000..766c46c133df9b617061694b9eb23ad8fecaceb8 --- /dev/null +++ b/assets/keymaps/atom.json @@ -0,0 +1,68 @@ +[ + { + "bindings": { + "cmd-k cmd-p": "workspace::ActivatePreviousPane", + "cmd-k cmd-n": "workspace::ActivateNextPane" + } + }, + { + "context": "Editor", + "bindings": { + "cmd-b": "editor::GoToDefinition", + "cmd-<": "editor::ScrollCursorCenter", + "cmd-g": [ + "editor::SelectNext", + { + "replace_newest": true + } + ], + "ctrl-shift-down": "editor::AddSelectionBelow", + "ctrl-shift-up": "editor::AddSelectionAbove", + "cmd-shift-backspace": "editor::DeleteToBeginningOfLine" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-r": "outline::Toggle" + } + }, + { + "context": "BufferSearchBar", + "bindings": { + "cmd-f3": "search::SelectNextMatch", + "cmd-shift-f3": "search::SelectPrevMatch" + } + }, + { + "context": "Workspace", + "bindings": { + "cmd-\\": "workspace::ToggleLeftSidebar", + "cmd-k cmd-b": "workspace::ToggleLeftSidebar", + "cmd-t": "file_finder::Toggle", + "cmd-shift-r": "project_symbols::Toggle" + } + }, + { + "context": "Pane", + "bindings": { + "alt-cmd-/": "search::ToggleRegex", + "ctrl-0": "project_panel::ToggleFocus" + } + }, + { + "context": "ProjectPanel", + "bindings": { + "ctrl-[": "project_panel::CollapseSelectedEntry", + "ctrl-b": "project_panel::CollapseSelectedEntry", + "h": "project_panel::CollapseSelectedEntry", + "ctrl-]": "project_panel::ExpandSelectedEntry", + "ctrl-f": "project_panel::ExpandSelectedEntry", + "ctrl-shift-c": "project_panel::CopyPath" + } + }, + { + "context": "Dock", + "bindings": {} + } +] \ No newline at end of file diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json new file mode 100644 index 0000000000000000000000000000000000000000..2e6e5e77e6abebd3d6c464895af5cdcaa9dc2075 --- /dev/null +++ b/assets/keymaps/jetbrains.json @@ -0,0 +1,78 @@ +[ + { + "bindings": { + "cmd-shift-[": "pane::ActivatePrevItem", + "cmd-shift-]": "pane::ActivateNextItem" + } + }, + { + "context": "Editor", + "bindings": { + "ctrl->": "zed::IncreaseBufferFontSize", + "ctrl-<": "zed::DecreaseBufferFontSize", + "cmd-d": "editor::DuplicateLine", + "cmd-pagedown": "editor::MovePageDown", + "cmd-pageup": "editor::MovePageUp", + "ctrl-alt-shift-b": "editor::SelectToPreviousWordStart", + "shift-enter": "editor::NewlineBelow", + "cmd--": "editor::Fold", + "cmd-=": "editor::UnfoldLines", + "alt-shift-g": "editor::SplitSelectionIntoLines", + "ctrl-g": [ + "editor::SelectNext", + { + "replace_newest": false + } + ], + "cmd-/": [ + "editor::ToggleComments", + { + "advance_downwards": true + } + ], + "shift-alt-up": "editor::MoveLineUp", + "shift-alt-down": "editor::MoveLineDown", + "cmd-[": "pane::GoBack", + "cmd-]": "pane::GoForward", + "alt-f7": "editor::FindAllReferences", + "cmd-alt-f7": "editor::FindAllReferences", + "cmd-b": "editor::GoToDefinition", + "cmd-alt-b": "editor::GoToDefinition", + "cmd-shift-b": "editor::GoToTypeDefinition", + "alt-enter": "editor::ToggleCodeActions", + "f2": "editor::GoToDiagnostic", + "cmd-f2": "editor::GoToPrevDiagnostic", + "ctrl-alt-shift-down": "editor::GoToHunk", + "ctrl-alt-shift-up": "editor::GoToPrevHunk", + "cmd-home": "editor::MoveToBeginning", + "cmd-end": "editor::MoveToEnd", + "cmd-shift-home": "editor::SelectToBeginning", + "cmd-shift-end": "editor::SelectToEnd" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-f12": "outline::Toggle", + "cmd-7": "outline::Toggle", + "cmd-shift-o": "file_finder::Toggle", + "cmd-l": "go_to_line::Toggle" + } + }, + { + "context": "Workspace", + "bindings": { + "cmd-shift-a": "command_palette::Toggle", + "cmd-alt-o": "project_symbols::Toggle", + "cmd-1": "workspace::ToggleLeftSidebar", + "cmd-6": "diagnostics::Deploy", + "alt-f12": "dock::FocusDock" + } + }, + { + "context": "Dock", + "bindings": { + "alt-f12": "dock::HideDock" + } + } +] \ No newline at end of file diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json new file mode 100644 index 0000000000000000000000000000000000000000..1d3dd887d7b4a57add7d5be95d34700e1be1143a --- /dev/null +++ b/assets/keymaps/sublime_text.json @@ -0,0 +1,60 @@ +[ + { + "bindings": { + "cmd-shift-[": "pane::ActivatePrevItem", + "cmd-shift-]": "pane::ActivateNextItem", + "ctrl-pagedown": "pane::ActivatePrevItem", + "ctrl-pageup": "pane::ActivateNextItem", + "ctrl-shift-tab": "pane::ActivateNextItem", + "ctrl-tab": "pane::ActivatePrevItem", + "cmd-+": "zed::IncreaseBufferFontSize" + } + }, + { + "context": "Editor", + "bindings": { + "ctrl-shift-up": "editor::AddSelectionAbove", + "ctrl-shift-down": "editor::AddSelectionBelow", + "cmd-shift-space": "editor::SelectAll", + "ctrl-shift-m": "editor::SelectLargerSyntaxNode", + "cmd-shift-a": "editor::SelectLargerSyntaxNode", + "shift-f12": "editor::FindAllReferences", + "alt-cmd-down": "editor::GoToDefinition", + "alt-shift-cmd-down": "editor::FindAllReferences", + "ctrl-.": "editor::GoToHunk", + "ctrl-,": "editor::GoToPrevHunk", + "ctrl-backspace": "editor::DeleteToPreviousWordStart", + "ctrl-delete": "editor::DeleteToNextWordEnd" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "cmd-r": "outline::Toggle" + } + }, + { + "context": "Pane", + "bindings": { + "f4": "search::SelectNextMatch", + "shift-f4": "search::SelectPrevMatch" + } + }, + { + "context": "Workspace", + "bindings": { + "ctrl-`": "dock::FocusDock", + "cmd-k cmd-b": "workspace::ToggleLeftSidebar", + "cmd-t": "file_finder::Toggle", + "shift-cmd-r": "project_symbols::Toggle", + // Currently busted: https://github.com/zed-industries/feedback/issues/898 + "ctrl-0": "project_panel::ToggleFocus" + } + }, + { + "context": "Dock", + "bindings": { + "ctrl-`": "dock::HideDock" + } + } +] \ No newline at end of file diff --git a/crates/install_cli/src/install_cli.rs b/crates/install_cli/src/install_cli.rs index 06561a28f8b6e04c13ef65f1400cbfef858d4db8..adf50586d759ae24f39e3187b080a30d894f8eec 100644 --- a/crates/install_cli/src/install_cli.rs +++ b/crates/install_cli/src/install_cli.rs @@ -1,11 +1,10 @@ use std::path::Path; -use anyhow::{Result, anyhow}; -use gpui::{AsyncAppContext, actions}; +use anyhow::{anyhow, Result}; +use gpui::{actions, AsyncAppContext}; use util::ResultExt; -actions!(cli, [ Install ]); - +actions!(cli, [Install]); pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { let cli_path = cx.platform().path_for_auxiliary_executable("cli")?; @@ -53,4 +52,4 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { } else { Err(anyhow!("error running osascript")) } -} \ No newline at end of file +} diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 01992d94311e7dd348b06d3a7b6b042780204010..9048719d3f58c9bb3dc91e08d4275e551e0c7b15 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -1,4 +1,4 @@ -use crate::parse_json_with_comments; +use crate::{parse_json_with_comments, Settings}; use anyhow::{Context, Result}; use assets::Assets; use collections::BTreeMap; @@ -45,6 +45,10 @@ impl KeymapFileContent { for path in ["keymaps/default.json", "keymaps/vim.json"] { Self::load(path, cx).unwrap(); } + + if let Some(base_keymap) = cx.global::().base_keymap { + Self::load(base_keymap.asset_path(), cx).log_err(); + } } pub fn load(asset_path: &str, cx: &mut MutableAppContext) -> Result<()> { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 501a88c42e14bcb3da9cadaa3f889a63580830df..ae6742894899f19c4f738bbee1a563a81858b2eb 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -24,6 +24,7 @@ use tree_sitter::Query; use util::ResultExt as _; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; +pub use watched_json::watch_files; #[derive(Clone)] pub struct Settings { @@ -54,6 +55,24 @@ pub struct Settings { pub telemetry_defaults: TelemetrySettings, pub telemetry_overrides: TelemetrySettings, pub auto_update: bool, + pub base_keymap: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub enum BaseKeymap { + JetBrains, + Sublime, + Atom, +} + +impl BaseKeymap { + pub fn asset_path(&self) -> &str { + match self { + BaseKeymap::JetBrains => "keymaps/jetbrains.json", + BaseKeymap::Sublime => "keymaps/sublime_text.json", + BaseKeymap::Atom => "keymaps/atom.json", + } + } } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -326,6 +345,8 @@ pub struct SettingsFileContent { pub telemetry: TelemetrySettings, #[serde(default)] pub auto_update: Option, + #[serde(default)] + pub base_keymap: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -396,6 +417,7 @@ impl Settings { telemetry_defaults: defaults.telemetry, telemetry_overrides: Default::default(), auto_update: defaults.auto_update.unwrap(), + base_keymap: Default::default(), } } @@ -433,6 +455,7 @@ impl Settings { merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); merge(&mut self.default_dock_anchor, data.default_dock_anchor); + merge(&mut self.base_keymap, Some(data.base_keymap)); // Ensure terminal font is loaded, so we can request it in terminal_element layout if let Some(terminal_font) = &data.terminal.font_family { @@ -610,6 +633,7 @@ impl Settings { }, telemetry_overrides: Default::default(), auto_update: true, + base_keymap: None, } } diff --git a/crates/settings/src/watched_json.rs b/crates/settings/src/watched_json.rs index e304842aa2127cba2b3d25f533b9e7c19c71a8d9..c4cc64cd620542548d3d17ea830d5f84db63b8cf 100644 --- a/crates/settings/src/watched_json.rs +++ b/crates/settings/src/watched_json.rs @@ -62,7 +62,18 @@ where } } -pub fn watch_settings_file( +pub fn watch_files( + defaults: Settings, + settings_file: WatchedJsonFile, + theme_registry: Arc, + keymap_file: WatchedJsonFile, + cx: &mut MutableAppContext, +) { + watch_settings_file(defaults, settings_file, theme_registry, cx); + watch_keymap_file(keymap_file, cx); +} + +pub(crate) fn watch_settings_file( defaults: Settings, mut file: WatchedJsonFile, theme_registry: Arc, @@ -77,13 +88,13 @@ pub fn watch_settings_file( .detach(); } -pub fn keymap_updated(content: KeymapFileContent, cx: &mut MutableAppContext) { +fn keymap_updated(content: KeymapFileContent, cx: &mut MutableAppContext) { cx.clear_bindings(); KeymapFileContent::load_defaults(cx); content.add_to_cx(cx).log_err(); } -pub fn settings_updated( +fn settings_updated( defaults: &Settings, content: SettingsFileContent, theme_registry: &Arc, @@ -95,10 +106,20 @@ pub fn settings_updated( cx.refresh_windows(); } -pub fn watch_keymap_file(mut file: WatchedJsonFile, cx: &mut MutableAppContext) { +fn watch_keymap_file(mut file: WatchedJsonFile, cx: &mut MutableAppContext) { cx.spawn(|mut cx| async move { + let mut settings_subscription = None; while let Some(content) = file.0.recv().await { - cx.update(|cx| keymap_updated(content, cx)); + cx.update(|cx| { + let old_base_keymap = cx.global::().base_keymap; + keymap_updated(content.clone(), cx); + settings_subscription = Some(cx.observe_global::(move |cx| { + let settings = cx.global::(); + if settings.base_keymap != old_base_keymap { + keymap_updated(content.clone(), cx); + } + })); + }); } }) .detach(); diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 0930bf0484e9a28880035c058ff2238c53324c5f..ae3278b7111c8e903293723759a1e8fd238a8697 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -47,11 +47,7 @@ impl ThemeSelector { let mut theme_names = registry .list(**cx.default_global::()) .collect::>(); - theme_names.sort_unstable_by(|a, b| { - a.is_light - .cmp(&b.is_light) - .then(a.name.cmp(&b.name)) - }); + theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name))); let matches = theme_names .iter() .map(|meta| StringMatch { diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index dc864a3ad17db2e7856eb8fa0174ccce724eb836..8bb055dad0a7c333f4b590a286d0d6e90baf0ec9 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -84,7 +84,8 @@ impl View for WelcomePage { ]) .constrained() .with_max_width(width) - .contained().with_uniform_padding(10.) + .contained() + .with_uniform_padding(10.) .aligned() .boxed(), ) @@ -174,7 +175,10 @@ impl WelcomePage { } }) .boxed(), - Label::new(label, style.label.text.clone()).contained().with_style(style.label.container).boxed(), + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) + .boxed(), ]) .align_children_center() .boxed() diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 234c40c69dff55d0279479564bb4175c63af90e5..e74bcdc1773d3caac4f3f9724607f93e2240736e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -17,7 +17,7 @@ mod toolbar; pub use smallvec; -use anyhow::{anyhow, Result, Context}; +use anyhow::{anyhow, Context, Result}; use call::ActiveCall; use client::{ proto::{self, PeerId}, @@ -267,27 +267,30 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { }) }, ); - + cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { cx.spawn(|workspace, mut cx| async move { - let err = install_cli::install_cli(&cx).await.context("Failed to create CLI symlink"); - + let err = install_cli::install_cli(&cx) + .await + .context("Failed to create CLI symlink"); + cx.update(|cx| { workspace.update(cx, |workspace, cx| { if matches!(err, Err(_)) { err.notify_err(workspace, cx); } else { workspace.show_notification(1, cx, |cx| { - cx.add_view(|_| MessageNotification::new_message("Successfully installed the 'zed' binary")) + cx.add_view(|_| { + MessageNotification::new_message( + "Successfully installed the `zed` binary", + ) + }) }); } }) }) - - }).detach(); - - - + }) + .detach(); }); let client = &app_state.client; diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index a9625bf78ee81e2d73e6a071f361e15dc12dd975..8407bd45167e239f950c3c987a21bd3c7f92a266 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -38,7 +38,7 @@ use std::{ use terminal_view::{get_working_directory, TerminalView}; use fs::RealFs; -use settings::watched_json::{watch_keymap_file, watch_settings_file, WatchedJsonFile}; +use settings::watched_json::WatchedJsonFile; use theme::ThemeRegistry; #[cfg(debug_assertions)] use util::StaffMode; @@ -123,7 +123,14 @@ fn main() { fs.clone(), )); - watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); + settings::watch_files( + default_settings, + settings_file_content, + themes.clone(), + keymap_file, + cx, + ); + if !stdout_is_a_pty() { upload_previous_panics(http.clone(), cx); } @@ -136,8 +143,6 @@ fn main() { languages::init(languages.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - watch_keymap_file(keymap_file, cx); - cx.set_global(client.clone()); context_menu::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 71e4b48db381112bbf6390cc78d58459ba158056..2fb956f5b6f6ce2c26e5ce0782024c4e367e6bfa 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -21,7 +21,7 @@ use gpui::{ geometry::vector::vec2f, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, + AssetSource, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; pub use lsp; @@ -144,8 +144,12 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }); }); cx.add_global_action(move |_: &install_cli::Install, cx| { - cx.spawn(|cx| async move { install_cli::install_cli(&cx).await.context("error creating CLI symlink") }) - .detach_and_log_err(cx); + cx.spawn(|cx| async move { + install_cli::install_cli(&cx) + .await + .context("error creating CLI symlink") + }) + .detach_and_log_err(cx); }); cx.add_action({ let app_state = app_state.clone(); From 5892f1660218fef98214bfc7c4551deceb30a704 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 7 Mar 2023 14:02:42 -0800 Subject: [PATCH 21/45] Add test for base keymap setting --- crates/settings/src/settings_file.rs | 143 ++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 575e9499d38a26008bea65a86907bbd31306db1c..1b05993bd5f8059a0ad326dddab6023bb511b0a2 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -54,10 +54,151 @@ impl SettingsFile { #[cfg(test)] mod tests { use super::*; - use crate::{watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap}; + use crate::{ + watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap, + }; use fs::FakeFs; + use gpui::{actions, Action}; use theme::ThemeRegistry; + #[gpui::test] + async fn test_base_keymap(cx: &mut gpui::TestAppContext) { + let executor = cx.background(); + let fs = FakeFs::new(executor.clone()); + let font_cache = cx.font_cache(); + + actions!(test, [A, B]); + // From the Atom keymap + actions!(workspace, [ActivatePreviousPane]); + // From the JetBrains keymap + actions!(pane, [ActivatePrevItem]); + + fs.save( + "/settings.json".as_ref(), + &r#" + { + "base_keymap": "Atom" + } + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + fs.save( + "/keymap.json".as_ref(), + &r#" + [ + { + "bindings": { + "backspace": "test::A" + } + } + ] + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + let settings_file = + WatchedJsonFile::new(fs.clone(), &executor, "/settings.json".as_ref()).await; + let keymaps_file = + WatchedJsonFile::new(fs.clone(), &executor, "/keymap.json".as_ref()).await; + + let default_settings = cx.read(Settings::test); + + cx.update(|cx| { + cx.add_global_action(|_: &A, _cx| { + }); + cx.add_global_action(|_: &B, _cx| { + }); + cx.add_global_action(|_: &ActivatePreviousPane, _cx| { + }); + cx.add_global_action(|_: &ActivatePrevItem, _cx| { + }); + watch_files( + default_settings, + settings_file, + ThemeRegistry::new((), font_cache), + keymaps_file, + cx, + ) + }); + + cx.foreground().run_until_parked(); + + // Test loading the keymap base at all + cx.update(|cx| { + assert_keybindings_for(cx, vec![("backspace", &A), ("k", &ActivatePreviousPane)], line!()); + }); + + // Test modifying the users keymap, while retaining the base keymap + fs.save( + "/keymap.json".as_ref(), + &r#" + [ + { + "bindings": { + "backspace": "test::B" + } + } + ] + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + cx.foreground().run_until_parked(); + + cx.update(|cx| { + assert_keybindings_for(cx, vec![("backspace", &B), ("k", &ActivatePreviousPane)], line!()); + }); + + // Test modifying the base, while retaining the users keymap + fs.save( + "/settings.json".as_ref(), + &r#" + { + "base_keymap": "JetBrains" + } + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + cx.foreground().run_until_parked(); + + cx.update(|cx| { + assert_keybindings_for(cx, vec![("backspace", &B), ("[", &ActivatePrevItem)], line!()); + }); + } + + fn assert_keybindings_for<'a>( + cx: &mut MutableAppContext, + actions: Vec<(&'static str, &'a dyn Action)>, + line: u32, + ) { + + for (key, action) in actions { + // assert that... + assert!(cx.available_actions(0, 0).any(|(_, bound_action, b)| { + // action names match... + bound_action.name() == action.name() + && bound_action.namespace() == action.namespace() + // and key strokes contain the given key + && b.iter() + .any(|binding| binding.keystrokes().iter().any(|k| k.key == key)) + }), "On {} Failed to find {} with keybinding {}", line, action.name(), key); + } + } + #[gpui::test] async fn test_watch_settings_files(cx: &mut gpui::TestAppContext) { let executor = cx.background(); From ab4b3293d16f015e66fe9bfd77a30c8e3a9bb023 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 7 Mar 2023 14:49:05 -0800 Subject: [PATCH 22/45] Fix project panel button and style it Co-authored-by: max --- crates/project/src/worktree.rs | 2 +- crates/project_panel/src/project_panel.rs | 130 ++++++++++------------ crates/settings/src/settings_file.rs | 51 +++++---- crates/theme/src/theme.rs | 1 + crates/workspace/src/workspace.rs | 1 + crates/zed/src/main.rs | 1 - styles/src/styleTree/projectPanel.ts | 33 ++++++ 7 files changed, 127 insertions(+), 92 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 8b622ab607db49f6f5b54845ccf7d70397752225..d53e84f6b1ee22e973f13308b3b789c4a043c080 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -867,7 +867,7 @@ impl LocalWorktree { let old_path = self.entry_for_id(entry_id)?.path.clone(); let new_path = new_path.into(); let abs_old_path = self.absolutize(&old_path); - let abs_new_path = self.absolutize(&new_path); + let abs_new_path = self.absolutize(new_path.as_ref()); let rename = cx.background().spawn({ let fs = self.fs.clone(); let abs_new_path = abs_new_path.clone(); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 9df6581d209c47b4e3763ca9acf12da1ce378189..5de68b12fd243e494ffa7cbc52aad1c27144450d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5,9 +5,8 @@ use futures::stream::StreamExt; use gpui::{ actions, anyhow::{anyhow, Result}, - color::Color, elements::{ - AnchorCorner, Canvas, ChildView, ConstrainedBox, ContainerStyle, Empty, Flex, + AnchorCorner, ChildView, ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, @@ -15,7 +14,7 @@ use gpui::{ impl_internal_actions, keymap_matcher::KeymapContext, platform::CursorStyle, - AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, + Action, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; @@ -29,7 +28,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use theme::ProjectPanelEntry; +use theme::{ContainedText, ProjectPanelEntry}; use unicase::UniCase; use workspace::Workspace; @@ -1317,79 +1316,36 @@ impl View for ProjectPanel { .boxed() } else { let parent_view_id = cx.handle().id(); - Stack::new() + Flex::column() .with_child( - MouseEventHandler::::new(1, cx, |_, cx| { - Stack::new() - .with_child( - Canvas::new(|bounds, _visible_bounds, cx| { - cx.scene.push_quad(gpui::Quad { - bounds, - background: Some(Color::transparent_black()), - ..Default::default() - }) - }) - .boxed(), - ) - .with_child( - MouseEventHandler::::new(2, cx, |state, cx| { - let style = &cx - .global::() - .theme - .search - .option_button - .style_for(state, false); - - let context_menu_item = cx - .global::() - .theme - .context_menu - .clone() - .item - .style_for(state, true) - .clone(); - - Flex::row() - .with_child( - Label::new( - "Open a new project!".to_string(), - context_menu_item.label.clone(), - ) - .contained() - .boxed(), - ) - .with_child({ - KeystrokeLabel::new( - cx.window_id(), - parent_view_id, - Box::new(workspace::Open), - context_menu_item.keystroke.container, - context_menu_item.keystroke.text.clone(), - ) - .flex_float() - .boxed() - }) - .contained() - .with_style(style.container) - .aligned() - .top() - .constrained() - .with_width(100.) - .with_height(20.) - .boxed() - }) - .on_click(MouseButton::Left, move |_, cx| { - cx.dispatch_action(workspace::Open) - }) - .with_cursor_style(CursorStyle::PointingHand) - .boxed(), + MouseEventHandler::::new(2, cx, { + let button_style = theme.open_project_button.clone(); + let context_menu_item_style = + cx.global::().theme.context_menu.item.clone(); + move |state, cx| { + let button_style = button_style.style_for(state, false).clone(); + let context_menu_item = + context_menu_item_style.style_for(state, true).clone(); + + keystroke_label( + parent_view_id, + "Open a new project", + &button_style, + context_menu_item.keystroke, + workspace::Open, + cx, ) .boxed() + } }) - // TODO is this nescessary? - .on_click(MouseButton::Left, |_, cx| cx.focus_parent_view()) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(workspace::Open) + }) + .with_cursor_style(CursorStyle::PointingHand) .boxed(), ) + .contained() + .with_style(container_style) .boxed() } } @@ -1401,6 +1357,38 @@ impl View for ProjectPanel { } } +fn keystroke_label( + view_id: usize, + label_text: &'static str, + label_style: &ContainedText, + keystroke_style: ContainedText, + action: A, + cx: &mut RenderContext, +) -> Container +where + A: Action, +{ + Flex::row() + .with_child( + Label::new(label_text, label_style.text.clone()) + .contained() + .boxed(), + ) + .with_child({ + KeystrokeLabel::new( + cx.window_id(), + view_id, + Box::new(action), + keystroke_style.container, + keystroke_style.text.clone(), + ) + .flex_float() + .boxed() + }) + .contained() + .with_style(label_style.container) +} + impl Entity for ProjectPanel { type Event = Event; } diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 1b05993bd5f8059a0ad326dddab6023bb511b0a2..50638205c519f5f882f963baeeb45c943e2895e6 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -111,14 +111,10 @@ mod tests { let default_settings = cx.read(Settings::test); cx.update(|cx| { - cx.add_global_action(|_: &A, _cx| { - }); - cx.add_global_action(|_: &B, _cx| { - }); - cx.add_global_action(|_: &ActivatePreviousPane, _cx| { - }); - cx.add_global_action(|_: &ActivatePrevItem, _cx| { - }); + cx.add_global_action(|_: &A, _cx| {}); + cx.add_global_action(|_: &B, _cx| {}); + cx.add_global_action(|_: &ActivatePreviousPane, _cx| {}); + cx.add_global_action(|_: &ActivatePrevItem, _cx| {}); watch_files( default_settings, settings_file, @@ -132,7 +128,11 @@ mod tests { // Test loading the keymap base at all cx.update(|cx| { - assert_keybindings_for(cx, vec![("backspace", &A), ("k", &ActivatePreviousPane)], line!()); + assert_keybindings_for( + cx, + vec![("backspace", &A), ("k", &ActivatePreviousPane)], + line!(), + ); }); // Test modifying the users keymap, while retaining the base keymap @@ -152,13 +152,17 @@ mod tests { ) .await .unwrap(); - + cx.foreground().run_until_parked(); cx.update(|cx| { - assert_keybindings_for(cx, vec![("backspace", &B), ("k", &ActivatePreviousPane)], line!()); + assert_keybindings_for( + cx, + vec![("backspace", &B), ("k", &ActivatePreviousPane)], + line!(), + ); }); - + // Test modifying the base, while retaining the users keymap fs.save( "/settings.json".as_ref(), @@ -172,11 +176,15 @@ mod tests { ) .await .unwrap(); - + cx.foreground().run_until_parked(); cx.update(|cx| { - assert_keybindings_for(cx, vec![("backspace", &B), ("[", &ActivatePrevItem)], line!()); + assert_keybindings_for( + cx, + vec![("backspace", &B), ("[", &ActivatePrevItem)], + line!(), + ); }); } @@ -185,17 +193,22 @@ mod tests { actions: Vec<(&'static str, &'a dyn Action)>, line: u32, ) { - for (key, action) in actions { // assert that... - assert!(cx.available_actions(0, 0).any(|(_, bound_action, b)| { - // action names match... - bound_action.name() == action.name() + assert!( + cx.available_actions(0, 0).any(|(_, bound_action, b)| { + // action names match... + bound_action.name() == action.name() && bound_action.namespace() == action.namespace() // and key strokes contain the given key && b.iter() .any(|binding| binding.keystrokes().iter().any(|k| k.key == key)) - }), "On {} Failed to find {} with keybinding {}", line, action.name(), key); + }), + "On {} Failed to find {} with keybinding {}", + line, + action.name(), + key + ); } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 43a10978d6fd1a36e03d59b5dda4523e13d30d10..e7252fbf61eff3da14f93731452eed6094f793ce 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -346,6 +346,7 @@ pub struct ProjectPanel { pub cut_entry: Interactive, pub filename_editor: FieldEditor, pub indent_width: f32, + pub open_project_button: Interactive, } #[derive(Clone, Debug, Deserialize, Default)] diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e74bcdc1773d3caac4f3f9724607f93e2240736e..f987d00ef1f67bd705e93c6de1121434add32d8a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2815,6 +2815,7 @@ fn open(_: &Open, cx: &mut MutableAppContext) { directories: true, multiple: true, }); + cx.spawn(|mut cx| async move { if let Some(paths) = paths.recv().await.flatten() { cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths })); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8407bd45167e239f950c3c987a21bd3c7f92a266..1b794b3857e2bad97158942aad6e12798d929eeb 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -237,7 +237,6 @@ fn main() { let app_state = app_state.clone(); async move { while let Some(paths) = open_paths_rx.next().await { - log::error!("OPEN PATHS FROM HANDLE"); cx.update(|cx| workspace::open_paths(&paths, &app_state, cx)) .detach(); } diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 90e0c82f5bf1d16e19acfa0c3df0a0454cabae86..2601a12691683c6e5dd85cdc5b99257c91d3165d 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -29,6 +29,39 @@ export default function projectPanel(colorScheme: ColorScheme) { } return { + openProjectButton: { + ...text(layer, "mono", "active", { size: "sm" }), + background: background(layer, "on"), + cornerRadius: 6, + border: border(layer, "on"), + margin: { + top: 20, + left: 10, + right: 10 + }, + padding: { + bottom: 2, + left: 10, + right: 10, + top: 2, + }, + active: { + ...text(layer, "mono", "on", "inverted"), + background: background(layer, "on", "inverted"), + border: border(layer, "on", "inverted"), + }, + clicked: { + ...text(layer, "mono", "on", "pressed"), + background: background(layer, "on", "pressed"), + border: border(layer, "on", "pressed"), + }, + hover: { + ...text(layer, "mono", "on", "hovered"), + background: background(layer, "on", "hovered"), + border: border(layer, "on", "hovered"), + }, + + }, background: background(layer), padding: { left: 12, right: 12, top: 6, bottom: 6 }, indentWidth: 8, From 904993dfc99ef2146357872d0bd9123952e9ca51 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 7 Mar 2023 16:08:01 -0800 Subject: [PATCH 23/45] Change open paths to replace the existing window if dispatched from a window co-authored-by: Max --- crates/journal/src/journal.rs | 2 +- crates/workspace/src/workspace.rs | 206 ++++++++++++++++++------------ crates/zed/src/main.rs | 6 +- crates/zed/src/zed.rs | 37 +++++- 4 files changed, 162 insertions(+), 89 deletions(-) diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 76a56af93d8c4cdc45a60a31e4ebc007899f61cc..c02d61c3e20b121a1b69589f2334b8b4c1becdb3 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -48,7 +48,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { async move { let (journal_dir, entry_path) = create_entry.await?; let (workspace, _) = cx - .update(|cx| workspace::open_paths(&[journal_dir], &app_state, cx)) + .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx)) .await; let opened = workspace diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f987d00ef1f67bd705e93c6de1121434add32d8a..65072637ac00b06d96400242051a05c1d7168e8b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -44,7 +44,8 @@ use gpui::{ platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, Platform, PromptLevel, RenderContext, - SizeConstraint, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, + SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + WindowBounds, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -188,12 +189,54 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { dock::init(cx); notifications::init(cx); - cx.add_global_action(open); + cx.add_global_action(|_: &Open, cx: &mut MutableAppContext| { + let mut paths = cx.prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }); + + cx.spawn(|mut cx| async move { + if let Some(paths) = paths.recv().await.flatten() { + cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths })); + } + }) + .detach(); + }); + cx.add_action(|_, _: &Open, cx: &mut ViewContext| { + let mut paths = cx.prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }); + + let handle = cx.handle().downgrade(); + cx.spawn(|_, mut cx| async move { + if let Some(paths) = paths.recv().await.flatten() { + cx.update(|cx| { + cx.dispatch_action_at(handle.window_id(), handle.id(), OpenPaths { paths }) + }) + } + }) + .detach(); + }); cx.add_global_action({ let app_state = Arc::downgrade(&app_state); move |action: &OpenPaths, cx: &mut MutableAppContext| { if let Some(app_state) = app_state.upgrade() { - open_paths(&action.paths, &app_state, cx).detach(); + open_paths(&action.paths, &app_state, None, cx).detach(); + } + } + }); + cx.add_action({ + let app_state = Arc::downgrade(&app_state); + move |_, action: &OpenPaths, cx: &mut ViewContext| { + if let Some(app_state) = app_state.upgrade() { + let window_id = cx.window_id(); + let action = action.clone(); + cx.as_mut().defer(move |cx| { + open_paths(&action.paths, &app_state, Some(window_id), cx).detach(); + }) } } }); @@ -488,6 +531,7 @@ pub struct Workspace { active_call: Option<(ModelHandle, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, + _window_subscriptions: [Subscription; 3], _apply_leader_updates: Task>, _observe_current_user: Task<()>, } @@ -519,10 +563,6 @@ impl Workspace { dock_default_factory: DockDefaultItemFactory, cx: &mut ViewContext, ) -> Self { - cx.observe_fullscreen(|_, _, cx| cx.notify()).detach(); - - cx.observe_window_activation(Self::on_window_activation_changed) - .detach(); cx.observe(&project, |_, _, cx| cx.notify()).detach(); cx.subscribe(&project, move |this, _, event, cx| { match event { @@ -629,6 +669,28 @@ impl Workspace { active_call = Some((call, subscriptions)); } + let subscriptions = [ + cx.observe_fullscreen(|_, _, cx| cx.notify()), + cx.observe_window_activation(Self::on_window_activation_changed), + cx.observe_window_bounds(move |_, mut bounds, display, cx| { + // Transform fixed bounds to be stored in terms of the containing display + if let WindowBounds::Fixed(mut window_bounds) = bounds { + if let Some(screen) = cx.platform().screen_by_id(display) { + let screen_bounds = screen.bounds(); + window_bounds + .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x()); + window_bounds + .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y()); + bounds = WindowBounds::Fixed(window_bounds); + } + } + + cx.background() + .spawn(DB.set_window_bounds(workspace_id, bounds, display)) + .detach_and_log_err(cx); + }), + ]; + let mut this = Workspace { modal: None, weak_self: weak_handle.clone(), @@ -660,6 +722,7 @@ impl Workspace { _observe_current_user, _apply_leader_updates, leader_updates_tx, + _window_subscriptions: subscriptions, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); cx.defer(|this, cx| this.update_window_title(cx)); @@ -676,6 +739,7 @@ impl Workspace { fn new_local( abs_paths: Vec, app_state: Arc, + requesting_window_id: Option, cx: &mut MutableAppContext, ) -> Task<( ViewHandle, @@ -731,42 +795,9 @@ impl Workspace { )) }); - let (bounds, display) = if let Some(bounds) = window_bounds_override { - (Some(bounds), None) - } else { - serialized_workspace - .as_ref() - .and_then(|serialized_workspace| { - let display = serialized_workspace.display?; - let mut bounds = serialized_workspace.bounds?; - - // Stored bounds are relative to the containing display. - // So convert back to global coordinates if that screen still exists - if let WindowBounds::Fixed(mut window_bounds) = bounds { - if let Some(screen) = cx.platform().screen_by_id(display) { - let screen_bounds = screen.bounds(); - window_bounds.set_origin_x( - window_bounds.origin_x() + screen_bounds.origin_x(), - ); - window_bounds.set_origin_y( - window_bounds.origin_y() + screen_bounds.origin_y(), - ); - bounds = WindowBounds::Fixed(window_bounds); - } else { - // Screen no longer exists. Return none here. - return None; - } - } - - Some((bounds, display)) - }) - .unzip() - }; - - // Use the serialized workspace to construct the new window - let (_, workspace) = cx.add_window( - (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), - |cx| { + let build_workspace = + |cx: &mut ViewContext, + serialized_workspace: Option| { let mut workspace = Workspace::new( serialized_workspace, workspace_id, @@ -775,29 +806,53 @@ impl Workspace { cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - cx.observe_window_bounds(move |_, mut bounds, display, cx| { - // Transform fixed bounds to be stored in terms of the containing display - if let WindowBounds::Fixed(mut window_bounds) = bounds { - if let Some(screen) = cx.platform().screen_by_id(display) { - let screen_bounds = screen.bounds(); - window_bounds.set_origin_x( - window_bounds.origin_x() - screen_bounds.origin_x(), - ); - window_bounds.set_origin_y( - window_bounds.origin_y() - screen_bounds.origin_y(), - ); - bounds = WindowBounds::Fixed(window_bounds); + workspace + }; + + let workspace = if let Some(window_id) = requesting_window_id { + cx.update(|cx| { + cx.replace_root_view(window_id, |cx| build_workspace(cx, serialized_workspace)) + }) + } else { + let (bounds, display) = if let Some(bounds) = window_bounds_override { + (Some(bounds), None) + } else { + serialized_workspace + .as_ref() + .and_then(|serialized_workspace| { + let display = serialized_workspace.display?; + let mut bounds = serialized_workspace.bounds?; + + // Stored bounds are relative to the containing display. + // So convert back to global coordinates if that screen still exists + if let WindowBounds::Fixed(mut window_bounds) = bounds { + if let Some(screen) = cx.platform().screen_by_id(display) { + let screen_bounds = screen.bounds(); + window_bounds.set_origin_x( + window_bounds.origin_x() + screen_bounds.origin_x(), + ); + window_bounds.set_origin_y( + window_bounds.origin_y() + screen_bounds.origin_y(), + ); + bounds = WindowBounds::Fixed(window_bounds); + } else { + // Screen no longer exists. Return none here. + return None; + } } - } - cx.background() - .spawn(DB.set_window_bounds(workspace_id, bounds, display)) - .detach_and_log_err(cx); - }) - .detach(); - workspace - }, - ); + Some((bounds, display)) + }) + .unzip() + }; + + // Use the serialized workspace to construct the new window + cx.add_window( + (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), + |cx| build_workspace(cx, serialized_workspace), + ) + .1 + }; notify_if_database_failed(&workspace, &mut cx); @@ -893,7 +948,7 @@ impl Workspace { if self.project.read(cx).is_local() { Task::Ready(Some(callback(self, cx))) } else { - let task = Self::new_local(Vec::new(), app_state.clone(), cx); + let task = Self::new_local(Vec::new(), app_state.clone(), None, cx); cx.spawn(|_vh, mut cx| async move { let (workspace, _) = task.await; workspace.update(&mut cx, callback) @@ -2809,21 +2864,6 @@ impl std::fmt::Debug for OpenPaths { } } -fn open(_: &Open, cx: &mut MutableAppContext) { - let mut paths = cx.prompt_for_paths(PathPromptOptions { - files: true, - directories: true, - multiple: true, - }); - - cx.spawn(|mut cx| async move { - if let Some(paths) = paths.recv().await.flatten() { - cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths })); - } - }) - .detach(); -} - pub struct WorkspaceCreated(WeakViewHandle); pub fn activate_workspace_for_project( @@ -2850,6 +2890,7 @@ pub async fn last_opened_workspace_paths() -> Option { pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, + requesting_window_id: Option, cx: &mut MutableAppContext, ) -> Task<( ViewHandle, @@ -2880,7 +2921,8 @@ pub fn open_paths( .contains(&false); cx.update(|cx| { - let task = Workspace::new_local(abs_paths, app_state.clone(), cx); + let task = + Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx); cx.spawn(|mut cx| async move { let (workspace, items) = task.await; @@ -2904,7 +2946,7 @@ pub fn open_new( cx: &mut MutableAppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static, ) -> Task<()> { - let task = Workspace::new_local(Vec::new(), app_state.clone(), cx); + let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); cx.spawn(|mut cx| async move { let (workspace, opened_paths) = task.await; diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 1b794b3857e2bad97158942aad6e12798d929eeb..13a44fef106a4c357d3c888ec415b5878a66b4e0 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -216,7 +216,7 @@ fn main() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); } else if let Ok(Some(paths)) = open_paths_rx.try_next() { - cx.update(|cx| workspace::open_paths(&paths, &app_state, cx)) + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) .detach(); } else { cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) @@ -237,7 +237,7 @@ fn main() { let app_state = app_state.clone(); async move { while let Some(paths) = open_paths_rx.next().await { - cx.update(|cx| workspace::open_paths(&paths, &app_state, cx)) + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) .detach(); } } @@ -603,7 +603,7 @@ async fn handle_cli_connection( paths }; let (workspace, items) = cx - .update(|cx| workspace::open_paths(&paths, &app_state, cx)) + .update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) .await; let mut errored = false; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2fb956f5b6f6ce2c26e5ce0782024c4e367e6bfa..63e026b9abd31dab1a9e0b4a950d841c91d78189 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -728,6 +728,10 @@ mod tests { "ca": null, "cb": null, }, + "d": { + "da": null, + "db": null, + }, }), ) .await; @@ -736,13 +740,14 @@ mod tests { open_paths( &[PathBuf::from("/root/a"), PathBuf::from("/root/b")], &app_state, + None, cx, ) }) .await; assert_eq!(cx.window_ids().len(), 1); - cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, cx)) + cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) .await; assert_eq!(cx.window_ids().len(), 1); let workspace_1 = cx.root_view::(cx.window_ids()[0]).unwrap(); @@ -756,11 +761,37 @@ mod tests { open_paths( &[PathBuf::from("/root/b"), PathBuf::from("/root/c")], &app_state, + None, + cx, + ) + }) + .await; + assert_eq!(cx.window_ids().len(), 2); + + // Replace existing windows + let window_id = cx.window_ids()[0]; + cx.update(|cx| { + open_paths( + &[PathBuf::from("/root/c"), PathBuf::from("/root/d")], + &app_state, + Some(window_id), cx, ) }) .await; assert_eq!(cx.window_ids().len(), 2); + let workspace_1 = cx.root_view::(window_id).unwrap(); + workspace_1.read_with(cx, |workspace, cx| { + assert_eq!( + workspace + .worktrees(cx) + .map(|w| w.read(cx).abs_path()) + .collect::>(), + &[Path::new("/root/c").into(), Path::new("/root/d").into()] + ); + assert!(workspace.left_sidebar().read(cx).is_open()); + assert!(workspace.active_pane().is_focused(cx)); + }); } #[gpui::test] @@ -772,7 +803,7 @@ mod tests { .insert_tree("/root", json!({"a": "hey"})) .await; - cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, cx)) + cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) .await; assert_eq!(cx.window_ids().len(), 1); @@ -810,7 +841,7 @@ mod tests { assert!(!cx.is_window_edited(workspace.window_id())); // Opening the buffer again doesn't impact the window's edited state. - cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, cx)) + cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) .await; let editor = workspace.read_with(cx, |workspace, cx| { workspace From 3594243644c27085c18e8725e1d9402f6bb58357 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 7 Mar 2023 16:22:13 -0800 Subject: [PATCH 24/45] Fix bug where open would offer to hang up a remote call Co-authored-by: max --- crates/workspace/src/workspace.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 65072637ac00b06d96400242051a05c1d7168e8b..1c1afea4aad2a58bc40a582053b0b08fa665213d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -228,16 +228,27 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { } } }); - cx.add_action({ + cx.add_async_action({ let app_state = Arc::downgrade(&app_state); - move |_, action: &OpenPaths, cx: &mut ViewContext| { - if let Some(app_state) = app_state.upgrade() { - let window_id = cx.window_id(); - let action = action.clone(); - cx.as_mut().defer(move |cx| { - open_paths(&action.paths, &app_state, Some(window_id), cx).detach(); - }) + move |workspace, action: &OpenPaths, cx: &mut ViewContext| { + if !workspace.project().read(cx).is_local() { + cx.propagate_action(); + return None; } + + let app_state = app_state.upgrade()?; + let window_id = cx.window_id(); + let action = action.clone(); + let close = workspace.prepare_to_close(false, cx); + + Some(cx.spawn_weak(|_, mut cx| async move { + let can_close = close.await?; + if can_close { + cx.update(|cx| open_paths(&action.paths, &app_state, Some(window_id), cx)) + .await; + } + Ok(()) + })) } }); From 350ddf20257e29bb9e4a55dbf2e380b703557927 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 7 Mar 2023 17:13:01 -0800 Subject: [PATCH 25/45] Add keymap picker UI Co-authored-by: Max --- Cargo.lock | 2 + crates/project_panel/src/project_panel.rs | 2 +- crates/settings/src/keymap_file.rs | 4 +- crates/settings/src/settings.rs | 46 ++++-- crates/welcome/Cargo.toml | 2 + crates/welcome/src/base_keymap_picker.rs | 175 ++++++++++++++++++++++ crates/welcome/src/welcome.rs | 17 ++- crates/zed/src/zed.rs | 3 +- 8 files changed, 225 insertions(+), 26 deletions(-) create mode 100644 crates/welcome/src/base_keymap_picker.rs diff --git a/Cargo.lock b/Cargo.lock index 2b894eff353636540473d147ecd6a864cd6d1db1..9d950712a894afe51c54611296e5c301645f2457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8030,9 +8030,11 @@ version = "0.1.0" dependencies = [ "anyhow", "editor", + "fuzzy", "gpui", "install_cli", "log", + "picker", "project", "settings", "theme", diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 5de68b12fd243e494ffa7cbc52aad1c27144450d..a95a8b2deb50f624a14b9ca5bdfa9700f603eb49 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1329,7 +1329,7 @@ impl View for ProjectPanel { keystroke_label( parent_view_id, - "Open a new project", + "Open project", &button_style, context_menu_item.keystroke, workspace::Open, diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 9048719d3f58c9bb3dc91e08d4275e551e0c7b15..2235bc351da078cd53129171b31b963e658962cc 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -46,8 +46,8 @@ impl KeymapFileContent { Self::load(path, cx).unwrap(); } - if let Some(base_keymap) = cx.global::().base_keymap { - Self::load(base_keymap.asset_path(), cx).log_err(); + if let Some(asset_path) = cx.global::().base_keymap.asset_path() { + Self::load(asset_path, cx).log_err(); } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index ae6742894899f19c4f738bbee1a563a81858b2eb..49f43e7a2d7b605703489fc292bbb5ad4107df67 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -55,24 +55,46 @@ pub struct Settings { pub telemetry_defaults: TelemetrySettings, pub telemetry_overrides: TelemetrySettings, pub auto_update: bool, - pub base_keymap: Option, + pub base_keymap: BaseKeymap, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] pub enum BaseKeymap { + #[default] + VSCode, JetBrains, Sublime, Atom, } impl BaseKeymap { - pub fn asset_path(&self) -> &str { + pub const OPTIONS: [(&'static str, Self); 4] = [ + ("VSCode (Default)", Self::VSCode), + ("Atom", Self::Atom), + ("JetBrains", Self::JetBrains), + ("Sublime", Self::Sublime), + ]; + + pub fn asset_path(&self) -> Option<&'static str> { match self { - BaseKeymap::JetBrains => "keymaps/jetbrains.json", - BaseKeymap::Sublime => "keymaps/sublime_text.json", - BaseKeymap::Atom => "keymaps/atom.json", + BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"), + BaseKeymap::Sublime => Some("keymaps/sublime_text.json"), + BaseKeymap::Atom => Some("keymaps/atom.json"), + BaseKeymap::VSCode => None, } } + + pub fn names() -> impl Iterator { + Self::OPTIONS.iter().map(|(name, _)| *name) + } + + pub fn from_names(option: &str) -> BaseKeymap { + Self::OPTIONS + .iter() + .copied() + .find_map(|(name, value)| (name == option).then(|| value)) + .unwrap_or_default() + } } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -455,7 +477,7 @@ impl Settings { merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); merge(&mut self.default_dock_anchor, data.default_dock_anchor); - merge(&mut self.base_keymap, Some(data.base_keymap)); + merge(&mut self.base_keymap, data.base_keymap); // Ensure terminal font is loaded, so we can request it in terminal_element layout if let Some(terminal_font) = &data.terminal.font_family { @@ -633,7 +655,7 @@ impl Settings { }, telemetry_overrides: Default::default(), auto_update: true, - base_keymap: None, + base_keymap: Default::default(), } } @@ -722,13 +744,7 @@ pub fn parse_json_with_comments(content: &str) -> Result )?) } -/// Expects the key to be unquoted, and the value to be valid JSON -/// (e.g. values should be unquoted for numbers and bools, quoted for strings) -pub fn write_settings_key( - settings_content: &mut String, - key_path: &[&str], - new_value: &T, -) { +fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_value: &Value) { let mut parser = tree_sitter::Parser::new(); parser.set_language(tree_sitter_json::language()).unwrap(); let tree = parser.parse(&settings_content, None).unwrap(); diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index e76636411bba5188084d7c585419dcd388aab6a4..d3b0e09697ce74bf4c7610d98699fef180f27ab8 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -14,6 +14,7 @@ test-support = [] anyhow = "1.0.38" log = "0.4" editor = { path = "../editor" } +fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } install_cli = { path = "../install_cli" } project = { path = "../project" } @@ -21,4 +22,5 @@ settings = { path = "../settings" } theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } util = { path = "../util" } +picker = { path = "../picker" } workspace = { path = "../workspace" } \ No newline at end of file diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs new file mode 100644 index 0000000000000000000000000000000000000000..a37bcb18371c95f8ea8fdc1181ec9d0af3a6409e --- /dev/null +++ b/crates/welcome/src/base_keymap_picker.rs @@ -0,0 +1,175 @@ +use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; +use gpui::{ + actions, + elements::{ChildView, Element as _, Label}, + AnyViewHandle, Entity, MutableAppContext, View, ViewContext, ViewHandle, +}; +use picker::{Picker, PickerDelegate}; +use settings::{settings_file::SettingsFile, BaseKeymap, Settings}; +use workspace::Workspace; + +pub struct BaseKeymapSelector { + matches: Vec, + picker: ViewHandle>, + selected_index: usize, +} + +actions!(welcome, [ToggleBaseKeymapSelector]); + +pub fn init(cx: &mut MutableAppContext) { + Picker::::init(cx); + cx.add_action({ + move |workspace, _: &ToggleBaseKeymapSelector, cx| BaseKeymapSelector::toggle(workspace, cx) + }); +} + +pub enum Event { + Dismissed, +} + +impl BaseKeymapSelector { + fn toggle(workspace: &mut Workspace, cx: &mut ViewContext) { + workspace.toggle_modal(cx, |_, cx| { + let this = cx.add_view(|cx| Self::new(cx)); + cx.subscribe(&this, Self::on_event).detach(); + this + }); + } + + fn new(cx: &mut ViewContext) -> Self { + let base = cx.global::().base_keymap; + let selected_index = BaseKeymap::OPTIONS + .iter() + .position(|(_, value)| *value == base) + .unwrap_or(0); + + let this = cx.weak_handle(); + Self { + picker: cx.add_view(|cx| Picker::new("Select a base keymap", this, cx)), + matches: Vec::new(), + selected_index, + } + } + + fn on_event( + workspace: &mut Workspace, + _: ViewHandle, + event: &Event, + cx: &mut ViewContext, + ) { + match event { + Event::Dismissed => { + workspace.dismiss_modal(cx); + } + } + } +} + +impl Entity for BaseKeymapSelector { + type Event = Event; +} + +impl View for BaseKeymapSelector { + fn ui_name() -> &'static str { + "BaseKeymapSelector" + } + + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { + ChildView::new(self.picker.clone(), cx).boxed() + } + + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + if cx.is_self_focused() { + cx.focus(&self.picker); + } + } +} + +impl PickerDelegate for BaseKeymapSelector { + fn match_count(&self) -> usize { + self.matches.len() + } + + fn selected_index(&self) -> usize { + self.selected_index + } + + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext) { + self.selected_index = ix; + } + + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> gpui::Task<()> { + let background = cx.background().clone(); + let candidates = BaseKeymap::names() + .enumerate() + .map(|(id, name)| StringMatchCandidate { + id, + char_bag: name.into(), + string: name.into(), + }) + .collect::>(); + + cx.spawn(|this, mut cx| async move { + let matches = if query.is_empty() { + candidates + .into_iter() + .enumerate() + .map(|(index, candidate)| StringMatch { + candidate_id: index, + string: candidate.string, + positions: Vec::new(), + score: 0.0, + }) + .collect() + } else { + match_strings( + &candidates, + &query, + false, + 100, + &Default::default(), + background, + ) + .await + }; + + this.update(&mut cx, |this, cx| { + this.matches = matches; + this.selected_index = this + .selected_index + .min(this.matches.len().saturating_sub(1)); + cx.notify(); + }); + }) + } + + fn confirm(&mut self, cx: &mut ViewContext) { + if let Some(selection) = self.matches.get(self.selected_index) { + let base_keymap = BaseKeymap::from_names(&selection.string); + SettingsFile::update(cx, move |settings| settings.base_keymap = Some(base_keymap)); + } + cx.emit(Event::Dismissed); + } + + fn dismiss(&mut self, cx: &mut ViewContext) { + cx.emit(Event::Dismissed) + } + + fn render_match( + &self, + ix: usize, + mouse_state: &mut gpui::MouseState, + selected: bool, + cx: &gpui::AppContext, + ) -> gpui::ElementBox { + let theme = &cx.global::().theme; + let keymap_match = &self.matches[ix]; + let style = theme.picker.item.style_for(mouse_state, selected); + + Label::new(keymap_match.string.clone(), style.label.clone()) + .with_highlights(keymap_match.positions.clone()) + .contained() + .with_style(style.container) + .boxed() + } +} diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 8bb055dad0a7c333f4b590a286d0d6e90baf0ec9..c780a06ee151242337a2ddd50d2f7d55744b4c36 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,3 +1,5 @@ +mod base_keymap_picker; + use std::borrow::Cow; use gpui::{ @@ -9,11 +11,15 @@ use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; use theme::CheckboxStyle; use workspace::{item::Item, PaneBackdrop, Welcome, Workspace, WorkspaceId}; +use crate::base_keymap_picker::ToggleBaseKeymapSelector; + pub fn init(cx: &mut MutableAppContext) { cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { let welcome_page = cx.add_view(WelcomePage::new); workspace.add_item(Box::new(welcome_page), cx) - }) + }); + + base_keymap_picker::init(cx); } pub struct WelcomePage { @@ -64,9 +70,9 @@ impl View for WelcomePage { .contained() .with_style(theme.welcome.logo_subheading.container) .boxed(), - self.render_cta_button(2, "Choose a theme", theme_selector::Toggle, width, cx), - self.render_cta_button(3, "Choose a keymap", theme_selector::Toggle, width, cx), - self.render_cta_button(4, "Install the CLI", install_cli::Install, width, cx), + self.render_cta_button("Choose a theme", theme_selector::Toggle, width, cx), + self.render_cta_button("Choose a keymap", ToggleBaseKeymapSelector, width, cx), + self.render_cta_button("Install the CLI", install_cli::Install, width, cx), self.render_settings_checkbox::( "Do you want to send telemetry?", &theme.welcome.checkbox, @@ -110,7 +116,6 @@ impl WelcomePage { fn render_cta_button( &self, - region_id: usize, label: L, action: A, width: f32, @@ -121,7 +126,7 @@ impl WelcomePage { A: 'static + Action + Clone, { let theme = cx.global::().theme.clone(); - MouseEventHandler::::new(region_id, cx, |state, _| { + MouseEventHandler::::new(0, cx, |state, _| { let style = theme.welcome.button.style_for(state, false); Label::new(label, style.text.clone()) .aligned() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 63e026b9abd31dab1a9e0b4a950d841c91d78189..3b48632265d5437f238bef5a19a48a6841f1cf14 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -35,7 +35,7 @@ use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode}; use uuid::Uuid; pub use workspace; -use workspace::{dock::Dock, open_new, sidebar::SidebarSide, AppState, Restart, Workspace}; +use workspace::{open_new, sidebar::SidebarSide, AppState, Restart, Workspace}; pub const FIRST_OPEN: &str = "first_open"; @@ -270,7 +270,6 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { workspace.toggle_sidebar(SidebarSide::Left, cx); let welcome_page = cx.add_view(|cx| welcome::WelcomePage::new(cx)); workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); - Dock::move_dock(workspace, settings::DockAnchor::Bottom, false, cx); cx.focus(welcome_page); cx.notify(); }) From 9842b7ad1a7f0c6008c9b030e2c7d8db9b63c0a2 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 8 Mar 2023 16:34:27 -0500 Subject: [PATCH 26/45] WIP Co-Authored-By: Mikayla Maki --- crates/theme/src/theme.rs | 3 + crates/welcome/src/welcome.rs | 95 ++++++++++------ styles/src/styleTree/welcome.ts | 187 ++++++++++++++++++-------------- 3 files changed, 170 insertions(+), 115 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e7252fbf61eff3da14f93731452eed6094f793ce..8fc6db3e5bfdaf2730670168a0e63821b0962ecd 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -858,6 +858,9 @@ pub struct WelcomeStyle { pub logo_subheading: ContainedText, pub checkbox: CheckboxStyle, pub button: Interactive, + pub button_group: ContainerStyle, + pub heading_group: ContainerStyle, + pub checkbox_group: ContainerStyle, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index c780a06ee151242337a2ddd50d2f7d55744b4c36..d5431e5108ca77d7856fff529122085ce74c581d 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -54,39 +54,72 @@ impl View for WelcomePage { self_handle.id(), Flex::column() .with_children([ - Image::new("images/zed-logo-90x90.png") - .constrained() - .with_width(90.) - .with_height(90.) - .aligned() + Flex::column() + .with_children([ + Image::new("images/zed-logo-90x90.png") + .constrained() + .with_width(90.) + .with_height(90.) + .aligned() + .contained() + .aligned() + .boxed(), + Label::new( + "Code at the speed of thought", + theme.welcome.logo_subheading.text.clone(), + ) + .aligned() + .contained() + .with_style(theme.welcome.logo_subheading.container) + .boxed(), + ]) .contained() - .aligned() + .with_style(theme.welcome.heading_group) + .boxed(), + Flex::row() + .with_children([ + self.render_cta_button( + "Choose a theme", + theme_selector::Toggle, + width, + cx, + ), + self.render_cta_button( + "Choose a keymap", + ToggleBaseKeymapSelector, + width, + cx, + ), + self.render_cta_button( + "Install the CLI", + install_cli::Install, + width, + cx, + ), + ]) + .contained() + .with_style(theme.welcome.button_group) + .boxed(), + Flex::column() + .with_children([ + self.render_settings_checkbox::( + "Do you want to send telemetry?", + &theme.welcome.checkbox, + metrics, + cx, + |content, checked| content.telemetry.set_metrics(checked), + ), + self.render_settings_checkbox::( + "Send crash reports", + &theme.welcome.checkbox, + diagnostics, + cx, + |content, checked| content.telemetry.set_diagnostics(checked), + ), + ]) + .contained() + .with_style(theme.welcome.checkbox_group) .boxed(), - Label::new( - "Code at the speed of thought", - theme.welcome.logo_subheading.text.clone(), - ) - .aligned() - .contained() - .with_style(theme.welcome.logo_subheading.container) - .boxed(), - self.render_cta_button("Choose a theme", theme_selector::Toggle, width, cx), - self.render_cta_button("Choose a keymap", ToggleBaseKeymapSelector, width, cx), - self.render_cta_button("Install the CLI", install_cli::Install, width, cx), - self.render_settings_checkbox::( - "Do you want to send telemetry?", - &theme.welcome.checkbox, - metrics, - cx, - |content, checked| content.telemetry.set_metrics(checked), - ), - self.render_settings_checkbox::( - "Send crash reports", - &theme.welcome.checkbox, - diagnostics, - cx, - |content, checked| content.telemetry.set_diagnostics(checked), - ), ]) .constrained() .with_max_width(width) diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index bfd67cec8d19d29d651e71acfc573f9aa4757c1c..2dc0f59b2e5a432251e630cdd60342807cffd158 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -4,91 +4,110 @@ import { border, background, foreground, text, TextProperties } from "./componen export default function welcome(colorScheme: ColorScheme) { - let layer = colorScheme.highest; + let layer = colorScheme.highest; - let checkboxBase = { - cornerRadius: 4, - padding: { - left: 3, - right: 3, - top: 3, - bottom: 3, - }, - shadow: colorScheme.popoverShadow, - border: border(layer), - margin: { - right: 8, - top: 5, - bottom: 5 - }, - }; - - let interactive_text_size: TextProperties = { size: "md" } - - return { - pageWidth: 450, - logoSubheading: { - ...text(layer, "sans", { size: "lg" }), - margin: { - top: 10, - bottom: 7, - }, - }, - button: { - background: background(layer), - border: border(layer, "active"), - cornerRadius: 4, - margin: { - top: 8, - bottom: 7 - }, - padding: { - top: 1, - bottom: 1, - left: 7, - right: 7, - }, - ...text(layer, "sans", "hovered", interactive_text_size), - hover: { - ...text(layer, "sans", "hovered", interactive_text_size), - background: background(layer, "hovered"), - border: border(layer, "hovered"), - }, - }, - checkbox: { - label: { - ...text(layer, "sans", interactive_text_size), - // Also supports margin, container, border, etc. - }, - container: { + let checkboxBase = { + cornerRadius: 4, + padding: { + left: 3, + right: 3, + top: 3, + bottom: 3, + }, + // shadow: colorScheme.popoverShadow, + border: border(layer), margin: { - top: 5, + right: 8, + top: 5, + bottom: 5 + }, + }; + + let interactive_text_size: TextProperties = { size: "sm" } + + return { + pageWidth: 320, + logoSubheading: { + ...text(layer, "sans", { size: "lg" }), + margin: { + top: 10, + bottom: 7, + }, + }, + buttonGroup: { + border: border(layer, "active"), + margin: { + top: 8, + bottom: 7 + }, + }, + headingGroup: { + margin: { + top: 8, + bottom: 7 + }, + }, + checkboxGroup: { + margin: { + top: 8, + bottom: 7 + }, + }, + button: { + background: background(layer), + border: border(layer, "default"), + cornerRadius: 4, + margin: { + top: 8, + bottom: 7 + }, + padding: { + top: 1, + bottom: 1, + left: 7, + right: 7, + }, + ...text(layer, "sans", "default", interactive_text_size), + hover: { + ...text(layer, "sans", "default", interactive_text_size), + background: background(layer, "default"), + border: border(layer, "active"), + }, }, - }, - width: 12, - height: 12, - checkIcon: "icons/check_12.svg", - checkIconColor: foreground(layer, "on"), - default: { - ...checkboxBase, - background: background(layer, "default"), - border: border(layer, "active") - }, - checked: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "active") - }, - hovered: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "hovered") - }, - hoveredAndChecked: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "active") - } + checkbox: { + label: { + ...text(layer, "sans", interactive_text_size), + // Also supports margin, container, border, etc. + }, + container: { + margin: { + top: 5, + }, + }, + width: 12, + height: 12, + checkIcon: "icons/check_12.svg", + checkIconColor: foreground(layer, "on"), + default: { + ...checkboxBase, + background: background(layer, "default"), + border: border(layer, "active") + }, + checked: { + ...checkboxBase, + background: background(layer, "hovered"), + border: border(layer, "active") + }, + hovered: { + ...checkboxBase, + background: background(layer, "hovered"), + border: border(layer, "hovered") + }, + hoveredAndChecked: { + ...checkboxBase, + background: background(layer, "hovered"), + border: border(layer, "active") + } + } } - } -} \ No newline at end of file +} From cc33f83e4e311fa454f8543ef14a57276f794162 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 8 Mar 2023 16:45:35 -0500 Subject: [PATCH 27/45] Add Zed logo icon Co-Authored-By: Mikayla Maki --- assets/icons/logo_96.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 assets/icons/logo_96.svg diff --git a/assets/icons/logo_96.svg b/assets/icons/logo_96.svg new file mode 100644 index 0000000000000000000000000000000000000000..dc98bb8bc249bfb1decb1771b33470b324dde96f --- /dev/null +++ b/assets/icons/logo_96.svg @@ -0,0 +1,3 @@ + + + From 344f59adf7af52b3f41aa2f37b1c0de798b2d8ef Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 8 Mar 2023 17:14:15 -0500 Subject: [PATCH 28/45] Tweak welcome design Co-Authored-By: Mikayla Maki --- crates/theme/src/theme.rs | 14 +++++++++++ crates/welcome/src/welcome.rs | 17 +++++++++---- styles/src/styleTree/welcome.ts | 42 +++++++++++++++++++++------------ 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 8fc6db3e5bfdaf2730670168a0e63821b0962ecd..13546b40cd958f6ff3c75513e4a9689aaa7526c1 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -855,6 +855,7 @@ pub struct FeedbackStyle { #[derive(Clone, Deserialize, Default)] pub struct WelcomeStyle { pub page_width: f32, + pub logo: IconStyle, pub logo_subheading: ContainedText, pub checkbox: CheckboxStyle, pub button: Interactive, @@ -863,6 +864,19 @@ pub struct WelcomeStyle { pub checkbox_group: ContainerStyle, } +#[derive(Clone, Deserialize, Default)] +pub struct IconStyle { + pub color: Color, + pub icon: String, + pub dimensions: Dimensions, +} + +#[derive(Clone, Deserialize, Default)] +pub struct Dimensions { + pub width: f32, + pub height: f32, +} + #[derive(Clone, Deserialize, Default)] pub struct CheckboxStyle { pub check_icon: String, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index d5431e5108ca77d7856fff529122085ce74c581d..246ff267171887ad11dbb802222663bbe289e91e 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -3,7 +3,7 @@ mod base_keymap_picker; use std::borrow::Cow; use gpui::{ - elements::{Empty, Flex, Image, Label, MouseEventHandler, ParentElement, Svg}, + elements::{Empty, Flex, Label, MouseEventHandler, ParentElement, Svg}, Action, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext, Subscription, View, ViewContext, }; @@ -56,10 +56,11 @@ impl View for WelcomePage { .with_children([ Flex::column() .with_children([ - Image::new("images/zed-logo-90x90.png") + Svg::new(theme.welcome.logo.icon.clone()) + .with_color(theme.welcome.logo.color) .constrained() - .with_width(90.) - .with_height(90.) + .with_width(theme.welcome.logo.dimensions.width) + .with_height(theme.welcome.logo.dimensions.height) .aligned() .contained() .aligned() @@ -75,8 +76,10 @@ impl View for WelcomePage { ]) .contained() .with_style(theme.welcome.heading_group) + .constrained() + .with_width(width) .boxed(), - Flex::row() + Flex::column() .with_children([ self.render_cta_button( "Choose a theme", @@ -99,6 +102,8 @@ impl View for WelcomePage { ]) .contained() .with_style(theme.welcome.button_group) + .constrained() + .with_width(width) .boxed(), Flex::column() .with_children([ @@ -119,6 +124,8 @@ impl View for WelcomePage { ]) .contained() .with_style(theme.welcome.checkbox_group) + .constrained() + .with_width(width) .boxed(), ]) .constrained() diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 2dc0f59b2e5a432251e630cdd60342807cffd158..137c6df11177f8fed9a156acc8b6a5a49d62017d 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -1,5 +1,6 @@ import { ColorScheme } from "../themes/common/colorScheme"; +import { withOpacity } from "../utils/color"; import { border, background, foreground, text, TextProperties } from "./components"; @@ -27,50 +28,61 @@ export default function welcome(colorScheme: ColorScheme) { return { pageWidth: 320, + logo: { + color: foreground(layer, "default"), + icon: "icons/logo_96.svg", + dimensions: { + width: 64, + height: 64, + } + }, logoSubheading: { - ...text(layer, "sans", { size: "lg" }), + ...text(layer, "sans", "variant", { size: "md" }), margin: { top: 10, bottom: 7, }, }, buttonGroup: { - border: border(layer, "active"), margin: { top: 8, - bottom: 7 + bottom: 16 }, }, headingGroup: { margin: { top: 8, - bottom: 7 + bottom: 12 }, }, checkboxGroup: { - margin: { - top: 8, - bottom: 7 + border: border(layer, "variant"), + background: withOpacity(background(layer, "hovered"), 0.25), + cornerRadius: 4, + padding: { + left: 12, + top: 2, + bottom: 2 }, }, button: { background: background(layer), - border: border(layer, "default"), + border: border(layer, "active"), cornerRadius: 4, margin: { - top: 8, - bottom: 7 + top: 4, + bottom: 4 }, padding: { - top: 1, - bottom: 1, + top: 3, + bottom: 3, left: 7, right: 7, }, ...text(layer, "sans", "default", interactive_text_size), hover: { ...text(layer, "sans", "default", interactive_text_size), - background: background(layer, "default"), + background: background(layer, "hovered"), border: border(layer, "active"), }, }, @@ -81,7 +93,7 @@ export default function welcome(colorScheme: ColorScheme) { }, container: { margin: { - top: 5, + top: 4, }, }, width: 12, @@ -101,7 +113,7 @@ export default function welcome(colorScheme: ColorScheme) { hovered: { ...checkboxBase, background: background(layer, "hovered"), - border: border(layer, "hovered") + border: border(layer, "active") }, hoveredAndChecked: { ...checkboxBase, From f62e0b502ab20c77a5e8296e0853fcfcbf2e9faa Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 12:12:27 -0800 Subject: [PATCH 29/45] Remove welcome experience action Make logo switch between light and dark co-authored-by: Nathan --- Cargo.lock | 1 + assets/images/zed-logo-90x90.png | Bin 12413 -> 0 bytes crates/project_panel/src/project_panel.rs | 2 +- crates/welcome/Cargo.toml | 1 + crates/welcome/src/welcome.rs | 25 ++++++++++++-- crates/zed/src/main.rs | 21 ++++++------ crates/zed/src/zed.rs | 29 +--------------- styles/src/styleTree/projectPanel.ts | 39 ++++++++-------------- 8 files changed, 51 insertions(+), 67 deletions(-) delete mode 100644 assets/images/zed-logo-90x90.png diff --git a/Cargo.lock b/Cargo.lock index 9d950712a894afe51c54611296e5c301645f2457..ac4cbb376150d43ac2c8dcae0dfa090f98920d98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8029,6 +8029,7 @@ name = "welcome" version = "0.1.0" dependencies = [ "anyhow", + "db", "editor", "fuzzy", "gpui", diff --git a/assets/images/zed-logo-90x90.png b/assets/images/zed-logo-90x90.png deleted file mode 100644 index 17f92d2c1afbb8459fd31bfdc7ace50a412ce0a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12413 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 WubU>a9)CZGQhKiXtn?`~@c#k#=|!0U diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a95a8b2deb50f624a14b9ca5bdfa9700f603eb49..08a83fbc435fc34ce89641897f1ba71c0eba3564 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1329,7 +1329,7 @@ impl View for ProjectPanel { keystroke_label( parent_view_id, - "Open project", + "Open a project", &button_style, context_menu_item.keystroke, workspace::Open, diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index d3b0e09697ce74bf4c7610d98699fef180f27ab8..3da90deb2d1a2f1f6a9fdbe761bb4edcc35f664e 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4" editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } +db = { path = "../db" } install_cli = { path = "../install_cli" } project = { path = "../project" } settings = { path = "../settings" } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 246ff267171887ad11dbb802222663bbe289e91e..a2386b8d2806ed03ec21840cab2bf42bbf3fb3cd 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,7 +1,8 @@ mod base_keymap_picker; -use std::borrow::Cow; +use std::{borrow::Cow, sync::Arc}; +use db::kvp::KEY_VALUE_STORE; use gpui::{ elements::{Empty, Flex, Label, MouseEventHandler, ParentElement, Svg}, Action, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext, @@ -9,10 +10,15 @@ use gpui::{ }; use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; use theme::CheckboxStyle; -use workspace::{item::Item, PaneBackdrop, Welcome, Workspace, WorkspaceId}; +use workspace::{ + item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, + WorkspaceId, +}; use crate::base_keymap_picker::ToggleBaseKeymapSelector; +pub const FIRST_OPEN: &str = "first_open"; + pub fn init(cx: &mut MutableAppContext) { cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { let welcome_page = cx.add_view(WelcomePage::new); @@ -22,6 +28,21 @@ pub fn init(cx: &mut MutableAppContext) { base_keymap_picker::init(cx); } +pub fn show_welcome_experience(app_state: &Arc, cx: &mut MutableAppContext) { + open_new(&app_state, cx, |workspace, cx| { + workspace.toggle_sidebar(SidebarSide::Left, cx); + let welcome_page = cx.add_view(|cx| WelcomePage::new(cx)); + workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); + cx.focus(welcome_page); + cx.notify(); + }) + .detach(); + + db::write_and_log(cx, || { + KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string()) + }); +} + pub struct WelcomePage { _settings_subscription: Subscription, } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 13a44fef106a4c357d3c888ec415b5878a66b4e0..9982b4114aabaeb13c1c41d066baf7a8a2f75919 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -36,6 +36,7 @@ use std::{ path::PathBuf, sync::Arc, thread, time::Duration, }; use terminal_view::{get_working_directory, TerminalView}; +use welcome::{show_welcome_experience, FIRST_OPEN}; use fs::RealFs; use settings::watched_json::WatchedJsonFile; @@ -46,10 +47,7 @@ use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, }; -use zed::{ - self, build_window_options, initialize_workspace, languages, menus, WelcomeExperience, - FIRST_OPEN, -}; +use zed::{self, build_window_options, initialize_workspace, languages, menus}; fn main() { let http = http::client(); @@ -206,7 +204,7 @@ fn main() { cx.platform().activate(true); let paths = collect_path_args(); if paths.is_empty() { - cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) + cx.spawn(|cx| async move { restore_or_create_workspace(&app_state, cx).await }) .detach() } else { cx.dispatch_global_action(OpenPaths { paths }); @@ -219,8 +217,11 @@ fn main() { cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) .detach(); } else { - cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) - .detach() + cx.spawn({ + let app_state = app_state.clone(); + |cx| async move { restore_or_create_workspace(&app_state, cx).await } + }) + .detach() } cx.spawn(|cx| { @@ -259,7 +260,7 @@ fn main() { }); } -async fn restore_or_create_workspace(mut cx: AsyncAppContext) { +async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { if let Some(location) = workspace::last_opened_workspace_paths().await { cx.update(|cx| { cx.dispatch_global_action(OpenPaths { @@ -267,9 +268,7 @@ async fn restore_or_create_workspace(mut cx: AsyncAppContext) { }) }); } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { - cx.update(|cx| { - cx.dispatch_global_action(WelcomeExperience); - }); + cx.update(|cx| show_welcome_experience(app_state, cx)); } else { cx.update(|cx| { cx.dispatch_global_action(NewFile); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3b48632265d5437f238bef5a19a48a6841f1cf14..3c093836f201146818b96ce1abaac6087a22fb14 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -8,7 +8,6 @@ use breadcrumbs::Breadcrumbs; pub use client; use collab_ui::{CollabTitlebarItem, ToggleContactsMenu}; use collections::VecDeque; -use db::kvp::KEY_VALUE_STORE; pub use editor; use editor::{Editor, MultiBuffer}; @@ -35,9 +34,7 @@ use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode}; use uuid::Uuid; pub use workspace; -use workspace::{open_new, sidebar::SidebarSide, AppState, Restart, Workspace}; - -pub const FIRST_OPEN: &str = "first_open"; +use workspace::{sidebar::SidebarSide, AppState, Restart, Workspace}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -69,7 +66,6 @@ actions!( DecreaseBufferFontSize, ResetBufferFontSize, ResetDatabase, - WelcomeExperience ] ); @@ -258,29 +254,6 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { workspace.toggle_sidebar_item_focus(SidebarSide::Left, 0, cx); }, ); - - cx.add_global_action({ - let app_state = app_state.clone(); - move |_: &WelcomeExperience, cx| { - if !matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { - return; //noop, in case someone fires this from the command palette - } - - open_new(&app_state, cx, |workspace, cx| { - workspace.toggle_sidebar(SidebarSide::Left, cx); - let welcome_page = cx.add_view(|cx| welcome::WelcomePage::new(cx)); - workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); - cx.focus(welcome_page); - cx.notify(); - }) - .detach(); - - db::write_and_log(cx, || { - KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string()) - }); - } - }); - activity_indicator::init(cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); settings::KeymapFileContent::load_defaults(cx); diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 2601a12691683c6e5dd85cdc5b99257c91d3165d..80cb884c4896de11d5c852468849b35787858508 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -30,37 +30,26 @@ export default function projectPanel(colorScheme: ColorScheme) { return { openProjectButton: { - ...text(layer, "mono", "active", { size: "sm" }), - background: background(layer, "on"), - cornerRadius: 6, - border: border(layer, "on"), + background: background(layer), + border: border(layer, "active"), + cornerRadius: 4, margin: { - top: 20, - left: 10, - right: 10 + top: 16, + left: 16, + right: 16, }, padding: { - bottom: 2, - left: 10, - right: 10, - top: 2, - }, - active: { - ...text(layer, "mono", "on", "inverted"), - background: background(layer, "on", "inverted"), - border: border(layer, "on", "inverted"), - }, - clicked: { - ...text(layer, "mono", "on", "pressed"), - background: background(layer, "on", "pressed"), - border: border(layer, "on", "pressed"), + top: 3, + bottom: 3, + left: 7, + right: 7, }, + ...text(layer, "sans", "default", { size: "sm" }), hover: { - ...text(layer, "mono", "on", "hovered"), - background: background(layer, "on", "hovered"), - border: border(layer, "on", "hovered"), + ...text(layer, "sans", "default", { size: "sm" }), + background: background(layer, "hovered"), + border: border(layer, "active"), }, - }, background: background(layer), padding: { left: 12, right: 12, top: 6, bottom: 6 }, From dad66eb3fbc7b981c7e433a3140571ced6cb42f6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 14:38:33 -0800 Subject: [PATCH 30/45] Make the workspace always open the dock --- crates/workspace/src/workspace.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1c1afea4aad2a58bc40a582053b0b08fa665213d..7c439522e8210878508f0a88a61a6a6c247e5ebe 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -742,6 +742,10 @@ impl Workspace { cx.defer(move |_, cx| { Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx) }); + } else { + if cx.global::().default_dock_anchor != DockAnchor::Expanded { + Dock::show(&mut this, false, cx); + } } this From 152755b04356cbb5d2990315fe81e5d57be08d23 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 17:56:39 -0800 Subject: [PATCH 31/45] Add blank pane experience --- crates/collab/src/tests.rs | 11 +- crates/collab/src/tests/integration_tests.rs | 30 +--- crates/collab_ui/src/collab_ui.rs | 1 + crates/command_palette/src/command_palette.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 10 +- crates/editor/src/editor_tests.rs | 20 +-- .../src/test/editor_lsp_test_context.rs | 12 +- crates/file_finder/src/file_finder.rs | 28 +--- crates/project_panel/src/project_panel.rs | 69 ++------- crates/terminal_view/src/terminal_view.rs | 10 +- crates/theme/src/theme.rs | 42 ++---- crates/theme/src/ui.rs | 119 +++++++++++++++ crates/welcome/src/welcome.rs | 142 ++++++++++-------- crates/workspace/src/dock.rs | 15 +- crates/workspace/src/pane.rs | 90 ++++++++--- crates/workspace/src/workspace.rs | 71 ++++++--- crates/zed/src/main.rs | 18 ++- crates/zed/src/zed.rs | 44 +----- styles/src/styleTree/contextMenu.ts | 9 +- styles/src/styleTree/welcome.ts | 20 ++- styles/src/styleTree/workspace.ts | 28 ++++ 21 files changed, 454 insertions(+), 339 deletions(-) create mode 100644 crates/theme/src/ui.rs diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index e9ffecf246d295934e15a2a3375af60f78a18c3e..8949b60993a041616d3d9381eadb65b2298b2488 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -198,6 +198,7 @@ impl TestServer { build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), dock_default_item_factory: |_, _| unimplemented!(), + background_actions: || unimplemented!(), }); Project::init(&client); @@ -434,15 +435,7 @@ impl TestClient { cx: &mut TestAppContext, ) -> ViewHandle { let (_, root_view) = cx.add_window(|_| EmptyView); - cx.add_view(&root_view, |cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }) + cx.add_view(&root_view, |cx| Workspace::test_new(project.clone(), cx)) } fn create_new_root_dir(&mut self) -> PathBuf { diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 1ab78ac310b0d23b10f08af62fbe8490f626161b..902242df012dd2428322447337a9794202333476 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -1449,15 +1449,7 @@ async fn test_host_disconnect( deterministic.run_until_parked(); assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); - let (_, workspace_b) = cx_b.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project_b.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "b.txt"), None, true, cx) @@ -4706,15 +4698,7 @@ async fn test_collaborating_with_code_actions( // Join the project as client B. let project_b = client_b.build_remote_project(project_id, cx_b).await; - let (_window_b, workspace_b) = cx_b.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project_b.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "main.rs"), None, true, cx) @@ -4937,15 +4921,7 @@ async fn test_collaborating_with_renames( .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; - let (_window_b, workspace_b) = cx_b.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project_b.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "one.rs"), None, true, cx) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 6abfec21f74f0918e6338bc77a2e8aa8acfe783b..2dd2b0e6b47e9bbea6e2c916d201b70282517841 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -86,6 +86,7 @@ fn join_project(action: &JoinProject, app_state: Arc, cx: &mut Mutable 0, project, app_state.dock_default_item_factory, + app_state.background_actions, cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 55522966fa48f1f25f402d999917144c0d884be1..52a0e1cdc0b15f62ef2a66da56f39ac0e2169a03 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -352,9 +352,7 @@ mod tests { }); let project = Project::test(app_state.fs.clone(), [], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let editor = cx.add_view(&workspace, |cx| { let mut editor = Editor::single_line(None, cx); editor.set_text("abc", cx); diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e447a26c6b64159689dd6fc27b37d537507d7f21..223255544290ba93523a27ea5085f7871801e2a6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -805,15 +805,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); // Create some diagnostics project.update(cx, |project, cx| { diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 269390f72f14f7722230234579826a3ffaea7301..21cc9f889580f10d3ece2e2dfee7c18c203c37c4 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -484,7 +484,9 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) { cx.set_global(Settings::test(cx)); cx.set_global(DragAndDrop::::default()); use workspace::item::Item; - let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(None, cx)); + let (_, pane) = cx.add_window(Default::default(), |cx| { + Pane::new(None, || unimplemented!(), cx) + }); let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); cx.add_view(&pane, |cx| { @@ -2354,10 +2356,10 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) { e.handle_input(") ", cx); }); cx.assert_editor_state(indoc! {" - ( one✅ - three - five ) ˇtwo one✅ four three six five ( one✅ - three + ( one✅ + three + five ) ˇtwo one✅ four three six five ( one✅ + three five ) ˇ"}); // Cut with three selections, one of which is full-line. @@ -5562,7 +5564,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { Settings::test_async(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - let (_, pane) = cx.add_window(|cx| Pane::new(None, cx)); + let (_, pane) = cx.add_window(|cx| Pane::new(None, || unimplemented!(), cx)); let leader = pane.update(cx, |_, cx| { let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); @@ -5831,11 +5833,11 @@ async fn go_to_hunk(deterministic: Arc, cx: &mut gpui::TestAppCon cx.assert_editor_state( &r#" ˇuse some::modified; - - + + fn main() { println!("hello there"); - + println!("around the"); println!("world"); } diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 1b6d846e710f4ebd6f84c8053ab88d4c36294ef5..fe9a7909b8538bd7df4d91a97ff058d19cd1b3da 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -65,15 +65,7 @@ impl<'a> EditorLspTestContext<'a> { .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }})) .await; - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); project .update(cx, |project, cx| { project.find_or_create_local_worktree("/root", true, cx) @@ -134,7 +126,7 @@ impl<'a> EditorLspTestContext<'a> { (let_chain) (await_expression) ] @indent - + (_ "[" "]" @end) @indent (_ "<" ">" @end) @indent (_ "{" "}" @end) @indent diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 273440dce253f54e8ccf5cb520bff321d403b1be..51d4df45f58fe59e32b4cc1eefbd10f9d49ddeaf 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -329,9 +329,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); cx.dispatch_action(window_id, Toggle); let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); @@ -385,9 +383,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); @@ -461,9 +457,7 @@ mod tests { cx, ) .await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); finder @@ -487,9 +481,7 @@ mod tests { cx, ) .await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); @@ -541,9 +533,7 @@ mod tests { cx, ) .await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); @@ -585,9 +575,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); // When workspace has an active item, sort items which are closer to that item // first when they have the same name. In this case, b.txt is closer to dir2's a.txt @@ -624,9 +612,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); finder diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 08a83fbc435fc34ce89641897f1ba71c0eba3564..079de79b1124b6064e7df9ca81e211147b2db1f0 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,15 +6,14 @@ use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - AnchorCorner, ChildView, ConstrainedBox, Container, ContainerStyle, Empty, Flex, - KeystrokeLabel, Label, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, - UniformList, UniformListState, + AnchorCorner, ChildView, ConstrainedBox, ContainerStyle, Empty, Flex, Label, + MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, geometry::vector::Vector2F, impl_internal_actions, keymap_matcher::KeymapContext, platform::CursorStyle, - Action, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, + AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; @@ -28,7 +27,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use theme::{ContainedText, ProjectPanelEntry}; +use theme::ProjectPanelEntry; use unicase::UniCase; use workspace::Workspace; @@ -1315,7 +1314,6 @@ impl View for ProjectPanel { .with_child(ChildView::new(&self.context_menu, cx).boxed()) .boxed() } else { - let parent_view_id = cx.handle().id(); Flex::column() .with_child( MouseEventHandler::::new(2, cx, { @@ -1327,12 +1325,11 @@ impl View for ProjectPanel { let context_menu_item = context_menu_item_style.style_for(state, true).clone(); - keystroke_label( - parent_view_id, + theme::ui::keystroke_label( "Open a project", &button_style, - context_menu_item.keystroke, - workspace::Open, + &context_menu_item.keystroke, + Box::new(workspace::Open), cx, ) .boxed() @@ -1357,38 +1354,6 @@ impl View for ProjectPanel { } } -fn keystroke_label( - view_id: usize, - label_text: &'static str, - label_style: &ContainedText, - keystroke_style: ContainedText, - action: A, - cx: &mut RenderContext, -) -> Container -where - A: Action, -{ - Flex::row() - .with_child( - Label::new(label_text, label_style.text.clone()) - .contained() - .boxed(), - ) - .with_child({ - KeystrokeLabel::new( - cx.window_id(), - view_id, - Box::new(action), - keystroke_style.container, - keystroke_style.text.clone(), - ) - .flex_float() - .boxed() - }) - .contained() - .with_style(label_style.container) -} - impl Entity for ProjectPanel { type Event = Event; } @@ -1474,15 +1439,7 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx)); assert_eq!( visible_entries_as_strings(&panel, 0..50, cx), @@ -1574,15 +1531,7 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx)); select_path(&panel, "root1", cx); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 3821185ec02e9d1981162863efa1a1708c9e3f97..110815e87027e7c018d3a25d6e7115c5b48b0be3 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -970,15 +970,7 @@ mod tests { let params = cx.update(AppState::test); let project = Project::test(params.fs.clone(), [], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); (project, workspace) } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 13546b40cd958f6ff3c75513e4a9689aaa7526c1..524b7656268a1b143b0857075e2e43eaea17a9bc 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -9,6 +9,9 @@ use gpui::{ use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; use std::{collections::HashMap, sync::Arc}; +use ui::{CheckboxStyle, IconStyle}; + +pub mod ui; pub use theme_registry::*; @@ -50,6 +53,7 @@ pub struct ThemeMeta { #[derive(Deserialize, Default)] pub struct Workspace { pub background: Color, + pub blank_pane: BlankPaneStyle, pub titlebar: Titlebar, pub tab_bar: TabBar, pub pane_divider: Border, @@ -69,6 +73,14 @@ pub struct Workspace { pub drop_target_overlay_color: Color, } +#[derive(Clone, Deserialize, Default)] +pub struct BlankPaneStyle { + pub logo: IconStyle, + pub keyboard_hints: ContainerStyle, + pub keyboard_hint: Interactive, + pub keyboard_hint_width: f32, +} + #[derive(Clone, Deserialize, Default)] pub struct Titlebar { #[serde(flatten)] @@ -858,46 +870,18 @@ pub struct WelcomeStyle { pub logo: IconStyle, pub logo_subheading: ContainedText, pub checkbox: CheckboxStyle, + pub checkbox_container: ContainerStyle, pub button: Interactive, pub button_group: ContainerStyle, pub heading_group: ContainerStyle, pub checkbox_group: ContainerStyle, } -#[derive(Clone, Deserialize, Default)] -pub struct IconStyle { - pub color: Color, - pub icon: String, - pub dimensions: Dimensions, -} - -#[derive(Clone, Deserialize, Default)] -pub struct Dimensions { - pub width: f32, - pub height: f32, -} - -#[derive(Clone, Deserialize, Default)] -pub struct CheckboxStyle { - pub check_icon: String, - pub check_icon_color: Color, - pub label: ContainedText, - pub container: ContainerStyle, - pub width: f32, - pub height: f32, - pub default: ContainerStyle, - pub checked: ContainerStyle, - pub hovered: ContainerStyle, - pub hovered_and_checked: ContainerStyle, -} - #[derive(Clone, Deserialize, Default)] pub struct ColorScheme { pub name: String, pub is_light: bool, - pub ramps: RampSet, - pub lowest: Layer, pub middle: Layer, pub highest: Layer, diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs new file mode 100644 index 0000000000000000000000000000000000000000..ca71db37231531f1d76312807f55551a7b181c9b --- /dev/null +++ b/crates/theme/src/ui.rs @@ -0,0 +1,119 @@ +use gpui::{ + color::Color, + elements::{ + ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, + MouseEventHandler, ParentElement, Svg, + }, + Action, Element, EventContext, RenderContext, View, +}; +use serde::Deserialize; + +use crate::ContainedText; + +#[derive(Clone, Deserialize, Default)] +pub struct CheckboxStyle { + pub icon: IconStyle, + pub label: ContainedText, + pub default: ContainerStyle, + pub checked: ContainerStyle, + pub hovered: ContainerStyle, + pub hovered_and_checked: ContainerStyle, +} + +pub fn checkbox( + label: &'static str, + style: &CheckboxStyle, + checked: bool, + cx: &mut RenderContext, + change: fn(checked: bool, cx: &mut EventContext) -> (), +) -> MouseEventHandler { + MouseEventHandler::::new(0, cx, |state, _| { + let indicator = if checked { + icon(&style.icon) + } else { + Empty::new() + .constrained() + .with_width(style.icon.dimensions.width) + .with_height(style.icon.dimensions.height) + }; + + Flex::row() + .with_children([ + indicator + .contained() + .with_style(if checked { + if state.hovered() { + style.hovered_and_checked + } else { + style.checked + } + } else { + if state.hovered() { + style.hovered + } else { + style.default + } + }) + .boxed(), + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) + .boxed(), + ]) + .align_children_center() + .boxed() + }) + .on_click(gpui::MouseButton::Left, move |_, cx| change(!checked, cx)) + .with_cursor_style(gpui::CursorStyle::PointingHand) +} + +#[derive(Clone, Deserialize, Default)] +pub struct IconStyle { + pub color: Color, + pub icon: String, + pub dimensions: Dimensions, +} + +#[derive(Clone, Deserialize, Default)] +pub struct Dimensions { + pub width: f32, + pub height: f32, +} + +pub fn icon(style: &IconStyle) -> ConstrainedBox { + Svg::new(style.icon.clone()) + .with_color(style.color) + .constrained() + .with_width(style.dimensions.width) + .with_height(style.dimensions.height) +} + +pub fn keystroke_label( + label_text: &'static str, + label_style: &ContainedText, + keystroke_style: &ContainedText, + action: Box, + cx: &mut RenderContext, +) -> Container { + // FIXME: Put the theme in it's own global so we can + // query the keystroke style on our own + Flex::row() + .with_child( + Label::new(label_text, label_style.text.clone()) + .contained() + .boxed(), + ) + .with_child({ + KeystrokeLabel::new( + cx.window_id(), + cx.handle().id(), + action, + keystroke_style.container, + keystroke_style.text.clone(), + ) + .flex_float() + .boxed() + }) + .contained() + .with_style(label_style.container) +} diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index a2386b8d2806ed03ec21840cab2bf42bbf3fb3cd..89f161a283fb46435b20a71cae60d69983625cc4 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -4,12 +4,12 @@ use std::{borrow::Cow, sync::Arc}; use db::kvp::KEY_VALUE_STORE; use gpui::{ - elements::{Empty, Flex, Label, MouseEventHandler, ParentElement, Svg}, + elements::{Flex, Label, MouseEventHandler, ParentElement}, Action, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext, Subscription, View, ViewContext, }; -use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; -use theme::CheckboxStyle; +use settings::{settings_file::SettingsFile, Settings}; + use workspace::{ item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, @@ -77,11 +77,7 @@ impl View for WelcomePage { .with_children([ Flex::column() .with_children([ - Svg::new(theme.welcome.logo.icon.clone()) - .with_color(theme.welcome.logo.color) - .constrained() - .with_width(theme.welcome.logo.dimensions.width) - .with_height(theme.welcome.logo.dimensions.height) + theme::ui::icon(&theme.welcome.logo) .aligned() .contained() .aligned() @@ -128,20 +124,34 @@ impl View for WelcomePage { .boxed(), Flex::column() .with_children([ - self.render_settings_checkbox::( + theme::ui::checkbox::( "Do you want to send telemetry?", &theme.welcome.checkbox, metrics, cx, - |content, checked| content.telemetry.set_metrics(checked), - ), - self.render_settings_checkbox::( + |checked, cx| { + SettingsFile::update(cx, move |file| { + file.telemetry.set_metrics(checked) + }) + }, + ) + .contained() + .with_style(theme.welcome.checkbox_container) + .boxed(), + theme::ui::checkbox::( "Send crash reports", &theme.welcome.checkbox, diagnostics, cx, - |content, checked| content.telemetry.set_diagnostics(checked), - ), + |checked, cx| { + SettingsFile::update(cx, move |file| { + file.telemetry.set_diagnostics(checked) + }) + }, + ) + .contained() + .with_style(theme.welcome.checkbox_container) + .boxed(), ]) .contained() .with_style(theme.welcome.checkbox_group) @@ -204,59 +214,59 @@ impl WelcomePage { .boxed() } - fn render_settings_checkbox( - &self, - label: &'static str, - style: &CheckboxStyle, - checked: bool, - cx: &mut RenderContext, - set_value: fn(&mut SettingsFileContent, checked: bool) -> (), - ) -> ElementBox { - MouseEventHandler::::new(0, cx, |state, _| { - let indicator = if checked { - Svg::new(style.check_icon.clone()) - .with_color(style.check_icon_color) - .constrained() - } else { - Empty::new().constrained() - }; + // fn render_settings_checkbox( + // &self, + // label: &'static str, + // style: &CheckboxStyle, + // checked: bool, + // cx: &mut RenderContext, + // set_value: fn(&mut SettingsFileContent, checked: bool) -> (), + // ) -> ElementBox { + // MouseEventHandler::::new(0, cx, |state, _| { + // let indicator = if checked { + // Svg::new(style.check_icon.clone()) + // .with_color(style.check_icon_color) + // .constrained() + // } else { + // Empty::new().constrained() + // }; - Flex::row() - .with_children([ - indicator - .with_width(style.width) - .with_height(style.height) - .contained() - .with_style(if checked { - if state.hovered() { - style.hovered_and_checked - } else { - style.checked - } - } else { - if state.hovered() { - style.hovered - } else { - style.default - } - }) - .boxed(), - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .boxed(), - ]) - .align_children_center() - .boxed() - }) - .on_click(gpui::MouseButton::Left, move |_, cx| { - SettingsFile::update(cx, move |content| set_value(content, !checked)) - }) - .with_cursor_style(gpui::CursorStyle::PointingHand) - .contained() - .with_style(style.container) - .boxed() - } + // Flex::row() + // .with_children([ + // indicator + // .with_width(style.width) + // .with_height(style.height) + // .contained() + // .with_style(if checked { + // if state.hovered() { + // style.hovered_and_checked + // } else { + // style.checked + // } + // } else { + // if state.hovered() { + // style.hovered + // } else { + // style.default + // } + // }) + // .boxed(), + // Label::new(label, style.label.text.clone()) + // .contained() + // .with_style(style.label.container) + // .boxed(), + // ]) + // .align_children_center() + // .boxed() + // }) + // .on_click(gpui::MouseButton::Left, move |_, cx| { + // SettingsFile::update(cx, move |content| set_value(content, !checked)) + // }) + // .with_cursor_style(gpui::CursorStyle::PointingHand) + // .contained() + // .with_style(style.container) + // .boxed() + // } } impl Item for WelcomePage { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 4281c04649ee12ea7ee6e02ee89784ca4620d22c..f86a9db71acf1b9b09cb66fd8b30d4cde9708a08 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -13,7 +13,7 @@ use gpui::{ use settings::{DockAnchor, Settings}; use theme::Theme; -use crate::{sidebar::SidebarSide, ItemHandle, Pane, Workspace}; +use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace}; pub use toggle_dock_button::ToggleDockButton; #[derive(PartialEq, Clone, Deserialize)] @@ -182,11 +182,12 @@ pub struct Dock { impl Dock { pub fn new( default_item_factory: DockDefaultItemFactory, + background_actions: BackgroundActions, cx: &mut ViewContext, ) -> Self { let position = DockPosition::Hidden(cx.global::().default_dock_anchor); - let pane = cx.add_view(|cx| Pane::new(Some(position.anchor()), cx)); + let pane = cx.add_view(|cx| Pane::new(Some(position.anchor()), background_actions, cx)); pane.update(cx, |pane, cx| { pane.set_active(false, cx); }); @@ -492,6 +493,7 @@ mod tests { 0, project.clone(), default_item_factory, + || unimplemented!(), cx, ) }); @@ -620,7 +622,14 @@ mod tests { cx.update(|cx| init(cx)); let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, default_item_factory, cx) + Workspace::new( + Default::default(), + 0, + project, + default_item_factory, + || unimplemented!(), + cx, + ) }); workspace.update(cx, |workspace, cx| { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fe339969617c2214606a9a1dbb6937737ca495c9..7e0e6bbe01d22bc891b22a93a92154e3a843608c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -110,6 +110,8 @@ impl_internal_actions!( const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; +pub type BackgroundActions = fn() -> &'static [(&'static str, &'static dyn Action)]; + pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { pane.activate_item(action.0, true, true, cx); @@ -215,6 +217,7 @@ pub struct Pane { toolbar: ViewHandle, tab_bar_context_menu: ViewHandle, docked: Option, + background_actions: BackgroundActions, } pub struct ItemNavHistory { @@ -271,7 +274,11 @@ enum ItemType { } impl Pane { - pub fn new(docked: Option, cx: &mut ViewContext) -> Self { + pub fn new( + docked: Option, + background_actions: BackgroundActions, + cx: &mut ViewContext, + ) -> Self { let handle = cx.weak_handle(); let context_menu = cx.add_view(ContextMenu::new); Self { @@ -292,6 +299,7 @@ impl Pane { toolbar: cx.add_view(|_| Toolbar::new(handle)), tab_bar_context_menu: context_menu, docked, + background_actions, } } @@ -1415,6 +1423,64 @@ impl Pane { .flex(1., false) .boxed() } + + fn render_blank_pane(&mut self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { + let background = theme.workspace.background; + let keystroke_style = &theme.context_menu.item; + let theme = &theme.workspace.blank_pane; + Stack::new() + .with_children([ + Empty::new() + .contained() + .with_background_color(background) + .boxed(), + Flex::column() + .align_children_center() + .with_children([ + theme::ui::icon(&theme.logo).aligned().boxed(), + Flex::column() + .with_children({ + enum KeyboardHint {} + let keyboard_hint = &theme.keyboard_hint; + (self.background_actions)().into_iter().enumerate().map( + move |(idx, (text, action))| { + let hint_action = action.boxed_clone(); + MouseEventHandler::::new( + idx, + cx, + move |state, cx| { + theme::ui::keystroke_label( + text, + &keyboard_hint.style_for(state, false), + &keystroke_style + .style_for(state, false) + .keystroke, + hint_action, + cx, + ) + .boxed() + }, + ) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_any_action(action.boxed_clone()) + }) + .with_cursor_style(CursorStyle::PointingHand) + .boxed() + }, + ) + }) + .contained() + .with_style(theme.keyboard_hints) + .constrained() + .with_max_width(theme.keyboard_hint_width) + .aligned() + .boxed(), + ]) + .aligned() + .boxed(), + ]) + .boxed() + } } impl Entity for Pane { @@ -1508,11 +1574,8 @@ impl View for Pane { enum EmptyPane {} let theme = cx.global::().theme.clone(); - dragged_item_receiver::(0, 0, false, None, cx, |_, _| { - Empty::new() - .contained() - .with_background_color(theme.workspace.background) - .boxed() + dragged_item_receiver::(0, 0, false, None, cx, |_, cx| { + self.render_blank_pane(&theme, cx) }) .on_down(MouseButton::Left, |_, cx| { cx.focus_parent_view(); @@ -1809,9 +1872,7 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -1899,9 +1960,7 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -1977,9 +2036,7 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // singleton view @@ -2088,8 +2145,7 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = - cx.add_window(|cx| Workspace::new(None, 0, project, |_, _| unimplemented!(), cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); add_labled_item(&workspace, &pane, "A", cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7c439522e8210878508f0a88a61a6a6c247e5ebe..65335d8671069d5e93a7852acbc5bed87852763d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -432,6 +432,7 @@ pub struct AppState { fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), pub dock_default_item_factory: DockDefaultItemFactory, + pub background_actions: BackgroundActions, } impl AppState { @@ -455,6 +456,7 @@ impl AppState { initialize_workspace: |_, _, _| {}, build_window_options: |_, _, _| Default::default(), dock_default_item_factory: |_, _| unimplemented!(), + background_actions: || unimplemented!(), }) } } @@ -542,6 +544,7 @@ pub struct Workspace { active_call: Option<(ModelHandle, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, + background_actions: BackgroundActions, _window_subscriptions: [Subscription; 3], _apply_leader_updates: Task>, _observe_current_user: Task<()>, @@ -572,6 +575,7 @@ impl Workspace { workspace_id: WorkspaceId, project: ModelHandle, dock_default_factory: DockDefaultItemFactory, + background_actions: BackgroundActions, cx: &mut ViewContext, ) -> Self { cx.observe(&project, |_, _, cx| cx.notify()).detach(); @@ -602,7 +606,7 @@ impl Workspace { }) .detach(); - let center_pane = cx.add_view(|cx| Pane::new(None, cx)); + let center_pane = cx.add_view(|cx| Pane::new(None, background_actions, cx)); let pane_id = center_pane.id(); cx.subscribe(¢er_pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) @@ -610,7 +614,7 @@ impl Workspace { .detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); - let dock = Dock::new(dock_default_factory, cx); + let dock = Dock::new(dock_default_factory, background_actions, cx); let dock_pane = dock.pane().clone(); let fs = project.read(cx).fs().clone(); @@ -730,6 +734,7 @@ impl Workspace { window_edited: false, active_call, database_id: workspace_id, + background_actions, _observe_current_user, _apply_leader_updates, leader_updates_tx, @@ -818,6 +823,7 @@ impl Workspace { workspace_id, project_handle, app_state.dock_default_item_factory, + app_state.background_actions, cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); @@ -1432,7 +1438,7 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(|cx| Pane::new(None, cx)); + let pane = cx.add_view(|cx| Pane::new(None, self.background_actions, cx)); let pane_id = pane.id(); cx.subscribe(&pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) @@ -2648,6 +2654,11 @@ impl Workspace { }) .detach(); } + + #[cfg(any(test, feature = "test-support"))] + pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { + Self::new(None, 0, project, |_, _| None, || &[], cx) + } } fn notify_if_database_failed(workspace: &ViewHandle, cx: &mut AsyncAppContext) { @@ -2988,17 +2999,10 @@ mod tests { use super::*; use fs::FakeFs; - use gpui::{executor::Deterministic, TestAppContext, ViewContext}; + use gpui::{executor::Deterministic, TestAppContext}; use project::{Project, ProjectEntryId}; use serde_json::json; - pub fn default_item_factory( - _workspace: &mut Workspace, - _cx: &mut ViewContext, - ) -> Option> { - unimplemented!() - } - #[gpui::test] async fn test_tab_disambiguation(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); @@ -3011,7 +3015,8 @@ mod tests { Default::default(), 0, project.clone(), - default_item_factory, + |_, _| unimplemented!(), + || unimplemented!(), cx, ) }); @@ -3083,7 +3088,8 @@ mod tests { Default::default(), 0, project.clone(), - default_item_factory, + |_, _| unimplemented!(), + || unimplemented!(), cx, ) }); @@ -3183,7 +3189,8 @@ mod tests { Default::default(), 0, project.clone(), - default_item_factory, + |_, _| unimplemented!(), + || unimplemented!(), cx, ) }); @@ -3222,7 +3229,14 @@ mod tests { let project = Project::test(fs, None, cx).await; let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, default_item_factory, cx) + Workspace::new( + Default::default(), + 0, + project, + |_, _| unimplemented!(), + || unimplemented!(), + cx, + ) }); let item1 = cx.add_view(&workspace, |cx| { @@ -3331,7 +3345,14 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, default_item_factory, cx) + Workspace::new( + Default::default(), + 0, + project, + |_, _| unimplemented!(), + || unimplemented!(), + cx, + ) }); // Create several workspace items with single project entries, and two @@ -3440,7 +3461,14 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, default_item_factory, cx) + Workspace::new( + Default::default(), + 0, + project, + |_, _| unimplemented!(), + || unimplemented!(), + cx, + ) }); let item = cx.add_view(&workspace, |cx| { @@ -3559,7 +3587,14 @@ mod tests { let project = Project::test(fs, [], cx).await; let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, default_item_factory, cx) + Workspace::new( + Default::default(), + 0, + project, + |_, _| unimplemented!(), + || unimplemented!(), + cx, + ) }); let item = cx.add_view(&workspace, |cx| { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9982b4114aabaeb13c1c41d066baf7a8a2f75919..2e2026309fca0da28ca878e9e8592589b945bd79 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -18,7 +18,7 @@ use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, }; -use gpui::{App, AssetSource, AsyncAppContext, MutableAppContext, Task, ViewContext}; +use gpui::{Action, App, AssetSource, AsyncAppContext, MutableAppContext, Task, ViewContext}; use isahc::{config::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; @@ -45,9 +45,10 @@ use theme::ThemeRegistry; use util::StaffMode; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, + self, dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, + OpenPaths, Workspace, }; -use zed::{self, build_window_options, initialize_workspace, languages, menus}; +use zed::{self, build_window_options, initialize_workspace, languages, menus, OpenSettings}; fn main() { let http = http::client(); @@ -186,6 +187,7 @@ fn main() { build_window_options, initialize_workspace, dock_default_item_factory, + background_actions, }); auto_update::init(http, client::ZED_SERVER_URL.clone(), cx); @@ -703,3 +705,13 @@ pub fn dock_default_item_factory( Some(Box::new(terminal_view)) } + +pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { + &[ + ("Go to file", &file_finder::Toggle), + ("Open the command palette", &command_palette::Toggle), + ("Focus the dock", &FocusDock), + ("Open recent projects", &recent_projects::OpenRecent), + ("Change your settings", &OpenSettings), + ] +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3c093836f201146818b96ce1abaac6087a22fb14..ae9fcc9b31de91ebce468fb6c5809c5ed47cfd5f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -889,9 +889,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1010,9 +1008,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); // Open a file within an existing worktree. cx.update(|cx| { @@ -1171,9 +1167,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); // Open a file within an existing worktree. cx.update(|cx| { @@ -1215,9 +1209,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(rust_lang())); - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap()); // Create a new untitled buffer @@ -1306,9 +1298,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), [], cx).await; project.update(cx, |project, _| project.languages().add(rust_lang())); - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); // Create a new untitled buffer cx.dispatch_action(window_id, NewFile); @@ -1361,9 +1351,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) - }); + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1437,15 +1425,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1709,15 +1689,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); diff --git a/styles/src/styleTree/contextMenu.ts b/styles/src/styleTree/contextMenu.ts index 30f44a63141a70edd01998057385dfcd7f15adfc..b4a21deba4a7d21c0372dc34ff204922cdcf5b1b 100644 --- a/styles/src/styleTree/contextMenu.ts +++ b/styles/src/styleTree/contextMenu.ts @@ -26,14 +26,19 @@ export default function contextMenu(colorScheme: ColorScheme) { hover: { background: background(layer, "hovered"), label: text(layer, "sans", "hovered", { size: "sm" }), + keystroke: { + ...text(layer, "sans", "hovered", { + size: "sm", + weight: "bold", + }), + padding: { left: 3, right: 3 }, + }, }, active: { background: background(layer, "active"), - label: text(layer, "sans", "active", { size: "sm" }), }, activeHover: { background: background(layer, "active"), - label: text(layer, "sans", "active", { size: "sm" }), }, }, separator: { diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 137c6df11177f8fed9a156acc8b6a5a49d62017d..7c437f110ef49e67acea1dfe72fe102f26205a61 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -86,20 +86,24 @@ export default function welcome(colorScheme: ColorScheme) { border: border(layer, "active"), }, }, + checkboxContainer: { + margin: { + top: 4, + }, + }, checkbox: { label: { ...text(layer, "sans", interactive_text_size), // Also supports margin, container, border, etc. }, - container: { - margin: { - top: 4, - }, + icon: { + color: foreground(layer, "on"), + icon: "icons/check_12.svg", + dimensions: { + width: 12, + height: 12, + } }, - width: 12, - height: 12, - checkIcon: "icons/check_12.svg", - checkIconColor: foreground(layer, "on"), default: { ...checkboxBase, background: background(layer, "default"), diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index c758e0227e2413ed86a15a10c28a22499d59131d..55b5c613410d1141b6a8e72c64aff2044c94cb51 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -41,6 +41,34 @@ export default function workspace(colorScheme: ColorScheme) { return { background: background(layer), + blankPane: { + logo: { + color: background(layer, "on"), + icon: "icons/logo_96.svg", + dimensions: { + width: 240, + height: 240, + } + }, + keyboardHints: { + margin: { + top: 32 + }, + padding: { + bottom: -8. + } + }, + keyboardHint: { + ...text(colorScheme.lowest, "sans", "variant", { size: "sm" }), + margin: { + bottom: 8 + }, + hover: { + ...text(colorScheme.lowest, "sans", "hovered", { size: "sm" }), + } + }, + keyboardHintWidth: 240, + }, joiningProjectAvatar: { cornerRadius: 40, width: 80, From cf6ea6d698e7bc3047e6a667f6e71c0889eb855c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 18:11:11 -0800 Subject: [PATCH 32/45] Fix bug with action keybindings not being resolved --- crates/editor/src/editor_tests.rs | 4 ++-- crates/theme/src/ui.rs | 22 ++++++++++++++++++++-- crates/workspace/src/dock.rs | 9 ++++++++- crates/workspace/src/pane.rs | 9 +++++++-- crates/workspace/src/workspace.rs | 6 ++++-- styles/src/styleTree/workspace.ts | 6 +++--- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 21cc9f889580f10d3ece2e2dfee7c18c203c37c4..0176a1e01bd999b2786a9e84aa79d39340dce45b 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -485,7 +485,7 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) { cx.set_global(DragAndDrop::::default()); use workspace::item::Item; let (_, pane) = cx.add_window(Default::default(), |cx| { - Pane::new(None, || unimplemented!(), cx) + Pane::new(0, None, || unimplemented!(), cx) }); let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); @@ -5564,7 +5564,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { Settings::test_async(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - let (_, pane) = cx.add_window(|cx| Pane::new(None, || unimplemented!(), cx)); + let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || unimplemented!(), cx)); let leader = pane.update(cx, |_, cx| { let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index ca71db37231531f1d76312807f55551a7b181c9b..5396265ad8fc0f5db41616a5acfd973e21c3ff70 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -97,6 +97,24 @@ pub fn keystroke_label( ) -> Container { // FIXME: Put the theme in it's own global so we can // query the keystroke style on our own + keystroke_label_for( + cx.window_id(), + cx.handle().id(), + label_text, + label_style, + keystroke_style, + action, + ) +} + +pub fn keystroke_label_for( + window_id: usize, + view_id: usize, + label_text: &'static str, + label_style: &ContainedText, + keystroke_style: &ContainedText, + action: Box, +) -> Container { Flex::row() .with_child( Label::new(label_text, label_style.text.clone()) @@ -105,8 +123,8 @@ pub fn keystroke_label( ) .with_child({ KeystrokeLabel::new( - cx.window_id(), - cx.handle().id(), + window_id, + view_id, action, keystroke_style.container, keystroke_style.text.clone(), diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index f86a9db71acf1b9b09cb66fd8b30d4cde9708a08..efba568b90458882d5d0d11e0250565dd2c1abca 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -187,7 +187,14 @@ impl Dock { ) -> Self { let position = DockPosition::Hidden(cx.global::().default_dock_anchor); - let pane = cx.add_view(|cx| Pane::new(Some(position.anchor()), background_actions, cx)); + let pane = cx.add_view(|cx| { + Pane::new( + cx.handle().id(), + Some(position.anchor()), + background_actions, + cx, + ) + }); pane.update(cx, |pane, cx| { pane.set_active(false, cx); }); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 7e0e6bbe01d22bc891b22a93a92154e3a843608c..abd410d875969a7d12611bae8d8e760ed9ade00e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -218,6 +218,7 @@ pub struct Pane { tab_bar_context_menu: ViewHandle, docked: Option, background_actions: BackgroundActions, + workspace_id: usize, } pub struct ItemNavHistory { @@ -275,6 +276,7 @@ enum ItemType { impl Pane { pub fn new( + workspace_id: usize, docked: Option, background_actions: BackgroundActions, cx: &mut ViewContext, @@ -300,6 +302,7 @@ impl Pane { tab_bar_context_menu: context_menu, docked, background_actions, + workspace_id, } } @@ -1442,6 +1445,7 @@ impl Pane { .with_children({ enum KeyboardHint {} let keyboard_hint = &theme.keyboard_hint; + let workspace_id = self.workspace_id; (self.background_actions)().into_iter().enumerate().map( move |(idx, (text, action))| { let hint_action = action.boxed_clone(); @@ -1449,14 +1453,15 @@ impl Pane { idx, cx, move |state, cx| { - theme::ui::keystroke_label( + theme::ui::keystroke_label_for( + cx.window_id(), + workspace_id, text, &keyboard_hint.style_for(state, false), &keystroke_style .style_for(state, false) .keystroke, hint_action, - cx, ) .boxed() }, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 65335d8671069d5e93a7852acbc5bed87852763d..40053b103a4341952c2a263f04b99e6be958616a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -606,7 +606,9 @@ impl Workspace { }) .detach(); - let center_pane = cx.add_view(|cx| Pane::new(None, background_actions, cx)); + let workspace_view_id = cx.handle().id(); + let center_pane = + cx.add_view(|cx| Pane::new(workspace_view_id, None, background_actions, cx)); let pane_id = center_pane.id(); cx.subscribe(¢er_pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) @@ -1438,7 +1440,7 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(|cx| Pane::new(None, self.background_actions, cx)); + let pane = cx.add_view(|cx| Pane::new(cx.handle().id(), None, self.background_actions, cx)); let pane_id = pane.id(); cx.subscribe(&pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 55b5c613410d1141b6a8e72c64aff2044c94cb51..cb99572853e1859d61dc4fde37b1e96d2faf0def 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -46,8 +46,8 @@ export default function workspace(colorScheme: ColorScheme) { color: background(layer, "on"), icon: "icons/logo_96.svg", dimensions: { - width: 240, - height: 240, + width: 256, + height: 256, } }, keyboardHints: { @@ -67,7 +67,7 @@ export default function workspace(colorScheme: ColorScheme) { ...text(colorScheme.lowest, "sans", "hovered", { size: "sm" }), } }, - keyboardHintWidth: 240, + keyboardHintWidth: 256, }, joiningProjectAvatar: { cornerRadius: 40, From a65dd0fd98b26d3eaa349c25e6e1d35a418ee720 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 18:15:29 -0800 Subject: [PATCH 33/45] Restore correct checkbox text --- crates/welcome/src/welcome.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 89f161a283fb46435b20a71cae60d69983625cc4..a1045ea1e8d9d20fe5ddd1ab35f4354f87cba509 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -125,7 +125,7 @@ impl View for WelcomePage { Flex::column() .with_children([ theme::ui::checkbox::( - "Do you want to send telemetry?", + "Send anonymous usage data to improve Zed (View what we're sending via Help > View Telemetry Log)", &theme.welcome.checkbox, metrics, cx, From 943ea61452a6550bec95a23db5a006bff8b38ea2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 18:47:52 -0800 Subject: [PATCH 34/45] Add a note on how to check the telemetry --- crates/theme/src/theme.rs | 1 + crates/theme/src/ui.rs | 22 +++++++++++++++++----- crates/welcome/src/welcome.rs | 21 +++++++++++++++++++-- crates/zed/src/menus.rs | 2 +- styles/src/styleTree/welcome.ts | 9 +++++++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 524b7656268a1b143b0857075e2e43eaea17a9bc..d331ca80a9fad31457fe648aaf7ae1b0e838d455 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -869,6 +869,7 @@ pub struct WelcomeStyle { pub page_width: f32, pub logo: IconStyle, pub logo_subheading: ContainedText, + pub usage_note: ContainedText, pub checkbox: CheckboxStyle, pub checkbox_container: ContainerStyle, pub button: Interactive, diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index 5396265ad8fc0f5db41616a5acfd973e21c3ff70..5441e711685970d3c33c9db94f9ac03815626531 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -4,7 +4,7 @@ use gpui::{ ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, MouseEventHandler, ParentElement, Svg, }, - Action, Element, EventContext, RenderContext, View, + Action, Element, ElementBox, EventContext, RenderContext, View, }; use serde::Deserialize; @@ -26,6 +26,21 @@ pub fn checkbox( checked: bool, cx: &mut RenderContext, change: fn(checked: bool, cx: &mut EventContext) -> (), +) -> MouseEventHandler { + let label = Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) + .boxed(); + + checkbox_with_label(label, style, checked, cx, change) +} + +pub fn checkbox_with_label( + label: ElementBox, + style: &CheckboxStyle, + checked: bool, + cx: &mut RenderContext, + change: fn(checked: bool, cx: &mut EventContext) -> (), ) -> MouseEventHandler { MouseEventHandler::::new(0, cx, |state, _| { let indicator = if checked { @@ -55,10 +70,7 @@ pub fn checkbox( } }) .boxed(), - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .boxed(), + label, ]) .align_children_center() .boxed() diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index a1045ea1e8d9d20fe5ddd1ab35f4354f87cba509..3a35920b88922bb2b7bf13e5bd849acd77e4be3a 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -124,8 +124,25 @@ impl View for WelcomePage { .boxed(), Flex::column() .with_children([ - theme::ui::checkbox::( - "Send anonymous usage data to improve Zed (View what we're sending via Help > View Telemetry Log)", + theme::ui::checkbox_with_label::( + Flex::column() + .with_children([ + Label::new( + "Send anonymous usage data", + theme.welcome.checkbox.label.text.clone(), + ) + .contained() + .with_style(theme.welcome.checkbox.label.container) + .boxed(), + Label::new( + "Help > View Telemetry", + theme.welcome.usage_note.text.clone(), + ) + .contained() + .with_style(theme.welcome.usage_note.container) + .boxed(), + ]) + .boxed(), &theme.welcome.checkbox, metrics, cx, diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 9381a909075b72364789e765600fd25885827f02..d5fb94b2b698aa8260fb9eab7bd5dee8c6af7b4c 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -137,7 +137,7 @@ pub fn menus() -> Vec> { items: vec![ MenuItem::action("Command Palette", command_palette::Toggle), MenuItem::separator(), - MenuItem::action("View Telemetry Log", crate::OpenTelemetryLog), + MenuItem::action("View Telemetry", crate::OpenTelemetryLog), MenuItem::action("View Dependency Licenses", crate::OpenLicenses), MenuItem::separator(), MenuItem::action( diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 7c437f110ef49e67acea1dfe72fe102f26205a61..4ad3e51cd84545ef796b89d608996ad1aee0d872 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -86,10 +86,19 @@ export default function welcome(colorScheme: ColorScheme) { border: border(layer, "active"), }, }, + usageNote: { + ...text(layer, "sans", "variant", { size: "2xs" }), + margin: { + top: -4 + } + }, checkboxContainer: { margin: { top: 4, }, + padding: { + bottom: 8, + } }, checkbox: { label: { From 709c101834b5429c9ea580415e5e4e736e88384f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 18:50:17 -0800 Subject: [PATCH 35/45] Adjust styles on usage note --- styles/src/styleTree/welcome.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 4ad3e51cd84545ef796b89d608996ad1aee0d872..e1bd5c82bbac65595ed3e0cd60b1f5957158889c 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -88,8 +88,9 @@ export default function welcome(colorScheme: ColorScheme) { }, usageNote: { ...text(layer, "sans", "variant", { size: "2xs" }), - margin: { - top: -4 + padding: { + top: -4, + } }, checkboxContainer: { From 325827699e0942e05e250ff38a24c6ef4c71fbb2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 19:02:13 -0800 Subject: [PATCH 36/45] Adjust styling for blank page experience --- styles/src/styleTree/workspace.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index cb99572853e1859d61dc4fde37b1e96d2faf0def..d798a22e9d65b18140e3664b9cb7bb2858ee9569 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -40,34 +40,40 @@ export default function workspace(colorScheme: ColorScheme) { const followerAvatarOuterWidth = followerAvatarWidth + 4 return { - background: background(layer), + background: background(colorScheme.lowest), blankPane: { logo: { - color: background(layer, "on"), + color: border(layer, "active").color, icon: "icons/logo_96.svg", dimensions: { - width: 256, - height: 256, + width: 272, + height: 272, } }, keyboardHints: { margin: { - top: 32 + top: 32, + // bottom: -8. }, padding: { - bottom: -8. - } + top: 8, + left: 8, + right: 8, + }, + background: background(colorScheme.lowest), + border: border(layer, "active"), + cornerRadius: 4, }, keyboardHint: { - ...text(colorScheme.lowest, "sans", "variant", { size: "sm" }), + ...text(layer, "sans", "variant", { size: "sm" }), margin: { bottom: 8 }, hover: { - ...text(colorScheme.lowest, "sans", "hovered", { size: "sm" }), + ...text(layer, "sans", "hovered", { size: "sm" }), } }, - keyboardHintWidth: 256, + keyboardHintWidth: 272, }, joiningProjectAvatar: { cornerRadius: 40, From f626920af1a7f9337900618723c7c44129e30097 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 8 Mar 2023 19:03:44 -0800 Subject: [PATCH 37/45] Remove permanent Zed stateless --- crates/db/src/db.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 989dcf0af561999fb31e108f712b92eb9ff499d3..ae9325ea2ea381d89b7e0cfddb48bc0cb22a0e16 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -42,7 +42,7 @@ const DB_FILE_NAME: &'static str = "db.sqlite"; lazy_static::lazy_static! { // !!!!!!! CHANGE BACK TO DEFAULT FALSE BEFORE SHIPPING - static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(true, |v| !v.is_empty()); + static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()); static ref DB_FILE_OPERATIONS: Mutex<()> = Mutex::new(()); pub static ref BACKUP_DB_PATH: RwLock> = RwLock::new(None); pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false); @@ -66,11 +66,11 @@ pub async fn open_db( let connection = async_iife!({ // Note: This still has a race condition where 1 set of migrations succeeds // (e.g. (Workspace, Editor)) and another fails (e.g. (Workspace, Terminal)) - // This will cause the first connection to have the database taken out + // This will cause the first connection to have the database taken out // from under it. This *should* be fine though. The second dabatase failure will // cause errors in the log and so should be observed by developers while writing // soon-to-be good migrations. If user databases are corrupted, we toss them out - // and try again from a blank. As long as running all migrations from start to end + // and try again from a blank. As long as running all migrations from start to end // on a blank database is ok, this race condition will never be triggered. // // Basically: Don't ever push invalid migrations to stable or everyone will have @@ -88,7 +88,7 @@ pub async fn open_db( }; } - // Take a lock in the failure case so that we move the db once per process instead + // Take a lock in the failure case so that we move the db once per process instead // of potentially multiple times from different threads. This shouldn't happen in the // normal path let _lock = DB_FILE_OPERATIONS.lock(); From 9187863d0ef9ecf69e7d071b180700bd8dbcd3e8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 9 Mar 2023 00:45:05 -0800 Subject: [PATCH 38/45] re-add spaces removed by new setting --- crates/collab/src/tests.rs | 4 +-- crates/editor/src/editor_tests.rs | 14 ++++----- crates/workspace/src/dock.rs | 4 +-- crates/workspace/src/workspace.rs | 52 +++++++------------------------ 4 files changed, 22 insertions(+), 52 deletions(-) diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 8949b60993a041616d3d9381eadb65b2298b2488..80df0ed6dfb59136ad6183b0a0f42e34a5b63264 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -197,8 +197,8 @@ impl TestServer { fs: fs.clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), - dock_default_item_factory: |_, _| unimplemented!(), - background_actions: || unimplemented!(), + dock_default_item_factory: |_, _| None, + background_actions: || &[], }); Project::init(&client); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 0176a1e01bd999b2786a9e84aa79d39340dce45b..d73c3c87db324aac0a47158c55bf10f705e48695 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -484,9 +484,7 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) { cx.set_global(Settings::test(cx)); cx.set_global(DragAndDrop::::default()); use workspace::item::Item; - let (_, pane) = cx.add_window(Default::default(), |cx| { - Pane::new(0, None, || unimplemented!(), cx) - }); + let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(0, None, || &[], cx)); let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); cx.add_view(&pane, |cx| { @@ -2356,10 +2354,10 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) { e.handle_input(") ", cx); }); cx.assert_editor_state(indoc! {" - ( one✅ - three - five ) ˇtwo one✅ four three six five ( one✅ - three + ( one✅ + three + five ) ˇtwo one✅ four three six five ( one✅ + three five ) ˇ"}); // Cut with three selections, one of which is full-line. @@ -5564,7 +5562,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { Settings::test_async(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || unimplemented!(), cx)); + let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || &[], cx)); let leader = pane.update(cx, |_, cx| { let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index efba568b90458882d5d0d11e0250565dd2c1abca..fd7638c5fbb492296a28e051177a8fd9699f3014 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -500,7 +500,7 @@ mod tests { 0, project.clone(), default_item_factory, - || unimplemented!(), + || &[], cx, ) }); @@ -634,7 +634,7 @@ mod tests { 0, project, default_item_factory, - || unimplemented!(), + || &[], cx, ) }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 40053b103a4341952c2a263f04b99e6be958616a..ef14bfa2fe090ed90b66f309789d9af632712c63 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -455,8 +455,8 @@ impl AppState { user_store, initialize_workspace: |_, _, _| {}, build_window_options: |_, _, _| Default::default(), - dock_default_item_factory: |_, _| unimplemented!(), - background_actions: || unimplemented!(), + dock_default_item_factory: |_, _| None, + background_actions: || &[], }) } } @@ -3017,8 +3017,8 @@ mod tests { Default::default(), 0, project.clone(), - |_, _| unimplemented!(), - || unimplemented!(), + |_, _| None, + || &[], cx, ) }); @@ -3090,8 +3090,8 @@ mod tests { Default::default(), 0, project.clone(), - |_, _| unimplemented!(), - || unimplemented!(), + |_, _| None, + || &[], cx, ) }); @@ -3191,8 +3191,8 @@ mod tests { Default::default(), 0, project.clone(), - |_, _| unimplemented!(), - || unimplemented!(), + |_, _| None, + || &[], cx, ) }); @@ -3231,14 +3231,7 @@ mod tests { let project = Project::test(fs, None, cx).await; let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project, - |_, _| unimplemented!(), - || unimplemented!(), - cx, - ) + Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx) }); let item1 = cx.add_view(&workspace, |cx| { @@ -3347,14 +3340,7 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project, - |_, _| unimplemented!(), - || unimplemented!(), - cx, - ) + Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx) }); // Create several workspace items with single project entries, and two @@ -3463,14 +3449,7 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project, - |_, _| unimplemented!(), - || unimplemented!(), - cx, - ) + Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx) }); let item = cx.add_view(&workspace, |cx| { @@ -3589,14 +3568,7 @@ mod tests { let project = Project::test(fs, [], cx).await; let (_, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project, - |_, _| unimplemented!(), - || unimplemented!(), - cx, - ) + Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx) }); let item = cx.add_view(&workspace, |cx| { From 4eb75f058f44b3d0cc8fd569cacd761d259ef82e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 9 Mar 2023 12:00:58 -0800 Subject: [PATCH 39/45] Fix bug with wrong view ids being passed --- crates/workspace/src/dock.rs | 3 ++- crates/workspace/src/workspace.rs | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index fd7638c5fbb492296a28e051177a8fd9699f3014..f5ee8cad51988f6662fd2106a60c89f2798ede88 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -181,6 +181,7 @@ pub struct Dock { impl Dock { pub fn new( + workspace_id: usize, default_item_factory: DockDefaultItemFactory, background_actions: BackgroundActions, cx: &mut ViewContext, @@ -189,7 +190,7 @@ impl Dock { let pane = cx.add_view(|cx| { Pane::new( - cx.handle().id(), + workspace_id, Some(position.anchor()), background_actions, cx, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ef14bfa2fe090ed90b66f309789d9af632712c63..b4b164ec3f6a3108c83c2c5e41b22eb6ed770ba3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -606,9 +606,10 @@ impl Workspace { }) .detach(); - let workspace_view_id = cx.handle().id(); + let weak_handle = cx.weak_handle(); + let center_pane = - cx.add_view(|cx| Pane::new(workspace_view_id, None, background_actions, cx)); + cx.add_view(|cx| Pane::new(weak_handle.id(), None, background_actions, cx)); let pane_id = center_pane.id(); cx.subscribe(¢er_pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) @@ -616,7 +617,12 @@ impl Workspace { .detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); - let dock = Dock::new(dock_default_factory, background_actions, cx); + let dock = Dock::new( + weak_handle.id(), + dock_default_factory, + background_actions, + cx, + ); let dock_pane = dock.pane().clone(); let fs = project.read(cx).fs().clone(); @@ -639,7 +645,6 @@ impl Workspace { } }); let handle = cx.handle(); - let weak_handle = cx.weak_handle(); // All leader updates are enqueued and then processed in a single task, so // that each asynchronous operation can be run in order. @@ -1440,7 +1445,14 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(|cx| Pane::new(cx.handle().id(), None, self.background_actions, cx)); + let pane = cx.add_view(|cx| { + Pane::new( + dbg!(self.weak_handle().id()), + None, + self.background_actions, + cx, + ) + }); let pane_id = pane.id(); cx.subscribe(&pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) From 718052bb726e9d4deb6d409e6758ddfd4c0f82e9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 9 Mar 2023 13:02:25 -0800 Subject: [PATCH 40/45] Undo accidental indent change of hoverpopover.ts --- styles/src/styleTree/hoverPopover.ts | 78 ++++++++++++++-------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/styles/src/styleTree/hoverPopover.ts b/styles/src/styleTree/hoverPopover.ts index a2a4467d7c5a9a08bc5cd589d4e9d129c7d8aa07..032c53112b27f9f6f47f378a23ef59a9ea40ac53 100644 --- a/styles/src/styleTree/hoverPopover.ts +++ b/styles/src/styleTree/hoverPopover.ts @@ -2,44 +2,44 @@ import { ColorScheme } from "../themes/common/colorScheme" import { background, border, text } from "./components" export default function HoverPopover(colorScheme: ColorScheme) { - let layer = colorScheme.middle - let baseContainer = { - background: background(layer), - cornerRadius: 8, - padding: { - left: 8, - right: 8, - top: 4, - bottom: 4, - }, - shadow: colorScheme.popoverShadow, - border: border(layer), - margin: { - left: -8, - }, - } + let layer = colorScheme.middle + let baseContainer = { + background: background(layer), + cornerRadius: 8, + padding: { + left: 8, + right: 8, + top: 4, + bottom: 4, + }, + shadow: colorScheme.popoverShadow, + border: border(layer), + margin: { + left: -8, + }, + } - return { - container: baseContainer, - infoContainer: { - ...baseContainer, - background: background(layer, "accent"), - border: border(layer, "accent"), - }, - warningContainer: { - ...baseContainer, - background: background(layer, "warning"), - border: border(layer, "warning"), - }, - errorContainer: { - ...baseContainer, - background: background(layer, "negative"), - border: border(layer, "negative"), - }, - block_style: { - padding: { top: 4 }, - }, - prose: text(layer, "sans", { size: "sm" }), - highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better - } + return { + container: baseContainer, + infoContainer: { + ...baseContainer, + background: background(layer, "accent"), + border: border(layer, "accent"), + }, + warningContainer: { + ...baseContainer, + background: background(layer, "warning"), + border: border(layer, "warning"), + }, + errorContainer: { + ...baseContainer, + background: background(layer, "negative"), + border: border(layer, "negative"), + }, + block_style: { + padding: { top: 4 }, + }, + prose: text(layer, "sans", { size: "sm" }), + highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better + } } From daed75096e98a4abdb95828e684d3e2089e050d3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 9 Mar 2023 13:13:23 -0800 Subject: [PATCH 41/45] Fix editor test to clearly show trailing whitespace Adjsut default dock size to be a multiple of 16 --- crates/editor/src/editor_tests.rs | 16 ++++++++++------ styles/src/styleTree/workspace.ts | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index d73c3c87db324aac0a47158c55bf10f705e48695..4f61bde39877bb0fb7ba40c0000077cf75c9fe82 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -2353,12 +2353,16 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) { e.paste(&Paste, cx); e.handle_input(") ", cx); }); - cx.assert_editor_state(indoc! {" - ( one✅ - three - five ) ˇtwo one✅ four three six five ( one✅ - three - five ) ˇ"}); + cx.assert_editor_state( + &([ + "( one✅ ", + "three ", + "five ) ˇtwo one✅ four three six five ( one✅ ", + "three ", + "five ) ˇ", + ] + .join("\n")), + ); // Cut with three selections, one of which is full-line. cx.set_state(indoc! {" diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index d798a22e9d65b18140e3664b9cb7bb2858ee9569..1789b523e7fdbb93ecd9a3cc14604079a7dc57e5 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -282,7 +282,7 @@ export default function workspace(colorScheme: ColorScheme) { }, dock: { initialSizeRight: 640, - initialSizeBottom: 300, + initialSizeBottom: 304, wash_color: withOpacity(background(colorScheme.highest), 0.5), panel: { border: border(colorScheme.middle), From 20064b5629d347621447c343fb53abc074663926 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 9 Mar 2023 15:38:28 -0800 Subject: [PATCH 42/45] Add welcome to menu remove debug --- crates/workspace/src/workspace.rs | 10 ++-------- crates/zed/src/menus.rs | 1 + 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b4b164ec3f6a3108c83c2c5e41b22eb6ed770ba3..609afded3325b97b8356c85b9b6955622a27e289 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1445,14 +1445,8 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(|cx| { - Pane::new( - dbg!(self.weak_handle().id()), - None, - self.background_actions, - cx, - ) - }); + let pane = + cx.add_view(|cx| Pane::new(self.weak_handle().id(), None, self.background_actions, cx)); let pane_id = pane.id(); cx.subscribe(&pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index d5fb94b2b698aa8260fb9eab7bd5dee8c6af7b4c..82422c5c19a848cf7533417dfe4e1f790353954c 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -139,6 +139,7 @@ pub fn menus() -> Vec> { MenuItem::separator(), MenuItem::action("View Telemetry", crate::OpenTelemetryLog), MenuItem::action("View Dependency Licenses", crate::OpenLicenses), + MenuItem::action("Show Welcome", workspace::Welcome), MenuItem::separator(), MenuItem::action( "Copy System Specs Into Clipboard", From 8ee25be7b93bab8a2a584eee89401bf6e341cf4f Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 9 Mar 2023 20:18:29 -0500 Subject: [PATCH 43/45] Update empty pane styling Co-Authored-By: Mikayla Maki --- assets/icons/logo_256.svg | 41 ++++++++++++++++++++++++++++ assets/icons/logo_shadow_256.svg | 23 ++++++++++++++++ crates/theme/src/theme.rs | 2 ++ crates/workspace/src/pane.rs | 17 +++++++----- crates/zed/src/main.rs | 2 +- styles/src/styleTree/workspace.ts | 44 ++++++++++++++++++------------- 6 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 assets/icons/logo_256.svg create mode 100644 assets/icons/logo_shadow_256.svg diff --git a/assets/icons/logo_256.svg b/assets/icons/logo_256.svg new file mode 100644 index 0000000000000000000000000000000000000000..4629600b4104cdbd18e545b967fa416d007c6675 --- /dev/null +++ b/assets/icons/logo_256.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/logo_shadow_256.svg b/assets/icons/logo_shadow_256.svg new file mode 100644 index 0000000000000000000000000000000000000000..6c331f43ab3f4f2c57b4569c212f5e423d8726d1 --- /dev/null +++ b/assets/icons/logo_shadow_256.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index d331ca80a9fad31457fe648aaf7ae1b0e838d455..8ee58a8af2885907bc6e2c880bcef11344fa559c 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -76,6 +76,8 @@ pub struct Workspace { #[derive(Clone, Deserialize, Default)] pub struct BlankPaneStyle { pub logo: IconStyle, + pub logo_shadow: IconStyle, + pub logo_container: ContainerStyle, pub keyboard_hints: ContainerStyle, pub keyboard_hint: Interactive, pub keyboard_hint_width: f32, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index abd410d875969a7d12611bae8d8e760ed9ade00e..6d20700bcc81aff330c6c61303f49a528009e5da 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1429,7 +1429,6 @@ impl Pane { fn render_blank_pane(&mut self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { let background = theme.workspace.background; - let keystroke_style = &theme.context_menu.item; let theme = &theme.workspace.blank_pane; Stack::new() .with_children([ @@ -1440,7 +1439,14 @@ impl Pane { Flex::column() .align_children_center() .with_children([ - theme::ui::icon(&theme.logo).aligned().boxed(), + Stack::new() + .with_children([ + theme::ui::icon(&theme.logo_shadow).aligned().boxed(), + theme::ui::icon(&theme.logo).aligned().boxed(), + ]) + .contained() + .with_style(theme.logo_container) + .boxed(), Flex::column() .with_children({ enum KeyboardHint {} @@ -1453,14 +1459,13 @@ impl Pane { idx, cx, move |state, cx| { + let style = keyboard_hint.style_for(state, false); theme::ui::keystroke_label_for( cx.window_id(), workspace_id, text, - &keyboard_hint.style_for(state, false), - &keystroke_style - .style_for(state, false) - .keystroke, + &style, + &style, hint_action, ) .boxed() diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 2e2026309fca0da28ca878e9e8592589b945bd79..cbcd1aa1c602bc6aeecee68f7cb5946e1fff2f86 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -709,7 +709,7 @@ pub fn dock_default_item_factory( pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { &[ ("Go to file", &file_finder::Toggle), - ("Open the command palette", &command_palette::Toggle), + ("Open command palette", &command_palette::Toggle), ("Focus the dock", &FocusDock), ("Open recent projects", &recent_projects::OpenRecent), ("Change your settings", &OpenSettings), diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 1789b523e7fdbb93ecd9a3cc14604079a7dc57e5..984b6b5ed354bf4b9300581d4594b25614ea788b 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -42,38 +42,46 @@ export default function workspace(colorScheme: ColorScheme) { return { background: background(colorScheme.lowest), blankPane: { + logoContainer: { + width: 256, + height: 256, + }, logo: { - color: border(layer, "active").color, - icon: "icons/logo_96.svg", + color: withOpacity("#000000", colorScheme.isLight ? 0.6 : 0.8), + icon: "icons/logo_256.svg", dimensions: { - width: 272, - height: 272, - } + width: 256, + height: 256, + }, + }, + logoShadow: { + color: withOpacity(colorScheme.isLight ? "#FFFFFF" : colorScheme.lowest.base.default.background, colorScheme.isLight ? 1 : 0.6), + icon: "icons/logo_shadow_256.svg", + dimensions: { + width: 256, + height: 256, + }, }, keyboardHints: { margin: { - top: 32, - // bottom: -8. - }, - padding: { - top: 8, - left: 8, - right: 8, + top: 96, }, - background: background(colorScheme.lowest), - border: border(layer, "active"), cornerRadius: 4, }, keyboardHint: { ...text(layer, "sans", "variant", { size: "sm" }), - margin: { - bottom: 8 + padding: { + top: 3, + left: 8, + right: 8, + bottom: 3 }, + cornerRadius: 8, hover: { - ...text(layer, "sans", "hovered", { size: "sm" }), + ...text(layer, "sans", "active", { size: "sm" }), } }, - keyboardHintWidth: 272, + keyboardHintWidth: 320, }, joiningProjectAvatar: { cornerRadius: 40, From 648f0e5b7b6ec4185af1e2fe0439f727b0147305 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 9 Mar 2023 19:18:17 -0800 Subject: [PATCH 44/45] Remove new logo from style tree --- styles/src/styleTree/workspace.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 984b6b5ed354bf4b9300581d4594b25614ea788b..bebe87ce55c9ee15b7a13a49e6cffe9dba54cff6 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -48,7 +48,7 @@ export default function workspace(colorScheme: ColorScheme) { }, logo: { color: withOpacity("#000000", colorScheme.isLight ? 0.6 : 0.8), - icon: "icons/logo_256.svg", + icon: "icons/logo_96.svg", dimensions: { width: 256, height: 256, @@ -56,7 +56,7 @@ export default function workspace(colorScheme: ColorScheme) { }, logoShadow: { color: withOpacity(colorScheme.isLight ? "#FFFFFF" : colorScheme.lowest.base.default.background, colorScheme.isLight ? 1 : 0.6), - icon: "icons/logo_shadow_256.svg", + icon: "icons/logo_96.svg", dimensions: { width: 256, height: 256, From 281ff92236cfa2fc73e793f9ae4666700ec5c5d7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 10 Mar 2023 09:54:20 -0800 Subject: [PATCH 45/45] Stub out blank pane experience --- assets/icons/logo_256.svg | 41 ------------------ assets/icons/logo_shadow_256.svg | 23 ---------- crates/workspace/src/pane.rs | 73 ++++---------------------------- 3 files changed, 8 insertions(+), 129 deletions(-) delete mode 100644 assets/icons/logo_256.svg delete mode 100644 assets/icons/logo_shadow_256.svg diff --git a/assets/icons/logo_256.svg b/assets/icons/logo_256.svg deleted file mode 100644 index 4629600b4104cdbd18e545b967fa416d007c6675..0000000000000000000000000000000000000000 --- a/assets/icons/logo_256.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/icons/logo_shadow_256.svg b/assets/icons/logo_shadow_256.svg deleted file mode 100644 index 6c331f43ab3f4f2c57b4569c212f5e423d8726d1..0000000000000000000000000000000000000000 --- a/assets/icons/logo_shadow_256.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6d20700bcc81aff330c6c61303f49a528009e5da..587f180a4aaa73722869f235faa713e98f593275 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -217,8 +217,8 @@ pub struct Pane { toolbar: ViewHandle, tab_bar_context_menu: ViewHandle, docked: Option, - background_actions: BackgroundActions, - workspace_id: usize, + _background_actions: BackgroundActions, + _workspace_id: usize, } pub struct ItemNavHistory { @@ -301,8 +301,8 @@ impl Pane { toolbar: cx.add_view(|_| Toolbar::new(handle)), tab_bar_context_menu: context_menu, docked, - background_actions, - workspace_id, + _background_actions: background_actions, + _workspace_id: workspace_id, } } @@ -1427,68 +1427,11 @@ impl Pane { .boxed() } - fn render_blank_pane(&mut self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { + fn render_blank_pane(&mut self, theme: &Theme, _cx: &mut RenderContext) -> ElementBox { let background = theme.workspace.background; - let theme = &theme.workspace.blank_pane; - Stack::new() - .with_children([ - Empty::new() - .contained() - .with_background_color(background) - .boxed(), - Flex::column() - .align_children_center() - .with_children([ - Stack::new() - .with_children([ - theme::ui::icon(&theme.logo_shadow).aligned().boxed(), - theme::ui::icon(&theme.logo).aligned().boxed(), - ]) - .contained() - .with_style(theme.logo_container) - .boxed(), - Flex::column() - .with_children({ - enum KeyboardHint {} - let keyboard_hint = &theme.keyboard_hint; - let workspace_id = self.workspace_id; - (self.background_actions)().into_iter().enumerate().map( - move |(idx, (text, action))| { - let hint_action = action.boxed_clone(); - MouseEventHandler::::new( - idx, - cx, - move |state, cx| { - let style = keyboard_hint.style_for(state, false); - theme::ui::keystroke_label_for( - cx.window_id(), - workspace_id, - text, - &style, - &style, - hint_action, - ) - .boxed() - }, - ) - .on_click(MouseButton::Left, move |_, cx| { - cx.dispatch_any_action(action.boxed_clone()) - }) - .with_cursor_style(CursorStyle::PointingHand) - .boxed() - }, - ) - }) - .contained() - .with_style(theme.keyboard_hints) - .constrained() - .with_max_width(theme.keyboard_hint_width) - .aligned() - .boxed(), - ]) - .aligned() - .boxed(), - ]) + Empty::new() + .contained() + .with_background_color(background) .boxed() } }