Settings refactor (#38367)

Co-Authored-By: Ben K <ben@zed.dev>
Co-Authored-By: Anthony <anthony@zed.dev>
Co-Authored-By: Mikayla <mikayla@zed.dev>

Release Notes:

- settings: Major internal changes to settings. The primary user-facing
effect is that some settings which did not make sense in project
settings files are no-longer read from there. (For example the inline
blame settings)

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Conrad Irwin
2025-09-18 16:47:23 +00:00
committed by GitHub
co-authored by Ben Kunkle Mikayla Maki Anthony
parent 0a9023bce0
commit fcdab160f9
219 changed files with 11697 additions and 11894 deletions
@@ -700,7 +700,7 @@ mod tests {
use dap::debugger_settings::DebuggerSettings;
use editor::Editor;
use gpui::{TestAppContext, UpdateGlobal, WindowHandle};
use project::{Project, project_settings::ProjectSettings};
use project::Project;
use serde_json::json;
use settings::SettingsStore;
use util::path;
@@ -714,8 +714,11 @@ mod tests {
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<ProjectSettings>(cx, |settings| {
settings.session.restore_unsaved_buffers = false
store.update_user_settings(cx, |settings| {
settings
.session
.get_or_insert_default()
.restore_unsaved_buffers = Some(false)
});
});
});
@@ -1,4 +1,3 @@
use std::collections::BTreeSet;
use std::{path::PathBuf, sync::Arc};
use anyhow::{Context as _, Result};
@@ -17,35 +16,32 @@ use markdown::{Markdown, MarkdownElement, MarkdownStyle};
use release_channel::ReleaseChannel;
use remote::{
ConnectionIdentifier, RemoteClient, RemoteConnectionOptions, RemotePlatform,
SshConnectionOptions, SshPortForwardOption, WslConnectionOptions,
SshConnectionOptions,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
pub use settings::SshConnection;
use settings::{Settings, WslConnection};
use theme::ThemeSettings;
use ui::{
ActiveTheme, Color, CommonAnimationExt, Context, Icon, IconName, IconSize, InteractiveElement,
IntoElement, Label, LabelCommon, Styled, Window, prelude::*,
};
use util::serde::default_true;
use util::MergeFrom;
use workspace::{AppState, ModalView, Workspace};
#[derive(Deserialize)]
pub struct SshSettings {
pub ssh_connections: Option<Vec<SshConnection>>,
pub wsl_connections: Option<Vec<WslConnection>>,
pub ssh_connections: Vec<SshConnection>,
pub wsl_connections: Vec<WslConnection>,
/// Whether to read ~/.ssh/config for ssh connection sources.
#[serde(default = "default_true")]
pub read_ssh_config: bool,
}
impl SshSettings {
pub fn ssh_connections(&self) -> impl Iterator<Item = SshConnection> + use<> {
self.ssh_connections.clone().into_iter().flatten()
self.ssh_connections.clone().into_iter()
}
pub fn wsl_connections(&self) -> impl Iterator<Item = WslConnection> + use<> {
self.wsl_connections.clone().into_iter().flatten()
self.wsl_connections.clone().into_iter()
}
pub fn fill_connection_options_from_settings(&self, options: &mut SshConnectionOptions) {
@@ -80,66 +76,7 @@ impl SshSettings {
}
}
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct SshConnection {
pub host: SharedString,
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub projects: BTreeSet<SshProject>,
/// Name to use for this server in UI.
#[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>,
// By default Zed will download the binary to the host directly.
// If this is set to true, Zed will download the binary to your local machine,
// and then upload it over the SSH connection. Useful if your SSH server has
// limited outbound internet access.
#[serde(skip_serializing_if = "Option::is_none")]
pub upload_binary_over_ssh: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port_forwards: Option<Vec<SshPortForwardOption>>,
}
impl From<SshConnection> for SshConnectionOptions {
fn from(val: SshConnection) -> Self {
SshConnectionOptions {
host: val.host.into(),
username: val.username,
port: val.port,
password: None,
args: Some(val.args),
nickname: val.nickname,
upload_binary_over_ssh: val.upload_binary_over_ssh.unwrap_or_default(),
port_forwards: val.port_forwards,
}
}
}
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
pub struct WslConnection {
pub distro_name: SharedString,
#[serde(default)]
pub user: Option<String>,
#[serde(default)]
pub projects: BTreeSet<SshProject>,
}
impl From<WslConnection> for WslConnectionOptions {
fn from(val: WslConnection) -> Self {
WslConnectionOptions {
distro_name: val.distro_name.into(),
user: val.user,
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
#[derive(Clone, PartialEq)]
pub enum Connection {
Ssh(SshConnection),
Wsl(WslConnection),
@@ -166,27 +103,26 @@ impl From<WslConnection> for Connection {
}
}
#[derive(Clone, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema)]
pub struct SshProject {
pub paths: Vec<String>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct RemoteSettingsContent {
pub ssh_connections: Option<Vec<SshConnection>>,
pub wsl_connections: Option<Vec<WslConnection>>,
pub read_ssh_config: Option<bool>,
}
impl Settings for SshSettings {
type FileContent = RemoteSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
sources.json_merge()
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let remote = &content.remote;
Self {
ssh_connections: remote.ssh_connections.clone().unwrap_or_default(),
wsl_connections: remote.wsl_connections.clone().unwrap_or_default(),
read_ssh_config: remote.read_ssh_config.unwrap(),
}
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
if let Some(ssh_connections) = content.remote.ssh_connections.clone() {
self.ssh_connections.extend(ssh_connections)
}
if let Some(wsl_connections) = content.remote.wsl_connections.clone() {
self.wsl_connections.extend(wsl_connections)
}
self.read_ssh_config
.merge_from(&content.remote.read_ssh_config);
}
}
pub struct RemoteConnectionPrompt {
+34 -44
View File
@@ -1,8 +1,7 @@
use crate::{
remote_connections::{
Connection, RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettingsContent,
SshConnection, SshConnectionHeader, SshProject, SshSettings, connect, connect_over_ssh,
open_remote_project,
Connection, RemoteConnectionModal, RemoteConnectionPrompt, SshConnection,
SshConnectionHeader, SshSettings, connect, connect_over_ssh, open_remote_project,
},
ssh_config::parse_ssh_config_hosts,
};
@@ -22,7 +21,10 @@ use remote::{
RemoteClient, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions,
remote_client::ConnectionIdentifier,
};
use settings::{Settings, SettingsStore, update_settings_file, watch_config_file};
use settings::{
RemoteSettingsContent, Settings as _, SettingsStore, SshProject, update_settings_file,
watch_config_file,
};
use smol::stream::StreamExt as _;
use std::{
borrow::Cow,
@@ -233,14 +235,15 @@ impl ProjectPicker {
cx.update(|_, cx| {
let fs = app_state.fs.clone();
update_settings_file::<SshSettings>(fs, cx, {
update_settings_file(fs, cx, {
let paths = paths
.iter()
.map(|path| path.to_string_lossy().to_string())
.collect();
move |setting, _| match index {
move |settings, _| match index {
ServerIndex::Ssh(index) => {
if let Some(server) = setting
if let Some(server) = settings
.remote
.ssh_connections
.as_mut()
.and_then(|connections| connections.get_mut(index.0))
@@ -249,7 +252,8 @@ impl ProjectPicker {
};
}
ServerIndex::Wsl(index) => {
if let Some(server) = setting
if let Some(server) = settings
.remote
.wsl_connections
.as_mut()
.and_then(|connections| connections.get_mut(index.0))
@@ -1317,7 +1321,7 @@ impl RemoteServerProjects {
else {
return;
};
update_settings_file::<SshSettings>(fs, cx, move |setting, cx| f(setting, cx));
update_settings_file(fs, cx, move |setting, cx| f(&mut setting.remote, cx));
}
fn delete_ssh_server(&mut self, server: SshServerIndex, cx: &mut Context<Self>) {
@@ -1356,7 +1360,7 @@ impl RemoteServerProjects {
setting
.wsl_connections
.get_or_insert(Default::default())
.push(crate::remote_connections::WslConnection {
.push(settings::WslConnection {
distro_name: SharedString::from(connection_options.distro_name),
user: connection_options.user,
projects: BTreeSet::new(),
@@ -1881,41 +1885,27 @@ impl RemoteServerProjects {
let ssh_settings = SshSettings::get_global(cx);
let mut should_rebuild = false;
let ssh_connections_changed =
ssh_settings
.ssh_connections
.as_ref()
.is_some_and(|connections| {
state
.servers
.iter()
.filter_map(|server| match server {
RemoteEntry::Project {
connection: Connection::Ssh(connection),
..
} => Some(connection),
_ => None,
})
.ne(connections.iter())
});
let ssh_connections_changed = ssh_settings.ssh_connections.iter().ne(state
.servers
.iter()
.filter_map(|server| match server {
RemoteEntry::Project {
connection: Connection::Ssh(connection),
..
} => Some(connection),
_ => None,
}));
let wsl_connections_changed =
ssh_settings
.wsl_connections
.as_ref()
.is_some_and(|connections| {
state
.servers
.iter()
.filter_map(|server| match server {
RemoteEntry::Project {
connection: Connection::Wsl(connection),
..
} => Some(connection),
_ => None,
})
.ne(connections.iter())
});
let wsl_connections_changed = ssh_settings.wsl_connections.iter().ne(state
.servers
.iter()
.filter_map(|server| match server {
RemoteEntry::Project {
connection: Connection::Wsl(connection),
..
} => Some(connection),
_ => None,
}));
if ssh_connections_changed || wsl_connections_changed {
should_rebuild = true;