Detailed changes
@@ -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"
@@ -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"] }
@@ -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",
@@ -1,6 +1,6 @@
-<svg width="440" height="128" xmlns="http://www.w3.org/2000/svg">
+<svg width="480" height="128" xmlns="http://www.w3.org/2000/svg">
<defs>
- <pattern id="tilePattern" width="22" height="22" patternUnits="userSpaceOnUse">
+ <pattern id="tilePattern" width="23" height="23" patternUnits="userSpaceOnUse">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
@@ -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"
@@ -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
@@ -146,8 +146,6 @@ pub fn init_settings(cx: &mut App) {
}
pub fn init(client: &Arc<Client>, 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(
@@ -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<InlineCompletionState>,
edit_prediction_settings: EditPredictionSettings,
+ edit_prediction_cursor_on_leading_whitespace: bool,
inline_completions_hidden_for_vim_mode: bool,
show_inline_completions_override: Option<bool>,
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<Self>,
) {
- 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<Self>) -> Option<PathBuf> {
+ pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
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)))
+ }
})
}
@@ -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, |_| {});
@@ -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
@@ -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<rustls::ClientConfig> = 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]
@@ -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,
@@ -4236,6 +4236,21 @@ impl MultiBufferSnapshot {
indent
}
+ pub fn is_line_whitespace_upto<T>(&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<MultiBufferRow> {
while row.0 > 0 {
row.0 -= 1;
@@ -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)
@@ -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<BTreeMap<TypeId, Box<dyn KeyB
/// Keymap configuration consisting of sections. Each section may have a context predicate which
/// determines whether its bindings are used.
-#[derive(Debug, Deserialize, Default, Clone, JsonSchema, Serialize)]
+#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
#[serde(transparent)]
pub struct KeymapFile(Vec<KeymapSection>);
/// 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<KeyBinding>,
- keymap_file: KeymapFile,
},
SomeFailedToLoad {
key_bindings: Vec<KeyBinding>,
- keymap_file: KeymapFile,
error_message: MarkdownString,
},
JsonParseFailure {
@@ -152,7 +150,7 @@ impl KeymapFile {
pub fn load_asset(asset_path: &str, cx: &App) -> anyhow::Result<Vec<KeyBinding>> {
match Self::load(asset_str::<SettingsAssets>(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::<Self>(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() {
@@ -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<String>,
- cx: &mut App,
- settings_changed: impl Fn(Result<serde_json::Value, anyhow::Error>, &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<T: Settings>(
fs: Arc<dyn Fs>,
cx: &App,
@@ -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"),
@@ -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 {
}
}
},
- ))),
+ )))
),
)
}
@@ -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)]
@@ -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<String>,
+ cx: &mut App,
+ settings_changed: impl Fn(Option<anyhow::Error>, &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<String>,
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 = <dyn 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 = <dyn 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<serde_json::Value, anyhow::Error>, cx: &mut App) {
+pub fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut App) {
struct SettingsParseErrorNotification;
let id = NotificationId::unique::<SettingsParseErrorNotification>();
- match result {
- Err(error) => {
+ match error {
+ Some(error) => {
if let Some(InvalidSettingsError::LocalSettings { .. }) =
error.downcast_ref::<InvalidSettingsError>()
{
@@ -1387,9 +1392,8 @@ pub fn handle_settings_changed(result: Result<serde_json::Value, anyhow::Error>,
})
});
}
- 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,
@@ -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<MigrationType>,
+}
+
+pub enum MigrationEvent {
+ ContentChanged {
+ migration_type: MigrationType,
+ migrated: bool,
+ },
+}
+
+pub struct MigrationNotification;
+
+impl EventEmitter<MigrationEvent> for MigrationNotification {}
+
+impl MigrationNotification {
+ pub fn try_global(cx: &App) -> Option<Entity<Self>> {
+ cx.try_global::<GlobalMigrationNotification>()
+ .map(|notifier| notifier.0.clone())
+ }
+
+ pub fn set_global(notifier: Entity<Self>, cx: &mut App) {
+ cx.set_global(GlobalMigrationNotification(notifier));
+ }
}
-pub fn migrate_settings(fs: Arc<dyn Fs>, 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<MigrationNotification>);
+
+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<ToolbarItemEvent> 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<Self>,
+ ) -> ToolbarItemLocation {
+ cx.notify();
+ let Some(target) = active_pane_item
+ .and_then(|item| item.act_as::<Editor>(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 = <dyn 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 = <dyn 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<Self>) -> 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 = <dyn 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<dyn Fs>) -> anyhow::Result<()> {
+async fn should_migrate_keymap(fs: Arc<dyn Fs>) -> Result<bool> {
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<dyn Fs>) -> Result<bool> {
+ 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<dyn Fs>) -> 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<dyn Fs>) -> anyhow::Result<()> {
.await
.with_context(|| format!("Failed to write keymap to file {:?}", keymap_path))?;
}
+ Ok(())
+}
+async fn write_settings_migration(fs: &Arc<dyn Fs>) -> 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(())
}
@@ -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),
@@ -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