diff --git a/Cargo.lock b/Cargo.lock index e2cf9c75697c57f19cb0babd1600ccaa23830eb5..b955567fd7eeb3d6206c0c03bbbac28f79813dfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2625,8 +2625,6 @@ dependencies = [ "rand 0.8.5", "release_channel", "rpc", - "rustls 0.23.22", - "rustls-native-certs 0.8.1", "schemars", "serde", "serde_json", @@ -6014,6 +6012,8 @@ dependencies = [ "futures 0.3.31", "http 1.2.0", "log", + "rustls 0.23.22", + "rustls-platform-verifier", "serde", "serde_json", "url", @@ -10502,16 +10502,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11492,6 +11492,33 @@ dependencies = [ "web-time", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" +dependencies = [ + "core-foundation 0.10.0", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.22", + "rustls-native-certs 0.8.1", + "rustls-platform-verifier-android", + "rustls-webpki 0.102.8", + "security-framework 3.0.1", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -15398,6 +15425,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.7" diff --git a/Cargo.toml b/Cargo.toml index ec893364e23b1d8704e97e64b747b679119a8594..555190ae7fd8ee340c6b600cda23927dd8d06cd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -482,7 +482,7 @@ rustc-demangle = "0.1.23" rust-embed = { version = "8.4", features = ["include-exclude"] } rustc-hash = "2.1.0" rustls = { version = "0.23.22" } -rustls-native-certs = "0.8.0" +rustls-platform-verifier = "0.5.0" schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] } semver = "1.0" serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index ce6dd7953dc77a4b3e46a8e65b894c1c8f90983e..81c40e9b2fbe97c19cc71f526cdb59e9f1173be5 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -27,11 +27,13 @@ "coffee": "coffeescript", "conf": "settings", "cpp": "cpp", + "cs": "csharp", "css": "css", "csv": "storage", "cxx": "cpp", "cts": "typescript", "ctsx": "react", + "cue": "cue", "dart": "dart", "dat": "storage", "db": "storage", @@ -66,6 +68,7 @@ "gitattributes": "vcs", "gitignore": "vcs", "gitkeep": "vcs", + "gitlab-ci.yml": "gitlab", "gitmodules": "vcs", "TAG_EDITMSG": "vcs", "MERGE_MSG": "vcs", @@ -113,6 +116,7 @@ "lockb": "bun", "log": "log", "lua": "lua", + "luau": "luau", "m4a": "audio", "m4v": "video", "markdown": "markdown", @@ -188,6 +192,7 @@ "scss": "sass", "sdf": "storage", "sh": "terminal", + "sol": "solidity", "sql": "storage", "sqlite": "storage", "stylelint.config.cjs": "stylelint", diff --git a/assets/icons/zed_predict_bg.svg b/assets/icons/zed_predict_bg.svg index 7912ae17aa62076a130249270cea2b79413f1168..1332b2fdeceb0e43e84890a8237dac40d7e6eb5a 100644 --- a/assets/icons/zed_predict_bg.svg +++ b/assets/icons/zed_predict_bg.svg @@ -1,6 +1,6 @@ - + - + diff --git a/assets/settings/default.json b/assets/settings/default.json index 2b07b3610a5ed3b33a706c8ab36cc3e26461776d..a6b43ea405f2b0bb1e3f302c79f1f5085ca90d2e 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -652,15 +652,15 @@ // There are 5 possible width values: // // 1. Small: This value is essentially a fixed width. - // "modal_width": "small" + // "modal_max_width": "small" // 2. Medium: - // "modal_width": "medium" + // "modal_max_width": "medium" // 3. Large: - // "modal_width": "large" + // "modal_max_width": "large" // 4. Extra Large: - // "modal_width": "xlarge" + // "modal_max_width": "xlarge" // 5. Fullscreen: This value removes any horizontal padding, as it consumes the whole viewport width. - // "modal_width": "full" + // "modal_max_width": "full" // // Default: small "modal_max_width": "small" diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index ac0d968eb280ab2425f57979dc6724e7abaee9a8..f52f9559e8446560faa1f93a819658f8261a45f2 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -33,8 +33,6 @@ postage.workspace = true rand.workspace = true release_channel.workspace = true rpc = { workspace = true, features = ["gpui"] } -rustls-native-certs.workspace = true -rustls.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index f6cb04c86612b39821cb64886451b9ffcc6ecc16..280d3c2d94b0911c2e2e6725ac623f9c2b016f98 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -146,8 +146,6 @@ pub fn init_settings(cx: &mut App) { } pub fn init(client: &Arc, cx: &mut App) { - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); - let client = Arc::downgrade(client); cx.on_action({ let client = client.clone(); @@ -1126,24 +1124,11 @@ impl Client { match url_scheme { Https => { - let client_config = { - let mut root_store = rustls::RootCertStore::empty(); - - let root_certs = rustls_native_certs::load_native_certs(); - for error in root_certs.errors { - log::warn!("error loading native certs: {:?}", error); - } - root_store.add_parsable_certificates(root_certs.certs); - rustls::ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth() - }; - let (stream, _) = async_tungstenite::async_tls::client_async_tls_with_connector( request, stream, - Some(client_config.into()), + Some(http_client::tls_config().into()), ) .await?; Ok(Connection::new( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e8fc0593e7b8cd05020e6ac482f6920837d790f7..54e9533d7ea316d5c94d1ab416be74cab0d87190 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -709,6 +709,7 @@ pub struct Editor { /// Used to prevent flickering as the user types while the menu is open stale_inline_completion_in_menu: Option, edit_prediction_settings: EditPredictionSettings, + edit_prediction_cursor_on_leading_whitespace: bool, inline_completions_hidden_for_vim_mode: bool, show_inline_completions_override: Option, menu_inline_completions_policy: MenuInlineCompletionsPolicy, @@ -1423,6 +1424,7 @@ impl Editor { show_inline_completions_override: None, menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider, edit_prediction_settings: EditPredictionSettings::Disabled, + edit_prediction_cursor_on_leading_whitespace: false, custom_context_menu: None, show_git_blame_gutter: false, show_git_blame_inline: false, @@ -1567,8 +1569,12 @@ impl Editor { if has_active_edit_prediction { key_context.add("copilot_suggestion"); key_context.add(EDIT_PREDICTION_KEY_CONTEXT); - - if showing_completions || self.edit_prediction_requires_modifier() { + if showing_completions + || self.edit_prediction_requires_modifier() + // Require modifier key when the cursor is on leading whitespace, to allow `tab` + // bindings to insert tab characters. + || self.edit_prediction_cursor_on_leading_whitespace + { key_context.add(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT); } } @@ -4931,23 +4937,6 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - let buffer = self.buffer.read(cx); - let snapshot = buffer.snapshot(cx); - let selection = self.selections.newest_adjusted(cx); - let cursor = selection.head(); - let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row)); - let suggested_indents = snapshot.suggested_indents([cursor.row], cx); - if let Some(suggested_indent) = suggested_indents.get(&MultiBufferRow(cursor.row)).copied() - { - if cursor.column < suggested_indent.len - && cursor.column <= current_indent.len - && current_indent.len <= suggested_indent.len - { - self.tab(&Default::default(), window, cx); - return; - } - } - if self.show_edit_predictions_in_menu() { self.hide_context_menu(window, cx); } @@ -5216,7 +5205,7 @@ impl Editor { return; }; - if &accept_keystroke.modifiers == modifiers { + if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() { if matches!( self.edit_prediction_preview, EditPredictionPreview::Inactive @@ -5298,6 +5287,9 @@ impl Editor { return None; } + self.edit_prediction_cursor_on_leading_whitespace = + multibuffer.is_line_whitespace_upto(cursor); + let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?; let edits = inline_completion .edits @@ -12835,11 +12827,17 @@ impl Editor { .and_then(|f| f.as_local()) } - fn target_file_abs_path(&self, cx: &mut Context) -> Option { + pub fn target_file_abs_path(&self, cx: &mut Context) -> Option { self.active_excerpt(cx).and_then(|(_, buffer, _)| { - let project_path = buffer.read(cx).project_path(cx)?; - let project = self.project.as_ref()?.read(cx); - project.absolute_path(&project_path, cx) + let buffer = buffer.read(cx); + if let Some(project_path) = buffer.project_path(cx) { + let project = self.project.as_ref()?.read(cx); + project.absolute_path(&project_path, cx) + } else { + buffer + .file() + .and_then(|file| file.as_local().map(|file| file.abs_path(cx))) + } }) } diff --git a/crates/editor/src/inline_completion_tests.rs b/crates/editor/src/inline_completion_tests.rs index c74de1fc9329a1984bc4c44b35815f13974e167b..10736adda6a8d9d90c52bcae5629d12c951d2fe9 100644 --- a/crates/editor/src/inline_completion_tests.rs +++ b/crates/editor/src/inline_completion_tests.rs @@ -1,10 +1,9 @@ use gpui::{prelude::*, Entity}; use indoc::indoc; use inline_completion::EditPredictionProvider; -use language::{Language, LanguageConfig}; use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint}; use project::Project; -use std::{num::NonZeroU32, ops::Range, sync::Arc}; +use std::ops::Range; use text::{Point, ToOffset}; use crate::{ @@ -124,54 +123,6 @@ async fn test_inline_completion_jump_button(cx: &mut gpui::TestAppContext) { "}); } -#[gpui::test] -async fn test_indentation(cx: &mut gpui::TestAppContext) { - init_test(cx, |settings| { - settings.defaults.tab_size = NonZeroU32::new(4) - }); - - let language = Arc::new( - Language::new( - LanguageConfig::default(), - Some(tree_sitter_rust::LANGUAGE.into()), - ) - .with_indents_query(r#"(_ "(" ")" @end) @indent"#) - .unwrap(), - ); - - let mut cx = EditorTestContext::new(cx).await; - cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); - let provider = cx.new(|_| FakeInlineCompletionProvider::default()); - assign_editor_completion_provider(provider.clone(), &mut cx); - - cx.set_state(indoc! {" - const a: A = ( - ˇ - ); - "}); - - propose_edits( - &provider, - vec![(Point::new(1, 0)..Point::new(1, 0), " const function()")], - &mut cx, - ); - cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx)); - - assert_editor_active_edit_completion(&mut cx, |_, edits| { - assert_eq!(edits.len(), 1); - assert_eq!(edits[0].1.as_str(), " const function()"); - }); - - // When the cursor is before the suggested indentation level, accepting a - // completion should just indent. - accept_completion(&mut cx); - cx.assert_editor_state(indoc! {" - const a: A = ( - ˇ - ); - "}); -} - #[gpui::test] async fn test_inline_completion_invalidation_range(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/http_client/Cargo.toml b/crates/http_client/Cargo.toml index 423bb66f7cd728a79dc444eb6d78680ce7ee66c3..633a785c3701f595ee3342f6552190f0c648a198 100644 --- a/crates/http_client/Cargo.toml +++ b/crates/http_client/Cargo.toml @@ -25,3 +25,5 @@ log.workspace = true serde.workspace = true serde_json.workspace = true url.workspace = true +rustls.workspace = true +rustls-platform-verifier.workspace = true diff --git a/crates/http_client/src/http_client.rs b/crates/http_client/src/http_client.rs index ec3a4e03c40f1aca22199ce1cd4ad8cb91b1d65f..9d249516826b045fee98208950dfee46034ab36c 100644 --- a/crates/http_client/src/http_client.rs +++ b/crates/http_client/src/http_client.rs @@ -8,14 +8,33 @@ pub use http::{self, Method, Request, Response, StatusCode, Uri}; use futures::future::BoxFuture; use http::request::Builder; +use rustls::ClientConfig; +use rustls_platform_verifier::ConfigVerifierExt; #[cfg(feature = "test-support")] use std::fmt; use std::{ any::type_name, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, OnceLock}, }; pub use url::Url; +static TLS_CONFIG: OnceLock = OnceLock::new(); + +pub fn tls_config() -> ClientConfig { + TLS_CONFIG + .get_or_init(|| { + // rustls uses the `aws_lc_rs` provider by default + // This only errors if the default provider has already + // been installed. We can ignore this `Result`. + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .ok(); + + ClientConfig::with_platform_verifier() + }) + .clone() +} + #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] pub enum RedirectPolicy { #[default] diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 4056e9acd08eecf3f10315960954a782f3ede98d..16f2d07a662577b06ef4f6706eb65958b531e37c 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -406,7 +406,7 @@ impl InlineCompletionButton { if let Some(editor_focus_handle) = self.editor_focus_handle.clone() { menu = menu.toggleable_entry( - "This File", + "This Buffer", self.editor_show_predictions, IconPosition::Start, Some(Box::new(ToggleEditPrediction)), @@ -451,33 +451,41 @@ impl InlineCompletionButton { let enabled = data_collection.is_enabled(); let is_open_source = data_collection.is_project_open_source(); let is_collecting = data_collection.is_enabled(); + let (icon_name, icon_color) = if is_open_source && is_collecting { + (IconName::Check, Color::Success) + } else { + (IconName::Check, Color::Accent) + }; menu = menu.item( - ContextMenuEntry::new("Share Training Data") + ContextMenuEntry::new("Training Data Collection") .toggleable(IconPosition::Start, data_collection.is_enabled()) - .icon_color(if is_open_source && is_collecting { - Color::Success - } else { - Color::Accent - }) + .icon(icon_name) + .icon_color(icon_color) .documentation_aside(move |cx| { let (msg, label_color, icon_name, icon_color) = match (is_open_source, is_collecting) { (true, true) => ( - "Project identified as open-source, and you're sharing data.", + "Project identified as open source, and you're sharing data.", Color::Default, IconName::Check, Color::Success, ), (true, false) => ( - "Project identified as open-source, but you're not sharing data.", + "Project identified as open source, but you're not sharing data.", + Color::Muted, + IconName::Close, Color::Muted, - IconName::XCircle, + ), + (false, true) => ( + "Project not identified as open source. No data captured.", + Color::Muted, + IconName::Close, Color::Muted, ), - (false, _) => ( - "Project not identified as open-source. No data captured.", + (false, false) => ( + "Project not identified as open source, and setting turned off.", Color::Muted, - IconName::XCircle, + IconName::Close, Color::Muted, ), }; @@ -485,7 +493,7 @@ impl InlineCompletionButton { .gap_2() .child( Label::new(indoc!{ - "Help us improve our open model by sharing data from open source repositories. \ + "Help us improve our open dataset model by sharing data from open source repositories. \ Zed must detect a license file in your repo for this setting to take effect." }) ) @@ -516,6 +524,16 @@ impl InlineCompletionButton { } }) ); + + if is_collecting && !is_open_source { + menu = menu.item( + ContextMenuEntry::new("No data captured.") + .disabled(true) + .icon(IconName::Close) + .icon_color(Color::Error) + .icon_size(IconSize::Small), + ); + } } } @@ -556,7 +574,7 @@ impl InlineCompletionButton { language::EditPredictionsMode::EagerPreview => true, }; menu = menu.separator().toggleable_entry( - "Eager Preview", + "Eager Preview Mode", is_eager_preview_enabled, IconPosition::Start, None, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index ca52eadd9a402824f22b62d5c2152081c270a668..0306011b3ad9127672e87bcf41146a852d00ed0c 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -4236,6 +4236,21 @@ impl MultiBufferSnapshot { indent } + pub fn is_line_whitespace_upto(&self, position: T) -> bool + where + T: ToOffset, + { + for char in self.reversed_chars_at(position) { + if !char.is_whitespace() { + return false; + } + if char == '\n' { + return true; + } + } + return true; + } + pub fn prev_non_blank_row(&self, mut row: MultiBufferRow) -> Option { while row.0 > 0 { row.0 -= 1; diff --git a/crates/reqwest_client/src/reqwest_client.rs b/crates/reqwest_client/src/reqwest_client.rs index 7d3844d5b796d84ea045ce85b794443c4f704e1b..7c84ec8bab91db394d91f46dbef0e7f5745f55f1 100644 --- a/crates/reqwest_client/src/reqwest_client.rs +++ b/crates/reqwest_client/src/reqwest_client.rs @@ -51,7 +51,10 @@ impl ReqwestClient { }) { client = client.proxy(proxy); } - let client = client.build()?; + + let client = client + .use_preconfigured_tls(http_client::tls_config()) + .build()?; let mut client: ReqwestClient = client.into(); client.proxy = proxy; Ok(client) diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index c264b56666358f067245c3efe90ffbb5f23cf588..10980bba426e4d26d2d1ac55b2d5cbae971f83fa 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -10,7 +10,7 @@ use schemars::{ schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SubschemaValidation}, JsonSchema, }; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use serde_json::Value; use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock}; use util::{asset_str, markdown::MarkdownString}; @@ -47,12 +47,12 @@ pub(crate) static KEY_BINDING_VALIDATORS: LazyLock); /// Keymap section which binds keystrokes to actions. -#[derive(Debug, Deserialize, Default, Clone, JsonSchema, Serialize)] +#[derive(Debug, Deserialize, Default, Clone, JsonSchema)] pub struct KeymapSection { /// Determines when these bindings are active. When just a name is provided, like `Editor` or /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`, @@ -97,9 +97,9 @@ impl KeymapSection { /// Unlike the other json types involved in keymaps (including actions), this doc-comment will not /// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The /// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`. -#[derive(Debug, Deserialize, Default, Clone, Serialize)] +#[derive(Debug, Deserialize, Default, Clone)] #[serde(transparent)] -pub struct KeymapAction(pub(crate) Value); +pub struct KeymapAction(Value); impl std::fmt::Display for KeymapAction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -133,11 +133,9 @@ impl JsonSchema for KeymapAction { pub enum KeymapFileLoadResult { Success { key_bindings: Vec, - keymap_file: KeymapFile, }, SomeFailedToLoad { key_bindings: Vec, - keymap_file: KeymapFile, error_message: MarkdownString, }, JsonParseFailure { @@ -152,7 +150,7 @@ impl KeymapFile { pub fn load_asset(asset_path: &str, cx: &App) -> anyhow::Result> { match Self::load(asset_str::(asset_path).as_ref(), cx) { - KeymapFileLoadResult::Success { key_bindings, .. } => Ok(key_bindings), + KeymapFileLoadResult::Success { key_bindings } => Ok(key_bindings), KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => Err(anyhow!( "Error loading built-in keymap \"{asset_path}\": {error_message}" )), @@ -202,7 +200,6 @@ impl KeymapFile { if content.is_empty() { return KeymapFileLoadResult::Success { key_bindings: Vec::new(), - keymap_file: KeymapFile(Vec::new()), }; } let keymap_file = match parse_json_with_comments::(content) { @@ -296,10 +293,7 @@ impl KeymapFile { } if errors.is_empty() { - KeymapFileLoadResult::Success { - key_bindings, - keymap_file, - } + KeymapFileLoadResult::Success { key_bindings } } else { let mut error_message = "Errors in user keymap file.\n".to_owned(); for (context, section_errors) in errors { @@ -317,7 +311,6 @@ impl KeymapFile { } KeymapFileLoadResult::SomeFailedToLoad { key_bindings, - keymap_file, error_message: MarkdownString(error_message), } } @@ -619,7 +612,7 @@ fn inline_code_string(text: &str) -> MarkdownString { #[cfg(test)] mod tests { - use super::KeymapFile; + use crate::KeymapFile; #[test] fn can_deserialize_keymap_with_trailing_comma() { diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index ba93391804b548d6bec0703da45acec8eb0bb36e..1277044a6322175fe40e4fd58daae5e62567428f 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,7 +1,7 @@ use crate::{settings_store::SettingsStore, Settings}; use fs::Fs; use futures::{channel::mpsc, StreamExt}; -use gpui::{App, BackgroundExecutor, ReadGlobal, UpdateGlobal}; +use gpui::{App, BackgroundExecutor, ReadGlobal}; use std::{path::PathBuf, sync::Arc, time::Duration}; pub const EMPTY_THEME_NAME: &str = "empty-theme"; @@ -78,40 +78,6 @@ pub fn watch_config_file( rx } -pub fn handle_settings_file_changes( - mut user_settings_file_rx: mpsc::UnboundedReceiver, - cx: &mut App, - settings_changed: impl Fn(Result, &mut App) + 'static, -) { - let user_settings_content = cx - .background_executor() - .block(user_settings_file_rx.next()) - .unwrap(); - SettingsStore::update_global(cx, |store, cx| { - let result = store.set_user_settings(&user_settings_content, cx); - if let Err(err) = &result { - log::error!("Failed to load user settings: {err}"); - } - settings_changed(result, cx); - }); - cx.spawn(move |cx| async move { - while let Some(user_settings_content) = user_settings_file_rx.next().await { - let result = cx.update_global(|store: &mut SettingsStore, cx| { - let result = store.set_user_settings(&user_settings_content, cx); - if let Err(err) = &result { - log::error!("Failed to load user settings: {err}"); - } - settings_changed(result, cx); - cx.refresh_windows(); - }); - if result.is_err() { - break; // App dropped - } - } - }) - .detach(); -} - pub fn update_settings_file( fs: Arc, cx: &App, diff --git a/crates/theme/src/icon_theme.rs b/crates/theme/src/icon_theme.rs index 132826a9d5a87e2478c8342cd38f289036c048ca..b4690512c5f2a2d3fc9a78fc61fe4f7b5a35cf4d 100644 --- a/crates/theme/src/icon_theme.rs +++ b/crates/theme/src/icon_theme.rs @@ -66,7 +66,9 @@ const FILE_ICONS: &[(&str, &str)] = &[ ("code", "icons/file_icons/code.svg"), ("coffeescript", "icons/file_icons/coffeescript.svg"), ("cpp", "icons/file_icons/cpp.svg"), + ("csharp", "icons/file_icons/file.svg"), ("css", "icons/file_icons/css.svg"), + ("cue", "icons/file_icons/file.svg"), ("dart", "icons/file_icons/dart.svg"), ("default", "icons/file_icons/file.svg"), ("diff", "icons/file_icons/diff.svg"), @@ -78,6 +80,7 @@ const FILE_ICONS: &[(&str, &str)] = &[ ("eslint", "icons/file_icons/eslint.svg"), ("font", "icons/file_icons/font.svg"), ("fsharp", "icons/file_icons/fsharp.svg"), + ("gitlab", "icons/file_icons/settings.svg"), ("gleam", "icons/file_icons/gleam.svg"), ("go", "icons/file_icons/go.svg"), ("graphql", "icons/file_icons/graphql.svg"), @@ -94,6 +97,7 @@ const FILE_ICONS: &[(&str, &str)] = &[ ("lock", "icons/file_icons/lock.svg"), ("log", "icons/file_icons/info.svg"), ("lua", "icons/file_icons/lua.svg"), + ("luau", "icons/file_icons/file.svg"), ("markdown", "icons/file_icons/book.svg"), ("metal", "icons/file_icons/metal.svg"), ("nim", "icons/file_icons/nim.svg"), @@ -112,6 +116,7 @@ const FILE_ICONS: &[(&str, &str)] = &[ ("sass", "icons/file_icons/sass.svg"), ("scala", "icons/file_icons/scala.svg"), ("settings", "icons/file_icons/settings.svg"), + ("solidity", "icons/file_icons/file.svg"), ("storage", "icons/file_icons/database.svg"), ("stylelint", "icons/file_icons/javascript.svg"), ("svelte", "icons/file_icons/html.svg"), diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 2349754293d08e7478f261d5aebfeea46b81f2a0..cd39638638b24411578a403d781b14f2870a3250 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -598,6 +598,7 @@ impl Render for ContextMenu { }) => { let handler = handler.clone(); let menu = cx.entity().downgrade(); + let icon_color = if *disabled { Color::Muted } else if toggle.is_some() { @@ -605,16 +606,18 @@ impl Render for ContextMenu { } else { icon_color.unwrap_or(Color::Default) }; + let label_color = if *disabled { Color::Muted } else { Color::Default }; + let label_element = if let Some(icon_name) = icon { h_flex() .gap_1p5() .when( - *icon_position == IconPosition::Start, + *icon_position == IconPosition::Start && toggle.is_none(), |flex| { flex.child( Icon::new(*icon_name) @@ -643,8 +646,10 @@ impl Render for ContextMenu { .color(label_color) .into_any_element() }; + let documentation_aside_callback = documentation_aside.clone(); + div() .id(("context-menu-child", ix)) .when_some( @@ -675,7 +680,7 @@ impl Render for ContextMenu { |list_item, (position, toggled)| { let contents = div().flex_none().child( - Icon::new(IconName::Check) + Icon::new(icon.unwrap_or(IconName::Check)) .color(icon_color) .size(*icon_size) ) @@ -778,7 +783,7 @@ impl Render for ContextMenu { } } }, - ))), + ))) ), ) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index cbd2519e602a362d9ba730e5a6785c022e0e5c70..e69226d97cd1a805da8972f806ff3fc3a411bcf0 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -34,7 +34,7 @@ use project::project_settings::ProjectSettings; use recent_projects::{open_ssh_project, SshSettings}; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use session::{AppSession, Session}; -use settings::{handle_settings_file_changes, watch_config_file, Settings, SettingsStore}; +use settings::{watch_config_file, Settings, SettingsStore}; use simplelog::ConfigBuilder; use std::{ env, @@ -52,8 +52,9 @@ use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN}; use workspace::{AppState, SerializedWorkspaceLocation, WorkspaceSettings, WorkspaceStore}; use zed::{ app_menus, build_window_options, derive_paths_with_position, handle_cli_connection, - handle_keymap_file_changes, handle_settings_changed, initialize_workspace, - inline_completion_registry, open_paths_with_positions, OpenListener, OpenRequest, + handle_keymap_file_changes, handle_settings_changed, handle_settings_file_changes, + initialize_workspace, inline_completion_registry, open_paths_with_positions, OpenListener, + OpenRequest, }; #[cfg(unix)] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e0e0cbaecfdaba0027c259e039b32468b440499b..9fd7499323d49fc1eb789252ef246eb127e730b8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -21,14 +21,16 @@ use command_palette_hooks::CommandPaletteFilter; use editor::ProposedChangesEditorToolbar; use editor::{scroll::Autoscroll, Editor, MultiBuffer}; use feature_flags::{FeatureFlagAppExt, FeatureFlagViewExt, GitUiFeatureFlag}; -use fs::Fs; use futures::{channel::mpsc, select_biased, StreamExt}; use gpui::{ actions, point, px, Action, App, AppContext as _, AsyncApp, Context, DismissEvent, Element, Entity, Focusable, KeyBinding, MenuItem, ParentElement, PathPromptOptions, PromptLevel, - ReadGlobal, SharedString, Styled, Task, TitlebarOptions, Window, WindowKind, WindowOptions, + ReadGlobal, SharedString, Styled, Task, TitlebarOptions, UpdateGlobal, Window, WindowKind, + WindowOptions, }; use image_viewer::ImageInfo; +use migrate::{MigrationBanner, MigrationEvent, MigrationNotification, MigrationType}; +use migrator::{migrate_keymap, migrate_settings}; pub use open_listener::*; use outline_panel::OutlinePanel; use paths::{local_settings_file_relative_path, local_tasks_file_relative_path}; @@ -150,6 +152,7 @@ pub fn initialize_workspace( let workspace_handle = cx.entity().clone(); let center_pane = workspace.active_pane().clone(); initialize_pane(workspace, ¢er_pane, window, cx); + cx.subscribe_in(&workspace_handle, window, { move |workspace, _, event, window, cx| match event { workspace::Event::PaneAdded(pane) => { @@ -855,7 +858,6 @@ fn initialize_pane( toolbar.add_item(breadcrumbs, window, cx); let buffer_search_bar = cx.new(|cx| search::BufferSearchBar::new(window, cx)); toolbar.add_item(buffer_search_bar.clone(), window, cx); - let proposed_change_bar = cx.new(|_| ProposedChangesEditorToolbar::new()); toolbar.add_item(proposed_change_bar, window, cx); let quick_action_bar = @@ -869,6 +871,8 @@ fn initialize_pane( toolbar.add_item(lsp_log_item, window, cx); let syntax_tree_item = cx.new(|_| language_tools::SyntaxTreeToolbarItemView::new()); toolbar.add_item(syntax_tree_item, window, cx); + let migration_banner = cx.new(|cx| MigrationBanner::new(workspace, cx)); + toolbar.add_item(migration_banner, window, cx); }) }); } @@ -1097,6 +1101,68 @@ fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Contex .detach(); } +pub fn handle_settings_file_changes( + mut user_settings_file_rx: mpsc::UnboundedReceiver, + cx: &mut App, + settings_changed: impl Fn(Option, &mut App) + 'static, +) { + MigrationNotification::set_global(cx.new(|_| MigrationNotification), cx); + let content = cx + .background_executor() + .block(user_settings_file_rx.next()) + .unwrap(); + let user_settings_content = if let Ok(Some(migrated_content)) = migrate_settings(&content) { + migrated_content + } else { + content + }; + SettingsStore::update_global(cx, |store, cx| { + let result = store.set_user_settings(&user_settings_content, cx); + if let Err(err) = &result { + log::error!("Failed to load user settings: {err}"); + } + settings_changed(result.err(), cx); + }); + cx.spawn(move |cx| async move { + while let Some(content) = user_settings_file_rx.next().await { + let user_settings_content; + let content_migrated; + + if let Ok(Some(migrated_content)) = migrate_settings(&content) { + user_settings_content = migrated_content; + content_migrated = true; + } else { + user_settings_content = content; + content_migrated = false; + } + + cx.update(|cx| { + if let Some(notifier) = MigrationNotification::try_global(cx) { + notifier.update(cx, |_, cx| { + cx.emit(MigrationEvent::ContentChanged { + migration_type: MigrationType::Settings, + migrated: content_migrated, + }); + }); + } + }) + .ok(); + let result = cx.update_global(|store: &mut SettingsStore, cx| { + let result = store.set_user_settings(&user_settings_content, cx); + if let Err(err) = &result { + log::error!("Failed to load user settings: {err}"); + } + settings_changed(result.err(), cx); + cx.refresh_windows(); + }); + if result.is_err() { + break; // App dropped + } + } + }) + .detach(); +} + pub fn handle_keymap_file_changes( mut user_keymap_file_rx: mpsc::UnboundedReceiver, cx: &mut App, @@ -1137,47 +1203,46 @@ pub fn handle_keymap_file_changes( cx.spawn(move |cx| async move { let mut user_keymap_content = String::new(); + let mut content_migrated = false; loop { select_biased! { _ = base_keymap_rx.next() => {}, _ = keyboard_layout_rx.next() => {}, content = user_keymap_file_rx.next() => { if let Some(content) = content { - user_keymap_content = content; + if let Ok(Some(migrated_content)) = migrate_keymap(&content) { + user_keymap_content = migrated_content; + content_migrated = true; + } else { + user_keymap_content = content; + content_migrated = false; + } } } }; cx.update(|cx| { + if let Some(notifier) = MigrationNotification::try_global(cx) { + notifier.update(cx, |_, cx| { + cx.emit(MigrationEvent::ContentChanged { + migration_type: MigrationType::Keymap, + migrated: content_migrated, + }); + }); + } let load_result = KeymapFile::load(&user_keymap_content, cx); match load_result { - KeymapFileLoadResult::Success { - key_bindings, - keymap_file, - } => { + KeymapFileLoadResult::Success { key_bindings } => { reload_keymaps(cx, key_bindings); - dismiss_app_notification(¬ification_id, cx); - show_keymap_migration_notification_if_needed( - keymap_file, - notification_id.clone(), - cx, - ); + dismiss_app_notification(¬ification_id.clone(), cx); } KeymapFileLoadResult::SomeFailedToLoad { key_bindings, - keymap_file, error_message, } => { if !key_bindings.is_empty() { reload_keymaps(cx, key_bindings); } - dismiss_app_notification(¬ification_id, cx); - if !show_keymap_migration_notification_if_needed( - keymap_file, - notification_id.clone(), - cx, - ) { - show_keymap_file_load_error(notification_id.clone(), error_message, cx); - } + show_keymap_file_load_error(notification_id.clone(), error_message, cx); } KeymapFileLoadResult::JsonParseFailure { error } => { show_keymap_file_json_error(notification_id.clone(), &error, cx) @@ -1209,66 +1274,6 @@ fn show_keymap_file_json_error( }); } -fn show_keymap_migration_notification_if_needed( - keymap_file: KeymapFile, - notification_id: NotificationId, - cx: &mut App, -) -> bool { - if !migrate::should_migrate_keymap(keymap_file) { - return false; - } - let message = MarkdownString(format!( - "Keymap migration needed, as the format for some actions has changed. \ - You can migrate your keymap by clicking below. A backup will be created at {}.", - MarkdownString::inline_code(&paths::keymap_backup_file().to_string_lossy()) - )); - show_markdown_app_notification( - notification_id, - message, - "Backup and Migrate Keymap".into(), - move |_, cx| { - let fs = ::global(cx); - cx.spawn(move |weak_notification, mut cx| async move { - migrate::migrate_keymap(fs).await.ok(); - weak_notification - .update(&mut cx, |_, cx| { - cx.emit(DismissEvent); - }) - .ok(); - }) - .detach(); - }, - cx, - ); - return true; -} - -fn show_settings_migration_notification_if_needed( - notification_id: NotificationId, - settings: serde_json::Value, - cx: &mut App, -) { - if !migrate::should_migrate_settings(&settings) { - return; - } - let message = MarkdownString(format!( - "Settings migration needed, as the format for some settings has changed. \ - You can migrate your settings by clicking below. A backup will be created at {}.", - MarkdownString::inline_code(&paths::settings_backup_file().to_string_lossy()) - )); - show_markdown_app_notification( - notification_id, - message, - "Backup and Migrate Settings".into(), - move |_, cx| { - let fs = ::global(cx); - migrate::migrate_settings(fs, cx); - cx.emit(DismissEvent); - }, - cx, - ); -} - fn show_keymap_file_load_error( notification_id: NotificationId, error_message: MarkdownString, @@ -1363,12 +1368,12 @@ pub fn load_default_keymap(cx: &mut App) { } } -pub fn handle_settings_changed(result: Result, cx: &mut App) { +pub fn handle_settings_changed(error: Option, cx: &mut App) { struct SettingsParseErrorNotification; let id = NotificationId::unique::(); - match result { - Err(error) => { + match error { + Some(error) => { if let Some(InvalidSettingsError::LocalSettings { .. }) = error.downcast_ref::() { @@ -1387,9 +1392,8 @@ pub fn handle_settings_changed(result: Result, }) }); } - Ok(settings) => { + None => { dismiss_app_notification(&id, cx); - show_settings_migration_notification_if_needed(id, settings, cx); } } } @@ -1672,7 +1676,7 @@ mod tests { use language::{LanguageMatcher, LanguageRegistry}; use project::{project_settings::ProjectSettings, Project, ProjectPath, WorktreeSettings}; use serde_json::json; - use settings::{handle_settings_file_changes, watch_config_file, SettingsStore}; + use settings::{watch_config_file, SettingsStore}; use std::{ path::{Path, PathBuf}, time::Duration, diff --git a/crates/zed/src/zed/migrate.rs b/crates/zed/src/zed/migrate.rs index 9116899937b98267d39e3e4476d63bafb64d2b87..cf4ae924cdbd20a56bfb95f5da1a7678ef9b2bdd 100644 --- a/crates/zed/src/zed/migrate.rs +++ b/crates/zed/src/zed/migrate.rs @@ -1,63 +1,256 @@ -use std::sync::Arc; - -use anyhow::Context; +use anyhow::{Context as _, Result}; +use editor::Editor; use fs::Fs; +use migrator::{migrate_keymap, migrate_settings}; use settings::{KeymapFile, SettingsStore}; +use util::ResultExt; -pub fn should_migrate_settings(settings: &serde_json::Value) -> bool { - let Ok(old_text) = serde_json::to_string(settings) else { - return false; - }; - migrator::migrate_settings(&old_text) - .ok() - .flatten() - .is_some() +use std::sync::Arc; + +use gpui::{Entity, EventEmitter, Global}; +use ui::prelude::*; +use workspace::item::ItemHandle; +use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum MigrationType { + Keymap, + Settings, +} + +pub struct MigrationBanner { + migration_type: Option, +} + +pub enum MigrationEvent { + ContentChanged { + migration_type: MigrationType, + migrated: bool, + }, +} + +pub struct MigrationNotification; + +impl EventEmitter for MigrationNotification {} + +impl MigrationNotification { + pub fn try_global(cx: &App) -> Option> { + cx.try_global::() + .map(|notifier| notifier.0.clone()) + } + + pub fn set_global(notifier: Entity, cx: &mut App) { + cx.set_global(GlobalMigrationNotification(notifier)); + } } -pub fn migrate_settings(fs: Arc, cx: &mut gpui::App) { - cx.background_executor() - .spawn(async move { - let old_text = SettingsStore::load_settings(&fs).await?; - let Some(new_text) = migrator::migrate_settings(&old_text)? else { - return anyhow::Ok(()); - }; - let settings_path = paths::settings_file().as_path(); - if fs.is_file(settings_path).await { - fs.atomic_write(paths::settings_backup_file().to_path_buf(), old_text) - .await - .with_context(|| { - "Failed to create settings backup in home directory".to_string() - })?; - let resolved_path = fs.canonicalize(settings_path).await.with_context(|| { - format!("Failed to canonicalize settings path {:?}", settings_path) - })?; - fs.atomic_write(resolved_path.clone(), new_text) - .await - .with_context(|| { - format!("Failed to write settings to file {:?}", resolved_path) - })?; - } else { - fs.atomic_write(settings_path.to_path_buf(), new_text) - .await - .with_context(|| { - format!("Failed to write settings to file {:?}", settings_path) - })?; +struct GlobalMigrationNotification(Entity); + +impl Global for GlobalMigrationNotification {} + +impl MigrationBanner { + pub fn new(_: &Workspace, cx: &mut Context<'_, Self>) -> Self { + if let Some(notifier) = MigrationNotification::try_global(cx) { + cx.subscribe( + ¬ifier, + move |migrator_banner, _, event: &MigrationEvent, cx| { + migrator_banner.handle_notification(event, cx); + }, + ) + .detach(); + } + Self { + migration_type: None, + } + } + + fn backup_file_name(&self) -> String { + match self.migration_type { + Some(MigrationType::Keymap) => paths::keymap_backup_file() + .file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned(), + Some(MigrationType::Settings) => paths::settings_backup_file() + .file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned(), + None => String::new(), + } + } + + fn handle_notification(&mut self, event: &MigrationEvent, cx: &mut Context<'_, Self>) { + match event { + MigrationEvent::ContentChanged { + migration_type, + migrated, + } => { + if self.migration_type == Some(*migration_type) { + let location = if *migrated { + ToolbarItemLocation::Secondary + } else { + ToolbarItemLocation::Hidden + }; + cx.emit(ToolbarItemEvent::ChangeLocation(location)); + cx.notify(); + } } - Ok(()) - }) - .detach_and_log_err(cx); + } + } } -pub fn should_migrate_keymap(keymap_file: KeymapFile) -> bool { - let Ok(old_text) = serde_json::to_string(&keymap_file) else { - return false; - }; - migrator::migrate_keymap(&old_text).ok().flatten().is_some() +impl EventEmitter for MigrationBanner {} + +impl ToolbarItemView for MigrationBanner { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + window: &mut Window, + cx: &mut Context, + ) -> ToolbarItemLocation { + cx.notify(); + let Some(target) = active_pane_item + .and_then(|item| item.act_as::(cx)) + .and_then(|editor| editor.update(cx, |editor, cx| editor.target_file_abs_path(cx))) + else { + return ToolbarItemLocation::Hidden; + }; + + if &target == paths::keymap_file() { + self.migration_type = Some(MigrationType::Keymap); + let fs = ::global(cx); + let should_migrate = should_migrate_keymap(fs); + cx.spawn_in(window, |this, mut cx| async move { + if let Ok(true) = should_migrate.await { + this.update(&mut cx, |_, cx| { + cx.emit(ToolbarItemEvent::ChangeLocation( + ToolbarItemLocation::Secondary, + )); + cx.notify(); + }) + .log_err(); + } + }) + .detach(); + } else if &target == paths::settings_file() { + self.migration_type = Some(MigrationType::Settings); + let fs = ::global(cx); + let should_migrate = should_migrate_settings(fs); + cx.spawn_in(window, |this, mut cx| async move { + if let Ok(true) = should_migrate.await { + this.update(&mut cx, |_, cx| { + cx.emit(ToolbarItemEvent::ChangeLocation( + ToolbarItemLocation::Secondary, + )); + cx.notify(); + }) + .log_err(); + } + }) + .detach(); + } + + return ToolbarItemLocation::Hidden; + } +} + +impl Render for MigrationBanner { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let migration_type = self.migration_type; + let file_type = match migration_type { + Some(MigrationType::Keymap) => "keymap", + Some(MigrationType::Settings) => "settings", + None => "", + }; + let backup_file_name = self.backup_file_name(); + + h_flex() + .py_1() + .pl_2() + .pr_1() + .flex_wrap() + .justify_between() + .bg(cx.theme().status().info_background.opacity(0.6)) + .border_1() + .border_color(cx.theme().colors().border_variant) + .rounded_md() + .overflow_hidden() + .child( + h_flex() + .gap_2() + .child( + Icon::new(IconName::Warning) + .size(IconSize::XSmall) + .color(Color::Warning), + ) + .child( + h_flex() + .gap_0p5() + .child( + Label::new(format!( + "Your {} file uses deprecated settings which can be \ + automatically updated. A backup will be saved to", + file_type + )) + .color(Color::Default), + ) + .child( + div() + .px_1() + .bg(cx.theme().colors().background) + .rounded_sm() + .child( + Label::new(backup_file_name) + .buffer_font(cx) + .size(LabelSize::Small), + ), + ), + ), + ) + .child( + Button::new("backup-and-migrate", "Backup and Update").on_click(move |_, _, cx| { + let fs = ::global(cx); + match migration_type { + Some(MigrationType::Keymap) => { + cx.spawn( + move |_| async move { write_keymap_migration(&fs).await.ok() }, + ) + .detach(); + } + Some(MigrationType::Settings) => { + cx.spawn( + move |_| async move { write_settings_migration(&fs).await.ok() }, + ) + .detach(); + } + None => unreachable!(), + } + }), + ) + .into_any_element() + } } -pub async fn migrate_keymap(fs: Arc) -> anyhow::Result<()> { +async fn should_migrate_keymap(fs: Arc) -> Result { let old_text = KeymapFile::load_keymap_file(&fs).await?; - let Some(new_text) = migrator::migrate_keymap(&old_text)? else { + if let Ok(Some(_)) = migrate_keymap(&old_text) { + return Ok(true); + }; + Ok(false) +} + +async fn should_migrate_settings(fs: Arc) -> Result { + let old_text = SettingsStore::load_settings(&fs).await?; + if let Ok(Some(_)) = migrate_settings(&old_text) { + return Ok(true); + }; + Ok(false) +} + +async fn write_keymap_migration(fs: &Arc) -> Result<()> { + let old_text = KeymapFile::load_keymap_file(fs).await?; + let Ok(Some(new_text)) = migrate_keymap(&old_text) else { return Ok(()); }; let keymap_path = paths::keymap_file().as_path(); @@ -77,6 +270,30 @@ pub async fn migrate_keymap(fs: Arc) -> anyhow::Result<()> { .await .with_context(|| format!("Failed to write keymap to file {:?}", keymap_path))?; } + Ok(()) +} +async fn write_settings_migration(fs: &Arc) -> Result<()> { + let old_text = SettingsStore::load_settings(fs).await?; + let Ok(Some(new_text)) = migrate_settings(&old_text) else { + return Ok(()); + }; + let settings_path = paths::settings_file().as_path(); + if fs.is_file(settings_path).await { + fs.atomic_write(paths::settings_backup_file().to_path_buf(), old_text) + .await + .with_context(|| "Failed to create settings backup in home directory".to_string())?; + let resolved_path = fs + .canonicalize(settings_path) + .await + .with_context(|| format!("Failed to canonicalize settings path {:?}", settings_path))?; + fs.atomic_write(resolved_path.clone(), new_text) + .await + .with_context(|| format!("Failed to write settings to file {:?}", resolved_path))?; + } else { + fs.atomic_write(settings_path.to_path_buf(), new_text) + .await + .with_context(|| format!("Failed to write settings to file {:?}", settings_path))?; + } Ok(()) } diff --git a/crates/zeta/src/onboarding_modal.rs b/crates/zeta/src/onboarding_modal.rs index 7ba7f4b50bfa75d0d9ff38ac81ce2978350eaf4e..435b8be5fc95aaa9e371877d4d30ec6834dd1cbb 100644 --- a/crates/zeta/src/onboarding_modal.rs +++ b/crates/zeta/src/onboarding_modal.rs @@ -168,7 +168,7 @@ impl Render for ZedPredictModal { .id("edit-prediction-onboarding") .key_context("ZedPredictModal") .relative() - .w(px(440.)) + .w(px(480.)) .h_full() .max_h(max_height) .p_4() @@ -201,7 +201,7 @@ impl Render for ZedPredictModal { svg() .path("icons/zed_predict_bg.svg") .text_color(cx.theme().colors().icon_disabled) - .w(px(418.)) + .w(px(460.)) .h(px(128.)) .overflow_hidden(), ), @@ -354,7 +354,7 @@ impl Render for ZedPredictModal { "training-data-checkbox", self.data_collection_opted_in.into(), ) - .label("Optionally share training data (OSS-only).") + .label("Open source repos: optionally share training data.") .fill() .on_click(cx.listener( move |this, state, _window, cx| { @@ -391,26 +391,27 @@ impl Render for ZedPredictModal { .border_color(cx.theme().colors().border_variant) .child( div().child( - Label::new("To improve edit predictions, help fine-tune Zed's model by sharing data from the open-source projects you work on.") + Label::new("To improve edit predictions, please consider contributing to our open dataset based on your interactions within open source repositories.") .mb_1() ) ) .child(info_item( - "We ask this exclusively for open-source projects.", + "We ask this exclusively for open source projects.", )) .child(info_item( - "Zed automatically detects if your project is open-source.", - )) - .child(info_item( - "This setting is valid for all OSS projects you open in Zed.", + "Zed automatically detects if your project is open source.", )) .child(info_item("Toggle it anytime via the status bar menu.")) .child(multiline_info_item( - "Files with sensitive data, like `.env`, are excluded", + "If turned on, this setting is valid for all open source projects", + label_item("you open in Zed.") + )) + .child(multiline_info_item( + "Files with sensitive data, like `.env`, are excluded by default", h_flex() .w_full() .flex_wrap() - .child(label_item("by default via the")) + .child(label_item("via the")) .child( Button::new("doc-link", "disabled_globs").on_click( cx.listener(Self::inline_completions_doc), diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 91cd144406937d67d78b3f72b604eb7a5d70ae9e..e1817cca02c5a18cecc1d44098da950ea540af26 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -1574,7 +1574,7 @@ Or to set a `socks5` proxy: ### Modal Max Width - Description: Max-width of the file finder modal. It can take one of these values: `small`, `medium`, `large`, `xlarge`, and `full`. -- Setting: `max_modal_width` +- Setting: `modal_max_width` - Default: `small` ## Preferred Line Length