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 2e7997458e..607f47b5cf 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 c74a76ccce..feb80633c4 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 0000000000..6ac312c37f --- /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 0000000000..1deede7611 --- /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 19c9a3d727..d3b6e4810f 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 b9e3ed550b..cc930774d5 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 1deede7611..face85c4b6 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 b9d80e7150..31960fbc1e 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 cc930774d5..298e8ef8b9 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 607f47b5cf..90da178c7b 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 31563010b7..0397032de8 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 6b51b06c9c..229e11ff3a 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 506ebc8c3d..575e9499d3 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 old_text = fs.load(path).await?; - let new_tmp = serde_json::to_value(new_file_content).unwrap(); - let new_json = new_tmp.as_object().unwrap(); + let new_text = update_settings_file(old_text, current_file_content, update); - // 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)); - } - } - - // 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) - } - - 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 484c542ede..49f2d982ba 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 6ac312c37f..3e450e2312 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 face85c4b6..385a8a5f00 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(); - Flex::new(gpui::Axis::Vertical) + let (diagnostics, metrics) = { + let telemetry = settings.telemetry(); + (telemetry.diagnostics(), telemetry.metrics()) + }; + + enum Metrics {} + enum Diagnostics {} + + 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 dc57468df6..423ce37d48 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 0000000000..f1325514cd --- /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 8a41f55c02..14c9d4c79f 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 | 477 ++++++++++++++++++++------------ 1 file changed, 293 insertions(+), 184 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 229e11ff3a..501a88c42e 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(); - first_key_start.get_or_insert_with(|| key.node.start_byte()); + if key_range.start > existing_value_range.end { + break; + } - 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()); + 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); + + if found_key { + existing_value_range = value_range; + depth += 1; + + 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(); + // 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(existing_value_range)) => { - // Existing theme key, overwrite - settings_content.replace_range(existing_value_range, &new_val); - } - - (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]; + + 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'); } -} - -pub fn parse_json_with_comments(content: &str) -> Result { - Ok(serde_json::from_reader( - json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()), - )?) + 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); + let old_object = to_json_object(old_file_content); + let new_object = 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"); + 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 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 + 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_update_object_empty_doc() { + 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#" - { - "theme": "One Dark" - } - "# - .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#" + { + "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 49f2d982ba..87b9d9845a 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 385a8a5f00..6985b70069 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 f1325514cd..6085782afd 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 90da178c7b..c8124fcc3a 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 2a5e05b401..90c47478f3 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 20f2300d89..f4d0dc1a46 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 d3b6e4810f..68b04c7e2f 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 298e8ef8b9..a9625bf78e 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 2fbac3613e..75cdf0e687 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 f4d0dc1a46..989dcf0af5 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 6985b70069..5a166218ce 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 2bd8808281..4281c04649 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 a80b9f8d83..b55c9942f8 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 98fcac664c..235df0202d 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 7443f19003..df10db91a0 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 31960fbc1e..442d28579f 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 75cdf0e687..5d13d41bba 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 - } + 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 + } - // 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(); + 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 f9f49e3c7d..c758e0227e 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 5a166218ce..afb38385a4 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_child( + Flex::column() .with_children([ - self.render_settings_checkbox::( - &theme.welcome.checkbox, - metrics, - cx, - |content, checked| { - content.telemetry.set_metrics(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( - "Do you want to send telemetry?", + "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(), - 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() } } 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 4b3c5b7bc5..1cefc558f0 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 08ed3ecc2d..5d03d6304e 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 afb38385a4..54898f92c8 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 c8124fcc3a..c25656bfa9 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 87b9d9845a..ded5c54f83 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 3e450e2312..e1231ad5f6 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 54898f92c8..7bf6c26302 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 6085782afd..114ff0b7df 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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 S@wSzq6;Yn9elF{r5}E+_=|!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 f9f5738ad2..a16c31595f 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 98f70e83f0..cb55669ab0 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 3bb036d336..203d130b5b 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 a0f54abe38..6fb0278218 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 06b6cf2a90..21c2d2c218 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 edf0354eec..b24f3492da 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 ac0d72dee9..2170d215af 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 37cb01ace8..cc49308e15 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 { - data, + source: ImageSource::Path(asset_path), + style: Default::default(), + } + } + + pub fn from_data(data: Arc) -> Self { + Self { + 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 1cefc558f0..9df6581d20 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 7bf6c26302..232fd1bcb7 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 ce595222f3..b142726994 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 ded5c54f83..9442f08d24 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 232fd1bcb7..f787a6cf24 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 114ff0b7df..02e3c11f91 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 | 28 +++--- crates/welcome/src/welcome.rs | 156 ++++++++++++++----------------- 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 b142726994..5df283bfee 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 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 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 f787a6cf24..f1d59ce7a1 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(), - ) + 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(), + // Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), + ]) .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(), - ]) + Label::new( + "Code at the speed of thought", + theme.editor.hover_popover.prose.clone(), + ) .boxed(), - ) - .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(), + ]) + .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 235df0202d..fe33996961 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 f1d59ce7a1..dcc72357ed 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 9442f08d24..43a10978d6 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 dcc72357ed..c24e0b5b24 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 edbced8323..3f69df981e 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 032c53112b..a2a4467d7c 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 02e3c11f91..bfd67cec8d 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 442d28579f..cd2cd6fccb 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 5d13d41bba..a9032a264b 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 c25656bfa9..2b894eff35 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 feb80633c4..8361895146 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 0000000000..bbbe989920 --- /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 0000000000..06561a28f8 --- /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 e1231ad5f6..e76636411b 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 c24e0b5b24..c6316c61d5 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 fc069fe6c8..2ba7a6cc40 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 141a345382..76f46f83c5 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 cd2cd6fccb..234c40c69d 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 68b04c7e2f..8589af77e0 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 bb519c7a95..9381a90907 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 a9032a264b..71e4b48db3 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 2e232c6197..e4d51f5730 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 e4d062d575..fe4b75dbef 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 d999730a0d..0930bf0484 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 c6316c61d5..dc864a3ad1 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 0000000000..766c46c133 --- /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 0000000000..2e6e5e77e6 --- /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 0000000000..1d3dd887d7 --- /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 06561a28f8..adf50586d7 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 01992d9431..9048719d3f 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 501a88c42e..ae67428948 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 e304842aa2..c4cc64cd62 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 0930bf0484..ae3278b711 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 dc864a3ad1..8bb055dad0 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 234c40c69d..e74bcdc177 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 a9625bf78e..8407bd4516 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 71e4b48db3..2fb956f5b6 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 575e9499d3..1b05993bd5 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 | 128 ++++++++++------------ 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, 126 insertions(+), 91 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 8b622ab607..d53e84f6b1 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 9df6581d20..5de68b12fd 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); + 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(); - 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(), + 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 1b05993bd5..50638205c5 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 43a10978d6..e7252fbf61 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 e74bcdc177..f987d00ef1 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 8407bd4516..1b794b3857 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 90e0c82f5b..2601a12691 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 | 208 ++++++++++++++++++------------ crates/zed/src/main.rs | 6 +- crates/zed/src/zed.rs | 37 +++++- 4 files changed, 163 insertions(+), 90 deletions(-) diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 76a56af93d..c02d61c3e2 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 f987d00ef1..65072637ac 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); - } - } - - cx.background() - .spawn(DB.set_window_bounds(workspace_id, bounds, display)) - .detach_and_log_err(cx); - }) - .detach(); 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; + } + } + + 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 1b794b3857..13a44fef10 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 2fb956f5b6..63e026b9ab 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 65072637ac..1c1afea4aa 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 2b894eff35..9d950712a8 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 5de68b12fd..a95a8b2deb 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 9048719d3f..2235bc351d 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 ae67428948..49f43e7a2d 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 e76636411b..d3b0e09697 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 0000000000..a37bcb1837 --- /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 8bb055dad0..c780a06ee1 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 63e026b9ab..3b48632265 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 | 189 ++++++++++++++++++-------------- 3 files changed, 171 insertions(+), 116 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e7252fbf61..8fc6db3e5b 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 c780a06ee1..d5431e5108 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 bfd67cec8d..2dc0f59b2e 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: { - margin: { - top: 5, + let checkboxBase = { + cornerRadius: 4, + padding: { + left: 3, + right: 3, + top: 3, + bottom: 3, }, - }, - 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") - } + // shadow: colorScheme.popoverShadow, + border: border(layer), + margin: { + 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"), + }, + }, + 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 0000000000..dc98bb8bc2 --- /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 8fc6db3e5b..13546b40cd 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 d5431e5108..246ff26717 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 2dc0f59b2e..137c6df111 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 9d950712a8..ac4cbb3761 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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 S@wSzq6;Yn9elF{r5}E+_=|!0U diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a95a8b2deb..08a83fbc43 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 d3b0e09697..3da90deb2d 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 246ff26717..a2386b8d28 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 13a44fef10..9982b4114a 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 3b48632265..3c093836f2 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 2601a12691..80cb884c48 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 1c1afea4aa..7c439522e8 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 e9ffecf246..8949b60993 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 1ab78ac310..902242df01 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 6abfec21f7..2dd2b0e6b4 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 55522966fa..52a0e1cdc0 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 e447a26c6b..2232555442 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 269390f72f..21cc9f8895 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 1b6d846e71..fe9a7909b8 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 273440dce2..51d4df45f5 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 08a83fbc43..079de79b11 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 3821185ec0..110815e870 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 13546b40cd..524b765626 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 0000000000..ca71db3723 --- /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 a2386b8d28..89f161a283 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 4281c04649..f86a9db71a 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 fe33996961..7e0e6bbe01 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 7c439522e8..65335d8671 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 9982b4114a..2e2026309f 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 3c093836f2..ae9fcc9b31 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 30f44a6314..b4a21deba4 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 137c6df111..7c437f110e 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 c758e0227e..55b5c61341 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 21cc9f8895..0176a1e01b 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 ca71db3723..5396265ad8 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 f86a9db71a..efba568b90 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 7e0e6bbe01..abd410d875 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 65335d8671..40053b103a 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 55b5c61341..cb99572853 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 89f161a283..a1045ea1e8 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 524b765626..d331ca80a9 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 5396265ad8..5441e71168 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 a1045ea1e8..3a35920b88 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 9381a90907..d5fb94b2b6 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 7c437f110e..4ad3e51cd8 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 4ad3e51cd8..e1bd5c82bb 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 cb99572853..d798a22e9d 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 989dcf0af5..ae9325ea2e 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 8949b60993..80df0ed6df 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 0176a1e01b..d73c3c87db 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 efba568b90..fd7638c5fb 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 40053b103a..ef14bfa2fe 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 fd7638c5fb..f5ee8cad51 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 ef14bfa2fe..b4b164ec3f 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 a2a4467d7c..032c53112b 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 d73c3c87db..4f61bde398 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 d798a22e9d..1789b523e7 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 b4b164ec3f..609afded33 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 d5fb94b2b6..82422c5c19 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 0000000000..4629600b41 --- /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 0000000000..6c331f43ab --- /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 d331ca80a9..8ee58a8af2 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 abd410d875..6d20700bcc 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 2e2026309f..cbcd1aa1c6 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 1789b523e7..984b6b5ed3 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. + top: 96, }, - padding: { - top: 8, - left: 8, - right: 8, - }, - 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 984b6b5ed3..bebe87ce55 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 4629600b41..0000000000 --- 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 6c331f43ab..0000000000 --- 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 6d20700bcc..587f180a4a 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() } }