refactor(desktop): encapsulate settings compatibility state

This commit is contained in:
Kang
2026-07-29 18:15:31 +08:00
parent d3fbfd9913
commit 34bf4f0ffe
81 changed files with 1105 additions and 645 deletions
@@ -624,7 +624,7 @@ impl NyaTermApp {
let observation_message = build_observation_message(
&observation,
&state.command,
&self.settings.summary.language,
&self.settings.summary().language,
);
let settings = self.ai.settings.config.clone();
let terminal_session_id = state.terminal_session_id.clone();
@@ -199,13 +199,13 @@ impl NyaTermApp {
{
return;
}
if self.settings.summary.terminal_low_latency_mode {
if self.settings.summary().terminal_low_latency_mode {
self.clear_command_suggestion_draft(cx);
finish!("low_latency_mode", 0);
}
if !self
.settings
.summary
.summary()
.interaction_command_suggestions_enabled
{
self.clear_command_suggestion_draft(cx);
@@ -294,7 +294,7 @@ impl NyaTermApp {
let min_chars_started_at = Instant::now();
let min_chars = self
.settings
.summary
.summary()
.interaction_command_suggestion_min_chars
.max(1) as usize;
let below_min_chars = terminal_input_tracker_below_min_chars(
@@ -421,7 +421,7 @@ impl NyaTermApp {
if this.terminal.assist.command_suggestion_search_gen != request_id {
return None;
}
if this.settings.summary.terminal_low_latency_mode {
if this.settings.summary().terminal_low_latency_mode {
this.hide_command_suggestions_if_present(cx);
return None;
}
@@ -528,7 +528,7 @@ impl NyaTermApp {
}
if !self
.settings
.summary
.summary()
.interaction_command_suggestions_enabled
{
let hide_started_at = Instant::now();
@@ -538,12 +538,12 @@ impl NyaTermApp {
}
let min_chars = self
.settings
.summary
.summary()
.interaction_command_suggestion_min_chars
.max(1) as usize;
let max_chars = self
.settings
.summary
.summary()
.interaction_command_suggestion_max_chars
.max(min_chars as u32) as usize;
let pattern_started_at = Instant::now();
@@ -599,7 +599,7 @@ impl NyaTermApp {
|| self.terminal.assist.command_suggestions_suppressed
|| !self
.settings
.summary
.summary()
.interaction_command_suggestions_enabled
|| get_tracked_command(&self.terminal.assist.command_input_tracker) != request.pattern
{
@@ -36,8 +36,8 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) {
self.commands.set_quick_view_mode(mode);
self.settings.summary.ui_quick_cmd_view_mode =
quick_command_view_mode_setting(mode).to_string();
self.settings
.set_quick_command_view_mode(quick_command_view_mode_setting(mode).to_string());
self.save_quick_command_ui_settings(cx);
}
@@ -47,8 +47,8 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) {
self.commands.set_quick_sort_mode(mode);
self.settings.summary.ui_quick_cmd_sort_mode =
quick_command_sort_mode_setting(mode).to_string();
self.settings
.set_quick_command_sort_mode(quick_command_sort_mode_setting(mode).to_string());
self.save_quick_command_ui_settings(cx);
}
@@ -57,7 +57,7 @@ impl NyaTermApp {
self.runtime.config_dir(),
self.runtime.portable_key_path().map(ToOwned::to_owned),
)
.and_then(|store| store.save_quick_command_ui_settings(&self.settings.summary))
.and_then(|store| store.save_quick_command_ui_settings(self.settings.summary()))
{
Ok(settings) => {
self.apply_gpui_settings(settings);
@@ -40,7 +40,7 @@ impl Render for ConnectionEditorWindow {
(
app.theme_palette(),
app.gpui_ui_font_family(),
app.settings.summary.ui_font_size.clamp(12, 24) as f32,
app.settings.summary().ui_font_size.clamp(12, 24) as f32,
app.connection_editor_title().to_string(),
)
});
@@ -168,8 +168,8 @@ impl NyaTermApp {
pub(in crate::features) fn cycle_connection_sort_mode(&mut self, cx: &mut Context<Self>) {
let sort_mode = self.connection_state.cycle_list_sort_mode();
self.settings.summary.ui_saved_connections_sort_mode =
sort_mode.persistence_id().to_string();
self.settings
.set_saved_connections_sort_mode(sort_mode.persistence_id().to_string());
self.persist_ui_layout();
self.terminal.view.status = format!("connections sorted by {}", sort_mode.label());
cx.notify();
@@ -25,6 +25,6 @@ pub(in crate::features) use markdown::{
impl NyaTermApp {
pub(in crate::features) fn tr(&self, key: &'static str) -> &'static str {
crate::i18n::text(&self.settings.summary.language, key)
crate::i18n::text(&self.settings.summary().language, key)
}
}
@@ -13,7 +13,7 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> impl IntoElement {
let unlocked =
self.settings.summary.has_master_password && self.security.secrets_unlocked();
self.settings.summary().has_master_password && self.security.secrets_unlocked();
let palette = self.theme_palette();
div()
.id(SharedString::from("security-secrets-toggle"))
@@ -81,7 +81,7 @@ impl NyaTermApp {
.on_click(cx.listener(|this, _, window, cx| {
if this.security_secrets_locked() {
this.open_security_unlock_prompt(window, cx);
} else if this.settings.summary.has_master_password {
} else if this.settings.summary().has_master_password {
this.lock_security_secrets(cx);
} else {
this.open_security_unlock_prompt(window, cx);
@@ -50,7 +50,7 @@ impl NyaTermApp {
NavItem::AiAssistant => self.ai_assistant_panel(cx).into_any_element(),
NavItem::ActiveSessions => self.active_sessions_panel(cx).into_any_element(),
NavItem::CommandHistory => self.command_history_panel(cx).into_any_element(),
NavItem::Stats if self.settings.summary.ui_show_remote_stats => {
NavItem::Stats if self.settings.summary().ui_show_remote_stats => {
self.stats_view(cx).into_any_element()
}
NavItem::Stats => crate::features::inspector::disabled_inspector_panel(
@@ -58,7 +58,7 @@ impl NyaTermApp {
self.tr("panel.resourceMonitorDisabled"),
)
.into_any_element(),
NavItem::Processes if self.settings.summary.ui_show_process_manager => {
NavItem::Processes if self.settings.summary().ui_show_process_manager => {
self.processes_view(cx).into_any_element()
}
NavItem::Processes => crate::features::inspector::disabled_inspector_panel(
@@ -66,7 +66,7 @@ impl NyaTermApp {
self.tr("processManager.disabled"),
)
.into_any_element(),
NavItem::Docker if self.settings.summary.ui_show_docker_manager => {
NavItem::Docker if self.settings.summary().ui_show_docker_manager => {
self.docker_view(cx).into_any_element()
}
NavItem::Docker => crate::features::inspector::disabled_inspector_panel(
@@ -30,7 +30,7 @@ impl NyaTermApp {
let compact_layout = !cfg!(target_os = "macos");
let narrow_left = compact_layout && self.shell.viewport_size().0 < 1024.;
let narrow_right = compact_layout && self.shell.viewport_size().0 < 768.;
let header_status_visible = self.settings.summary.ui_header_status_visible;
let header_status_visible = self.settings.summary().ui_header_status_visible;
let header_status = self.header_status_content();
// Match Tauri Header: h-10.
div()
@@ -279,7 +279,8 @@ impl NyaTermApp {
fn header_status_dropdown(&self, cx: &mut Context<Self>) -> impl IntoElement {
let palette = self.theme_palette();
let selected = HeaderStatusMode::from_setting(&self.settings.summary.ui_header_status_mode);
let selected =
HeaderStatusMode::from_setting(&self.settings.summary().ui_header_status_mode);
let mut menu = div()
.id("header-status-menu")
.absolute()
@@ -328,7 +329,7 @@ impl NyaTermApp {
}
fn header_status_content(&self) -> HeaderStatusContent {
let mode = HeaderStatusMode::from_setting(&self.settings.summary.ui_header_status_mode);
let mode = HeaderStatusMode::from_setting(&self.settings.summary().ui_header_status_mode);
match mode {
HeaderStatusMode::Session => HeaderStatusContent {
icon_path: self.title_context_icon().unwrap_or(mode.icon_path()),
@@ -336,7 +337,7 @@ impl NyaTermApp {
},
HeaderStatusMode::DateTime => HeaderStatusContent {
icon_path: mode.icon_path(),
label: format_header_datetime(local_now(), &self.settings.summary.language),
label: format_header_datetime(local_now(), &self.settings.summary().language),
},
HeaderStatusMode::Resources | HeaderStatusMode::Host => {
let label = self
@@ -351,7 +352,8 @@ impl NyaTermApp {
}
fn remote_stats_header_label(&self, mode: HeaderStatusMode) -> Option<String> {
if self.session.active_ssh_config().is_none() || !self.settings.summary.ui_show_remote_stats
if self.session.active_ssh_config().is_none()
|| !self.settings.summary().ui_show_remote_stats
{
return None;
}
@@ -396,7 +398,7 @@ impl NyaTermApp {
fn remote_stats_header_fallback(&self) -> String {
if self.session.active_ssh_config().is_none() {
self.tr("panel.resourceMonitorNoSession").to_string()
} else if !self.settings.summary.ui_show_remote_stats {
} else if !self.settings.summary().ui_show_remote_stats {
self.tr("panel.resourceMonitorDisabled").to_string()
} else {
let stats = self.remote_ops.stats_presentation();
@@ -418,8 +420,8 @@ impl NyaTermApp {
mode: HeaderStatusMode,
cx: &mut Context<Self>,
) {
self.settings.summary.ui_header_status_mode = mode.persistence_id().to_string();
self.settings.summary.ui_header_status_visible = true;
self.settings
.set_header_status_mode(mode.persistence_id().to_string());
self.shell.close_header_status_menu();
self.shell
.set_header_status_rendered_minute(current_unix_minute());
@@ -432,7 +434,7 @@ impl NyaTermApp {
visible: bool,
cx: &mut Context<Self>,
) {
self.settings.summary.ui_header_status_visible = visible;
self.settings.set_header_status_visible(visible);
self.shell.close_header_status_menu();
self.persist_header_status_settings();
cx.notify();
@@ -448,14 +450,14 @@ impl NyaTermApp {
}
pub(in crate::features) fn header_status_needs_remote_stats(&self) -> bool {
self.settings.summary.ui_header_status_visible
&& HeaderStatusMode::from_setting(&self.settings.summary.ui_header_status_mode)
self.settings.summary().ui_header_status_visible
&& HeaderStatusMode::from_setting(&self.settings.summary().ui_header_status_mode)
.needs_remote_stats()
}
pub(in crate::features) fn header_status_clock_refresh_due(&self) -> bool {
self.settings.summary.ui_header_status_visible
&& HeaderStatusMode::from_setting(&self.settings.summary.ui_header_status_mode)
self.settings.summary().ui_header_status_visible
&& HeaderStatusMode::from_setting(&self.settings.summary().ui_header_status_mode)
== HeaderStatusMode::DateTime
&& self.shell.header_status_rendered_minute() != current_unix_minute()
}
@@ -327,7 +327,7 @@ impl NyaTermApp {
match submenu {
TitleMenuSubmenu::Theme => {
for &theme in crate::theme::APPEARANCE_THEME_IDS {
let current = self.settings.summary.theme.as_str();
let current = self.settings.summary().theme.as_str();
let selected = current == theme
|| (current == "catppuccin" && theme == "catppuccin-mocha");
let label = crate::theme::appearance_theme_label(theme);
@@ -346,8 +346,8 @@ impl NyaTermApp {
}
}
TitleMenuSubmenu::Language => {
let english = matches!(self.settings.summary.language.as_str(), "en" | "en-US");
let chinese = matches!(self.settings.summary.language.as_str(), "zh" | "zh-CN");
let english = matches!(self.settings.summary().language.as_str(), "en" | "en-US");
let chinese = matches!(self.settings.summary().language.as_str(), "zh" | "zh-CN");
menu = menu
.child(title_menu_item(
palette,
@@ -53,7 +53,7 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> AnyElement {
let palette = self.theme_palette();
let language = self.settings.summary.language.clone();
let language = self.settings.summary().language.clone();
let fields = ConnectionEditorFields::new(
self.connection_state.editor_fields().clone(),
self.connection_state.editor_menu_focus_handle(),
@@ -14,19 +14,21 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> impl IntoElement {
let palette = self.theme_palette();
let master_password = self.settings.master_password();
let master_password_draft = master_password.draft.to_string();
let master_password_enabled = master_password.enabled;
let master_password_input = self
.text_input_box(
"settings.security.master-password",
&self.settings.master_password.draft.clone(),
&master_password_draft,
TextInputSetup::masked(),
cx,
)
.into_any_element();
let master_password_enabled = self.settings.master_password.enabled;
let master_password_switch_enabled = !self.cloud_sync.settings().enabled;
let has_stored_master_password = self.settings.summary.has_master_password;
let idle_minutes = self.settings.summary.idle_lock_minutes;
let host_key_policy = match self.settings.summary.host_key_policy.as_str() {
let has_stored_master_password = self.settings.summary().has_master_password;
let idle_minutes = self.settings.summary().idle_lock_minutes;
let host_key_policy = match self.settings.summary().host_key_policy.as_str() {
"strict" | "reject" => "strict",
"accept" | "accept_new" => "accept",
_ => "prompt",
@@ -91,8 +93,7 @@ impl NyaTermApp {
)),
))
.when(
has_stored_master_password
&& self.settings.master_password.draft.is_empty(),
has_stored_master_password && master_password_draft.is_empty(),
|this| {
this.child(
div()
@@ -130,13 +131,13 @@ impl NyaTermApp {
settings_switch(
palette,
"settings-screen-lock-enabled",
self.settings.summary.enable_screen_lock,
self.settings.summary().enable_screen_lock,
cx.listener(|this, _, _, cx| {
this.toggle_screen_lock_enabled(cx);
}),
),
))
.when(self.settings.summary.enable_screen_lock, |this| {
.when(self.settings.summary().enable_screen_lock, |this| {
this.child(settings_form_row(
palette,
idle_lock_label,
@@ -20,12 +20,12 @@ impl NyaTermApp {
let x11_display_input = self
.text_input_box(
"settings.terminal.x11-display",
&self.settings.summary.x11_display.clone(),
&self.settings.summary().x11_display.clone(),
TextInputSetup::placeholder(self.tr("settings.x11DisplayPlaceholder")),
cx,
)
.into_any_element();
let action_links_enabled = self.settings.summary.terminal_action_links_enabled;
let action_links_enabled = self.settings.summary().terminal_action_links_enabled;
div()
.flex()
@@ -47,7 +47,10 @@ impl NyaTermApp {
palette,
"terminal-scrollback-minus",
"terminal-scrollback-plus",
self.settings.summary.terminal_scrollback_lines.to_string(),
self.settings
.summary()
.terminal_scrollback_lines
.to_string(),
cx.listener(|this, _, _, cx| {
this.adjust_terminal_scrollback_lines(-100, cx);
}),
@@ -67,7 +70,7 @@ impl NyaTermApp {
"terminal-keepalive-minus",
"terminal-keepalive-plus",
self.settings
.summary
.summary()
.terminal_keep_alive_interval
.to_string(),
cx.listener(|this, _, _, cx| {
@@ -99,7 +102,7 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-hardware-acceleration",
self.settings.summary.terminal_hardware_acceleration,
self.settings.summary().terminal_hardware_acceleration,
cx.listener(|this, _, _, cx| {
this.toggle_terminal_hardware_acceleration(cx);
}),
@@ -112,7 +115,7 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-low-latency-mode",
self.settings.summary.terminal_low_latency_mode,
self.settings.summary().terminal_low_latency_mode,
cx.listener(|this, _, _, cx| {
this.toggle_terminal_low_latency_mode(cx);
}),
@@ -127,7 +130,7 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-workspace-padding",
self.settings.summary.terminal_show_workspace_padding,
self.settings.summary().terminal_show_workspace_padding,
cx.listener(|this, _, _, cx| {
this.toggle_terminal_workspace_padding(cx);
}),
@@ -140,7 +143,7 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-line-numbers",
self.settings.summary.terminal_show_line_numbers,
self.settings.summary().terminal_show_line_numbers,
cx.listener(|this, _, _, cx| {
this.toggle_terminal_line_numbers(cx);
}),
@@ -153,13 +156,13 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-timestamps",
self.settings.summary.terminal_show_timestamps,
self.settings.summary().terminal_show_timestamps,
cx.listener(|this, _, _, cx| {
this.toggle_terminal_timestamps(cx);
}),
),
))
.when(self.settings.summary.terminal_show_timestamps, |this| {
.when(self.settings.summary().terminal_show_timestamps, |this| {
this.child(settings_form_row(
palette,
self.tr("settings.showTimestampMilliseconds"),
@@ -169,7 +172,7 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-timestamp-ms",
self.settings.summary.terminal_show_timestamp_milliseconds,
self.settings.summary().terminal_show_timestamp_milliseconds,
cx.listener(|this, _, _, cx| {
this.toggle_terminal_timestamp_milliseconds(cx);
}),
@@ -185,7 +188,9 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-multi-line-paste",
self.settings.summary.terminal_show_multi_line_paste_dialog,
self.settings
.summary()
.terminal_show_multi_line_paste_dialog,
cx.listener(|this, _, _, cx| {
this.toggle_multi_line_paste_dialog(cx);
}),
@@ -198,7 +203,7 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-paste-image-path",
self.settings.summary.terminal_paste_image_as_path,
self.settings.summary().terminal_paste_image_as_path,
cx.listener(|this, _, _, cx| {
this.toggle_paste_image_as_path(cx);
}),
@@ -211,13 +216,13 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-remote-stats",
self.settings.summary.ui_show_remote_stats,
self.settings.summary().ui_show_remote_stats,
cx.listener(|this, _, _, cx| {
this.toggle_remote_stats_panel(cx);
}),
),
))
.when(self.settings.summary.ui_show_remote_stats, |this| {
.when(self.settings.summary().ui_show_remote_stats, |this| {
this.child(settings_form_row(
palette,
self.tr("settings.remoteStatsInterval"),
@@ -228,7 +233,7 @@ impl NyaTermApp {
palette,
"terminal-remote-stats-interval-minus",
"terminal-remote-stats-interval-plus",
self.settings.summary.ui_remote_stats_interval.to_string(),
self.settings.summary().ui_remote_stats_interval.to_string(),
cx.listener(|this, _, _, cx| {
this.adjust_remote_stats_interval(-1, cx);
}),
@@ -247,13 +252,13 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-process-manager",
self.settings.summary.ui_show_process_manager,
self.settings.summary().ui_show_process_manager,
cx.listener(|this, _, _, cx| {
this.toggle_process_manager_panel(cx);
}),
),
))
.when(self.settings.summary.ui_show_process_manager, |this| {
.when(self.settings.summary().ui_show_process_manager, |this| {
this.child(settings_form_row(
palette,
self.tr("settings.processManagerInterval"),
@@ -265,7 +270,7 @@ impl NyaTermApp {
"terminal-process-interval-minus",
"terminal-process-interval-plus",
self.settings
.summary
.summary()
.ui_process_manager_interval
.to_string(),
cx.listener(|this, _, _, cx| {
@@ -286,13 +291,13 @@ impl NyaTermApp {
settings_switch(
palette,
"terminal-docker-manager",
self.settings.summary.ui_show_docker_manager,
self.settings.summary().ui_show_docker_manager,
cx.listener(|this, _, _, cx| {
this.toggle_docker_manager_panel(cx);
}),
),
))
.when(self.settings.summary.ui_show_docker_manager, |this| {
.when(self.settings.summary().ui_show_docker_manager, |this| {
this.child(settings_form_row(
palette,
self.tr("settings.dockerManagerInterval"),
@@ -303,7 +308,10 @@ impl NyaTermApp {
palette,
"terminal-docker-interval-minus",
"terminal-docker-interval-plus",
self.settings.summary.ui_docker_manager_interval.to_string(),
self.settings
.summary()
.ui_docker_manager_interval
.to_string(),
cx.listener(|this, _, _, cx| {
this.adjust_docker_manager_interval(-1, cx);
}),
@@ -353,7 +361,7 @@ impl NyaTermApp {
self.tr("settings.actionLinksMatcherIpv4"),
"192.168.1.1",
self.tr("settings.actionLinksMatcherIpv4Desc"),
self.settings.summary.terminal_action_links_matchers.ipv4,
self.settings.summary().terminal_action_links_matchers.ipv4,
action_links_enabled,
cx.listener(|this, _, _, cx| {
this.toggle_terminal_action_links_matcher("ipv4", cx);
@@ -366,7 +374,7 @@ impl NyaTermApp {
"localhost:8080",
self.tr("settings.actionLinksMatcherHostPortDesc"),
self.settings
.summary
.summary()
.terminal_action_links_matchers
.host_port,
action_links_enabled,
@@ -380,7 +388,10 @@ impl NyaTermApp {
self.tr("settings.actionLinksMatcherArchive"),
"backup.tar.gz",
self.tr("settings.actionLinksMatcherArchiveDesc"),
self.settings.summary.terminal_action_links_matchers.archive,
self.settings
.summary()
.terminal_action_links_matchers
.archive,
action_links_enabled,
cx.listener(|this, _, _, cx| {
this.toggle_terminal_action_links_matcher("archive", cx);
@@ -19,8 +19,8 @@ impl NyaTermApp {
) -> impl IntoElement {
let palette = self.theme_palette();
let is_dark = self.terminal_theme_is_dark();
let rules = self.settings.keyword_config.rules.clone();
let keyword_highlighting_enabled = self.settings.keyword_config.enabled;
let rules = self.settings.keyword_config().rules.clone();
let keyword_highlighting_enabled = self.settings.keyword_config().enabled;
let expanded = self.settings.keyword_highlight_presentation().expanded_id;
let builtin_ids = nyaterm_core::builtin_keyword_rule_ids();
let pattern_count_template = self.tr("settings.keywordHighlightPatternCount");
@@ -63,7 +63,7 @@ impl NyaTermApp {
settings_switch_with_enabled(
palette,
"settings-keyword-highlights-wrap",
self.settings.keyword_config.across_wrapped_lines,
self.settings.keyword_config().across_wrapped_lines,
keyword_highlighting_enabled,
cx.listener(|this, _, _, cx| {
this.toggle_keyword_highlights_wrapped(cx);
@@ -106,7 +106,7 @@ impl NyaTermApp {
&id, is_dark,
);
let enabled = self
.settings.keyword_config
.settings.keyword_config()
.builtin_rules
.get(&id)
.copied()
@@ -26,7 +26,7 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> impl IntoElement {
let palette = self.theme_palette();
let engines = self.settings.summary.search_custom_engines.clone();
let engines = self.settings.summary().search_custom_engines.clone();
let interaction = self.settings.search_engine_presentation();
let expanded_index = interaction.expanded_index;
let icon_picker_index = interaction.icon_picker_index;
@@ -15,7 +15,7 @@ impl NyaTermApp {
let palette = self.theme_palette();
let permissions = self
.settings
.summary
.summary()
.transfer_default_file_permissions
.clone();
@@ -53,7 +53,12 @@ impl NyaTermApp {
.text_size(px(12.))
.font_weight(FontWeight(700.))
.text_color(rgb(palette.text))
.child(self.settings.summary.transfer_download_threads.to_string()),
.child(
self.settings
.summary()
.transfer_download_threads
.to_string(),
),
)
.child(small_button(
palette,
@@ -90,7 +95,7 @@ impl NyaTermApp {
.text_size(px(12.))
.font_weight(FontWeight(700.))
.text_color(rgb(palette.text))
.child(self.settings.summary.transfer_upload_threads.to_string()),
.child(self.settings.summary().transfer_upload_threads.to_string()),
)
.child(small_button(
palette,
@@ -127,7 +132,7 @@ impl NyaTermApp {
.text_size(px(12.))
.font_weight(FontWeight(700.))
.text_color(rgb(palette.text))
.child(self.settings.summary.transfer_max_retries.to_string()),
.child(self.settings.summary().transfer_max_retries.to_string()),
)
.child(small_button(
palette,
@@ -164,7 +169,7 @@ impl NyaTermApp {
.text_size(px(12.))
.font_weight(FontWeight(700.))
.text_color(rgb(palette.text))
.child(self.settings.summary.transfer_buffer_size.to_string()),
.child(self.settings.summary().transfer_buffer_size.to_string()),
)
.child(small_button(
palette,
@@ -184,7 +189,7 @@ impl NyaTermApp {
settings_switch(
palette,
"settings-transfer-preserve-timestamps",
self.settings.summary.transfer_preserve_timestamps,
self.settings.summary().transfer_preserve_timestamps,
cx.listener(|this, _, _, cx| {
this.toggle_transfer_preserve_timestamps(cx);
}),
@@ -199,7 +204,7 @@ impl NyaTermApp {
settings_switch(
palette,
"settings-transfer-resume-broken",
self.settings.summary.transfer_resume_broken_transfer,
self.settings.summary().transfer_resume_broken_transfer,
cx.listener(|this, _, _, cx| {
this.toggle_transfer_resume_broken(cx);
}),
@@ -11,11 +11,11 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> impl IntoElement {
let palette = self.theme_palette();
let editor_type = self.settings.summary.transfer_editor_type.clone();
let editor_type = self.settings.summary().transfer_editor_type.clone();
let default_editor_input = self
.text_input_box(
"settings.transfer.default-editor",
&self.settings.summary.transfer_default_editor.clone(),
&self.settings.summary().transfer_default_editor.clone(),
TextInputSetup::placeholder(self.tr("settings.defaultEditor")),
cx,
)
@@ -17,7 +17,7 @@ impl NyaTermApp {
let download_path_input = self
.text_input_box(
"settings.transfer.download-path",
&self.settings.summary.transfer_download_path.clone(),
&self.settings.summary().transfer_download_path.clone(),
TextInputSetup::placeholder(self.tr("settings.downloadPath")),
cx,
)
@@ -63,7 +63,7 @@ impl NyaTermApp {
settings_switch(
palette,
"transfer-ask-save",
self.settings.summary.transfer_ask_save_location,
self.settings.summary().transfer_ask_save_location,
cx.listener(|this, _, _, cx| {
this.toggle_transfer_ask_save_location(cx);
}),
@@ -16,13 +16,13 @@ impl NyaTermApp {
let recording_path_input = self
.text_input_box(
"settings.recording.path",
&self.settings.summary.recording_path.clone(),
&self.settings.summary().recording_path.clone(),
TextInputSetup::placeholder(self.tr("settings.recordingPath")),
cx,
)
.into_any_element();
let memory_mib =
(self.settings.summary.recording_memory_limit_bytes / (1024 * 1024)).max(1);
(self.settings.summary().recording_memory_limit_bytes / (1024 * 1024)).max(1);
div().flex().flex_col().gap_3().child(settings_form_section(
palette,
@@ -61,7 +61,7 @@ impl NyaTermApp {
settings_switch(
palette,
"settings-recording-auto",
self.settings.summary.recording_auto_start,
self.settings.summary().recording_auto_start,
cx.listener(|this, _, _, cx| {
this.toggle_recording_auto_start(cx);
}),
@@ -76,7 +76,7 @@ impl NyaTermApp {
settings_switch(
palette,
"settings-recording-labels",
self.settings.summary.recording_include_io_labels,
self.settings.summary().recording_include_io_labels,
cx.listener(|this, _, _, cx| {
this.toggle_recording_io_labels(cx);
}),
@@ -91,7 +91,7 @@ impl NyaTermApp {
settings_switch(
palette,
"settings-recording-timestamps",
self.settings.summary.recording_include_timestamps,
self.settings.summary().recording_include_timestamps,
cx.listener(|this, _, _, cx| {
this.toggle_recording_timestamps(cx);
}),
@@ -15,8 +15,8 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> impl IntoElement {
let palette = self.theme_palette();
let font_size_label = self.settings.summary.terminal_font_size.to_string();
let ui_font_size_label = self.settings.summary.ui_font_size.to_string();
let font_size_label = self.settings.summary().terminal_font_size.to_string();
let ui_font_size_label = self.settings.summary().ui_font_size.to_string();
div()
.flex()
@@ -71,7 +71,7 @@ impl NyaTermApp {
{
let path_label = self
.settings
.summary
.summary()
.background_image_path
.as_deref()
.map(|p| {
@@ -91,7 +91,7 @@ impl NyaTermApp {
}
})
.unwrap_or_else(|| self.tr("settings.backgroundImageEmpty").to_string());
let has_image = self.settings.summary.background_image_path.is_some();
let has_image = self.settings.summary().background_image_path.is_some();
div()
.flex()
.flex_col()
@@ -228,7 +228,7 @@ impl NyaTermApp {
settings_switch(
palette,
"appearance-cursor-blink",
self.settings.summary.cursor_blink,
self.settings.summary().cursor_blink,
cx.listener(|this, _, _, cx| {
this.toggle_cursor_blink(cx);
}),
@@ -247,7 +247,7 @@ impl NyaTermApp {
(
self.tr("settings.terminalFontFamily"),
self.tr("settings.terminalFontFamilyDesc"),
self.settings.summary.terminal_font_family.clone(),
self.settings.summary().terminal_font_family.clone(),
"JetBrains Mono",
self.settings.terminal_font_options().to_vec(),
)
@@ -255,7 +255,7 @@ impl NyaTermApp {
(
self.tr("settings.uiFontFamily"),
self.tr("settings.uiFontFamilyDesc"),
self.settings.summary.ui_font_family.clone(),
self.settings.summary().ui_font_family.clone(),
"Inter",
self.settings.ui_font_options().to_vec(),
)
@@ -408,9 +408,9 @@ impl NyaTermApp {
) -> impl IntoElement {
let palette = self.theme_palette();
let value = if content {
self.settings.summary.background_content_opacity
self.settings.summary().background_content_opacity
} else {
self.settings.summary.background_image_opacity
self.settings.summary().background_image_opacity
};
let label = if content {
self.tr("settings.backgroundContentOpacity")
@@ -523,12 +523,12 @@ impl NyaTermApp {
};
let current = if terminal {
self.settings
.summary
.summary()
.terminal_theme
.as_deref()
.filter(|theme| !theme.trim().is_empty())
} else {
Some(self.settings.summary.theme.as_str())
Some(self.settings.summary().theme.as_str())
};
let value = current
.map(appearance_theme_label)
@@ -582,7 +582,7 @@ impl NyaTermApp {
fn appearance_contrast_select(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let palette = self.theme_palette();
let id = "appearance-minimum-contrast";
let current = self.settings.summary.minimum_contrast_ratio.clone();
let current = self.settings.summary().minimum_contrast_ratio.clone();
let label_for = |ratio: &str| match ratio {
"3" => self.tr("settings.minimumContrastRatio_3"),
"4.5" => self.tr("settings.minimumContrastRatio_4_5"),
@@ -623,7 +623,7 @@ impl NyaTermApp {
) -> impl IntoElement {
let palette = self.theme_palette();
let id = "appearance-background-fit";
let current = match self.settings.summary.background_image_fit.as_str() {
let current = match self.settings.summary().background_image_fit.as_str() {
"contain" => "contain",
"stretch" | "fill" => "stretch",
"tile" => "tile",
@@ -673,9 +673,9 @@ impl NyaTermApp {
"appearance-font-weight"
};
let current = if bold {
self.settings.summary.terminal_font_weight_bold
self.settings.summary().terminal_font_weight_bold
} else {
self.settings.summary.terminal_font_weight
self.settings.summary().terminal_font_weight
};
let label_for = |weight| match weight {
300 => self.tr("settings.fontWeight_300"),
@@ -720,7 +720,7 @@ impl NyaTermApp {
fn appearance_cursor_style_select(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let palette = self.theme_palette();
let id = "appearance-cursor-style";
let current = self.settings.summary.cursor_style.clone();
let current = self.settings.summary().cursor_style.clone();
let label_for = |style: &str| match style {
"underline" => self.tr("settings.cursorUnderline"),
"bar" => self.tr("settings.cursorBar"),
@@ -15,7 +15,7 @@ impl NyaTermApp {
) -> impl IntoElement {
let palette = self.theme_palette();
// Tauri GeneralTab: language, nested startup layout, tray, confirm, diagnostics.
let language = self.settings.summary.language.clone();
let language = self.settings.summary().language.clone();
let language_label = match language.as_str() {
"zh-CN" | "zh" => "简体中文",
"zh-TW" => "繁體中文",
@@ -23,12 +23,12 @@ impl NyaTermApp {
"ja" => "日本語",
other => other,
};
let diagnostics_level = self.settings.summary.diagnostics_level.clone();
let retention = self.settings.summary.diagnostics_retention_days;
let diagnostics_level = self.settings.summary().diagnostics_level.clone();
let retention = self.settings.summary().diagnostics_retention_days;
let days_unit = self.tr("common.days");
let header_status_mode =
HeaderStatusMode::from_setting(&self.settings.summary.ui_header_status_mode);
let header_status_visible = self.settings.summary.ui_header_status_visible;
HeaderStatusMode::from_setting(&self.settings.summary().ui_header_status_mode);
let header_status_visible = self.settings.summary().ui_header_status_visible;
div()
.flex()
@@ -121,13 +121,13 @@ impl NyaTermApp {
settings_switch(
palette,
"general-startup-restore",
self.settings.summary.startup_restore,
self.settings.summary().startup_restore,
cx.listener(|this, _, _, cx| {
this.toggle_startup_restore(cx);
}),
),
))
.when(self.settings.summary.startup_restore, |this| {
.when(self.settings.summary().startup_restore, |this| {
this.child(
div()
.pl_3()
@@ -143,7 +143,7 @@ impl NyaTermApp {
settings_switch(
palette,
"general-startup-restore-window-layout",
self.settings.summary.startup_restore_window_layout,
self.settings.summary().startup_restore_window_layout,
cx.listener(|this, _, _, cx| {
this.toggle_startup_restore_window_layout(cx);
}),
@@ -158,7 +158,7 @@ impl NyaTermApp {
settings_switch(
palette,
"general-minimize-to-tray",
self.settings.summary.minimize_to_tray,
self.settings.summary().minimize_to_tray,
cx.listener(|this, _, _, cx| {
this.toggle_minimize_to_tray(cx);
}),
@@ -171,7 +171,7 @@ impl NyaTermApp {
settings_switch(
palette,
"general-confirm-close",
self.settings.summary.confirm_on_close,
self.settings.summary().confirm_on_close,
cx.listener(|this, _, _, cx| {
this.toggle_confirm_on_close(cx);
}),
@@ -19,47 +19,47 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> impl IntoElement {
let palette = self.theme_palette();
let encoding = self.settings.summary.interaction_default_encoding.clone();
let encoding = self.settings.summary().interaction_default_encoding.clone();
// Built before the form, which reads `self` throughout: creating the
// box needs it mutably.
let word_separators_input = self
.text_input_box(
"settings.interaction.word-separators",
&self.settings.summary.interaction_word_separators.clone(),
&self.settings.summary().interaction_word_separators.clone(),
TextInputSetup::default(),
cx,
)
.into_any_element();
let double_action = self
.settings
.summary
.summary()
.interaction_tab_double_click_action
.clone();
let middle_action = self
.settings
.summary
.summary()
.interaction_tab_middle_click_action
.clone();
let right_action = self
.settings
.summary
.summary()
.interaction_tab_right_click_action
.clone();
let delay_ms = self
.settings
.summary
.summary()
.interaction_duplicate_session_command_delay_ms;
let min_chars = self
.settings
.summary
.summary()
.interaction_command_suggestion_min_chars;
let max_chars = self
.settings
.summary
.summary()
.interaction_command_suggestion_max_chars;
let suggestions_enabled = self
.settings
.summary
.summary()
.interaction_command_suggestions_enabled;
div()
@@ -81,7 +81,7 @@ impl NyaTermApp {
settings_switch(
palette,
"interaction-copy-select",
self.settings.summary.interaction_copy_on_select,
self.settings.summary().interaction_copy_on_select,
cx.listener(|this, _, _, cx| {
this.toggle_interaction_copy_on_select(cx);
}),
@@ -94,7 +94,7 @@ impl NyaTermApp {
settings_switch(
palette,
"interaction-right-paste",
self.settings.summary.interaction_right_click_paste,
self.settings.summary().interaction_right_click_paste,
cx.listener(|this, _, _, cx| {
this.toggle_interaction_right_click_paste(cx);
}),
@@ -219,7 +219,7 @@ impl NyaTermApp {
settings_switch(
palette,
"interaction-alt-meta",
self.settings.summary.interaction_alt_as_meta,
self.settings.summary().interaction_alt_as_meta,
cx.listener(|this, _, _, cx| {
this.toggle_alt_as_meta(cx);
}),
@@ -234,7 +234,7 @@ impl NyaTermApp {
settings_switch(
palette,
"interaction-mac-ime",
self.settings.summary.interaction_mac_ime_compatibility,
self.settings.summary().interaction_mac_ime_compatibility,
cx.listener(|this, _, _, cx| {
this.toggle_mac_ime_compatibility(cx);
}),
@@ -15,7 +15,7 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> impl IntoElement {
let palette = self.theme_palette();
let overrides = self.settings.summary.keybindings.len();
let overrides = self.settings.summary().keybindings.len();
let search = self.settings.keybinding_presentation().search_draft;
let search_field = self.text_input(
"settings.keybindings.search",
@@ -106,7 +106,7 @@ impl NyaTermApp {
if needle.is_empty() {
return true;
}
let keys = shortcut_keys_for(shortcut.id, &self.settings.summary.keybindings)
let keys = shortcut_keys_for(shortcut.id, &self.settings.summary().keybindings)
.unwrap_or_else(|| shortcut.default_keys.to_string());
let display = format_hotkey_for_display(&keys).to_ascii_lowercase();
shortcut.label.to_ascii_lowercase().contains(&needle)
@@ -140,10 +140,14 @@ impl NyaTermApp {
ShortcutNativeStatus::Contextual => "contextual",
},
);
let is_custom = self.settings.summary.keybindings.contains_key(shortcut.id);
let is_custom = self
.settings
.summary()
.keybindings
.contains_key(shortcut.id);
let interaction = self.settings.keybinding_presentation();
let is_recording = interaction.recording_id.as_deref() == Some(shortcut.id);
let effective_keys = shortcut_keys_for(shortcut.id, &self.settings.summary.keybindings)
let effective_keys = shortcut_keys_for(shortcut.id, &self.settings.summary().keybindings)
.unwrap_or_else(|| shortcut.default_keys.to_string());
let conflict = if is_recording {
interaction
@@ -144,7 +144,7 @@ impl NyaTermApp {
.sum();
let search_active = !self.transfer.browser_view().search.trim().is_empty();
let search_expanded = self.transfer.browser_view().search_expanded || search_active;
let show_hidden_files = self.settings.summary.ui_file_explorer_show_hidden_files;
let show_hidden_files = self.settings.summary().ui_file_explorer_show_hidden_files;
let search_input = search_expanded.then(|| {
let field = self.text_input(
"transfer.browser.search",
@@ -19,7 +19,7 @@ impl NyaTermApp {
transfer_browser_entry_is_visible(
entry,
&query,
self.settings.summary.ui_file_explorer_show_hidden_files,
self.settings.summary().ui_file_explorer_show_hidden_files,
)
})
.cloned()
@@ -76,7 +76,7 @@ impl NyaTermApp {
if shortcut_matches(
event,
"fileExplorer.rename",
&self.settings.summary.keybindings,
&self.settings.summary().keybindings,
) && self.selected_transfer_entries().len() == 1
&& self.session.active_ssh_config().is_some()
&& !self.transfer.rename_dialog_is_open()
@@ -209,32 +209,17 @@ impl NyaTermApp {
};
let enabled = self
.settings
.summary
.ui_file_explorer_auto_sync_cwd_connection_ids
.iter()
.any(|id| id == &connection_id);
.toggle_file_explorer_auto_sync_cwd(connection_id);
if enabled {
self.settings
.summary
.ui_file_explorer_auto_sync_cwd_connection_ids
.retain(|id| id != &connection_id);
self.transfer
.set_browser_status("Auto CWD disabled for this connection");
} else {
self.settings
.summary
.ui_file_explorer_auto_sync_cwd_connection_ids
.retain(|id| id != &connection_id);
self.settings
.summary
.ui_file_explorer_auto_sync_cwd_connection_ids
.push(connection_id);
self.transfer
.set_browser_status("Auto CWD enabled for this connection");
} else {
self.transfer
.set_browser_status("Auto CWD disabled for this connection");
}
self.transfer.reset_browser_auto_sync_cwd();
self.persist_transfer_browser_ui_settings();
if !enabled {
if enabled {
self.start_transfer_sync_cwd_job(window, cx);
} else {
cx.notify();
@@ -245,14 +230,13 @@ impl NyaTermApp {
&mut self,
cx: &mut Context<Self>,
) {
self.settings.summary.ui_file_explorer_show_hidden_files =
!self.settings.summary.ui_file_explorer_show_hidden_files;
if !self.settings.summary.ui_file_explorer_show_hidden_files {
let show_hidden_files = self.settings.toggle_file_explorer_hidden_files();
if !show_hidden_files {
self.transfer
.retain_browser_selection(|path| !remote_file_name(path).starts_with('.'));
}
self.transfer.set_browser_list_offset(0);
let status = if self.settings.summary.ui_file_explorer_show_hidden_files {
let status = if show_hidden_files {
"hidden files shown".to_string()
} else {
"hidden files hidden".to_string()
@@ -267,7 +251,7 @@ impl NyaTermApp {
return false;
};
self.settings
.summary
.summary()
.ui_file_explorer_auto_sync_cwd_connection_ids
.iter()
.any(|id| id == &connection_id)
@@ -280,7 +264,7 @@ impl NyaTermApp {
};
let favorites = self
.settings
.summary
.summary()
.ui_file_explorer_favorite_dirs_by_connection_id
.get(&connection_id)
.cloned()
@@ -307,17 +291,8 @@ impl NyaTermApp {
return;
};
let favorites = self.transfer.browser_favorites_owned();
if favorites.is_empty() {
self.settings
.summary
.ui_file_explorer_favorite_dirs_by_connection_id
.remove(&connection_id);
} else {
self.settings
.summary
.ui_file_explorer_favorite_dirs_by_connection_id
.insert(connection_id, favorites);
}
self.settings
.set_file_explorer_favorites(connection_id, favorites);
self.persist_transfer_browser_ui_settings();
cx.notify();
}
@@ -327,7 +302,7 @@ impl NyaTermApp {
self.runtime.config_dir(),
self.runtime.portable_key_path().map(ToOwned::to_owned),
)
.and_then(|store| store.save_file_explorer_favorite_dirs(&self.settings.summary))
.and_then(|store| store.save_file_explorer_favorite_dirs(self.settings.summary()))
{
Ok(settings) => {
self.apply_gpui_settings(settings);
@@ -499,7 +499,7 @@ impl NyaTermApp {
cx.notify();
return;
}
if self.settings.summary.transfer_ask_save_location {
if self.settings.summary().transfer_ask_save_location {
let remote_paths = entries
.into_iter()
.map(|entry| entry.path)
@@ -170,7 +170,7 @@ impl NyaTermApp {
entry: &SftpFileEntry,
) -> bool {
entry.file_type != SftpFileType::Directory
&& self.settings.summary.transfer_editor_type == "external"
&& self.settings.summary().transfer_editor_type == "external"
&& !is_known_binary_file(&entry.name)
}
@@ -179,7 +179,7 @@ impl NyaTermApp {
entry: &SftpFileEntry,
) -> bool {
entry.file_type != SftpFileType::Directory
&& self.settings.summary.transfer_editor_type == "internal"
&& self.settings.summary().transfer_editor_type == "internal"
}
pub(in crate::features) fn open_transfer_default(
@@ -188,7 +188,7 @@ impl NyaTermApp {
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.settings.summary.transfer_editor_type == "internal" {
if self.settings.summary().transfer_editor_type == "internal" {
self.open_transfer_editor(entry, window, cx);
} else {
self.open_transfer_external(entry, window, cx);
@@ -374,7 +374,7 @@ impl NyaTermApp {
self.transfer.set_remote_path(entry.path.clone());
let remote_path = entry.path.clone();
let local_path = self.transfer_external_open_path(&entry, session_id.as_deref());
let default_editor = self.settings.summary.transfer_default_editor.clone();
let default_editor = self.settings.summary().transfer_default_editor.clone();
let transfer_options = self.sftp_transfer_options();
let id = self.next_transfer_id("sftp-open-external");
let control = SftpTransferControl::new();
@@ -246,7 +246,7 @@ impl NyaTermApp {
let status = if path == current_path {
TransferBrowserChildrenMenuStatus::Ready(transfer_browser_child_directories(
&self.transfer.browser_view().entries,
self.settings.summary.ui_file_explorer_show_hidden_files,
self.settings.summary().ui_file_explorer_show_hidden_files,
))
} else {
TransferBrowserChildrenMenuStatus::Loading
@@ -17,7 +17,7 @@ impl NyaTermApp {
let width = (self.shell.viewport_size().0 - 32.).clamp(280., 480.);
let docs_url = if self
.settings
.summary
.summary()
.language
.to_ascii_lowercase()
.starts_with("zh")
@@ -22,13 +22,13 @@ impl NyaTermApp {
cx,
);
let password_focus = password_input.read(cx).focus_handle();
let overlay_focus = if self.settings.summary.has_master_password {
let overlay_focus = if self.settings.summary().has_master_password {
password_focus.clone()
} else {
self.security.screen_lock_focus().clone()
};
let lock_status = if self.security.screen_lock_status().trim().is_empty() {
if self.settings.summary.has_master_password {
if self.settings.summary().has_master_password {
self.tr("lockScreen.passwordPlaceholder").to_string()
} else {
self.tr("settings.masterPasswordRequired").to_string()
@@ -168,7 +168,7 @@ impl NyaTermApp {
.text_color(rgb(palette.text_muted))
.child(self.tr("lockScreen.message")),
)
.when(self.settings.summary.has_master_password, |this| {
.when(self.settings.summary().has_master_password, |this| {
this.child(
div()
.w(px(280.))
@@ -224,7 +224,7 @@ impl NyaTermApp {
),
)
})
.when(!self.settings.summary.has_master_password, |this| {
.when(!self.settings.summary().has_master_password, |this| {
this.child(
div()
.text_center()
@@ -15,7 +15,7 @@ impl NyaTermApp {
let palette = self.theme_palette();
let docs_url = if self
.settings
.summary
.summary()
.language
.to_ascii_lowercase()
.starts_with("zh")
@@ -36,7 +36,7 @@ impl Render for QuickCommandWindow {
(
app.theme_palette(),
app.gpui_ui_font_family(),
app.settings.summary.ui_font_size.clamp(12, 24) as f32,
app.settings.summary().ui_font_size.clamp(12, 24) as f32,
app.quick_command_editor_title().to_string(),
)
});
@@ -54,7 +54,7 @@ impl Render for RemoteFileEditorWindow {
(
app.theme_palette(),
app.gpui_ui_font_family(),
app.settings.summary.ui_font_size.clamp(12, 24) as f32,
app.settings.summary().ui_font_size.clamp(12, 24) as f32,
format!(
"{}{}",
if workspace.tabs.iter().any(|tab| tab.dirty) {
+4 -4
View File
@@ -59,7 +59,7 @@ impl NyaTermApp {
let palette = self.theme_palette();
let wallpaper_path = self
.settings
.summary
.summary()
.background_image_path
.as_ref()
.map(|p| p.trim())
@@ -67,8 +67,8 @@ impl NyaTermApp {
.filter(|p| std::path::Path::new(p).is_file())
.map(|p| p.to_string());
let wallpaper_opacity =
(self.settings.summary.background_image_opacity.min(100) as f32) / 100.0;
let wallpaper_fit = self.settings.summary.background_image_fit.clone();
(self.settings.summary().background_image_opacity.min(100) as f32) / 100.0;
let wallpaper_fit = self.settings.summary().background_image_fit.clone();
let wallpaper_tile_size = wallpaper_path
.as_deref()
.filter(|_| wallpaper_fit == "tile")
@@ -80,7 +80,7 @@ impl NyaTermApp {
.bg(self.shell_transparent_color(palette.bg))
.text_color(rgb(palette.text))
.font_family(self.gpui_ui_font_family())
.text_size(px(self.settings.summary.ui_font_size.clamp(12, 24) as f32))
.text_size(px(self.settings.summary().ui_font_size.clamp(12, 24) as f32))
.on_click(cx.listener(|this, _, _, _| {
this.mark_user_activity();
}))
@@ -29,7 +29,7 @@ impl NyaTermApp {
return;
}
let target = recording_file_path(
&self.settings.summary,
self.settings.summary(),
self.runtime.config_dir(),
&session_name,
);
@@ -107,9 +107,9 @@ impl NyaTermApp {
let manager = self.recording.manager_for_job();
let writer = self.recording.writer();
let job_session_id = session_id.to_string();
let memory_limit = self.settings.summary.recording_memory_limit_bytes as usize;
let include_io_labels = self.settings.summary.recording_include_io_labels;
let include_timestamps = self.settings.summary.recording_include_timestamps;
let memory_limit = self.settings.summary().recording_memory_limit_bytes as usize;
let include_io_labels = self.settings.summary().recording_include_io_labels;
let include_timestamps = self.settings.summary().recording_include_timestamps;
let task = cx.background_spawn(async move {
writer.flush();
manager.set_memory_limit(memory_limit);
@@ -207,9 +207,9 @@ impl NyaTermApp {
let manager = self.recording.manager_for_job();
let writer = self.recording.writer();
let job_session_id = session_id.to_string();
let memory_limit = self.settings.summary.recording_memory_limit_bytes as usize;
let include_io_labels = self.settings.summary.recording_include_io_labels;
let include_timestamps = self.settings.summary.recording_include_timestamps;
let memory_limit = self.settings.summary().recording_memory_limit_bytes as usize;
let include_io_labels = self.settings.summary().recording_include_io_labels;
let include_timestamps = self.settings.summary().recording_include_timestamps;
let task = cx.background_spawn(async move {
writer.flush();
manager.set_memory_limit(memory_limit);
@@ -249,11 +249,11 @@ impl NyaTermApp {
session_name: &str,
cx: &mut Context<Self>,
) {
if !self.settings.summary.recording_auto_start {
if !self.settings.summary().recording_auto_start {
return;
}
let path = recording_file_path(
&self.settings.summary,
self.settings.summary(),
self.runtime.config_dir(),
session_name,
);
@@ -119,7 +119,7 @@ impl NyaTermApp {
}
let delay_ms = u64::from(
self.settings
.summary
.summary()
.interaction_duplicate_session_command_delay_ms,
);
self.session.dialogs.open_startup_command(action, delay_ms);
@@ -239,7 +239,7 @@ impl NyaTermApp {
view.append_text(banner);
} else {
let mut view = TerminalViewState::new();
view.set_encoding(&self.settings.summary.interaction_default_encoding);
view.set_encoding(&self.settings.summary().interaction_default_encoding);
view.append_text(banner);
self.terminal
.view
@@ -10,7 +10,7 @@ impl NyaTermApp {
metadata: SessionRuntimeMetadata,
) {
self.session.register_session_metadata(session_id, metadata);
let encoding = self.settings.summary.interaction_default_encoding.clone();
let encoding = self.settings.summary().interaction_default_encoding.clone();
let view = self
.terminal
.view
@@ -547,7 +547,7 @@ impl NyaTermApp {
session_id.clone(),
TerminalViewState::from_output_with_encoding(
seed_output,
&self.settings.summary.interaction_default_encoding,
&self.settings.summary().interaction_default_encoding,
),
);
}
@@ -595,7 +595,7 @@ impl NyaTermApp {
// Do not append local log text through the full terminal decode path
// on connect success — that competes with the first SSH/PTY frames.
// Auto-recording file open is deferred to the idle plane.
if self.settings.summary.recording_auto_start {
if self.settings.summary().recording_auto_start {
self.recording
.schedule_auto_start(session_id.clone(), session_info.name.clone());
}
@@ -316,9 +316,9 @@ impl NyaTermApp {
SshSessionConfigBuildContext {
config_dir: self.runtime.config_dir().to_path_buf(),
portable_key_path: self.runtime.portable_key_path().map(ToOwned::to_owned),
host_key_policy: self.settings.summary.host_key_policy.clone(),
x11_display: self.settings.summary.x11_display.clone(),
keep_alive_interval_secs: self.settings.summary.terminal_keep_alive_interval,
host_key_policy: self.settings.summary().host_key_policy.clone(),
x11_display: self.settings.summary().x11_display.clone(),
keep_alive_interval_secs: self.settings.summary().terminal_keep_alive_interval,
host_key_prompts: self.session.prompts.host_key_broker(),
credential_prompts: self.session.prompts.credential_broker(),
otp_provider: self.session.prompts.otp_provider(),
@@ -18,7 +18,7 @@ impl NyaTermApp {
/// Connect/register must not open the config database or rewrite settings on
/// the UI thread — that path was a major connect-time freeze source.
pub(in crate::features) fn persist_open_tabs(&mut self) {
if !self.settings.summary.startup_restore {
if !self.settings.summary().startup_restore {
return;
}
self.terminal.view.runtime.open_tabs_persist_dirty = true;
@@ -28,7 +28,7 @@ impl NyaTermApp {
/// Force a durable open-tabs write (window close / explicit quit paths).
pub(in crate::features) fn flush_open_tabs_now(&mut self) {
if !self.settings.summary.startup_restore {
if !self.settings.summary().startup_restore {
self.terminal.view.runtime.open_tabs_persist_dirty = false;
self.terminal.view.runtime.window_layout_persist_dirty = false;
return;
@@ -44,14 +44,14 @@ impl NyaTermApp {
/// rewriting settings is never done on the UI tick — that freezes connect
/// and the first idle frame after connect.
pub(in crate::features) fn flush_pending_session_persistence(&mut self) {
if !self.settings.summary.startup_restore {
if !self.settings.summary().startup_restore {
self.terminal.view.runtime.open_tabs_persist_dirty = false;
self.terminal.view.runtime.window_layout_persist_dirty = false;
return;
}
let need_tabs = self.terminal.view.runtime.open_tabs_persist_dirty;
let need_layout = self.terminal.view.runtime.window_layout_persist_dirty
&& self.settings.summary.startup_restore_window_layout;
&& self.settings.summary().startup_restore_window_layout;
if !need_tabs && !need_layout {
return;
}
@@ -122,14 +122,14 @@ impl NyaTermApp {
/// Synchronous durable write used by window-close / quit (must not race exit).
fn flush_pending_session_persistence_sync(&mut self) {
if !self.settings.summary.startup_restore {
if !self.settings.summary().startup_restore {
self.terminal.view.runtime.open_tabs_persist_dirty = false;
self.terminal.view.runtime.window_layout_persist_dirty = false;
return;
}
let need_tabs = self.terminal.view.runtime.open_tabs_persist_dirty;
let need_layout = self.terminal.view.runtime.window_layout_persist_dirty
&& self.settings.summary.startup_restore_window_layout;
&& self.settings.summary().startup_restore_window_layout;
if !need_tabs && !need_layout {
return;
}
@@ -339,7 +339,7 @@ impl NyaTermApp {
if !should_restore {
return;
}
if !self.settings.summary.startup_restore {
if !self.settings.summary().startup_restore {
self.mark_startup_restore_complete();
return;
}
@@ -128,7 +128,7 @@ impl NyaTermApp {
x11_forwarding: false,
x11_display: String::new(),
deferred_pty: true,
keep_alive_interval_secs: self.settings.summary.terminal_keep_alive_interval,
keep_alive_interval_secs: self.settings.summary().terminal_keep_alive_interval,
cols: 80,
rows: 24,
pixel_width: 0,
@@ -136,7 +136,7 @@ impl NyaTermApp {
host_key_verifier: Some(Arc::new(NativeHostKeyVerifier {
config_dir: self.runtime.config_dir().to_path_buf(),
portable_key_path: self.runtime.portable_key_path().map(ToOwned::to_owned),
policy: self.settings.summary.host_key_policy.clone(),
policy: self.settings.summary().host_key_policy.clone(),
prompt_broker: self.session.prompts.host_key_broker(),
})),
credential_provider: Some(self.session.prompts.credential_broker()),
@@ -1,8 +1,8 @@
//! Compatibility-sensitive settings state shared by the settings feature owner.
pub(in crate::features) struct SettingsMasterPasswordState {
pub enabled: bool,
pub draft: String,
pub(super) struct SettingsMasterPasswordState {
pub(super) enabled: bool,
pub(super) draft: String,
}
impl SettingsMasterPasswordState {
@@ -11,4 +11,6 @@ mod state;
pub(in crate::features) use security_state::{
SecurityCatalogState, SecurityFeatureFocus, SecurityFeatureState,
};
pub(in crate::features) use state::{SearchEngineMenu, SettingsFeatureFocus, SettingsFeatureState};
pub(in crate::features) use state::{
SearchEngineMenu, SettingsFeatureFocus, SettingsFeatureState, UiLayoutSettingsUpdate,
};
@@ -17,6 +17,7 @@ impl NyaTermApp {
self.cloud_sync.settings_draft_snapshot();
let (ai_settings, ai_model_draft, ai_base_url_draft, ai_secret_draft) =
self.ai.settings_draft_snapshot();
let master_password = self.settings.master_password();
self.shell
.set_settings_draft_snapshot(SettingsDraftSnapshot {
settings: self.settings.summary.clone(),
@@ -29,8 +30,8 @@ impl NyaTermApp {
translation_settings,
translation_secret_draft,
keyword_highlights: self.settings.keyword_config.clone(),
master_password_enabled: self.settings.master_password.enabled,
master_password_draft: self.settings.master_password.draft.clone(),
master_password_enabled: master_password.enabled,
master_password_draft: master_password.draft.to_string(),
});
}
@@ -38,7 +39,8 @@ impl NyaTermApp {
let Some(snapshot) = self.shell.settings_draft_snapshot() else {
return false;
};
snapshot.settings != self.settings.summary
let master_password = self.settings.master_password();
snapshot.settings != *self.settings.summary()
|| !self.ai.settings_draft_matches(
&snapshot.ai_settings,
&snapshot.ai_model_draft,
@@ -53,9 +55,9 @@ impl NyaTermApp {
&snapshot.translation_settings,
&snapshot.translation_secret_draft,
)
|| snapshot.keyword_highlights != self.settings.keyword_config
|| snapshot.master_password_enabled != self.settings.master_password.enabled
|| snapshot.master_password_draft != self.settings.master_password.draft
|| snapshot.keyword_highlights != *self.settings.keyword_config()
|| snapshot.master_password_enabled != master_password.enabled
|| snapshot.master_password_draft != master_password.draft
}
/// Returns true when a settings save should stay in the in-memory draft.
@@ -78,12 +80,11 @@ impl NyaTermApp {
if !settings.enabled {
return None;
}
if !self.settings.master_password.enabled {
let master_password = self.settings.master_password();
if !master_password.enabled {
return Some("Enable a master password before enabling cloud sync".to_string());
}
if !self.settings.summary.has_master_password
&& self.settings.master_password.draft.is_empty()
{
if !self.settings.summary().has_master_password && master_password.draft.is_empty() {
return Some("Enter a master password before enabling cloud sync".to_string());
}
let missing = match settings.provider.as_str() {
@@ -216,16 +217,17 @@ impl NyaTermApp {
return;
}
let settings = self.settings.summary.clone();
let settings = self.settings.summary().clone();
let ai_settings = self.pending_ai_settings();
let cloud_sync_settings = self.cloud_sync.pending_settings();
let translation_settings = self.translation.pending_settings();
let keyword_highlights = self.settings.keyword_config.clone();
let master_password_update = if self.settings.master_password.draft.is_empty() {
(self.settings.summary.has_master_password && !self.settings.master_password.enabled)
let keyword_highlights = self.settings.keyword_config().clone();
let master_password = self.settings.master_password();
let master_password_update = if master_password.draft.is_empty() {
(self.settings.summary().has_master_password && !master_password.enabled)
.then_some(None)
} else {
Some(Some(self.settings.master_password.draft.clone()))
Some(Some(master_password.draft.to_string()))
};
let result = ConnectionStore::open_with_portable_key_path(
self.runtime.config_dir(),
@@ -343,14 +345,17 @@ impl NyaTermApp {
snapshot.translation_settings,
snapshot.translation_secret_draft,
);
self.settings.keyword_config = snapshot.keyword_highlights;
self.settings.master_password.enabled = snapshot.master_password_enabled;
self.settings.master_password.draft = snapshot.master_password_draft;
self.settings
.replace_keyword_config(snapshot.keyword_highlights);
self.settings.restore_master_password_draft(
snapshot.master_password_enabled,
snapshot.master_password_draft,
);
self.recording
.set_memory_limit(self.settings.summary.recording_memory_limit_bytes as usize);
.set_memory_limit(self.settings.summary().recording_memory_limit_bytes as usize);
self.transfer
.set_duplicate_policy(SftpDuplicatePolicy::from_legacy_value(
&self.settings.summary.transfer_duplicate_strategy,
&self.settings.summary().transfer_duplicate_strategy,
));
self.sync_terminal_encodings_from_settings();
self.invalidate_terminal_cell_metrics(cx);
@@ -373,8 +378,7 @@ impl NyaTermApp {
pub(in crate::features) fn toggle_settings_master_password(&mut self, cx: &mut Context<Self>) {
self.terminal.view.status = match self
.settings
.master_password
.toggle(self.cloud_sync.settings().enabled)
.toggle_master_password(self.cloud_sync.settings().enabled)
{
Ok(true) => "master password enabled; enter a password".to_string(),
Ok(false) => "master password removal staged".to_string(),
@@ -389,7 +393,7 @@ impl NyaTermApp {
text: String,
cx: &mut Context<Self>,
) {
if !self.settings.master_password.edit_draft(text) {
if !self.settings.edit_master_password_draft(text) {
return;
}
self.terminal.view.status = "master password edited; apply to persist".to_string();
@@ -1,5 +1,7 @@
//! Authoritative application settings and grouped state for the settings experience.
use std::collections::HashMap;
use gpui::FocusHandle;
use nyaterm_core::{
AppSettingsSummary, KeywordHighlightConfig, KeywordHighlightRule, SearchEngineConfig,
@@ -14,9 +16,9 @@ use super::catalog::{SettingsMasterPasswordState, StoreStatus};
pub(in crate::features) struct SettingsFeatureState {
/// Compatibility-sensitive values loaded and persisted through `nyaterm-core`.
pub summary: AppSettingsSummary,
pub keyword_config: KeywordHighlightConfig,
pub master_password: SettingsMasterPasswordState,
pub(super) summary: AppSettingsSummary,
pub(super) keyword_config: KeywordHighlightConfig,
pub(super) master_password: SettingsMasterPasswordState,
pub(super) store_status: StoreStatus,
search_engines: SearchEngineSettingsState,
keyword_highlights: KeywordHighlightSettingsState,
@@ -45,6 +47,37 @@ pub(in crate::features) struct StoreStatusView<'a> {
pub ready: bool,
}
/// Borrowed staged master-password state. Deliberately does not implement
/// `Debug` so the secret draft cannot be exposed through aggregate logging.
pub(in crate::features) struct MasterPasswordView<'a> {
pub enabled: bool,
pub draft: &'a str,
}
pub(in crate::features) struct UiLayoutSettingsUpdate {
pub left_panel_width: u32,
pub right_panel_width: u32,
pub transfer_height: u32,
pub quick_command_height: u32,
pub quick_command_visible: bool,
pub serial_send_height: u32,
pub serial_send_visible: bool,
pub active_left_panel: Option<String>,
pub active_right_panel: Option<String>,
pub left_panel_collapsed: bool,
pub right_panel_collapsed: bool,
pub saved_connections_sort_mode: String,
pub activity_bar_left_top: Vec<String>,
pub activity_bar_left_bottom: Vec<String>,
pub activity_bar_right_top: Vec<String>,
pub activity_bar_right_bottom: Vec<String>,
pub activity_bar_show_labels: bool,
pub panel_multi_open: bool,
pub left_open_panels: Vec<String>,
pub right_open_panels: Vec<String>,
pub panel_stack_sizes: HashMap<String, u32>,
}
struct SearchEngineSettingsState {
expanded_index: Option<usize>,
icon_picker_index: Option<usize>,
@@ -147,10 +180,338 @@ impl SettingsFeatureState {
}
}
pub fn rebase_master_password(&mut self) {
pub(in crate::features) fn rebase_master_password(&mut self) {
self.master_password.reset(self.summary.has_master_password);
}
pub(in crate::features) fn summary(&self) -> &AppSettingsSummary {
&self.summary
}
pub(in crate::features) fn replace_summary(&mut self, summary: AppSettingsSummary) {
self.summary = summary;
}
pub(in crate::features) fn keyword_config(&self) -> &KeywordHighlightConfig {
&self.keyword_config
}
pub(in crate::features) fn replace_keyword_config(&mut self, config: KeywordHighlightConfig) {
self.keyword_config = config;
}
pub(in crate::features) fn toggle_keyword_highlights(&mut self) -> bool {
self.keyword_config.enabled = !self.keyword_config.enabled;
self.keyword_config.enabled
}
pub(in crate::features) fn toggle_keyword_highlights_wrapped(&mut self) -> bool {
self.keyword_config.across_wrapped_lines = !self.keyword_config.across_wrapped_lines;
self.keyword_config.across_wrapped_lines
}
pub(in crate::features) fn toggle_keyword_highlight_builtin(&mut self, rule_id: String) {
let enabled = self
.keyword_config
.builtin_rules
.get(&rule_id)
.copied()
.unwrap_or(true);
self.keyword_config.builtin_rules.insert(rule_id, !enabled);
}
pub(in crate::features) fn toggle_keyword_highlight_rule(&mut self, rule_id: &str) -> bool {
let Some(rule) = self
.keyword_config
.rules
.iter_mut()
.find(|rule| rule.id == rule_id)
else {
return false;
};
rule.enabled = !rule.enabled;
true
}
pub(in crate::features) fn set_keyword_highlight_rule_color(
&mut self,
rule_id: &str,
dark: bool,
color: String,
) -> bool {
let Some(rule) = self
.keyword_config
.rules
.iter_mut()
.find(|rule| rule.id == rule_id)
else {
return false;
};
if dark {
rule.color_dark = color;
} else {
rule.color_light = color;
}
true
}
pub(in crate::features) fn apply_keyword_highlight_rule_input(
&mut self,
rule_id: &str,
field: KeywordHighlightEditorField,
text: String,
) -> bool {
let Some(rule) = self
.keyword_config
.rules
.iter_mut()
.find(|rule| rule.id == rule_id)
else {
return false;
};
match field {
KeywordHighlightEditorField::Name => rule.name = text,
KeywordHighlightEditorField::Patterns => {
rule.patterns = text.split('\n').map(ToOwned::to_owned).collect();
}
KeywordHighlightEditorField::ColorDark => rule.color_dark = text,
KeywordHighlightEditorField::ColorLight => rule.color_light = text,
}
self.begin_keyword_highlight_edit(rule_id.to_string(), field);
true
}
pub(in crate::features) fn master_password(&self) -> MasterPasswordView<'_> {
MasterPasswordView {
enabled: self.master_password.enabled,
draft: &self.master_password.draft,
}
}
pub(in crate::features) fn restore_master_password_draft(
&mut self,
enabled: bool,
draft: String,
) {
self.master_password.enabled = enabled;
self.master_password.draft = draft;
}
pub(in crate::features) fn toggle_master_password(
&mut self,
cloud_sync_enabled: bool,
) -> Result<bool, &'static str> {
self.master_password.toggle(cloud_sync_enabled)
}
pub(in crate::features) fn edit_master_password_draft(&mut self, text: String) -> bool {
self.master_password.edit_draft(text)
}
pub(in crate::features) fn set_quick_command_view_mode(&mut self, mode: String) {
self.summary.ui_quick_cmd_view_mode = mode;
}
pub(in crate::features) fn set_quick_command_sort_mode(&mut self, mode: String) {
self.summary.ui_quick_cmd_sort_mode = mode;
}
pub(in crate::features) fn set_saved_connections_sort_mode(&mut self, mode: String) {
self.summary.ui_saved_connections_sort_mode = mode;
}
pub(in crate::features) fn set_header_status_mode(&mut self, mode: String) {
self.summary.ui_header_status_mode = mode;
self.summary.ui_header_status_visible = true;
}
pub(in crate::features) fn set_header_status_visible(&mut self, visible: bool) {
self.summary.ui_header_status_visible = visible;
}
pub(in crate::features) fn set_keybindings(&mut self, keybindings: HashMap<String, String>) {
self.summary.keybindings = keybindings;
}
pub(in crate::features) fn set_transfer_download_path(&mut self, path: String) {
self.summary.transfer_download_path = path;
}
pub(in crate::features) fn set_recording_path(&mut self, path: String) {
self.summary.recording_path = path;
}
pub(in crate::features) fn set_transfer_default_editor(&mut self, path: String) {
self.summary.transfer_default_editor = path;
}
pub(in crate::features) fn set_tab_double_click_action(&mut self, action: String) {
self.summary.interaction_tab_double_click_action = action;
}
pub(in crate::features) fn set_tab_middle_click_action(&mut self, action: String) {
self.summary.interaction_tab_middle_click_action = action;
}
pub(in crate::features) fn set_tab_right_click_action(&mut self, action: String) {
self.summary.interaction_tab_right_click_action = action;
}
pub(in crate::features) fn apply_ui_layout(&mut self, update: UiLayoutSettingsUpdate) {
self.summary.ui_left_panel_width = update.left_panel_width;
self.summary.ui_right_panel_width = update.right_panel_width;
self.summary.ui_transfer_height = update.transfer_height;
self.summary.ui_quick_cmd_height = update.quick_command_height;
self.summary.ui_quick_cmd_visible = update.quick_command_visible;
self.summary.ui_serial_send_height = update.serial_send_height;
self.summary.ui_serial_send_visible = update.serial_send_visible;
self.summary.ui_active_left_panel = update.active_left_panel;
self.summary.ui_active_right_panel = update.active_right_panel;
self.summary.ui_left_panel_collapsed = update.left_panel_collapsed;
self.summary.ui_right_panel_collapsed = update.right_panel_collapsed;
self.summary.ui_saved_connections_sort_mode = update.saved_connections_sort_mode;
self.summary.ui_activity_bar_left_top = update.activity_bar_left_top;
self.summary.ui_activity_bar_left_bottom = update.activity_bar_left_bottom;
self.summary.ui_activity_bar_right_top = update.activity_bar_right_top;
self.summary.ui_activity_bar_right_bottom = update.activity_bar_right_bottom;
self.summary.ui_activity_bar_show_labels = update.activity_bar_show_labels;
self.summary.ui_panel_multi_open = update.panel_multi_open;
self.summary.ui_left_open_panels = update.left_open_panels;
self.summary.ui_right_open_panels = update.right_open_panels;
self.summary.ui_panel_stack_sizes = update.panel_stack_sizes;
}
pub(in crate::features) fn set_appearance_theme(&mut self, theme: String) {
self.summary.theme = theme;
}
pub(in crate::features) fn set_terminal_font_family(&mut self, family: String) -> bool {
if self.summary.terminal_font_family == family {
return false;
}
self.summary.terminal_font_family = family;
true
}
pub(in crate::features) fn set_terminal_font_size(&mut self, size: u16) -> bool {
if self.summary.terminal_font_size == size {
return false;
}
self.summary.terminal_font_size = size;
true
}
pub(in crate::features) fn set_cursor_style(&mut self, style: String) {
self.summary.cursor_style = style;
}
pub(in crate::features) fn toggle_cursor_blink(&mut self) -> bool {
self.summary.cursor_blink = !self.summary.cursor_blink;
self.summary.cursor_blink
}
pub(in crate::features) fn set_terminal_theme(&mut self, theme: Option<String>) {
self.summary.terminal_theme = theme;
}
pub(in crate::features) fn set_minimum_contrast_ratio(&mut self, ratio: String) -> bool {
if self.summary.minimum_contrast_ratio == ratio {
return false;
}
self.summary.minimum_contrast_ratio = ratio;
true
}
pub(in crate::features) fn set_ui_font_family(&mut self, family: String) {
self.summary.ui_font_family = family;
}
pub(in crate::features) fn set_ui_font_size(&mut self, size: u16) {
self.summary.ui_font_size = size;
}
pub(in crate::features) fn set_terminal_font_weight(&mut self, weight: u16) -> bool {
if self.summary.terminal_font_weight == weight {
return false;
}
self.summary.terminal_font_weight = weight;
true
}
pub(in crate::features) fn set_terminal_font_weight_bold(&mut self, weight: u16) -> bool {
if self.summary.terminal_font_weight_bold == weight {
return false;
}
self.summary.terminal_font_weight_bold = weight;
true
}
pub(in crate::features) fn select_background_image(&mut self, path: String) {
self.summary.background_image_path = Some(path);
if self.summary.background_image_fit.trim().is_empty() {
self.summary.background_image_fit = "cover".to_string();
}
}
pub(in crate::features) fn clear_background_image(&mut self) {
self.summary.background_image_path = None;
}
pub(in crate::features) fn set_background_image_fit(&mut self, fit: String) {
self.summary.background_image_fit = fit;
}
pub(in crate::features) fn set_background_image_opacity(&mut self, opacity: u8) -> bool {
if self.summary.background_image_opacity == opacity {
return false;
}
self.summary.background_image_opacity = opacity;
true
}
pub(in crate::features) fn set_background_content_opacity(&mut self, opacity: u8) -> bool {
if self.summary.background_content_opacity == opacity {
return false;
}
self.summary.background_content_opacity = opacity;
true
}
pub(in crate::features) fn toggle_file_explorer_auto_sync_cwd(
&mut self,
connection_id: String,
) -> bool {
let ids = &mut self.summary.ui_file_explorer_auto_sync_cwd_connection_ids;
let was_enabled = ids.iter().any(|id| id == &connection_id);
ids.retain(|id| id != &connection_id);
if !was_enabled {
ids.push(connection_id);
}
!was_enabled
}
pub(in crate::features) fn toggle_file_explorer_hidden_files(&mut self) -> bool {
self.summary.ui_file_explorer_show_hidden_files =
!self.summary.ui_file_explorer_show_hidden_files;
self.summary.ui_file_explorer_show_hidden_files
}
pub(in crate::features) fn set_file_explorer_favorites(
&mut self,
connection_id: String,
favorites: Vec<String>,
) {
if favorites.is_empty() {
self.summary
.ui_file_explorer_favorite_dirs_by_connection_id
.remove(&connection_id);
} else {
self.summary
.ui_file_explorer_favorite_dirs_by_connection_id
.insert(connection_id, favorites);
}
}
pub(in crate::features) fn store_status(&self) -> StoreStatusView<'_> {
StoreStatusView {
path: &self.store_status.path,
@@ -570,12 +931,16 @@ fn adjusted_index_after_remove(value: Option<usize>, removed: usize) -> Option<u
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use gpui::TestAppContext;
use nyaterm_core::{
AppSettingsSummary, KeywordHighlightConfig, KeywordHighlightRule, SearchEngineConfig,
};
use super::{SearchEngineMenu, SettingsFeatureFocus, SettingsFeatureState};
use super::{
SearchEngineMenu, SettingsFeatureFocus, SettingsFeatureState, UiLayoutSettingsUpdate,
};
use crate::models::{
ConfigPathPromptKind, KeywordHighlightEditorField, SnapshotPasswordPromptKind,
};
@@ -717,4 +1082,96 @@ mod tests {
assert_eq!(status.message, "store reopened");
assert!(status.ready);
}
#[test]
fn settings_owner_applies_ui_layout_as_one_transition() {
let mut state = settings_state();
state.apply_ui_layout(UiLayoutSettingsUpdate {
left_panel_width: 320,
right_panel_width: 360,
transfer_height: 240,
quick_command_height: 180,
quick_command_visible: false,
serial_send_height: 160,
serial_send_visible: true,
active_left_panel: Some("connections".to_string()),
active_right_panel: Some("sftp".to_string()),
left_panel_collapsed: false,
right_panel_collapsed: true,
saved_connections_sort_mode: "recent".to_string(),
activity_bar_left_top: vec!["connections".to_string()],
activity_bar_left_bottom: vec!["settings".to_string()],
activity_bar_right_top: vec!["sftp".to_string()],
activity_bar_right_bottom: vec!["ai".to_string()],
activity_bar_show_labels: true,
panel_multi_open: true,
left_open_panels: vec!["connections".to_string()],
right_open_panels: vec!["sftp".to_string()],
panel_stack_sizes: HashMap::from([("left:connections".to_string(), 600)]),
});
let summary = state.summary();
assert_eq!(summary.ui_left_panel_width, 320);
assert_eq!(summary.ui_right_panel_width, 360);
assert!(!summary.ui_quick_cmd_visible);
assert!(summary.ui_serial_send_visible);
assert_eq!(summary.ui_active_left_panel.as_deref(), Some("connections"));
assert!(summary.ui_right_panel_collapsed);
assert_eq!(summary.ui_activity_bar_right_bottom, ["ai"]);
assert_eq!(summary.ui_panel_stack_sizes["left:connections"], 600);
}
#[test]
fn settings_owner_controls_keyword_catalog_transitions() {
let mut state = settings_state();
state.add_keyword_highlight_rule(KeywordHighlightRule {
id: "warning".to_string(),
name: "warning".to_string(),
patterns: vec!["WARN".to_string()],
color_dark: "#ffffff".to_string(),
color_light: "#000000".to_string(),
enabled: true,
});
assert!(state.toggle_keyword_highlights());
assert!(state.toggle_keyword_highlights_wrapped());
state.toggle_keyword_highlight_builtin("builtin-error".to_string());
assert!(state.toggle_keyword_highlight_rule("warning"));
assert!(state.set_keyword_highlight_rule_color("warning", true, "#123456".to_string()));
assert!(state.apply_keyword_highlight_rule_input(
"warning",
KeywordHighlightEditorField::Patterns,
"WARN\nERROR".to_string(),
));
let config = state.keyword_config();
assert!(config.enabled);
assert!(config.across_wrapped_lines);
assert!(!config.builtin_rules["builtin-error"]);
assert!(!config.rules[0].enabled);
assert_eq!(config.rules[0].color_dark, "#123456");
assert_eq!(config.rules[0].patterns, ["WARN", "ERROR"]);
}
#[test]
fn settings_owner_exposes_master_password_as_a_borrowed_view() {
let mut state = settings_state();
assert_eq!(state.master_password.toggle(false), Ok(true));
assert!(
state
.master_password
.edit_draft("replacement secret".to_string())
);
let view = state.master_password();
assert!(view.enabled);
assert_eq!(view.draft, "replacement secret");
state.summary.has_master_password = false;
state.rebase_master_password();
let view = state.master_password();
assert!(!view.enabled);
assert!(view.draft.is_empty());
}
}
@@ -37,7 +37,7 @@ impl Render for SettingsWindow {
(
app.theme_palette(),
app.gpui_ui_font_family(),
app.settings.summary.ui_font_size.clamp(12, 24) as f32,
app.settings.summary().ui_font_size.clamp(12, 24) as f32,
app.tr("settings.title").to_string(),
)
});
@@ -89,26 +89,13 @@ impl NyaTermApp {
.collect()
}
pub(in crate::features) fn sync_activity_layout_to_settings(&mut self) {
self.settings.summary.ui_activity_bar_left_top =
self.shell.chrome.activity_bar_layout.left_top.clone();
self.settings.summary.ui_activity_bar_left_bottom =
self.shell.chrome.activity_bar_layout.left_bottom.clone();
self.settings.summary.ui_activity_bar_right_top =
self.shell.chrome.activity_bar_layout.right_top.clone();
self.settings.summary.ui_activity_bar_right_bottom =
self.shell.chrome.activity_bar_layout.right_bottom.clone();
self.settings.summary.ui_activity_bar_show_labels =
self.shell.chrome.activity_bar_layout.show_labels;
}
pub(in crate::features) fn apply_activity_layout_from_settings(&mut self) {
self.shell.chrome.activity_bar_layout = ActivityBarLayoutState {
left_top: self.settings.summary.ui_activity_bar_left_top.clone(),
left_bottom: self.settings.summary.ui_activity_bar_left_bottom.clone(),
right_top: self.settings.summary.ui_activity_bar_right_top.clone(),
right_bottom: self.settings.summary.ui_activity_bar_right_bottom.clone(),
show_labels: self.settings.summary.ui_activity_bar_show_labels,
left_top: self.settings.summary().ui_activity_bar_left_top.clone(),
left_bottom: self.settings.summary().ui_activity_bar_left_bottom.clone(),
right_top: self.settings.summary().ui_activity_bar_right_top.clone(),
right_bottom: self.settings.summary().ui_activity_bar_right_bottom.clone(),
show_labels: self.settings.summary().ui_activity_bar_show_labels,
};
self.normalize_activity_bar_layout();
}
@@ -14,34 +14,34 @@ const TERMINAL_FONT_SIZE_MAX: i16 = 72;
impl NyaTermApp {
pub(in crate::features) fn apply_gpui_settings(&mut self, settings: AppSettingsSummary) {
self.settings.summary = settings;
self.settings.replace_summary(settings);
self.invalidate_paint_theme_caches();
}
pub(in crate::features) fn gpui_terminal_font_family(&self) -> String {
gpui_platform_font_family(
&self.settings.summary.terminal_font_family,
&self.settings.summary().terminal_font_family,
gpui_terminal_font_fallback(),
true,
)
}
pub(in crate::features) fn gpui_ui_font_family(&self) -> String {
let raw = if self.settings.summary.ui_font_family.trim().is_empty() {
self.settings.summary.terminal_font_family.as_str()
let raw = if self.settings.summary().ui_font_family.trim().is_empty() {
self.settings.summary().terminal_font_family.as_str()
} else {
self.settings.summary.ui_font_family.as_str()
self.settings.summary().ui_font_family.as_str()
};
gpui_platform_font_family(raw, gpui_ui_font_fallback(), false)
}
pub(in crate::features) fn theme_palette(&self) -> ThemePalette {
theme_palette(&self.settings.summary.theme)
theme_palette(&self.settings.summary().theme)
}
pub(in crate::features) fn wallpaper_enabled(&self) -> bool {
self.settings
.summary
.summary()
.background_image_path
.as_deref()
.map(str::trim)
@@ -53,7 +53,7 @@ impl NyaTermApp {
if !self.wallpaper_enabled() {
return rgb(color);
}
let alpha = ((self.settings.summary.background_content_opacity.min(100) as f32 / 100.0)
let alpha = ((self.settings.summary().background_content_opacity.min(100) as f32 / 100.0)
* 255.0)
.round() as u32;
rgba((color << 8) | alpha.min(0xff))
@@ -80,19 +80,19 @@ impl NyaTermApp {
pub(in crate::features) fn resolved_keyword_highlight_rules(
&self,
) -> Arc<Vec<ResolvedKeywordHighlightRule>> {
if self.settings.summary.terminal_low_latency_mode {
if self.settings.summary().terminal_low_latency_mode {
return Arc::new(Vec::new());
}
if let Some(cached) = self.terminal.paint.cached_keyword_highlight_rules.as_ref() {
return cached.clone();
}
// Cache miss (settings path / first call without ensure): build once without storing.
if !self.settings.keyword_config.enabled {
if !self.settings.keyword_config().enabled {
return Arc::new(Vec::new());
}
Arc::new(merge_keyword_highlight_rules_for_paint(
&self.settings.keyword_config.rules,
&self.settings.keyword_config.builtin_rules,
&self.settings.keyword_config().rules,
&self.settings.keyword_config().builtin_rules,
self.terminal_theme_is_dark(),
))
}
@@ -104,21 +104,21 @@ impl NyaTermApp {
}
fn ensure_keyword_highlight_rules_cache(&mut self) {
if self.settings.summary.terminal_low_latency_mode {
if self.settings.summary().terminal_low_latency_mode {
self.terminal.paint.cached_keyword_highlight_rules = Some(Arc::new(Vec::new()));
return;
}
if self.terminal.paint.cached_keyword_highlight_rules.is_some() {
return;
}
let rules = if !self.settings.keyword_config.enabled {
let rules = if !self.settings.keyword_config().enabled {
Arc::new(Vec::new())
} else {
// terminal_theme_is_dark uses palette; ensure palette first.
self.ensure_terminal_theme_palette_cache();
Arc::new(merge_keyword_highlight_rules_for_paint(
&self.settings.keyword_config.rules,
&self.settings.keyword_config.builtin_rules,
&self.settings.keyword_config().rules,
&self.settings.keyword_config().builtin_rules,
self.terminal_theme_is_dark(),
))
};
@@ -127,16 +127,16 @@ impl NyaTermApp {
/// Terminal surface palette: follows optional `terminal_theme`, else UI theme.
pub(in crate::features) fn terminal_theme_palette(&self) -> ThemePalette {
let ui_theme = self.settings.summary.theme.as_str();
let ui_theme = self.settings.summary().theme.as_str();
let terminal_theme = self
.settings
.summary
.summary()
.terminal_theme
.as_deref()
.map(str::trim)
.filter(|s| !s.is_empty())
.unwrap_or("");
let contrast = self.settings.summary.minimum_contrast_ratio.as_str();
let contrast = self.settings.summary().minimum_contrast_ratio.as_str();
if let Some((cached_ui, cached_term, cached_contrast, palette)) =
self.terminal.paint.cached_terminal_theme_palette.as_ref()
{
@@ -157,17 +157,17 @@ impl NyaTermApp {
}
fn ensure_terminal_theme_palette_cache(&mut self) {
let ui_theme = self.settings.summary.theme.clone();
let ui_theme = self.settings.summary().theme.clone();
let terminal_theme = self
.settings
.summary
.summary()
.terminal_theme
.as_deref()
.map(str::trim)
.filter(|s| !s.is_empty())
.unwrap_or("")
.to_string();
let contrast = self.settings.summary.minimum_contrast_ratio.clone();
let contrast = self.settings.summary().minimum_contrast_ratio.clone();
if let Some((cached_ui, cached_term, cached_contrast, _)) =
self.terminal.paint.cached_terminal_theme_palette.as_ref()
{
@@ -237,7 +237,7 @@ impl NyaTermApp {
} else {
theme
};
self.settings.summary.theme = theme.to_string();
self.settings.set_appearance_theme(theme.to_string());
self.save_appearance_settings(cx);
}
@@ -246,10 +246,9 @@ impl NyaTermApp {
family: &str,
cx: &mut Context<Self>,
) {
if self.settings.summary.terminal_font_family == family {
if !self.settings.set_terminal_font_family(family.to_string()) {
return;
}
self.settings.summary.terminal_font_family = family.to_string();
self.invalidate_terminal_cell_metrics(cx);
self.save_appearance_settings(cx);
}
@@ -273,22 +272,20 @@ impl NyaTermApp {
delta: i16,
cx: &mut Context<Self>,
) {
let next = (self.settings.summary.terminal_font_size as i16 + delta)
let next = (self.settings.summary().terminal_font_size as i16 + delta)
.clamp(TERMINAL_FONT_SIZE_MIN, TERMINAL_FONT_SIZE_MAX);
if self.settings.summary.terminal_font_size == next as u16 {
if !self.settings.set_terminal_font_size(next as u16) {
return;
}
self.settings.summary.terminal_font_size = next as u16;
self.invalidate_terminal_cell_metrics(cx);
self.save_appearance_settings(cx);
}
pub(in crate::features) fn reset_terminal_font_size(&mut self, cx: &mut Context<Self>) {
let default_size = AppSettingsSummary::default().terminal_font_size;
if self.settings.summary.terminal_font_size == default_size {
if !self.settings.set_terminal_font_size(default_size) {
return;
}
self.settings.summary.terminal_font_size = default_size;
self.invalidate_terminal_cell_metrics(cx);
self.save_appearance_settings(cx);
}
@@ -302,15 +299,15 @@ impl NyaTermApp {
"underline" | "bar" => style,
_ => "block",
};
self.settings.summary.cursor_style = normalized.to_string();
self.settings.set_cursor_style(normalized.to_string());
self.save_appearance_settings(cx);
self.terminal.view.status = format!("cursor style → {normalized}");
}
pub(in crate::features) fn toggle_cursor_blink(&mut self, cx: &mut Context<Self>) {
self.settings.summary.cursor_blink = !self.settings.summary.cursor_blink;
let cursor_blink = self.settings.toggle_cursor_blink();
self.save_appearance_settings(cx);
self.terminal.view.status = if self.settings.summary.cursor_blink {
self.terminal.view.status = if cursor_blink {
"cursor blink on".to_string()
} else {
"cursor blink off".to_string()
@@ -322,16 +319,16 @@ impl NyaTermApp {
theme: Option<&str>,
cx: &mut Context<Self>,
) {
self.settings.summary.terminal_theme =
theme.map(str::trim).filter(|s| !s.is_empty()).map(|s| {
if s == "catppuccin" {
"catppuccin-mocha".to_string()
} else {
s.to_string()
}
});
let theme = theme.map(str::trim).filter(|s| !s.is_empty()).map(|s| {
if s == "catppuccin" {
"catppuccin-mocha".to_string()
} else {
s.to_string()
}
});
self.settings.set_terminal_theme(theme);
self.save_appearance_settings(cx);
self.terminal.view.status = match self.settings.summary.terminal_theme.as_deref() {
self.terminal.view.status = match self.settings.summary().terminal_theme.as_deref() {
Some(id) => format!("terminal theme → {id}"),
None => "terminal theme → follow UI".to_string(),
};
@@ -346,10 +343,9 @@ impl NyaTermApp {
"3" | "4.5" | "7" | "21" => ratio,
_ => "1",
};
if self.settings.summary.minimum_contrast_ratio == ratio {
if !self.settings.set_minimum_contrast_ratio(ratio.to_string()) {
return;
}
self.settings.summary.minimum_contrast_ratio = ratio.to_string();
self.save_appearance_settings(cx);
self.terminal.view.status = format!("minimum contrast → {ratio}");
}
@@ -359,7 +355,7 @@ impl NyaTermApp {
family: &str,
cx: &mut Context<Self>,
) {
self.settings.summary.ui_font_family = family.to_string();
self.settings.set_ui_font_family(family.to_string());
self.save_appearance_settings(cx);
}
@@ -371,9 +367,9 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) {
let raw = if terminal {
&self.settings.summary.terminal_font_family
&self.settings.summary().terminal_font_family
} else {
&self.settings.summary.ui_font_family
&self.settings.summary().ui_font_family
};
let fallback = if terminal { "JetBrains Mono" } else { "Inter" };
let mut fonts = appearance_font_stack(raw, fallback);
@@ -391,9 +387,9 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) {
let raw = if terminal {
&self.settings.summary.terminal_font_family
&self.settings.summary().terminal_font_family
} else {
&self.settings.summary.ui_font_family
&self.settings.summary().ui_font_family
};
let fallback = if terminal { "JetBrains Mono" } else { "Inter" };
let mut fonts = appearance_font_stack(raw, fallback);
@@ -415,9 +411,9 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) {
let raw = if terminal {
&self.settings.summary.terminal_font_family
&self.settings.summary().terminal_font_family
} else {
&self.settings.summary.ui_font_family
&self.settings.summary().ui_font_family
};
let fallback = if terminal { "JetBrains Mono" } else { "Inter" };
let mut fonts = appearance_font_stack(raw, fallback);
@@ -447,8 +443,8 @@ impl NyaTermApp {
}
pub(in crate::features) fn adjust_ui_font_size(&mut self, delta: i16, cx: &mut Context<Self>) {
let next = (self.settings.summary.ui_font_size as i16 + delta).clamp(12, 24) as u16;
self.settings.summary.ui_font_size = next;
let next = (self.settings.summary().ui_font_size as i16 + delta).clamp(12, 24) as u16;
self.settings.set_ui_font_size(next);
self.save_appearance_settings(cx);
}
@@ -461,10 +457,9 @@ impl NyaTermApp {
300 | 400 | 500 | 600 | 700 | 800 => weight,
_ => 400,
};
if self.settings.summary.terminal_font_weight == weight {
if !self.settings.set_terminal_font_weight(weight) {
return;
}
self.settings.summary.terminal_font_weight = weight;
self.invalidate_terminal_cell_metrics(cx);
self.save_appearance_settings(cx);
}
@@ -478,10 +473,9 @@ impl NyaTermApp {
300 | 400 | 500 | 600 | 700 | 800 => weight,
_ => 700,
};
if self.settings.summary.terminal_font_weight_bold == weight {
if !self.settings.set_terminal_font_weight_bold(weight) {
return;
}
self.settings.summary.terminal_font_weight_bold = weight;
self.invalidate_terminal_cell_metrics(cx);
self.save_appearance_settings(cx);
}
@@ -495,7 +489,7 @@ impl NyaTermApp {
}
pub(in crate::features) fn prompt_background_image(&mut self, cx: &mut Context<Self>) {
if self.settings.summary.background_image_path.is_some() {
if self.settings.summary().background_image_path.is_some() {
// allow replace
}
let options = PathPromptOptions {
@@ -515,10 +509,8 @@ impl NyaTermApp {
};
let _ = this.update(cx, |this, cx| {
if let Some(path) = path {
this.settings.summary.background_image_path = Some(path.display().to_string());
if this.settings.summary.background_image_fit.trim().is_empty() {
this.settings.summary.background_image_fit = "cover".to_string();
}
this.settings
.select_background_image(path.display().to_string());
this.save_appearance_settings(cx);
this.terminal.view.status = "wallpaper image selected".to_string();
} else {
@@ -532,7 +524,7 @@ impl NyaTermApp {
}
pub(in crate::features) fn clear_background_image(&mut self, cx: &mut Context<Self>) {
self.settings.summary.background_image_path = None;
self.settings.clear_background_image();
self.settings.close_appearance_menu();
self.save_appearance_settings(cx);
self.terminal.view.status = "wallpaper cleared".to_string();
@@ -549,7 +541,8 @@ impl NyaTermApp {
"tile" => "tile",
_ => "cover",
};
self.settings.summary.background_image_fit = normalized.to_string();
self.settings
.set_background_image_fit(normalized.to_string());
self.save_appearance_settings(cx);
self.terminal.view.status = format!("wallpaper fit → {normalized}");
}
@@ -560,10 +553,9 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) {
let next = value.min(100);
if self.settings.summary.background_image_opacity == next {
if !self.settings.set_background_image_opacity(next) {
return;
}
self.settings.summary.background_image_opacity = next;
self.save_appearance_settings(cx);
}
@@ -573,10 +565,9 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) {
let next = value.min(100);
if self.settings.summary.background_content_opacity == next {
if !self.settings.set_background_content_opacity(next) {
return;
}
self.settings.summary.background_content_opacity = next;
self.save_appearance_settings(cx);
}
@@ -589,7 +580,7 @@ impl NyaTermApp {
self.runtime.config_dir(),
self.runtime.portable_key_path().map(ToOwned::to_owned),
)
.and_then(|store| store.save_appearance_settings(&self.settings.summary))
.and_then(|store| store.save_appearance_settings(self.settings.summary()))
{
Ok(settings) => {
self.apply_gpui_settings(settings);
@@ -3,7 +3,7 @@ use crate::features::NyaTermApp;
impl NyaTermApp {
pub(in crate::features) fn sync_session_event_bridge_config(&self) {
self.session.configure_event_bridge(
self.settings.summary.interaction_default_encoding.clone(),
self.settings.summary().interaction_default_encoding.clone(),
self.terminal_scrollback_line_limit(),
);
}
@@ -256,18 +256,18 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) -> bool {
if self.security.screen_locked()
|| !self.settings.summary.enable_screen_lock
|| self.settings.summary.idle_lock_minutes == 0
|| !self.settings.summary().enable_screen_lock
|| self.settings.summary().idle_lock_minutes == 0
{
return false;
}
let idle_for = self.security.screen_lock_idle_for();
let lock_after =
Duration::from_secs(u64::from(self.settings.summary.idle_lock_minutes) * 60);
Duration::from_secs(u64::from(self.settings.summary().idle_lock_minutes) * 60);
if idle_for < lock_after {
return false;
}
let lock_status = if self.settings.summary.has_master_password {
let lock_status = if self.settings.summary().has_master_password {
"Enter the master password to unlock.".to_string()
} else {
"No master password is configured.".to_string()
@@ -276,9 +276,9 @@ impl NyaTermApp {
self.forget_text_inputs("lock-screen.password");
self.terminal.view.status = format!(
"screen locked after {} minute(s) idle",
self.settings.summary.idle_lock_minutes
self.settings.summary().idle_lock_minutes
);
if self.settings.summary.has_master_password {
if self.settings.summary().has_master_password {
let field = self.text_input("lock-screen.password", "", TextInputSetup::masked(), cx);
window.focus(&field.read(cx).focus_handle());
} else {
@@ -371,7 +371,7 @@ impl NyaTermApp {
if self.terminal.view.runtime.visual_bell_ticks > 0 {
return true;
}
if self.settings.summary.cursor_blink
if self.settings.summary().cursor_blink
&& !self.visible_terminal_session_ids().is_empty()
&& self
.terminal
@@ -538,30 +538,30 @@ impl NyaTermApp {
let right_panel = self.current_right_panel();
if (right_panel == Some(NavItem::Stats) || self.header_status_needs_remote_stats())
&& self.settings.summary.ui_show_remote_stats
&& self.settings.summary().ui_show_remote_stats
&& !self.remote_ops.stats_is_pending()
&& remote_refresh_due(
self.remote_ops.stats_last_refresh_at(),
self.settings.summary.ui_remote_stats_interval.max(1),
self.settings.summary().ui_remote_stats_interval.max(1),
)
{
self.refresh_stats(window, cx);
dirty = true;
} else if right_panel == Some(NavItem::Processes)
&& self.settings.summary.ui_show_process_manager
&& self.settings.summary().ui_show_process_manager
&& !self.remote_ops.process_is_pending()
&& remote_refresh_due(
self.remote_ops.process_last_refresh_at(),
self.settings.summary.ui_process_manager_interval.max(3),
self.settings.summary().ui_process_manager_interval.max(3),
)
{
self.refresh_processes(window, cx);
dirty = true;
} else if right_panel == Some(NavItem::Docker)
&& self.settings.summary.ui_show_docker_manager
&& self.settings.summary().ui_show_docker_manager
&& !self.remote_ops.docker_is_pending()
{
let interval = self.settings.summary.ui_docker_manager_interval.max(3);
let interval = self.settings.summary().ui_docker_manager_interval.max(3);
if remote_refresh_due(self.remote_ops.docker_last_refresh_at(), interval) {
self.refresh_docker(window, cx);
dirty = true;
@@ -674,7 +674,7 @@ impl NyaTermApp {
// can stay slow without stretching cursor blink to multi-second periods.
// Under output pressure / connect settle, keep last blink phase.
let mut surface_visual_dirty = false;
if runtime_cursor_blink_allowed(output_pressure, self.settings.summary.cursor_blink) {
if runtime_cursor_blink_allowed(output_pressure, self.settings.summary().cursor_blink) {
let next_blink_at = self
.terminal
.view
@@ -689,7 +689,7 @@ impl NyaTermApp {
} else {
self.terminal.view.runtime.cursor_blink_next_at = Some(next_blink_at);
}
} else if !self.settings.summary.cursor_blink {
} else if !self.settings.summary().cursor_blink {
if !self.terminal.view.runtime.cursor_blink_on {
surface_visual_dirty = true;
}
@@ -348,7 +348,7 @@ impl NyaTermApp {
self.note_zmodem_output_discontinuity(&session_id, bytes, cx);
self.note_ai_agent_output_discontinuity(&session_id, bytes, cx);
self.session.route_session_events_to_ui(&session_id);
let encoding = self.settings.summary.interaction_default_encoding.clone();
let encoding = self.settings.summary().interaction_default_encoding.clone();
let view = self
.terminal
.view
@@ -39,7 +39,7 @@ impl NyaTermApp {
return true;
}
let keybindings = self.settings.summary.keybindings.clone();
let keybindings = self.settings.summary().keybindings.clone();
if shortcut_matches(event, "terminal.copy", &keybindings) {
self.copy_terminal_selection_or_visible(cx);
return true;
@@ -10,7 +10,7 @@ impl NyaTermApp {
/// First display chord for empty-workspace / UI labels (Tauri-style chips).
pub(in crate::features) fn display_shortcut_for(&self, id: &str, fallback: &str) -> String {
use crate::shortcuts::{format_hotkey_for_display, shortcut_keys_for};
let raw = shortcut_keys_for(id, &self.settings.summary.keybindings)
let raw = shortcut_keys_for(id, &self.settings.summary().keybindings)
.unwrap_or_else(|| fallback.to_string());
let display = format_hotkey_for_display(&raw);
display
@@ -59,7 +59,7 @@ impl NyaTermApp {
cx.notify();
return;
}
let mut keybindings = self.settings.summary.keybindings.clone();
let mut keybindings = self.settings.summary().keybindings.clone();
let is_default = crate::shortcuts::SHORTCUT_REGISTRY
.iter()
.find(|s| s.id == shortcut_id)
@@ -77,7 +77,7 @@ impl NyaTermApp {
shortcut_id: String,
cx: &mut Context<Self>,
) {
let mut keybindings = self.settings.summary.keybindings.clone();
let mut keybindings = self.settings.summary().keybindings.clone();
keybindings.remove(&shortcut_id);
self.save_keybindings(keybindings, format!("shortcut {shortcut_id} reset"), cx);
}
@@ -92,7 +92,7 @@ impl NyaTermApp {
success_message: String,
cx: &mut Context<Self>,
) {
self.settings.summary.keybindings = keybindings.clone();
self.settings.set_keybindings(keybindings.clone());
if self.defer_settings_persistence(cx) {
self.settings.finish_keybinding_recording();
self.terminal.view.status = success_message.replace("saved", "staged");
@@ -177,7 +177,7 @@ impl NyaTermApp {
}
let existing = crate::shortcuts::shortcut_keys_for(
shortcut.id,
&self.settings.summary.keybindings,
&self.settings.summary().keybindings,
)
.unwrap_or_else(|| shortcut.default_keys.to_string());
let normalized_existing = existing
@@ -8,8 +8,8 @@ const MAX_KEYWORD_HIGHLIGHT_IMPORT_BYTES: u64 = 4 * 1024 * 1024;
impl NyaTermApp {
pub(in crate::features) fn toggle_keyword_highlights(&mut self, cx: &mut Context<Self>) {
self.settings.keyword_config.enabled = !self.settings.keyword_config.enabled;
if !self.settings.keyword_config.enabled {
let enabled = self.settings.toggle_keyword_highlights();
if !enabled {
self.settings.clear_keyword_highlight_edit();
self.forget_text_inputs("keyword.highlight.");
}
@@ -20,8 +20,7 @@ impl NyaTermApp {
&mut self,
cx: &mut Context<Self>,
) {
self.settings.keyword_config.across_wrapped_lines =
!self.settings.keyword_config.across_wrapped_lines;
self.settings.toggle_keyword_highlights_wrapped();
self.save_keyword_highlights(cx);
}
@@ -34,10 +33,10 @@ impl NyaTermApp {
self.runtime.config_dir(),
self.runtime.portable_key_path().map(ToOwned::to_owned),
)
.and_then(|store| store.save_keyword_highlights(&self.settings.keyword_config))
.and_then(|store| store.save_keyword_highlights(self.settings.keyword_config()))
{
Ok(config) => {
self.settings.keyword_config = config;
self.settings.replace_keyword_config(config);
self.settings
.set_store_message("keyword highlight settings saved".to_string());
self.settings.set_store_ready(true);
@@ -158,7 +157,7 @@ impl NyaTermApp {
self.runtime.portable_key_path().map(ToOwned::to_owned),
) {
if let Ok(config) = store.load_keyword_highlights() {
self.settings.keyword_config = config;
self.settings.replace_keyword_config(config);
self.forget_text_inputs("keyword.highlight.");
}
}
@@ -169,17 +168,7 @@ impl NyaTermApp {
rule_id: String,
cx: &mut Context<Self>,
) {
let enabled = self
.settings
.keyword_config
.builtin_rules
.get(&rule_id)
.copied()
.unwrap_or(true);
self.settings
.keyword_config
.builtin_rules
.insert(rule_id, !enabled);
self.settings.toggle_keyword_highlight_builtin(rule_id);
self.save_keyword_highlights(cx);
}
@@ -188,14 +177,7 @@ impl NyaTermApp {
rule_id: String,
cx: &mut Context<Self>,
) {
if let Some(rule) = self
.settings
.keyword_config
.rules
.iter_mut()
.find(|rule| rule.id == rule_id)
{
rule.enabled = !rule.enabled;
if self.settings.toggle_keyword_highlight_rule(&rule_id) {
self.save_keyword_highlights(cx);
}
}
@@ -266,18 +248,10 @@ impl NyaTermApp {
} else {
normalize_keyword_highlight_color(color)
};
if let Some(rule) = self
if self
.settings
.keyword_config
.rules
.iter_mut()
.find(|rule| rule.id == rule_id)
.set_keyword_highlight_rule_color(&rule_id, dark, color.clone())
{
if dark {
rule.color_dark = color.clone();
} else {
rule.color_light = color.clone();
}
let field = if dark {
KeywordHighlightEditorField::ColorDark
} else {
@@ -301,7 +275,7 @@ impl NyaTermApp {
) {
let value = self
.settings
.keyword_config
.keyword_config()
.rules
.iter()
.find(|rule| rule.id == rule_id)
@@ -389,29 +363,13 @@ impl NyaTermApp {
KeywordHighlightEditorField::ColorDark | KeywordHighlightEditorField::ColorLight
)
.then(|| normalize_keyword_highlight_color(&text));
let Some(rule) = self
let value = normalized_color.clone().unwrap_or(text);
if !self
.settings
.keyword_config
.rules
.iter_mut()
.find(|rule| rule.id == rule_id)
else {
.apply_keyword_highlight_rule_input(rule_id, field, value)
{
return;
};
match field {
KeywordHighlightEditorField::Name => rule.name = text,
KeywordHighlightEditorField::Patterns => {
rule.patterns = text.split('\n').map(ToOwned::to_owned).collect();
}
KeywordHighlightEditorField::ColorDark => {
rule.color_dark = normalized_color.clone().unwrap_or_default();
}
KeywordHighlightEditorField::ColorLight => {
rule.color_light = normalized_color.clone().unwrap_or_default();
}
}
self.settings
.begin_keyword_highlight_edit(rule_id.to_string(), field);
if let Some(color) = normalized_color {
self.reset_text_input(&keyword_highlight_text_input_id(rule_id, field), &color, cx);
}
@@ -4,7 +4,7 @@ use gpui::{
};
use nyaterm_core::ConnectionStore;
use crate::features::NyaTermApp;
use crate::features::{NyaTermApp, settings::UiLayoutSettingsUpdate};
use crate::models::{
BottomPanelMode, NavItem, PanelResizeSide, PanelSide, panel_collapsed_from_persistence,
};
@@ -16,8 +16,6 @@ const BOTTOM_PANEL_HEIGHT_MAX: f32 = 520.;
impl NyaTermApp {
pub(in crate::features) fn set_bottom_panel_mode(&mut self, mode: BottomPanelMode) {
self.shell.bottom_panel.mode = mode;
self.settings.summary.ui_quick_cmd_visible = mode == BottomPanelMode::QuickCommands;
self.settings.summary.ui_serial_send_visible = mode == BottomPanelMode::CommandSend;
self.persist_ui_layout();
}
@@ -76,90 +74,110 @@ impl NyaTermApp {
}
pub(in crate::features) fn apply_ui_layout_from_settings(&mut self) {
self.shell.panels.left_width = self.settings.summary.ui_left_panel_width as f32;
self.shell.panels.right_width = self.settings.summary.ui_right_panel_width as f32;
self.shell.panels.left_width = self.settings.summary().ui_left_panel_width as f32;
self.shell.panels.right_width = self.settings.summary().ui_right_panel_width as f32;
self.transfer
.set_panel_height(self.settings.summary.ui_transfer_height as f32);
.set_panel_height(self.settings.summary().ui_transfer_height as f32);
self.shell.bottom_panel.quick_commands_height =
self.settings.summary.ui_quick_cmd_height as f32;
self.settings.summary().ui_quick_cmd_height as f32;
self.shell.bottom_panel.command_send_height =
self.settings.summary.ui_serial_send_height as f32;
self.settings.summary().ui_serial_send_height as f32;
self.apply_activity_layout_from_settings();
self.shell.panels.active_left = self
.settings
.summary
.summary()
.ui_active_left_panel
.as_deref()
.and_then(NavItem::from_persistence_id)
.filter(|item| self.panel_side_for_item(*item) == Some(PanelSide::Left));
self.shell.panels.active_right = self
.settings
.summary
.summary()
.ui_active_right_panel
.as_deref()
.and_then(NavItem::from_persistence_id)
.filter(|item| self.panel_side_for_item(*item) == Some(PanelSide::Right));
self.shell.panels.left_collapsed = panel_collapsed_from_persistence(
self.settings.summary.ui_left_panel_collapsed,
self.settings.summary.ui_panel_multi_open,
self.settings.summary().ui_left_panel_collapsed,
self.settings.summary().ui_panel_multi_open,
self.shell.panels.active_left.is_some(),
!self.settings.summary.ui_left_open_panels.is_empty(),
!self.settings.summary().ui_left_open_panels.is_empty(),
);
self.shell.panels.right_collapsed = panel_collapsed_from_persistence(
self.settings.summary.ui_right_panel_collapsed,
self.settings.summary.ui_panel_multi_open,
self.settings.summary().ui_right_panel_collapsed,
self.settings.summary().ui_panel_multi_open,
self.shell.panels.active_right.is_some(),
!self.settings.summary.ui_right_open_panels.is_empty(),
!self.settings.summary().ui_right_open_panels.is_empty(),
);
self.apply_panel_stack_from_settings();
if !self.settings.summary.has_master_password {
if !self.settings.summary().has_master_password {
self.security.unlock_without_master_password();
}
}
pub(in crate::features) fn persist_ui_layout(&mut self) {
self.settings.summary.ui_left_panel_width =
self.shell.panels.left_width.round().clamp(160., 720.) as u32;
self.settings.summary.ui_right_panel_width =
self.shell.panels.right_width.round().clamp(200., 720.) as u32;
self.settings.summary.ui_transfer_height =
self.transfer.panel_height().round().clamp(60., 600.) as u32;
self.settings.summary.ui_quick_cmd_height =
self.shell
let update = UiLayoutSettingsUpdate {
left_panel_width: self.shell.panels.left_width.round().clamp(160., 720.) as u32,
right_panel_width: self.shell.panels.right_width.round().clamp(200., 720.) as u32,
transfer_height: self.transfer.panel_height().round().clamp(60., 600.) as u32,
quick_command_height: self
.shell
.bottom_panel
.quick_commands_height
.round()
.clamp(QUICK_CMD_HEIGHT_MIN, BOTTOM_PANEL_HEIGHT_MAX) as u32;
self.settings.summary.ui_serial_send_height =
self.shell
.clamp(QUICK_CMD_HEIGHT_MIN, BOTTOM_PANEL_HEIGHT_MAX)
as u32,
quick_command_visible: self.shell.bottom_panel.mode == BottomPanelMode::QuickCommands,
serial_send_height: self
.shell
.bottom_panel
.command_send_height
.round()
.clamp(SERIAL_SEND_HEIGHT_MIN, BOTTOM_PANEL_HEIGHT_MAX) as u32;
self.settings.summary.ui_active_left_panel = self
.shell
.panels
.active_left
.map(|item| item.persistence_id().to_string());
self.settings.summary.ui_active_right_panel = self
.shell
.panels
.active_right
.map(|item| item.persistence_id().to_string());
self.settings.summary.ui_left_panel_collapsed = self.shell.panels.left_collapsed;
self.settings.summary.ui_right_panel_collapsed = self.shell.panels.right_collapsed;
self.settings.summary.ui_saved_connections_sort_mode = self
.connection_state
.list_sort_mode()
.persistence_id()
.to_string();
self.sync_activity_layout_to_settings();
self.sync_panel_stack_to_settings();
.clamp(SERIAL_SEND_HEIGHT_MIN, BOTTOM_PANEL_HEIGHT_MAX)
as u32,
serial_send_visible: self.shell.bottom_panel.mode == BottomPanelMode::CommandSend,
active_left_panel: self
.shell
.panels
.active_left
.map(|item| item.persistence_id().to_string()),
active_right_panel: self
.shell
.panels
.active_right
.map(|item| item.persistence_id().to_string()),
left_panel_collapsed: self.shell.panels.left_collapsed,
right_panel_collapsed: self.shell.panels.right_collapsed,
saved_connections_sort_mode: self
.connection_state
.list_sort_mode()
.persistence_id()
.to_string(),
activity_bar_left_top: self.shell.chrome.activity_bar_layout.left_top.clone(),
activity_bar_left_bottom: self.shell.chrome.activity_bar_layout.left_bottom.clone(),
activity_bar_right_top: self.shell.chrome.activity_bar_layout.right_top.clone(),
activity_bar_right_bottom: self.shell.chrome.activity_bar_layout.right_bottom.clone(),
activity_bar_show_labels: self.shell.chrome.activity_bar_layout.show_labels,
panel_multi_open: self.shell.panels.multi_open,
left_open_panels: self.shell.panels.left_open.clone(),
right_open_panels: self.shell.panels.right_open.clone(),
panel_stack_sizes: self
.shell
.panels
.stack_sizes
.iter()
.filter_map(|(key, value)| {
let scaled = (*value * 1000.).round();
(scaled.is_finite() && scaled > 0.).then(|| (key.clone(), scaled as u32))
})
.collect(),
};
self.settings.apply_ui_layout(update);
if let Ok(store) = ConnectionStore::open_with_portable_key_path(
self.runtime.config_dir(),
self.runtime.portable_key_path().map(ToOwned::to_owned),
) {
match store.save_ui_layout_settings(&self.settings.summary) {
match store.save_ui_layout_settings(self.settings.summary()) {
Ok(summary) => {
self.apply_gpui_settings(summary);
self.settings.set_store_ready(true);
@@ -366,29 +366,13 @@ impl NyaTermApp {
}
}
pub(in crate::features) fn sync_panel_stack_to_settings(&mut self) {
self.settings.summary.ui_panel_multi_open = self.shell.panels.multi_open;
self.settings.summary.ui_left_open_panels = self.shell.panels.left_open.clone();
self.settings.summary.ui_right_open_panels = self.shell.panels.right_open.clone();
self.settings.summary.ui_panel_stack_sizes = self
.shell
.panels
.stack_sizes
.iter()
.filter_map(|(key, value)| {
let scaled = (*value * 1000.).round();
(scaled.is_finite() && scaled > 0.).then(|| (key.clone(), scaled as u32))
})
.collect();
}
pub(in crate::features) fn apply_panel_stack_from_settings(&mut self) {
self.shell.panels.multi_open = self.settings.summary.ui_panel_multi_open;
self.shell.panels.left_open = self.settings.summary.ui_left_open_panels.clone();
self.shell.panels.right_open = self.settings.summary.ui_right_open_panels.clone();
self.shell.panels.multi_open = self.settings.summary().ui_panel_multi_open;
self.shell.panels.left_open = self.settings.summary().ui_left_open_panels.clone();
self.shell.panels.right_open = self.settings.summary().ui_right_open_panels.clone();
self.shell.panels.stack_sizes = self
.settings
.summary
.summary()
.ui_panel_stack_sizes
.iter()
.filter_map(|(key, value)| (*value > 0).then(|| (key.clone(), (*value as f32) / 1000.)))
@@ -225,22 +225,24 @@ impl NyaTermApp {
let action = normalize_tab_mouse_action(action);
match target {
TabMouseActionTarget::Double => {
if self.settings.summary.interaction_tab_double_click_action == action {
if self.settings.summary().interaction_tab_double_click_action == action {
return;
}
self.settings.summary.interaction_tab_double_click_action = action.to_string();
self.settings
.set_tab_double_click_action(action.to_string());
}
TabMouseActionTarget::Middle => {
if self.settings.summary.interaction_tab_middle_click_action == action {
if self.settings.summary().interaction_tab_middle_click_action == action {
return;
}
self.settings.summary.interaction_tab_middle_click_action = action.to_string();
self.settings
.set_tab_middle_click_action(action.to_string());
}
TabMouseActionTarget::Right => {
if self.settings.summary.interaction_tab_right_click_action == action {
if self.settings.summary().interaction_tab_right_click_action == action {
return;
}
self.settings.summary.interaction_tab_right_click_action = action.to_string();
self.settings.set_tab_right_click_action(action.to_string());
}
}
self.save_interaction_settings(cx);
@@ -260,7 +262,7 @@ impl NyaTermApp {
cx.stop_propagation();
let action = self
.settings
.summary
.summary()
.interaction_tab_middle_click_action
.clone();
if action == "none" {
@@ -276,7 +278,7 @@ impl NyaTermApp {
cx.stop_propagation();
let action = self
.settings
.summary
.summary()
.interaction_tab_right_click_action
.clone();
if action == "none" {
@@ -301,7 +303,7 @@ impl NyaTermApp {
if is_left_double_click {
let action = self
.settings
.summary
.summary()
.interaction_tab_double_click_action
.clone();
if action != "none" {
@@ -247,8 +247,8 @@ impl NyaTermApp {
}
pub(in crate::features) fn persist_terminal_window_layout(&mut self) {
if !self.settings.summary.startup_restore
|| !self.settings.summary.startup_restore_window_layout
if !self.settings.summary().startup_restore
|| !self.settings.summary().startup_restore_window_layout
{
return;
}
@@ -260,8 +260,8 @@ impl NyaTermApp {
if self.terminal.windows.restored {
return;
}
if !self.settings.summary.startup_restore
|| !self.settings.summary.startup_restore_window_layout
if !self.settings.summary().startup_restore
|| !self.settings.summary().startup_restore_window_layout
{
self.terminal.windows.restored = true;
return;
@@ -452,8 +452,8 @@ impl NyaTermApp {
}
}
pub(in crate::features) fn persist_workspace_pane_layout(&mut self) {
if !self.settings.summary.startup_restore
|| !self.settings.summary.startup_restore_window_layout
if !self.settings.summary().startup_restore
|| !self.settings.summary().startup_restore_window_layout
{
return;
}
@@ -500,8 +500,8 @@ impl NyaTermApp {
if self.shell.workspace.pane_layout_restored {
return;
}
if !self.settings.summary.startup_restore
|| !self.settings.summary.startup_restore_window_layout
if !self.settings.summary().startup_restore
|| !self.settings.summary().startup_restore_window_layout
{
self.shell.workspace.pane_layout_restored = true;
return;
@@ -408,7 +408,7 @@ impl NyaTermApp {
pub(in crate::features) fn refresh_cloud_sync_history(&mut self) {
let history = read_cloud_sync_history(
self.runtime.log_dir(),
self.settings.summary.diagnostics_retention_days,
self.settings.summary().diagnostics_retention_days,
CLOUD_SYNC_HISTORY_LIMIT,
)
.unwrap_or_default();
@@ -21,9 +21,9 @@ impl NyaTermApp {
pub(in crate::features) fn toggle_cloud_sync_enabled(&mut self, cx: &mut Context<Self>) {
if !self.cloud_sync.settings().enabled
&& (!self.settings.master_password.enabled
|| (!self.settings.summary.has_master_password
&& self.settings.master_password.draft.is_empty()))
&& (!self.settings.master_password().enabled
|| (!self.settings.summary().has_master_password
&& self.settings.master_password().draft.is_empty()))
{
self.shell.set_settings_active_tab(SettingsTab::Security);
self.cloud_sync
@@ -91,8 +91,8 @@ impl NyaTermApp {
}
pub(in crate::features) fn cloud_sync_form_enabled(&self) -> bool {
self.settings.master_password.enabled
&& (self.settings.summary.has_master_password
|| !self.settings.master_password.draft.is_empty())
self.settings.master_password().enabled
&& (self.settings.summary().has_master_password
|| !self.settings.master_password().draft.is_empty())
}
}
@@ -64,7 +64,7 @@ impl NyaTermApp {
let selected_for_paste = selected.clone();
let search_engines: Vec<(String, String, Option<String>)> = self
.settings
.summary
.summary()
.search_custom_engines
.iter()
.filter(|engine| {
@@ -118,9 +118,9 @@ impl NyaTermApp {
.collect();
let selection_link_kind: Option<&'static str> = None;
let selection_actions: Vec<(String, ActionLinkAction)> =
if self.settings.summary.terminal_action_links_enabled && has_selection {
if self.settings.summary().terminal_action_links_enabled && has_selection {
let trimmed = selected.trim();
let matchers = &self.settings.summary.terminal_action_links_matchers;
let matchers = &self.settings.summary().terminal_action_links_matchers;
let entity = find_action_links(trimmed, matchers, true)
.into_iter()
.find(|item| item.text == trimmed || item.value == trimmed)
@@ -186,7 +186,7 @@ fn terminal_scroll_snapshot_request_action_links_enabled(
impl NyaTermApp {
pub(in crate::features) fn terminal_scrollback_line_limit(&self) -> usize {
self.settings
.summary
.summary()
.terminal_scrollback_lines
.clamp(100, 100_000) as usize
}
@@ -216,7 +216,7 @@ impl NyaTermApp {
self.terminal.view.frame_pipeline.submit_output(
session_id.to_string(),
data,
self.settings.summary.interaction_default_encoding.clone(),
self.settings.summary().interaction_default_encoding.clone(),
self.terminal_scrollback_line_limit(),
);
}
@@ -228,7 +228,7 @@ impl NyaTermApp {
if outputs.is_empty() {
return;
}
let encoding = self.settings.summary.interaction_default_encoding.clone();
let encoding = self.settings.summary().interaction_default_encoding.clone();
let scrollback_limit = self.terminal_scrollback_line_limit();
let submissions = outputs
.into_iter()
@@ -271,9 +271,12 @@ impl NyaTermApp {
self.terminal.view.frame_pipeline.request_snapshot(
session_id.to_string(),
offset,
self.settings.summary.terminal_action_links_enabled
&& !self.settings.summary.terminal_low_latency_mode,
self.settings.summary.terminal_action_links_matchers.clone(),
self.settings.summary().terminal_action_links_enabled
&& !self.settings.summary().terminal_low_latency_mode,
self.settings
.summary()
.terminal_action_links_matchers
.clone(),
);
true
}
@@ -299,7 +302,10 @@ impl NyaTermApp {
session_id.to_string(),
offset,
false,
self.settings.summary.terminal_action_links_matchers.clone(),
self.settings
.summary()
.terminal_action_links_matchers
.clone(),
);
true
}
@@ -390,21 +396,27 @@ impl NyaTermApp {
if priority {
let action_links_enabled = terminal_scroll_snapshot_request_action_links_enabled(
priority,
self.settings.summary.terminal_action_links_enabled,
self.settings.summary.terminal_low_latency_mode,
self.settings.summary().terminal_action_links_enabled,
self.settings.summary().terminal_low_latency_mode,
);
self.terminal.view.frame_pipeline.request_priority_snapshot(
session_id.to_string(),
offset,
action_links_enabled,
self.settings.summary.terminal_action_links_matchers.clone(),
self.settings
.summary()
.terminal_action_links_matchers
.clone(),
);
} else {
self.terminal.view.frame_pipeline.request_snapshot(
session_id.to_string(),
offset,
false,
self.settings.summary.terminal_action_links_matchers.clone(),
self.settings
.summary()
.terminal_action_links_matchers
.clone(),
);
}
true
@@ -418,14 +430,14 @@ impl NyaTermApp {
) -> bool {
if session_id.is_empty()
|| offset == 0
|| !self.settings.summary.terminal_action_links_enabled
|| self.settings.summary.terminal_low_latency_mode
|| !self.settings.summary().terminal_action_links_enabled
|| self.settings.summary().terminal_low_latency_mode
{
return false;
}
let matcher_key = terminal_action_link_matcher_key(
true,
&self.settings.summary.terminal_action_links_matchers,
&self.settings.summary().terminal_action_links_matchers,
);
let Some(view) = self.terminal.view.views.get_mut(session_id) else {
return false;
@@ -452,7 +464,10 @@ impl NyaTermApp {
session_id.to_string(),
offset,
true,
self.settings.summary.terminal_action_links_matchers.clone(),
self.settings
.summary()
.terminal_action_links_matchers
.clone(),
);
true
}
@@ -490,7 +505,7 @@ impl NyaTermApp {
self.terminal.view.frame_pipeline.seed_session(
session_id.to_string(),
output,
self.settings.summary.interaction_default_encoding.clone(),
self.settings.summary().interaction_default_encoding.clone(),
self.terminal_scrollback_line_limit(),
);
}
@@ -997,14 +1012,15 @@ impl NyaTermApp {
"terminal frame snapshot row reuse"
);
// Snapshot applies only dirties the surface, not chrome.
let current_action_link_matcher_key = (self.settings.summary.terminal_action_links_enabled
&& !self.settings.summary.terminal_low_latency_mode)
.then(|| {
terminal_action_link_matcher_key(
true,
&self.settings.summary.terminal_action_links_matchers,
)
});
let current_action_link_matcher_key =
(self.settings.summary().terminal_action_links_enabled
&& !self.settings.summary().terminal_low_latency_mode)
.then(|| {
terminal_action_link_matcher_key(
true,
&self.settings.summary().terminal_action_links_matchers,
)
});
let should_paint = self.terminal_session_has_visible_surface(&frame.session_id)
&& self
.terminal
@@ -1164,7 +1180,7 @@ impl NyaTermApp {
session_id: &str,
data: &[u8],
) -> String {
let encoding = self.settings.summary.interaction_default_encoding.clone();
let encoding = self.settings.summary().interaction_default_encoding.clone();
let view = self
.terminal
.view
@@ -1213,7 +1229,7 @@ impl NyaTermApp {
if let Some(session_id) = session_id {
let is_active = self.session.active_id() == Some(session_id);
let encoding = self.settings.summary.interaction_default_encoding.clone();
let encoding = self.settings.summary().interaction_default_encoding.clone();
let view = self
.terminal
.view
@@ -29,7 +29,11 @@ impl NyaTermApp {
cx.notify();
return;
}
if self.settings.summary.terminal_show_multi_line_paste_dialog && is_multi_line_paste(&text)
if self
.settings
.summary()
.terminal_show_multi_line_paste_dialog
&& is_multi_line_paste(&text)
{
self.terminal.paste.open(text);
self.terminal.view.status = "multi-line paste confirmation opened".to_string();
@@ -93,7 +93,7 @@ impl NyaTermApp {
self.terminal
.view
.screen
.set_encoding(&self.settings.summary.interaction_default_encoding);
.set_encoding(&self.settings.summary().interaction_default_encoding);
self.terminal.view.status = "session closed".to_string();
}
} else {
@@ -160,7 +160,7 @@ impl NyaTermApp {
self.terminal
.view
.screen
.set_encoding(&self.settings.summary.interaction_default_encoding);
.set_encoding(&self.settings.summary().interaction_default_encoding);
}
}
@@ -179,7 +179,7 @@ impl NyaTermApp {
// Tauri minimize_to_tray: hide window instead of taskbar minimize when enabled.
// GPUI lacks a portable tray today; minimize still uses the platform minimize path,
// and the flag is honored as a documented no-op tray intent with status feedback.
if self.settings.summary.minimize_to_tray {
if self.settings.summary().minimize_to_tray {
window.minimize_window();
self.terminal.view.status =
"minimized (tray mode preferred; OS tray polish pending)".to_string();
@@ -195,7 +195,7 @@ impl NyaTermApp {
cx: &mut Context<Self>,
) {
let open_sessions = self.ordered_sessions().len();
if self.settings.summary.confirm_on_close && open_sessions > 0 {
if self.settings.summary().confirm_on_close && open_sessions > 0 {
// Reuse close-all confirmation as quit-with-sessions gate (Tauri confirm_on_close).
self.session.dialogs.request_quit_after_close_all();
self.open_close_all_sessions_confirm(window, cx);
@@ -205,7 +205,7 @@ impl NyaTermApp {
return;
}
// Persist workspace before exit when startup restore is enabled.
if self.settings.summary.startup_restore {
if self.settings.summary().startup_restore {
self.flush_open_tabs_now();
}
window.remove_window();
@@ -244,7 +244,7 @@ impl NyaTermApp {
let quit_after = self.session.dialogs.take_close_all_sessions_confirm();
self.close_all_sessions(cx);
if quit_after {
if self.settings.summary.startup_restore {
if self.settings.summary().startup_restore {
self.flush_open_tabs_now();
}
self.terminal.view.status = "sessions closed; closing window".to_string();
@@ -713,9 +713,9 @@ impl NyaTermApp {
if track_suggestions {
if terminal_should_track_command_suggestion_input(
track_suggestions,
self.settings.summary.terminal_low_latency_mode,
self.settings.summary().terminal_low_latency_mode,
self.settings
.summary
.summary()
.interaction_command_suggestions_enabled,
) {
self.note_command_suggestion_input(&bytes, cx);
@@ -851,9 +851,9 @@ impl NyaTermApp {
if track_suggestions {
if terminal_should_track_command_suggestion_input(
track_suggestions,
self.settings.summary.terminal_low_latency_mode,
self.settings.summary().terminal_low_latency_mode,
self.settings
.summary
.summary()
.interaction_command_suggestions_enabled,
) {
self.note_command_suggestion_input(&primary_bytes, cx);
@@ -1392,7 +1392,7 @@ impl NyaTermApp {
/// Keep all live terminal screens on the current interaction encoding.
pub(in crate::features) fn sync_terminal_encodings_from_settings(&mut self) {
let label = self.settings.summary.interaction_default_encoding.clone();
let label = self.settings.summary().interaction_default_encoding.clone();
self.terminal.view.screen.set_encoding(&label);
self.terminal.view.output_decoder.set_encoding(&label);
for view in self.terminal.view.views.values_mut() {
@@ -1628,7 +1628,7 @@ impl NyaTermApp {
terminal_key_bytes_for_mode_and_settings(
event,
self.terminal_key_mode_for_session(session_id),
self.settings.summary.interaction_alt_as_meta,
self.settings.summary().interaction_alt_as_meta,
)
}
@@ -1637,7 +1637,7 @@ impl NyaTermApp {
event: &KeyDownEvent,
) -> bool {
terminal_should_defer_key_text_to_input_handler_for_state(
self.settings.summary.interaction_mac_ime_compatibility,
self.settings.summary().interaction_mac_ime_compatibility,
&self.terminal.input.ime_marked_text,
event,
)
@@ -1716,7 +1716,7 @@ impl NyaTermApp {
};
let matcher_key = terminal_action_link_matcher_key(
true,
&self.settings.summary.terminal_action_links_matchers,
&self.settings.summary().terminal_action_links_matchers,
);
if view.frame_action_links.as_ref().is_some_and(|links| {
links.matcher_key == matcher_key && links.covers_all_snapshot_rows(snapshot.as_ref())
@@ -1726,7 +1726,7 @@ impl NyaTermApp {
let Some(action_links) = crate::models::prepare_terminal_frame_action_links(
snapshot.as_ref(),
true,
&self.settings.summary.terminal_action_links_matchers,
&self.settings.summary().terminal_action_links_matchers,
) else {
return;
};
@@ -1774,8 +1774,8 @@ impl NyaTermApp {
) else {
return false;
};
let action_links_enabled = self.settings.summary.terminal_action_links_enabled
&& !self.settings.summary.terminal_low_latency_mode;
let action_links_enabled = self.settings.summary().terminal_action_links_enabled
&& !self.settings.summary().terminal_low_latency_mode;
self.ensure_terminal_live_action_links_for_snapshot(
session_id,
display_offset,
@@ -1788,12 +1788,12 @@ impl NyaTermApp {
let palette = self.terminal_theme_palette();
let transparent_background = self.wallpaper_enabled();
let font_family = self.gpui_terminal_font_family();
let font_size = self.settings.summary.terminal_font_size as f32;
let normal_weight = self.settings.summary.terminal_font_weight as f32;
let bold_weight = self.settings.summary.terminal_font_weight_bold as f32;
let show_line_numbers = self.settings.summary.terminal_show_line_numbers;
let show_timestamps = self.settings.summary.terminal_show_timestamps;
let show_timestamp_ms = self.settings.summary.terminal_show_timestamp_milliseconds;
let font_size = self.settings.summary().terminal_font_size as f32;
let normal_weight = self.settings.summary().terminal_font_weight as f32;
let bold_weight = self.settings.summary().terminal_font_weight_bold as f32;
let show_line_numbers = self.settings.summary().terminal_show_line_numbers;
let show_timestamps = self.settings.summary().terminal_show_timestamps;
let show_timestamp_ms = self.settings.summary().terminal_show_timestamp_milliseconds;
let (cell_w, cell_h) = self
.terminal
.layout
@@ -1803,14 +1803,14 @@ impl NyaTermApp {
let visual_bell = is_active && self.terminal.view.runtime.visual_bell_ticks > 0;
let layout_cache = view.render_cache.layout_cache.clone();
let render_degraded =
view.render_degraded || self.settings.summary.terminal_low_latency_mode;
view.render_degraded || self.settings.summary().terminal_low_latency_mode;
let has_new = view.has_new_while_scrolled;
let performance_overlay = view.performance_overlay;
let skipped = view.skipped_output_chars;
let protocol_state = view.protocol_state;
let search_matches = if is_active
&& !input_latency_active
&& !self.settings.summary.terminal_low_latency_mode
&& !self.settings.summary().terminal_low_latency_mode
&& self.terminal.search.open
&& self.terminal.search.mode == TerminalSearchMode::Buffer
{
@@ -1820,7 +1820,7 @@ impl NyaTermApp {
};
let action_link_matcher_key = terminal_action_link_matcher_key(
action_links_enabled,
&self.settings.summary.terminal_action_links_matchers,
&self.settings.summary().terminal_action_links_matchers,
);
let frame_action_links = action_links_enabled
.then(|| {
@@ -1845,7 +1845,9 @@ impl NyaTermApp {
&frame_action_links,
action_links_enabled,
action_links_enabled,
is_active && !input_latency_active && !self.settings.summary.terminal_low_latency_mode,
is_active
&& !input_latency_active
&& !self.settings.summary().terminal_low_latency_mode,
);
let has_action_link_decorations =
crate::features::terminal::terminal_surface::terminal_action_links_have_ranges_for_snapshot(
@@ -1991,7 +1993,7 @@ impl NyaTermApp {
|| mode == TerminalPerformanceMode::Overloaded
|| user_scroll_active
|| input_latency_active
|| self.settings.summary.terminal_low_latency_mode;
|| self.settings.summary().terminal_low_latency_mode;
let render_degraded = render_degraded_view || render_pressure;
let configured_keyword_rules = self.resolved_keyword_highlight_rules();
let clear_keyword_highlights = configured_keyword_rules.is_empty();
@@ -2036,8 +2038,8 @@ impl NyaTermApp {
retained_surface_snapshot,
);
let snapshot_duration = snapshot_started_at.elapsed();
let action_links_enabled = self.settings.summary.terminal_action_links_enabled
&& !self.settings.summary.terminal_low_latency_mode;
let action_links_enabled = self.settings.summary().terminal_action_links_enabled
&& !self.settings.summary().terminal_low_latency_mode;
if let Some(snapshot) = snapshot.as_ref() {
self.ensure_terminal_live_action_links_for_snapshot(
session_id,
@@ -2050,12 +2052,12 @@ impl NyaTermApp {
let palette = self.terminal_theme_palette();
let transparent_background = self.wallpaper_enabled();
let font_family = self.gpui_terminal_font_family();
let font_size = self.settings.summary.terminal_font_size as f32;
let normal_weight = self.settings.summary.terminal_font_weight as f32;
let bold_weight = self.settings.summary.terminal_font_weight_bold as f32;
let show_line_numbers = self.settings.summary.terminal_show_line_numbers;
let show_timestamps = self.settings.summary.terminal_show_timestamps;
let show_timestamp_ms = self.settings.summary.terminal_show_timestamp_milliseconds;
let font_size = self.settings.summary().terminal_font_size as f32;
let normal_weight = self.settings.summary().terminal_font_weight as f32;
let bold_weight = self.settings.summary().terminal_font_weight_bold as f32;
let show_line_numbers = self.settings.summary().terminal_show_line_numbers;
let show_timestamps = self.settings.summary().terminal_show_timestamps;
let show_timestamp_ms = self.settings.summary().terminal_show_timestamp_milliseconds;
let (cell_w, cell_h) = self
.terminal
.layout
@@ -2133,7 +2135,7 @@ impl NyaTermApp {
let remote_cursor_visible = snapshot.cursor.visible
&& snapshot.cursor.shape != nyaterm_terminal::CursorShape::Hidden
&& cursor_row != usize::MAX;
let blink_enabled = self.settings.summary.cursor_blink || snapshot.cursor.blinking;
let blink_enabled = self.settings.summary().cursor_blink || snapshot.cursor.blinking;
let show_cursor = terminal_cursor_visible_for_display_offset(
is_active,
is_disconnected,
@@ -2145,8 +2147,8 @@ impl NyaTermApp {
let cursor_style = match snapshot.cursor.shape {
nyaterm_terminal::CursorShape::Underline => "underline".to_string(),
nyaterm_terminal::CursorShape::Beam => "bar".to_string(),
nyaterm_terminal::CursorShape::Hidden => self.settings.summary.cursor_style.clone(),
nyaterm_terminal::CursorShape::Block => self.settings.summary.cursor_style.clone(),
nyaterm_terminal::CursorShape::Hidden => self.settings.summary().cursor_style.clone(),
nyaterm_terminal::CursorShape::Block => self.settings.summary().cursor_style.clone(),
};
let search_mapping_started_at = Instant::now();
@@ -2161,7 +2163,7 @@ impl NyaTermApp {
let enhanced = paint_policy.enhanced_decorations;
let action_link_matcher_key = terminal_action_link_matcher_key(
action_links_enabled,
&self.settings.summary.terminal_action_links_matchers,
&self.settings.summary().terminal_action_links_matchers,
);
let frame_action_links = action_links_enabled
.then(|| {
@@ -75,8 +75,8 @@ impl NyaTermApp {
event: &MouseMoveEvent,
cx: &mut Context<Self>,
) {
if !self.settings.summary.terminal_action_links_enabled
|| self.settings.summary.terminal_low_latency_mode
if !self.settings.summary().terminal_action_links_enabled
|| self.settings.summary().terminal_low_latency_mode
|| self.runtime_output_pressure_active()
{
self.clear_action_link_tooltip(cx);
@@ -179,7 +179,7 @@ impl NyaTermApp {
position: Point<Pixels>,
cx: &App,
) -> Option<(ActionLinkMatch, Vec<ActionLinkAction>)> {
if !self.settings.summary.terminal_action_links_enabled {
if !self.settings.summary().terminal_action_links_enabled {
return None;
}
let session_id = self.terminal_session_at_point(position)?;
@@ -203,8 +203,8 @@ impl NyaTermApp {
}
let cell = self.point_to_terminal_cell_for_session(session_id, position, cx)?;
let action_link_matcher_key = terminal_action_link_matcher_key(
self.settings.summary.terminal_action_links_enabled,
&self.settings.summary.terminal_action_links_matchers,
self.settings.summary().terminal_action_links_enabled,
&self.settings.summary().terminal_action_links_matchers,
);
let offset = self.terminal_display_offset_for_session(session_id);
let snapshot = self.terminal_snapshot_for_session(session_id, offset);
@@ -245,7 +245,7 @@ impl NyaTermApp {
.cloned()
})
.or_else(|| {
let matchers = &self.settings.summary.terminal_action_links_matchers;
let matchers = &self.settings.summary().terminal_action_links_matchers;
match_at_offset(line, byte_offset, matchers)
})?;
let actions = actions_for_match(&item);
@@ -453,8 +453,8 @@ impl NyaTermApp {
|| view.output_burst_bytes > 0
|| view.performance_mode == TerminalPerformanceMode::Overloaded;
terminal_expensive_interactions_enabled(
self.settings.summary.terminal_action_links_enabled
&& !self.settings.summary.terminal_low_latency_mode,
self.settings.summary().terminal_action_links_enabled
&& !self.settings.summary().terminal_low_latency_mode,
is_active,
render_degraded,
runtime_output_pressure,
@@ -68,7 +68,7 @@ pub(in crate::features) fn terminal_bounds_tracker(
if input_entity
.read(cx)
.settings
.summary
.summary()
.interaction_mac_ime_compatibility
{
window.handle_input(&focus, ElementInputHandler::new(bounds, input_entity), cx);
@@ -25,7 +25,7 @@ impl NyaTermApp {
}
pub(in crate::features) fn fallback_terminal_cell_size(&self) -> (f32, f32) {
let font_size = self.settings.summary.terminal_font_size.max(8) as f32;
let font_size = self.settings.summary().terminal_font_size.max(8) as f32;
// Prefer painted fixed 18px when font is near default; scale with font otherwise.
let cell_h = if (font_size - 14.).abs() < 0.5 {
18.
@@ -40,7 +40,7 @@ impl NyaTermApp {
/// Refresh monospaced cell metrics from GPUI TextSystem for the configured terminal font.
pub(in crate::features) fn refresh_terminal_cell_metrics(&mut self, cx: &App) {
let font_size = self.settings.summary.terminal_font_size.max(8) as f32;
let font_size = self.settings.summary().terminal_font_size.max(8) as f32;
let family = self.gpui_terminal_font_family();
let text_system = cx.text_system();
let font_id = text_system.resolve_font(&gpui::font(SharedString::from(family)));
@@ -68,9 +68,9 @@ impl NyaTermApp {
}
pub(in crate::features) fn terminal_content_insets(&self) -> TerminalViewportInsets {
if self.settings.summary.terminal_show_workspace_padding
&& !self.settings.summary.terminal_show_line_numbers
&& !self.settings.summary.terminal_show_timestamps
if self.settings.summary().terminal_show_workspace_padding
&& !self.settings.summary().terminal_show_line_numbers
&& !self.settings.summary().terminal_show_timestamps
{
// Tauri applies workspace padding as `pl-2`; it does not add a
// default margin or vertical padding around the terminal grid.
@@ -98,9 +98,9 @@ impl NyaTermApp {
let snapshot = self.terminal_snapshot_for_session(session_id, display_offset);
terminal_gutter_metrics(
cell_w,
self.settings.summary.terminal_show_timestamps,
self.settings.summary.terminal_show_timestamp_milliseconds,
self.settings.summary.terminal_show_line_numbers,
self.settings.summary().terminal_show_timestamps,
self.settings.summary().terminal_show_timestamp_milliseconds,
self.settings.summary().terminal_show_line_numbers,
terminal_line_number_digits(snapshot.as_ref()),
)
.total_width()
@@ -352,12 +352,12 @@ impl NyaTermApp {
} else {
selected.end
};
if self.settings.summary.interaction_copy_on_select {
if self.settings.summary().interaction_copy_on_select {
let _ = self.copy_terminal_selection(cx);
}
let _ = self.move_smart_input_cursor(target, cx);
self.clear_terminal_selection(cx);
} else if self.settings.summary.interaction_copy_on_select {
} else if self.settings.summary().interaction_copy_on_select {
let _ = self.copy_terminal_selection(cx);
} else if self.terminal.selection.selection.is_some() {
// One shell notify for status after drag ends (not per mouse move).
@@ -452,7 +452,7 @@ impl NyaTermApp {
}
let idx = cell.col.min(cells.len().saturating_sub(1));
// xterm wordSeparator semantics: characters listed are separators, not word body.
let separators = self.settings.summary.interaction_word_separators.as_str();
let separators = self.settings.summary().interaction_word_separators.as_str();
let is_word = |cell: &TerminalTextCell| terminal_text_cell_is_word(cell, separators);
if !is_word(&cells[idx]) {
return (idx, idx.saturating_add(1));
@@ -50,9 +50,9 @@ impl NyaTermApp {
&& self
.terminal_protocol_state_for_session(&session_id)
.mouse_reporting;
let low_latency_mode = self.settings.summary.terminal_low_latency_mode;
let low_latency_mode = self.settings.summary().terminal_low_latency_mode;
let action_links_enabled =
self.settings.summary.terminal_action_links_enabled && !low_latency_mode;
self.settings.summary().terminal_action_links_enabled && !low_latency_mode;
let render_output_pressure = self.runtime_output_pressure_active();
let render_pressure = self
.terminal
@@ -98,7 +98,7 @@ impl NyaTermApp {
);
let action_link_matcher_key = terminal_action_link_matcher_key(
action_links_enabled,
&self.settings.summary.terminal_action_links_matchers,
&self.settings.summary().terminal_action_links_matchers,
);
let keyword_rules = if session_id.is_empty() {
if render_degraded || !is_active {
@@ -132,7 +132,7 @@ impl NyaTermApp {
// cursor placement, or when there is no session (empty bootstrap canvas).
let needs_shell_viewport_snapshot = session_id.is_empty()
|| (is_active
&& self.settings.summary.interaction_mac_ime_compatibility
&& self.settings.summary().interaction_mac_ime_compatibility
&& !self.terminal.input.ime_marked_text.is_empty());
let snapshot = if needs_shell_viewport_snapshot {
self.terminal_snapshot_for_session(
@@ -150,16 +150,16 @@ impl NyaTermApp {
let snapshot_rows = snapshot.row_count();
let snapshot_cols = snapshot.cols;
let viewport_snapshot_duration = snapshot_stage_started_at.elapsed();
let show_line_numbers = self.settings.summary.terminal_show_line_numbers;
let show_timestamps = self.settings.summary.terminal_show_timestamps;
let show_timestamp_ms = self.settings.summary.terminal_show_timestamp_milliseconds;
let show_line_numbers = self.settings.summary().terminal_show_line_numbers;
let show_timestamps = self.settings.summary().terminal_show_timestamps;
let show_timestamp_ms = self.settings.summary().terminal_show_timestamp_milliseconds;
let gutter_enabled = show_line_numbers || show_timestamps;
// Prefer remote cursor visibility/shape from the terminal model; settings
// supply the default paint style when the model reports a block cursor.
let remote_cursor_visible = snapshot.cursor.visible
&& snapshot.cursor.shape != nyaterm_terminal::CursorShape::Hidden
&& cursor_row != usize::MAX;
let blink_enabled = self.settings.summary.cursor_blink || snapshot.cursor.blinking;
let blink_enabled = self.settings.summary().cursor_blink || snapshot.cursor.blinking;
let show_cursor = is_active
&& !session_id.is_empty()
&& !is_disconnected
@@ -169,8 +169,8 @@ impl NyaTermApp {
let cursor_style = match snapshot.cursor.shape {
nyaterm_terminal::CursorShape::Underline => "underline".to_string(),
nyaterm_terminal::CursorShape::Beam => "bar".to_string(),
nyaterm_terminal::CursorShape::Hidden => self.settings.summary.cursor_style.clone(),
nyaterm_terminal::CursorShape::Block => self.settings.summary.cursor_style.clone(),
nyaterm_terminal::CursorShape::Hidden => self.settings.summary().cursor_style.clone(),
nyaterm_terminal::CursorShape::Block => self.settings.summary().cursor_style.clone(),
};
let (abs_start, abs_end) = terminal_snapshot_absolute_range(&snapshot);
let _ = abs_end;
@@ -333,7 +333,7 @@ impl NyaTermApp {
let terminal_font_family = self.gpui_terminal_font_family();
let ime_preedit_text = (is_active
&& !session_id.is_empty()
&& self.settings.summary.interaction_mac_ime_compatibility
&& self.settings.summary().interaction_mac_ime_compatibility
&& !self.terminal.input.ime_marked_text.is_empty())
.then(|| self.terminal.input.ime_marked_text.clone());
let ime_preedit_position = ime_preedit_text.as_ref().map(|_| {
@@ -409,7 +409,7 @@ impl NyaTermApp {
.pr(px(8.))
.text_color(rgb(palette.text_dimmed))
.font_family(terminal_font_family.clone())
.text_size(px(self.settings.summary.terminal_font_size as f32))
.text_size(px(self.settings.summary().terminal_font_size as f32))
.when(show_timestamps, |this| {
this.child(div().w(px(ts_w)).flex_none().child(ts_label))
})
@@ -464,9 +464,9 @@ impl NyaTermApp {
cell_h,
palette,
terminal_font_family.clone(),
self.settings.summary.terminal_font_size as f32,
self.settings.summary.terminal_font_weight as f32,
self.settings.summary.terminal_font_weight_bold as f32,
self.settings.summary().terminal_font_size as f32,
self.settings.summary().terminal_font_weight as f32,
self.settings.summary().terminal_font_weight_bold as f32,
);
if let Some(cache) = layout_cache {
grid = grid.with_layout_cache(cache);
@@ -487,9 +487,9 @@ impl NyaTermApp {
cell_h,
palette,
terminal_font_family.clone(),
self.settings.summary.terminal_font_size as f32,
self.settings.summary.terminal_font_weight as f32,
self.settings.summary.terminal_font_weight_bold as f32,
self.settings.summary().terminal_font_size as f32,
self.settings.summary().terminal_font_weight as f32,
self.settings.summary().terminal_font_weight_bold as f32,
);
if let Some(cache) = layout_cache {
grid = grid.with_layout_cache(cache);
@@ -507,7 +507,7 @@ impl NyaTermApp {
.unwrap_or(palette.link);
let sync_status_label = if sync_is_paused { "Paused" } else { "Syncing" };
let output_session_id = session_id.clone();
let terminal_font_size = self.settings.summary.terminal_font_size as f32;
let terminal_font_size = self.settings.summary().terminal_font_size as f32;
let performance_overlay = self
.terminal
.view
@@ -581,7 +581,7 @@ impl NyaTermApp {
.font_family(terminal_font_family.clone())
.text_size(px(terminal_font_size))
.font_weight(FontWeight(
self.settings.summary.terminal_font_weight as f32,
self.settings.summary().terminal_font_weight as f32,
))
.text_color(rgb(palette.terminal_fg))
.child(
@@ -855,7 +855,7 @@ impl NyaTermApp {
this.close_action_link_menu(cx);
let mods = event.modifiers;
let skip_selection =
this.settings.summary.terminal_action_links_enabled
this.settings.summary().terminal_action_links_enabled
&& (mods.alt || mods.control || mods.platform);
if !skip_selection {
this.start_terminal_selection_for_session(
@@ -893,7 +893,7 @@ impl NyaTermApp {
return;
}
}
if this.settings.summary.interaction_right_click_paste {
if this.settings.summary().interaction_right_click_paste {
this.paste_from_clipboard(window, cx);
this.clear_terminal_selection(cx);
} else {
@@ -947,7 +947,7 @@ impl NyaTermApp {
}
window.focus(&this.terminal.input.focus);
let modifiers = event.modifiers();
if this.settings.summary.terminal_action_links_enabled {
if this.settings.summary().terminal_action_links_enabled {
if modifiers.alt {
if this.try_open_action_link_menu_at_click(event, cx) {
cx.stop_propagation();
@@ -1032,7 +1032,7 @@ impl NyaTermApp {
.text_color(rgb(palette.terminal_fg))
.font_family(terminal_font_family.clone())
.text_size(px(
self.settings.summary.terminal_font_size as f32
self.settings.summary().terminal_font_size as f32,
))
.child(marked_text),
)
@@ -1260,7 +1260,7 @@ impl NyaTermApp {
expensive_interactions_enabled,
output_burst_bytes,
?performance_mode,
action_links_enabled = self.settings.summary.terminal_action_links_enabled,
action_links_enabled = self.settings.summary().terminal_action_links_enabled,
search_open = self.terminal.search.open,
search_matches = search_matches_len,
render_cache_hits,
@@ -44,7 +44,7 @@ impl Render for TransferExternalSyncWindow {
(
app.theme_palette(),
app.gpui_ui_font_family(),
app.settings.summary.ui_font_size.clamp(12, 24) as f32,
app.settings.summary().ui_font_size.clamp(12, 24) as f32,
app.tr("fileExplorer.fileModified").to_string(),
)
});
@@ -260,7 +260,7 @@ impl NyaTermApp {
entry.file_type == SftpFileType::Directory
&& entry.name != "."
&& entry.name != ".."
&& (self.settings.summary.ui_file_explorer_show_hidden_files
&& (self.settings.summary().ui_file_explorer_show_hidden_files
|| !entry.name.starts_with('.'))
});
entries.sort_by(|left, right| {
@@ -5,10 +5,12 @@ use crate::features::NyaTermApp;
impl NyaTermApp {
pub(in crate::features) fn sftp_transfer_options(&self) -> SftpTransferOptions {
SftpTransferOptions::default()
.with_buffer_size_bytes(self.settings.summary.transfer_buffer_size as usize * 1024)
.with_max_retries(self.settings.summary.transfer_max_retries)
.with_preserve_timestamps(self.settings.summary.transfer_preserve_timestamps)
.with_default_file_permissions(&self.settings.summary.transfer_default_file_permissions)
.with_resume_broken_transfer(self.settings.summary.transfer_resume_broken_transfer)
.with_buffer_size_bytes(self.settings.summary().transfer_buffer_size as usize * 1024)
.with_max_retries(self.settings.summary().transfer_max_retries)
.with_preserve_timestamps(self.settings.summary().transfer_preserve_timestamps)
.with_default_file_permissions(
&self.settings.summary().transfer_default_file_permissions,
)
.with_resume_broken_transfer(self.settings.summary().transfer_resume_broken_transfer)
}
}
@@ -36,7 +36,8 @@ impl NyaTermApp {
};
let _ = this.update(cx, |this, cx| {
if let Some(path) = path {
this.settings.summary.transfer_download_path = path.display().to_string();
this.settings
.set_transfer_download_path(path.display().to_string());
this.save_transfer_settings("transfer download path saved", cx);
} else {
this.terminal.view.status = "download path selection cancelled".to_string();
@@ -64,7 +65,7 @@ impl NyaTermApp {
};
let _ = this.update(cx, |this, cx| {
if let Some(path) = path {
this.settings.summary.recording_path = path.display().to_string();
this.settings.set_recording_path(path.display().to_string());
this.save_recording_settings(cx);
} else {
this.terminal.view.status = "recording path selection cancelled".to_string();
@@ -95,7 +96,8 @@ impl NyaTermApp {
};
let _ = this.update(cx, |this, cx| {
if let Some(path) = path {
this.settings.summary.transfer_default_editor = path.display().to_string();
this.settings
.set_transfer_default_editor(path.display().to_string());
this.save_transfer_settings("transfer editor path saved", cx);
} else {
this.terminal.view.status = "editor path selection cancelled".to_string();
@@ -108,7 +110,7 @@ impl NyaTermApp {
}
pub(in crate::features) fn resolved_transfer_download_dir(&self) -> Option<PathBuf> {
let configured = self.settings.summary.transfer_download_path.trim();
let configured = self.settings.summary().transfer_download_path.trim();
if configured.is_empty() {
return default_transfer_download_dir();
}
@@ -149,7 +151,7 @@ impl NyaTermApp {
if value.is_empty() {
let file_name =
download_file_name_from_remote_path(&self.transfer.normalized_remote_path());
let download_path = self.settings.summary.transfer_download_path.trim();
let download_path = self.settings.summary().transfer_download_path.trim();
if download_path.is_empty() {
PathBuf::from(file_name)
} else {
+14 -2
View File
@@ -236,8 +236,13 @@ these as staged extraction candidates, not as formatting-only refactor targets.
and backing child settings-module-private. Cross-domain persistence adapters
now update its message/readiness through owner methods, rendering receives a
borrowed immutable view, and store reopen replaces path/message/readiness as
one transition. Filesystem prompts, persistence and GPUI notification stay
in their existing adapters.
one transition. The next compatibility-catalog batch made
`AppSettingsSummary`, `KeywordHighlightConfig` and staged master-password
state settings-module-private as well. Cross-domain readers now use borrowed
immutable access, appearance/keyword/browser preferences enter through
semantic transitions, and the shell persists its complete UI layout through
one typed owner update instead of piecemeal field writes. Filesystem prompts,
persistence and GPUI notification stay in their existing adapters.
- Translation and native-update background state now have authoritative
`TranslationFeatureState` and `UpdateFeatureState` owners. Eighteen app fields
became two feature fields; each owner constructs and retains its own job
@@ -1491,6 +1496,7 @@ Current ownership map:
| Command history and persistence worker | Private state in `NyaTermApp.commands` | Persisted catalog plus background runtime | History snapshots, queue admission, event polling and idle checks enter through `CommandFeatureState`; failed optimistic use-count updates roll back on the owner. |
| Send-command composer/options/progress | Private children in `NyaTermApp.send_command` | Transient editor and send lifecycle | Views receive immutable presentation data; control edits, mutually-exclusive menus, data/mode defaults, progress counters and cancellation enter through `SendCommandFeatureState`. Session selection, terminal writes, GPUI/text-input routing and status remain in adapters. |
| Settings interaction and prompts | Private children in `NyaTermApp.settings` | Transient settings UI and prompt lifecycle | Search-engine rows, keyword-highlight editing, appearance menus, keybinding recording/search and config/diagnostics/import/password prompt admission enter through `SettingsFeatureState`; views use immutable presentation values and read-only focus/font access. Persistence, native filesystem prompts, text inputs and GPUI notification remain in adapters. |
| Application settings and keyword catalogs | Settings-module-private children in `NyaTermApp.settings` | Compatibility-sensitive persisted configuration plus staged master-password input | Cross-domain consumers borrow immutable summaries/configs and use semantic owner transitions. Shell UI layout is replaced through one typed update; master-password presentation is a non-`Debug` borrowed view. Serialization, encryption and persistence remain in `nyaterm-core` and existing adapters. |
| Global storage status | Settings-module-private child in `NyaTermApp.settings` | Runtime persistence health/presentation state | Cross-domain persistence adapters update message/readiness through `SettingsFeatureState`; rendering receives a borrowed immutable view, while store reopen replaces path/message/readiness together. Database work and compatibility handling remain in existing adapters and `nyaterm-core`. |
| AI settings/chat/history/discovery/agent/panel | AI-module-private children in `NyaTermApp.ai` | Persisted settings plus transient UI and background lifecycle | Desktop consumers use read-only slices/queries and semantic transitions; settings draft groups, menu exclusion, confirmations, request/focus preparation, detected-error throttling, picker clamping and Agent capture/reset enter through `AiFeatureState`. Persistence, terminal-context collection, GPUI focus/rendering and notification remain in adapters. |
| Shell viewport/navigation/panels/chrome/workspace | Shell-module-private children in `NyaTermApp.shell` | Transient GPUI composition and interaction state | Other desktop modules use read-only geometry/navigation/pane queries and semantic transitions; menu exclusion, settings-window lifecycle, mobile panels, failure chrome, submenu paths and pane ownership update through `ShellFeatureState`. Persistence, rendering, GPUI windows/notification and terminal coordination remain in adapters. |
@@ -1755,6 +1761,12 @@ honest remaining list.
implementation type settings-module-private, moved cross-domain status
writes behind owner transitions, and gave rendering a borrowed immutable
view without changing storage execution or compatibility formats.
The following settings compatibility-catalog batch completed child
encapsulation for `SettingsFeatureState`: summary, keyword-highlight config
and staged master-password state are now settings-module-private, all
cross-domain reads are immutable, keyword/appearance/browser mutations use
owner transitions, and UI layout persistence applies one typed atomic
update. No mutable-reference accessor was introduced.
What remains at the
composition root is stores, runtime and focused feature owners.
Group by cohesion where a cluster exists; do not force the count down for
+21
View File
@@ -268,6 +268,27 @@ check_no_multiline_matches_excluding \
'(self|this|app)\.settings[[:space:]]*\.[[:space:]]*store_status[[:space:]]*(\.|=)' \
crates/nyaterm-desktop/src/features \
'crates/nyaterm-desktop/src/features/settings/**'
check_no_multiline_matches \
"SettingsFeatureState compatibility children must stay inside the settings module" \
'struct[[:space:]]+SettingsFeatureState[[:space:]]*\{[^}]*pub([[:space:]]|\(crate\)|\(in crate\)|\(in crate::features\))[[:space:]]+(summary|keyword_config|master_password)[[:space:]]*:' \
crates/nyaterm-desktop/src/features/settings/state.rs
check_no_matches \
"SettingsMasterPasswordState implementation must stay inside the settings module" \
'pub([[:space:]]|\(crate\)|\(in crate\)|\(in crate::features\))[[:space:]]+struct[[:space:]]+SettingsMasterPasswordState' \
crates/nyaterm-desktop/src/features/settings/catalog.rs
check_no_matches \
"settings compatibility implementation types must not be re-exported" \
'pub([^;]*)use([^;]*)(SettingsMasterPasswordState|StoreStatus)' \
crates/nyaterm-desktop/src/features/settings/mod.rs
check_no_multiline_matches_excluding \
"settings compatibility children must use SettingsFeatureState methods outside settings" \
'(self|this|app)\.settings[[:space:]]*\.[[:space:]]*(summary|keyword_config|master_password)[[:space:]]*(\.|=|\[)' \
crates/nyaterm-desktop/src/features \
'crates/nyaterm-desktop/src/features/settings/**'
check_no_matches \
"settings compatibility state must not expose mutable-reference accessors" \
'fn[[:space:]]+(summary|keyword_config|master_password)(_mut|_mutable)|->[[:space:]]*&mut[[:space:]]+(AppSettingsSummary|KeywordHighlightConfig|SettingsMasterPasswordState)' \
crates/nyaterm-desktop/src/features/settings
check_no_matches \
"translation state must stay grouped under TranslationFeatureState" \