diff --git a/Cargo.lock b/Cargo.lock index f599385230bac594ad5759ae3f90b53217501979..c98a90bb09f0f8b4e9f43facb6bb67c5df2f8e40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13507,6 +13507,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "settings", "shlex", "smol", "tempfile", diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index cf92c58df2a5a7d582036e1ed21a658223d55863..e5744458601b7fd208bd97c11da7d136cb329f05 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -8,8 +8,8 @@ use language_model::LanguageModel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{ - DefaultAgentView, LanguageModelParameters, LanguageModelSelection, NotifyWhenAgentWaiting, - Settings, SettingsContent, + DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection, + NotifyWhenAgentWaiting, Settings, SettingsContent, }; use util::MergeFrom; @@ -24,7 +24,7 @@ pub fn init(cx: &mut App) { AgentSettings::register(cx); } -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug)] pub struct AgentSettings { pub enabled: bool, pub button: bool, diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index faa6b524ff49fffbd5c8113e164b2c9ab158d71a..bbb84de4bd6b806c7fa095efe7301aac5ea3ab6b 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -413,8 +413,8 @@ impl AgentConfiguration { always_allow_tool_actions, move |state, _window, cx| { let allow = state == &ToggleState::Selected; - update_settings_file::(fs.clone(), cx, move |settings, _| { - settings.set_always_allow_tool_actions(allow); + update_settings_file(fs.clone(), cx, move |settings, _| { + settings.agent.get_or_insert_default.set_always_allow_tool_actions(allow); }); }, ) @@ -431,8 +431,11 @@ impl AgentConfiguration { single_file_review, move |state, _window, cx| { let allow = state == &ToggleState::Selected; - update_settings_file::(fs.clone(), cx, move |settings, _| { - settings.set_single_file_review(allow); + update_settings_file(fs.clone(), cx, move |settings, _| { + settings + .agent + .get_or_insert_default() + .set_single_file_review(allow); }); }, ) @@ -1279,7 +1282,7 @@ async fn open_new_agent_servers_entry_in_settings_editor( let settings = cx.global::(); let mut unique_server_name = None; - let edits = settings.edits_for_update::(&text, |file| { + let edits = settings.edits_for_update(&text, |settings| { let server_name: Option = (0..u8::MAX) .map(|i| { if i == 0 { @@ -1288,20 +1291,26 @@ async fn open_new_agent_servers_entry_in_settings_editor( format!("your_agent_{}", i).into() } }) - .find(|name| !file.custom.contains_key(name)); + .find(|name| { + !settings + .agent_servers + .is_some_and(|agent_servers| agent_servers.custom.contains_key(name)) + }); if let Some(server_name) = server_name { unique_server_name = Some(server_name.clone()); - file.custom.insert( - server_name, - CustomAgentServerSettings { - command: AgentServerCommand { + settings + .agent_servers + .get_or_insert_default() + .custom + .insert( + server_name, + settings::CustomAgentServerSettings { path: "path_to_executable".into(), args: vec![], env: Some(HashMap::default()), + default_mode: None, }, - default_mode: None, - }, - ); + ); } }); diff --git a/crates/agent_ui/src/agent_configuration/tool_picker.rs b/crates/agent_ui/src/agent_configuration/tool_picker.rs index 2ba92fa6b7993664d278cfd57d851dcfd9cb0922..c624948944c0624e75e385d1b4b15aa77fea9bcd 100644 --- a/crates/agent_ui/src/agent_configuration/tool_picker.rs +++ b/crates/agent_ui/src/agent_configuration/tool_picker.rs @@ -1,14 +1,11 @@ use std::{collections::BTreeMap, sync::Arc}; -use agent_settings::{ - AgentProfileContent, AgentProfileId, AgentProfileSettings, AgentSettings, AgentSettingsContent, - ContextServerPresetContent, -}; +use agent_settings::{AgentProfileId, AgentProfileSettings}; use assistant_tool::{ToolSource, ToolWorkingSet}; use fs::Fs; use gpui::{App, Context, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, Window}; use picker::{Picker, PickerDelegate}; -use settings::update_settings_file; +use settings::{AgentProfileContent, ContextServerPresetContent, update_settings_file}; use ui::{ListItem, ListItemSpacing, prelude::*}; use util::ResultExt as _; @@ -266,15 +263,19 @@ impl PickerDelegate for ToolPickerDelegate { is_enabled }; - update_settings_file::(self.fs.clone(), cx, { + update_settings_file(self.fs.clone(), cx, { let profile_id = self.profile_id.clone(); let default_profile = self.profile_settings.clone(); let server_id = server_id.clone(); let tool_name = tool_name.clone(); - move |settings: &mut AgentSettingsContent, _cx| { - let profiles = settings.profiles.get_or_insert_default(); + move |settings, _cx| { + let profiles = settings + .agent + .get_or_insert_default() + .profiles + .get_or_insert_default(); let profile = profiles - .entry(profile_id) + .entry(profile_id.0) .or_insert_with(|| AgentProfileContent { name: default_profile.name.into(), tools: default_profile.tools, diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index bc4b345d81d8a20d9c1d7f27bbce55dca2d700d7..576a523fb05b41db3eaf7e02d844aa8720db8a24 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -1059,17 +1059,14 @@ impl AgentPanel { match self.active_view.which_font_size_used() { WhichFontSize::AgentFont => { if persist { - update_settings_file::( - self.fs.clone(), - cx, - move |settings, cx| { - let agent_font_size = - ThemeSettings::get_global(cx).agent_font_size(cx) + delta; - let _ = settings - .agent_font_size - .insert(Some(theme::clamp_font_size(agent_font_size).into())); - }, - ); + update_settings_file(self.fs.clone(), cx, move |settings, cx| { + let agent_font_size = + ThemeSettings::get_global(cx).agent_font_size(cx) + delta; + let _ = settings + .theme + .agent_font_size + .insert(Some(theme::clamp_font_size(agent_font_size).into())); + }); } else { theme::adjust_agent_font_size(cx, |size| size + delta); } @@ -1176,11 +1173,9 @@ impl AgentPanel { .is_none_or(|model| model.provider.id() != provider.id()) && let Some(model) = provider.default_model(cx) { - update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| settings.set_model(model), - ); + update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings.agent.get_or_insert_default().set_model(model) + }); } self.new_thread(&NewThread::default(), window, cx); @@ -1425,7 +1420,7 @@ impl Focusable for AgentPanel { } fn agent_panel_dock_position(cx: &App) -> DockPosition { - AgentSettings::get_global(cx).dock + AgentSettings::get_global(cx).dock.into() } impl EventEmitter for AgentPanel {} @@ -1444,13 +1439,11 @@ impl Panel for AgentPanel { } fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context) { - settings::update_settings_file::(self.fs.clone(), cx, move |settings, _| { - let dock = match position { - DockPosition::Left => AgentDockPosition::Left, - DockPosition::Bottom => AgentDockPosition::Bottom, - DockPosition::Right => AgentDockPosition::Right, - }; - settings.set_dock(dock); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings + .agent + .get_or_insert_default() + .set_dock(position.into()); }); } diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index d979db5e0468b696d32ed755aec1ef47e2fd3df3..b40b996ae74f93220d88c97e8ae7d99dd8576cf1 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -3,7 +3,7 @@ use crate::{ language_model_selector::{LanguageModelSelector, language_model_selector}, ui::BurnModeTooltip, }; -use agent_settings::{AgentSettings, CompletionMode}; +use agent_settings::CompletionMode; use anyhow::Result; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet}; use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections_creases}; @@ -41,7 +41,10 @@ use project::{Project, Worktree}; use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate}; use rope::Point; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore, update_settings_file}; +use settings::{ + LanguageModelProviderSetting, LanguageModelSelection, Settings, SettingsStore, + update_settings_file, +}; use std::{ any::TypeId, cmp, @@ -294,11 +297,16 @@ impl TextThreadEditor { language_model_selector( |cx| LanguageModelRegistry::read_global(cx).default_model(), move |model, cx| { - update_settings_file::( - fs.clone(), - cx, - move |settings, _| settings.set_model(model.clone()), - ); + update_settings_file(fs.clone(), cx, move |settings, _| { + let provider = model.provider_id().0.to_string(); + let model = model.id().0.to_string(); + settings.agent.get_or_insert_default().set_model( + LanguageModelSelection { + provider: LanguageModelProviderSetting(provider), + model: model.clone(), + }, + ) + }); }, window, cx, diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index a3f63c527693a19bb7ac1cd87c104cee3d5cfa6e..a5255f419b38dfc14dac78177f87769b428fe31b 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -1789,8 +1789,8 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, show_value_hints: true, edit_debounce_ms: 0, @@ -1806,8 +1806,8 @@ async fn test_mutual_editor_inlay_hint_cache_update( }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.inlay_hints = Some(InlayHintSettings { show_value_hints: true, enabled: true, edit_debounce_ms: 0, @@ -2039,8 +2039,8 @@ async fn test_inlay_hint_refresh_is_forwarded( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.inlay_hints = Some(InlayHintSettings { show_value_hints: true, enabled: false, edit_debounce_ms: 0, @@ -2056,8 +2056,8 @@ async fn test_inlay_hint_refresh_is_forwarded( }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { + store.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.inlay_hints = Some(InlayHintSettings { show_value_hints: true, enabled: true, edit_debounce_ms: 0, @@ -2242,14 +2242,14 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |settings| { - settings.lsp_document_colors = Some(DocumentColorsRenderMode::None); + settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None); }); }); }); cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |settings| { - settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay); + settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay); }); }); }); diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 8f501b2f970019c24c36e65fa94099b80454dfe2..c14e1a6be26332a776107d604e7ed2c7b4c04be5 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -4846,17 +4846,13 @@ impl Panel for OutlinePanel { } fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context) { - settings::update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| { - let dock = match position { - DockPosition::Left | DockPosition::Bottom => OutlinePanelDockPosition::Left, - DockPosition::Right => OutlinePanelDockPosition::Right, - }; - settings.dock = Some(dock); - }, - ); + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + let dock = match position { + DockPosition::Left | DockPosition::Bottom => OutlinePanelDockPosition::Left, + DockPosition::Right => OutlinePanelDockPosition::Right, + }; + settings.outline_panel.get_or_insert_default().dock = Some(dock); + }); } fn size(&self, _: &Window, cx: &App) -> Pixels { diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index aa0ce7661b29123c25fdf20cbde5f53e6525d2d6..013c8f6724b55861add48ff94c8e79e4e5bb4756 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -626,7 +626,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; @@ -640,8 +640,11 @@ mod tests { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(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) }); }); }); diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index 3e6810239c80c72d74624bcc243157290fcd93fa..5fd52ada77e28ba3afb2b5b6337fe02f5a95a029 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeSet; use std::{path::PathBuf, sync::Arc}; use anyhow::{Context as _, Result}; @@ -17,30 +16,26 @@ use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use release_channel::ReleaseChannel; use remote::{ ConnectionIdentifier, RemoteClient, RemoteConnectionOptions, RemotePlatform, - SshConnectionOptions, SshPortForwardOption, + SshConnectionOptions, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::Settings; +pub use settings::SshConnection; 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>, - /// Whether to read ~/.ssh/config for ssh connection sources. - #[serde(default = "default_true")] + pub ssh_connections: Vec, pub read_ssh_config: bool, } impl SshSettings { pub fn ssh_connections(&self) -> impl Iterator + use<> { - self.ssh_connections.clone().into_iter().flatten() + self.ssh_connections.clone().into_iter() } pub fn fill_connection_options_from_settings(&self, options: &mut SshConnectionOptions) { @@ -75,67 +70,22 @@ 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, - #[serde(skip_serializing_if = "Option::is_none")] - pub port: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(default)] - pub args: Vec, - #[serde(default)] - pub projects: BTreeSet, - /// Name to use for this server in UI. - #[serde(skip_serializing_if = "Option::is_none")] - pub nickname: Option, - // 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, - - #[serde(skip_serializing_if = "Option::is_none")] - pub port_forwards: Option>, -} - -impl From 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, +impl Settings for SshSettings { + fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + let remote = &content.remote; + Self { + ssh_connections: remote.ssh_connections.clone().unwrap_or_default(), + read_ssh_config: remote.read_ssh_config.unwrap(), } } -} - -#[derive(Clone, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema)] -pub struct SshProject { - pub paths: Vec, -} -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] -pub struct RemoteSettingsContent { - pub ssh_connections: Option>, - pub read_ssh_config: Option, -} - -impl Settings for SshSettings { - type FileContent = RemoteSettingsContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - sources.json_merge() + 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) + } + self.read_ssh_config + .merge_from(&content.remote.read_ssh_config); } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} } pub struct RemoteConnectionPrompt { diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 39032642b887350730c16a12d696253c256cfd72..d100c33905b89cff1b13bf6e1d0d1add4cf84605 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -1,7 +1,7 @@ use crate::{ remote_connections::{ - RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettingsContent, SshConnection, - SshConnectionHeader, SshProject, SshSettings, connect_over_ssh, open_remote_project, + RemoteConnectionModal, RemoteConnectionPrompt, SshConnection, SshConnectionHeader, + SshSettings, connect_over_ssh, open_remote_project, }, ssh_config::parse_ssh_config_hosts, }; @@ -20,7 +20,10 @@ use remote::{ RemoteClient, RemoteConnectionOptions, SshConnectionOptions, remote_client::ConnectionIdentifier, }; -use settings::{Settings, SettingsStore, update_settings_file, watch_config_file}; +use settings::{ + RemoteSettingsContent, Settings, SettingsStore, SshProject, update_settings_file, + watch_config_file, +}; use smol::stream::StreamExt as _; use std::{ borrow::Cow, @@ -173,13 +176,14 @@ impl ProjectPicker { cx.update(|_, cx| { let fs = app_state.fs.clone(); - update_settings_file::(fs, cx, { + update_settings_file(fs, cx, { let paths = paths .iter() .map(|path| path.to_string_lossy().to_string()) .collect(); move |setting, _| { if let Some(server) = setting + .remote .ssh_connections .as_mut() .and_then(|connections| connections.get_mut(ix)) @@ -987,7 +991,7 @@ impl RemoteServerProjects { else { return; }; - update_settings_file::(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: usize, cx: &mut Context) { @@ -1403,24 +1407,15 @@ impl RemoteServerProjects { cx: &mut Context, ) -> impl IntoElement { let ssh_settings = SshSettings::get_global(cx); - let mut should_rebuild = false; - - if ssh_settings - .ssh_connections - .as_ref() - .is_some_and(|connections| { - state - .servers - .iter() - .filter_map(|server| match server { - RemoteEntry::Project { connection, .. } => Some(connection), - RemoteEntry::SshConfig { .. } => None, - }) - .ne(connections.iter()) + + let mut should_rebuild = state + .servers + .iter() + .filter_map(|server| match server { + RemoteEntry::Project { connection, .. } => Some(connection), + RemoteEntry::SshConfig { .. } => None, }) - { - should_rebuild = true; - }; + .ne(&ssh_settings.ssh_connections); if !should_rebuild && ssh_settings.read_ssh_config { let current_ssh_hosts: BTreeSet = state diff --git a/crates/remote/Cargo.toml b/crates/remote/Cargo.toml index 5985bcae827c42f4ae535b1dd859e436167e3fe5..3aea56c6411b1a084219734361cc2da9578a6b1e 100644 --- a/crates/remote/Cargo.toml +++ b/crates/remote/Cargo.toml @@ -35,6 +35,7 @@ rpc = { workspace = true, features = ["gpui"] } schemars.workspace = true serde.workspace = true serde_json.workspace = true +settings.workspace = true shlex.workspace = true smol.workspace = true tempfile.workspace = true diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index 42c6da04b5c39b0b1133b2d13549585fa9433ef7..10946cd11c2a137f1c8951999bc47fa20a27fb67 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -15,8 +15,7 @@ use itertools::Itertools; use parking_lot::Mutex; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use rpc::proto::Envelope; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +pub use settings::SshPortForwardOption; use smol::{ fs, process::{self, Child, Stdio}, @@ -53,14 +52,19 @@ pub struct SshConnectionOptions { pub upload_binary_over_ssh: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)] -pub struct SshPortForwardOption { - #[serde(skip_serializing_if = "Option::is_none")] - pub local_host: Option, - pub local_port: u16, - #[serde(skip_serializing_if = "Option::is_none")] - pub remote_host: Option, - pub remote_port: u16, +impl From for SshConnectionOptions { + fn from(val: settings::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)] diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index cde160886c1590c8042492f87034786a38246914..eff8f9ff60f348efd09bf3be4195a29ebe6c76af 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -44,6 +44,9 @@ pub struct SettingsContent { #[serde(flatten)] pub editor: EditorSettingsContent, + #[serde(flatten)] + pub remote: RemoteSettingsContent, + /// Settings related to the file finder. pub file_finder: Option, @@ -712,3 +715,52 @@ pub enum ImageFileSizeUnit { /// Displays file size in decimal units (e.g., KB, MB). Decimal, } + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct RemoteSettingsContent { + pub ssh_connections: Option>, + pub read_ssh_config: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct SshConnection { + pub host: SharedString, + #[serde(skip_serializing_if = "Option::is_none")] + pub username: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub port: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] + pub args: Vec, + #[serde(default)] + pub projects: collections::BTreeSet, + /// Name to use for this server in UI. + #[serde(skip_serializing_if = "Option::is_none")] + pub nickname: Option, + // 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, + + #[serde(skip_serializing_if = "Option::is_none")] + pub port_forwards: Option>, +} + +#[derive( + Clone, Debug, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema, +)] +pub struct SshProject { + pub paths: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)] +pub struct SshPortForwardOption { + #[serde(skip_serializing_if = "Option::is_none")] + pub local_host: Option, + pub local_port: u16, + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_host: Option, + pub remote_port: u16, +} diff --git a/crates/settings/src/settings_content/project.rs b/crates/settings/src/settings_content/project.rs index 541b700950be45b83d54e93427f236b867cfb136..3628f70ac6a61da6cac46517eadb60581ca1bd11 100644 --- a/crates/settings/src/settings_content/project.rs +++ b/crates/settings/src/settings_content/project.rs @@ -139,7 +139,7 @@ pub struct DapSettings { pub args: Vec, } -#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)] pub struct SessionSettingsContent { /// Whether or not to restore unsaved buffers on restart. /// diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 790889cf93585b406c895e4754b814e952940f2c..4ba961773df2e605660a841f755be939f64faae0 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -5,7 +5,7 @@ use std::{ }; use anyhow::Context as _; -use editor::{Editor, EditorSettingsControls}; +use editor::Editor; use feature_flags::{FeatureFlag, FeatureFlagAppExt}; use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, ReadGlobal, ScrollHandle, actions}; use settings::{