mirror of
https://github.com/nyakang/nyaterm.git
synced 2026-10-09 08:01:10 +00:00
refactor(desktop): group background feature state
This commit is contained in:
@@ -6,7 +6,7 @@ use crate::models::{
|
||||
ActivityBarLayoutState, BottomPanelMode, CloudSyncInputField, CloudSyncSecretDraft,
|
||||
GithubGistAuthState, HeaderStatusState, MainMode, NavItem, PanelSide, RecordingWritePipeline,
|
||||
RightFocus, SessionEventBridge, SettingsTab, StartupCommandAction, StoreStatus,
|
||||
TerminalFramePipeline, TranslateInputField, TranslationSecretDraft,
|
||||
TerminalFramePipeline,
|
||||
};
|
||||
use crate::terminal::initial_terminal_screen;
|
||||
use gpui::{Context, ScrollHandle};
|
||||
@@ -26,9 +26,10 @@ use super::super::{
|
||||
QuickCommandFeatureState, RemoteOpsFeatureFocus, RemoteOpsFeatureState, SecurityFeatureFocus,
|
||||
SecurityFeatureState, SendCommandFeatureFocus, SendCommandFeatureState,
|
||||
SftpDuplicatePromptBroker, TerminalFeatureFocus, TerminalFeatureState, TextInputRegistry,
|
||||
TransferFeatureFocus, TransferFeatureState, ai_active_profile_drafts, ai_usage_counts,
|
||||
appearance_font_options, quick_command_sort_mode_from_setting,
|
||||
quick_command_view_mode_from_setting, spawn_command_persistence_worker,
|
||||
TransferFeatureFocus, TransferFeatureState, TranslationFeatureState, UpdateFeatureState,
|
||||
ai_active_profile_drafts, ai_usage_counts, appearance_font_options,
|
||||
quick_command_sort_mode_from_setting, quick_command_view_mode_from_setting,
|
||||
spawn_command_persistence_worker,
|
||||
};
|
||||
use super::NyaTermApp;
|
||||
use crate::models::panel_collapsed_from_persistence;
|
||||
@@ -60,8 +61,6 @@ impl NyaTermApp {
|
||||
};
|
||||
let (session_start_tx, session_start_rx) = mpsc::channel();
|
||||
let (tunnel_tx, tunnel_rx) = mpsc::channel();
|
||||
let (translate_tx, translate_rx) = mpsc::channel();
|
||||
let (update_tx, update_rx) = mpsc::channel();
|
||||
let (github_gist_auth_tx, github_gist_auth_rx) = mpsc::channel();
|
||||
let (command_persistence_tx, command_persistence_rx) = spawn_command_persistence_worker(
|
||||
runtime.config_dir().to_path_buf(),
|
||||
@@ -289,7 +288,6 @@ impl NyaTermApp {
|
||||
.collect::<HashMap<_, _>>();
|
||||
let panel_multi_open = settings.ui_panel_multi_open;
|
||||
let settings_master_password_enabled = settings.has_master_password;
|
||||
let translate_target_language = translation_settings.target_language.clone();
|
||||
let mut terminal_output_decoder = TerminalOutputDecoder::default();
|
||||
terminal_output_decoder.set_encoding(&settings.interaction_default_encoding);
|
||||
let mut terminal_screen = initial_terminal_screen();
|
||||
@@ -427,6 +425,8 @@ impl NyaTermApp {
|
||||
},
|
||||
),
|
||||
remote_ops: RemoteOpsFeatureState::new(RemoteOpsFeatureFocus {}),
|
||||
translation: TranslationFeatureState::new(translation_settings),
|
||||
update: UpdateFeatureState::new(),
|
||||
command_history: Arc::from(command_history),
|
||||
command_persistence_tx,
|
||||
command_persistence_rx,
|
||||
@@ -439,7 +439,6 @@ impl NyaTermApp {
|
||||
action_link_menu: None,
|
||||
action_link_tooltip: None,
|
||||
action_link_hover_pending: None,
|
||||
translation_dialog: None,
|
||||
bottom_panel: if settings.ui_serial_send_visible {
|
||||
BottomPanelMode::CommandSend
|
||||
} else if settings.ui_quick_cmd_visible {
|
||||
@@ -476,23 +475,6 @@ impl NyaTermApp {
|
||||
tunnel_tx,
|
||||
tunnel_rx,
|
||||
pending_tunnels: Vec::new(),
|
||||
translate_tx,
|
||||
translate_rx,
|
||||
translate_provider: "google".to_string(),
|
||||
translation_settings,
|
||||
translation_secret_draft: TranslationSecretDraft::default(),
|
||||
translate_target_language,
|
||||
translate_input: String::new(),
|
||||
translate_result: None,
|
||||
translate_status: "Google translation ready".to_string(),
|
||||
translate_pending: false,
|
||||
translate_focused_field: TranslateInputField::Text,
|
||||
update_tx,
|
||||
update_rx,
|
||||
update_status: format!("Current version {}", env!("CARGO_PKG_VERSION")),
|
||||
update_info: None,
|
||||
update_pending: false,
|
||||
update_dialog_open: false,
|
||||
about_open: false,
|
||||
remote_editor_window: None,
|
||||
remote_editor_window_open_pending: false,
|
||||
|
||||
@@ -5,9 +5,9 @@ use std::time::Instant;
|
||||
use gpui::{FocusHandle, ScrollHandle, WindowHandle};
|
||||
use nyaterm_core::{
|
||||
AiExecutionProfile, AppRuntime, AppSettingsSummary, CloudSyncHistoryEntry, CloudSyncSettings,
|
||||
CloudSyncState, CommandHistoryEntry, Group, KeywordHighlightConfig, NativeServices,
|
||||
NativeUpdateInfo, OtpEntry, ProxyConfig, ProxyGroup, QuickCommand, QuickCommandCategory,
|
||||
SavedConnection, SavedCredential, SavedPassword, SshKey, TunnelConfig, TunnelGroup,
|
||||
CloudSyncState, CommandHistoryEntry, Group, KeywordHighlightConfig, NativeServices, OtpEntry,
|
||||
ProxyConfig, ProxyGroup, QuickCommand, QuickCommandCategory, SavedConnection, SavedCredential,
|
||||
SavedPassword, SshKey, TunnelConfig, TunnelGroup,
|
||||
};
|
||||
use nyaterm_legacy::MigrationInventory;
|
||||
use nyaterm_transport::{
|
||||
@@ -22,8 +22,7 @@ use super::panels::SendCommandFeatureState;
|
||||
use super::remote::RemoteOpsFeatureState;
|
||||
use super::remote_editor_window::RemoteFileEditorWindow;
|
||||
use super::runtime_jobs::{
|
||||
CommandPersistenceRequest, CommandPersistenceResult, SessionStartResult, TranslateJobResult,
|
||||
TunnelJobResult, UpdateJobResult,
|
||||
CommandPersistenceRequest, CommandPersistenceResult, SessionStartResult, TunnelJobResult,
|
||||
};
|
||||
use super::session::{
|
||||
CredentialPromptBroker, CredentialPromptState, HostKeyPromptBroker, HostKeyPromptRequest,
|
||||
@@ -35,6 +34,8 @@ use super::settings_window::SettingsWindow;
|
||||
use super::terminal::TerminalFeatureState;
|
||||
use super::text_inputs::TextInputRegistry;
|
||||
use super::transfers::TransferFeatureState;
|
||||
use super::translation::TranslationFeatureState;
|
||||
use super::update::UpdateFeatureState;
|
||||
use crate::models::{
|
||||
ActionLinkMenuState, ActionLinkTooltipState, ActiveSessionMenuState,
|
||||
ActivityBarContextMenuState, ActivityBarLayoutState, BottomPanelMode, BottomPanelResizeState,
|
||||
@@ -44,11 +45,9 @@ use crate::models::{
|
||||
PanelStackResizeState, RecordingPathPromptKind, RecordingWritePipeline, RightFocus,
|
||||
SessionEventBridge, SessionRuntimeMetadata, SettingsTab, SnapshotPasswordPromptState,
|
||||
StartupCommandAction, StoreStatus, SyncInputGroup, TabActionsSubmenu, TerminalFrameEvent,
|
||||
TitleMenu, TitleMenuSubmenu, TranslateInputField, TranslationDialogState,
|
||||
TranslationSecretDraft, WorkspacePaneNode, WorkspaceSplitDirection, WorkspaceSplitResizeState,
|
||||
WorkspaceSplitState,
|
||||
TitleMenu, TitleMenuSubmenu, WorkspacePaneNode, WorkspaceSplitDirection,
|
||||
WorkspaceSplitResizeState, WorkspaceSplitState,
|
||||
};
|
||||
use nyaterm_core::{TranslateResult, TranslationSettings};
|
||||
|
||||
mod construct;
|
||||
mod types;
|
||||
@@ -89,6 +88,8 @@ pub struct NyaTermApp {
|
||||
pub(in crate::features) terminal: TerminalFeatureState,
|
||||
pub(in crate::features) send_command: SendCommandFeatureState,
|
||||
pub(in crate::features) transfer: TransferFeatureState,
|
||||
pub(in crate::features) translation: TranslationFeatureState,
|
||||
pub(in crate::features) update: UpdateFeatureState,
|
||||
pub(in crate::features) command_history: Arc<[CommandHistoryEntry]>,
|
||||
pub(in crate::features) command_persistence_tx: mpsc::Sender<CommandPersistenceRequest>,
|
||||
pub(in crate::features) command_persistence_rx: mpsc::Receiver<CommandPersistenceResult>,
|
||||
@@ -105,7 +106,6 @@ pub struct NyaTermApp {
|
||||
pub(in crate::features) action_link_hover_pending:
|
||||
Option<(String, Instant, ActionLinkTooltipState)>,
|
||||
|
||||
pub(in crate::features) translation_dialog: Option<TranslationDialogState>,
|
||||
pub(in crate::features) bottom_panel: BottomPanelMode,
|
||||
pub(in crate::features) quick_cmd_height: f32,
|
||||
pub(in crate::features) serial_send_height: f32,
|
||||
@@ -140,23 +140,6 @@ pub struct NyaTermApp {
|
||||
pub(in crate::features) tunnel_tx: mpsc::Sender<TunnelJobResult>,
|
||||
pub(in crate::features) tunnel_rx: mpsc::Receiver<TunnelJobResult>,
|
||||
pub(in crate::features) pending_tunnels: Vec<String>,
|
||||
pub(in crate::features) translate_tx: mpsc::Sender<TranslateJobResult>,
|
||||
pub(in crate::features) translate_rx: mpsc::Receiver<TranslateJobResult>,
|
||||
pub(in crate::features) translate_provider: String,
|
||||
pub(in crate::features) translation_settings: TranslationSettings,
|
||||
pub(in crate::features) translation_secret_draft: TranslationSecretDraft,
|
||||
pub(in crate::features) translate_target_language: String,
|
||||
pub(in crate::features) translate_input: String,
|
||||
pub(in crate::features) translate_result: Option<TranslateResult>,
|
||||
pub(in crate::features) translate_status: String,
|
||||
pub(in crate::features) translate_pending: bool,
|
||||
pub(in crate::features) translate_focused_field: TranslateInputField,
|
||||
pub(in crate::features) update_tx: mpsc::Sender<UpdateJobResult>,
|
||||
pub(in crate::features) update_rx: mpsc::Receiver<UpdateJobResult>,
|
||||
pub(in crate::features) update_status: String,
|
||||
pub(in crate::features) update_info: Option<NativeUpdateInfo>,
|
||||
pub(in crate::features) update_pending: bool,
|
||||
pub(in crate::features) update_dialog_open: bool,
|
||||
pub(in crate::features) about_open: bool,
|
||||
pub(in crate::features) remote_editor_window: Option<WindowHandle<RemoteFileEditorWindow>>,
|
||||
pub(in crate::features) remote_editor_window_open_pending: bool,
|
||||
|
||||
@@ -206,9 +206,9 @@ impl NyaTermApp {
|
||||
));
|
||||
}
|
||||
TitleMenu::Help => {
|
||||
let update_label = if self.update_pending {
|
||||
let update_label = if self.update.pending {
|
||||
tr("updater.checking")
|
||||
} else if self.update_info.as_ref().is_some_and(|info| info.available) {
|
||||
} else if self.update.info.as_ref().is_some_and(|info| info.available) {
|
||||
tr("updater.newVersionAvailable")
|
||||
} else {
|
||||
tr("menu.checkForUpdates")
|
||||
|
||||
@@ -29,6 +29,7 @@ mod transfer_external_sync_window;
|
||||
mod transfers;
|
||||
mod translation;
|
||||
mod tunnels;
|
||||
mod update;
|
||||
mod view_widgets;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
@@ -77,7 +78,7 @@ pub(in crate::features) use runtime_jobs::{
|
||||
AiChatJobOutput, AiChatJobResult, AiChatWorkerEvent, AiDiscoveryJobResult,
|
||||
CommandPersistenceRequest, CommandPersistenceResult, DockerJobResult, ProcessJobResult,
|
||||
SessionStartResult, SessionStartSuccess, StatsJobResult, TunnelJobOutput, TunnelJobResult,
|
||||
UpdateJobResult, spawn_command_persistence_worker,
|
||||
spawn_command_persistence_worker,
|
||||
};
|
||||
pub(in crate::features) use session::{
|
||||
CredentialPromptBroker, HostKeyPromptBroker, NativeOtpProvider, SftpDuplicatePromptBroker,
|
||||
@@ -101,6 +102,8 @@ pub(in crate::features) use transfers::{
|
||||
TransferFeatureFocus, TransferFeatureState, duplicate_decision_label, duplicate_policy_label,
|
||||
format_file_size, transfer_job_title, transfer_status_label,
|
||||
};
|
||||
pub(in crate::features) use translation::TranslationFeatureState;
|
||||
pub(in crate::features) use update::UpdateFeatureState;
|
||||
pub(in crate::features) use view_widgets::{
|
||||
child_window_header, child_window_titlebar, color_icon, connection_type_icon,
|
||||
dialog_action_button, logo_mark, metric, modal_close_icon_button,
|
||||
|
||||
@@ -36,25 +36,25 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let deepl_key_value = self.translation_secret_draft.deepl_api_key.clone();
|
||||
let baidu_app_id_value = self.translation_settings.baidu_app_id.clone();
|
||||
let baidu_key_value = self.translation_secret_draft.baidu_app_key.clone();
|
||||
let ali_app_id_value = self.translation_settings.ali_app_id.clone();
|
||||
let ali_key_value = self.translation_secret_draft.ali_app_key.clone();
|
||||
let youdao_app_id_value = self.translation_settings.youdao_app_id.clone();
|
||||
let youdao_key_value = self.translation_secret_draft.youdao_app_key.clone();
|
||||
let deepl_key_value = self.translation.secret_draft.deepl_api_key.clone();
|
||||
let baidu_app_id_value = self.translation.settings.baidu_app_id.clone();
|
||||
let baidu_key_value = self.translation.secret_draft.baidu_app_key.clone();
|
||||
let ali_app_id_value = self.translation.settings.ali_app_id.clone();
|
||||
let ali_key_value = self.translation.secret_draft.ali_app_key.clone();
|
||||
let youdao_app_id_value = self.translation.settings.youdao_app_id.clone();
|
||||
let youdao_key_value = self.translation.secret_draft.youdao_app_key.clone();
|
||||
|
||||
let deepl_configured = !self.translation_settings.deepl_api_key.trim().is_empty()
|
||||
|| !self.translation_secret_draft.deepl_api_key.is_empty();
|
||||
let baidu_configured = !self.translation_settings.baidu_app_id.trim().is_empty()
|
||||
&& (!self.translation_settings.baidu_app_key.trim().is_empty()
|
||||
|| !self.translation_secret_draft.baidu_app_key.is_empty());
|
||||
let ali_configured = !self.translation_settings.ali_app_id.trim().is_empty()
|
||||
&& (!self.translation_settings.ali_app_key.trim().is_empty()
|
||||
|| !self.translation_secret_draft.ali_app_key.is_empty());
|
||||
let youdao_configured = !self.translation_settings.youdao_app_id.trim().is_empty()
|
||||
&& (!self.translation_settings.youdao_app_key.trim().is_empty()
|
||||
|| !self.translation_secret_draft.youdao_app_key.is_empty());
|
||||
let deepl_configured = !self.translation.settings.deepl_api_key.trim().is_empty()
|
||||
|| !self.translation.secret_draft.deepl_api_key.is_empty();
|
||||
let baidu_configured = !self.translation.settings.baidu_app_id.trim().is_empty()
|
||||
&& (!self.translation.settings.baidu_app_key.trim().is_empty()
|
||||
|| !self.translation.secret_draft.baidu_app_key.is_empty());
|
||||
let ali_configured = !self.translation.settings.ali_app_id.trim().is_empty()
|
||||
&& (!self.translation.settings.ali_app_key.trim().is_empty()
|
||||
|| !self.translation.secret_draft.ali_app_key.is_empty());
|
||||
let youdao_configured = !self.translation.settings.youdao_app_id.trim().is_empty()
|
||||
&& (!self.translation.settings.youdao_app_key.trim().is_empty()
|
||||
|| !self.translation.secret_draft.youdao_app_key.is_empty());
|
||||
|
||||
let target_language_label = self.tr("settings.targetLanguage");
|
||||
let target_language_desc = self.tr("settings.targetLanguageDesc");
|
||||
@@ -103,7 +103,8 @@ impl NyaTermApp {
|
||||
let code = *code;
|
||||
let label = *label;
|
||||
let selected = self
|
||||
.translation_settings
|
||||
.translation
|
||||
.settings
|
||||
.target_language
|
||||
.eq_ignore_ascii_case(code);
|
||||
settings_choice_chip(
|
||||
@@ -112,8 +113,8 @@ impl NyaTermApp {
|
||||
label,
|
||||
selected,
|
||||
cx.listener(move |this, _, _, cx| {
|
||||
this.translation_settings.target_language = code.to_string();
|
||||
this.translate_target_language = code.to_string();
|
||||
this.translation.settings.target_language = code.to_string();
|
||||
this.translation.target_language = code.to_string();
|
||||
this.save_translation_settings(cx);
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -217,7 +217,7 @@ impl NyaTermApp {
|
||||
this.terminal.view.status =
|
||||
"terminal visible screen is empty".to_string();
|
||||
} else {
|
||||
let provider = this.translate_provider.clone();
|
||||
let provider = this.translation.provider.clone();
|
||||
let provider_label = match provider.as_str() {
|
||||
"google" => this.tr("translation.google"),
|
||||
"microsoft" => {
|
||||
|
||||
@@ -13,11 +13,11 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let update_info = self.update_info.clone();
|
||||
let checking = self.update_pending;
|
||||
let update_info = self.update.info.clone();
|
||||
let checking = self.update.pending;
|
||||
let failed = !checking
|
||||
&& update_info.is_none()
|
||||
&& self.update_status.starts_with("update check failed:");
|
||||
&& self.update.status.starts_with("update check failed:");
|
||||
let available =
|
||||
!checking && !failed && update_info.as_ref().is_some_and(|info| info.available);
|
||||
let portable = self.runtime.mode() == RuntimeMode::Portable;
|
||||
@@ -137,7 +137,7 @@ impl NyaTermApp {
|
||||
.text_xs()
|
||||
.line_height(px(18.))
|
||||
.text_color(rgb(palette.danger))
|
||||
.child(self.update_status.clone()),
|
||||
.child(self.update.status.clone()),
|
||||
)
|
||||
})
|
||||
.when(available && portable, |this| {
|
||||
|
||||
@@ -567,10 +567,10 @@ impl NyaTermApp {
|
||||
overlay.action_link_tooltip_open
|
||||
&& !overlay.action_link_menu_open
|
||||
&& !overlay.terminal_context_menu_open
|
||||
&& self.translation_dialog.is_none(),
|
||||
&& self.translation.dialog.is_none(),
|
||||
|this| this.child(self.action_link_tooltip_overlay(cx)),
|
||||
)
|
||||
.when(self.translation_dialog.is_some(), |this| {
|
||||
.when(self.translation.dialog.is_some(), |this| {
|
||||
this.child(self.translation_dialog_overlay(cx))
|
||||
})
|
||||
.when(overlay.command_suggestions_open, |this| {
|
||||
@@ -639,7 +639,7 @@ impl NyaTermApp {
|
||||
this.child(self.close_all_sessions_confirm_overlay(cx))
|
||||
})
|
||||
.when(self.about_open, |this| this.child(self.about_overlay(cx)))
|
||||
.when(self.update_dialog_open, |this| {
|
||||
.when(self.update.dialog_open, |this| {
|
||||
this.child(self.update_overlay(cx))
|
||||
})
|
||||
.when(self.modal_child_window_open(), |this| {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::time::Instant;
|
||||
|
||||
use nyaterm_core::{
|
||||
AiCommandCard, AiMode, AiModelDiscovery, CommandHistoryEntry, CommandObservation,
|
||||
ConnectionStore, NativeUpdateInfo, TranslateResult,
|
||||
ConnectionStore,
|
||||
};
|
||||
use nyaterm_transport::{
|
||||
DockerComposeService, DockerContainerDetails, RemoteDockerOverview, RemoteProcess, RemoteStats,
|
||||
@@ -54,16 +54,6 @@ pub(in crate::features) struct StatsJobResult {
|
||||
pub(in crate::features) result: Result<RemoteStats, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::features) struct TranslateJobResult {
|
||||
pub(in crate::features) result: Result<TranslateResult, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::features) struct UpdateJobResult {
|
||||
pub(in crate::features) result: Result<NativeUpdateInfo, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::features) enum CommandPersistenceRequest {
|
||||
AppendHistory(Vec<String>),
|
||||
|
||||
@@ -541,15 +541,15 @@ impl NyaTermApp {
|
||||
store.load_app_settings_summary().unwrap_or_default(),
|
||||
);
|
||||
self.apply_ui_layout_from_settings();
|
||||
self.translation_settings = store
|
||||
self.translation.settings = store
|
||||
.load_translation_settings()
|
||||
.unwrap_or_else(|_| TranslationSettings {
|
||||
target_language: self.settings.language.clone(),
|
||||
..TranslationSettings::default()
|
||||
});
|
||||
self.translation_secret_draft = TranslationSecretDraft::default();
|
||||
self.translate_target_language =
|
||||
self.translation_settings.target_language.clone();
|
||||
self.translation.secret_draft = TranslationSecretDraft::default();
|
||||
self.translation.target_language =
|
||||
self.translation.settings.target_language.clone();
|
||||
self.recording_manager
|
||||
.set_memory_limit(self.settings.recording_memory_limit_bytes as usize);
|
||||
self.cloud_sync_settings = store
|
||||
@@ -592,10 +592,10 @@ impl NyaTermApp {
|
||||
self.command_history = Arc::default();
|
||||
self.keyword_highlights = KeywordHighlightConfig::default();
|
||||
self.apply_gpui_settings(AppSettingsSummary::default());
|
||||
self.translation_settings = TranslationSettings::default();
|
||||
self.translation_secret_draft = TranslationSecretDraft::default();
|
||||
self.translate_target_language =
|
||||
self.translation_settings.target_language.clone();
|
||||
self.translation.settings = TranslationSettings::default();
|
||||
self.translation.secret_draft = TranslationSecretDraft::default();
|
||||
self.translation.target_language =
|
||||
self.translation.settings.target_language.clone();
|
||||
self.store_status = StoreStatus {
|
||||
path,
|
||||
message: format!("failed to load sessions: {error}"),
|
||||
@@ -614,9 +614,10 @@ impl NyaTermApp {
|
||||
self.quick_command_categories.clear();
|
||||
self.command_history = Arc::default();
|
||||
self.apply_gpui_settings(AppSettingsSummary::default());
|
||||
self.translation_settings = TranslationSettings::default();
|
||||
self.translation_secret_draft = TranslationSecretDraft::default();
|
||||
self.translate_target_language = self.translation_settings.target_language.clone();
|
||||
self.translation.settings = TranslationSettings::default();
|
||||
self.translation.secret_draft = TranslationSecretDraft::default();
|
||||
self.translation.target_language =
|
||||
self.translation.settings.target_language.clone();
|
||||
self.store_status = StoreStatus {
|
||||
path: self
|
||||
.runtime
|
||||
|
||||
@@ -6,7 +6,6 @@ mod security_runtime;
|
||||
mod security_state;
|
||||
mod settings_runtime;
|
||||
mod state;
|
||||
mod update_runtime;
|
||||
|
||||
pub(in crate::features) use security_state::{SecurityFeatureFocus, SecurityFeatureState};
|
||||
pub(in crate::features) use state::{SettingsFeatureFocus, SettingsFeatureState};
|
||||
|
||||
@@ -19,8 +19,8 @@ impl NyaTermApp {
|
||||
ai_secret_draft: self.ai.settings.secret_draft.clone(),
|
||||
cloud_sync_settings: self.cloud_sync_settings.clone(),
|
||||
cloud_sync_secret_draft: self.cloud_sync_secret_draft.clone(),
|
||||
translation_settings: self.translation_settings.clone(),
|
||||
translation_secret_draft: self.translation_secret_draft.clone(),
|
||||
translation_settings: self.translation.settings.clone(),
|
||||
translation_secret_draft: self.translation.secret_draft.clone(),
|
||||
keyword_highlights: self.keyword_highlights.clone(),
|
||||
master_password_enabled: self.settings_master_password_enabled,
|
||||
master_password_draft: self.settings_master_password_draft.clone(),
|
||||
@@ -38,8 +38,8 @@ impl NyaTermApp {
|
||||
|| snapshot.ai_secret_draft != self.ai.settings.secret_draft
|
||||
|| snapshot.cloud_sync_settings != self.cloud_sync_settings
|
||||
|| snapshot.cloud_sync_secret_draft != self.cloud_sync_secret_draft
|
||||
|| snapshot.translation_settings != self.translation_settings
|
||||
|| snapshot.translation_secret_draft != self.translation_secret_draft
|
||||
|| snapshot.translation_settings != self.translation.settings
|
||||
|| snapshot.translation_secret_draft != self.translation.secret_draft
|
||||
|| snapshot.keyword_highlights != self.keyword_highlights
|
||||
|| snapshot.master_password_enabled != self.settings_master_password_enabled
|
||||
|| snapshot.master_password_draft != self.settings_master_password_draft
|
||||
@@ -61,18 +61,18 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn pending_translation_settings(&self) -> TranslationSettings {
|
||||
let mut next = self.translation_settings.clone();
|
||||
if !self.translation_secret_draft.deepl_api_key.is_empty() {
|
||||
next.deepl_api_key = self.translation_secret_draft.deepl_api_key.clone();
|
||||
let mut next = self.translation.settings.clone();
|
||||
if !self.translation.secret_draft.deepl_api_key.is_empty() {
|
||||
next.deepl_api_key = self.translation.secret_draft.deepl_api_key.clone();
|
||||
}
|
||||
if !self.translation_secret_draft.baidu_app_key.is_empty() {
|
||||
next.baidu_app_key = self.translation_secret_draft.baidu_app_key.clone();
|
||||
if !self.translation.secret_draft.baidu_app_key.is_empty() {
|
||||
next.baidu_app_key = self.translation.secret_draft.baidu_app_key.clone();
|
||||
}
|
||||
if !self.translation_secret_draft.ali_app_key.is_empty() {
|
||||
next.ali_app_key = self.translation_secret_draft.ali_app_key.clone();
|
||||
if !self.translation.secret_draft.ali_app_key.is_empty() {
|
||||
next.ali_app_key = self.translation.secret_draft.ali_app_key.clone();
|
||||
}
|
||||
if !self.translation_secret_draft.youdao_app_key.is_empty() {
|
||||
next.youdao_app_key = self.translation_secret_draft.youdao_app_key.clone();
|
||||
if !self.translation.secret_draft.youdao_app_key.is_empty() {
|
||||
next.youdao_app_key = self.translation.secret_draft.youdao_app_key.clone();
|
||||
}
|
||||
next
|
||||
}
|
||||
@@ -326,13 +326,14 @@ impl NyaTermApp {
|
||||
self.settings_master_password_draft.clear();
|
||||
self.ai.settings.config = saved_ai_settings;
|
||||
self.cloud_sync_settings = saved_cloud_sync_settings;
|
||||
self.translation_settings = saved_translation_settings;
|
||||
self.translation.settings = saved_translation_settings;
|
||||
self.keyword_highlights = saved_keyword_highlights;
|
||||
self.translation_secret_draft = TranslationSecretDraft::default();
|
||||
self.translation.secret_draft = TranslationSecretDraft::default();
|
||||
self.cloud_sync_secret_draft = CloudSyncSecretDraft::default();
|
||||
self.ai.settings.secret_draft.clear();
|
||||
self.sync_ai_drafts_from_active_profile();
|
||||
self.translate_target_language = self.translation_settings.target_language.clone();
|
||||
self.translation.target_language =
|
||||
self.translation.settings.target_language.clone();
|
||||
self.recording_manager
|
||||
.set_memory_limit(self.settings.recording_memory_limit_bytes as usize);
|
||||
self.transfer.paths.duplicate_policy = SftpDuplicatePolicy::from_legacy_value(
|
||||
@@ -384,8 +385,8 @@ impl NyaTermApp {
|
||||
self.ai.settings.secret_draft = snapshot.ai_secret_draft;
|
||||
self.cloud_sync_settings = snapshot.cloud_sync_settings;
|
||||
self.cloud_sync_secret_draft = snapshot.cloud_sync_secret_draft;
|
||||
self.translation_settings = snapshot.translation_settings;
|
||||
self.translation_secret_draft = snapshot.translation_secret_draft;
|
||||
self.translation.settings = snapshot.translation_settings;
|
||||
self.translation.secret_draft = snapshot.translation_secret_draft;
|
||||
self.keyword_highlights = snapshot.keyword_highlights;
|
||||
self.settings_master_password_enabled = snapshot.master_password_enabled;
|
||||
self.settings_master_password_draft = snapshot.master_password_draft;
|
||||
@@ -397,7 +398,7 @@ impl NyaTermApp {
|
||||
self.invalidate_terminal_cell_metrics(cx);
|
||||
self.invalidate_paint_theme_caches();
|
||||
self.sync_ai_drafts_from_active_profile();
|
||||
self.translate_target_language = self.translation_settings.target_language.clone();
|
||||
self.translation.target_language = self.translation.settings.target_language.clone();
|
||||
self.refresh_visible_terminal_surfaces(cx);
|
||||
}
|
||||
self.finish_settings_page(cx);
|
||||
|
||||
@@ -499,8 +499,8 @@ impl NyaTermApp {
|
||||
&& !self.remote_ops.stats.pending
|
||||
&& !self.remote_ops.process.pending
|
||||
&& !self.remote_ops.docker.pending
|
||||
&& !self.translate_pending
|
||||
&& !self.update_pending
|
||||
&& !self.translation.pending
|
||||
&& !self.update.pending
|
||||
&& !self.ai.chat.focus_pending
|
||||
&& !self.transfer.file_ops.rename_focus_pending
|
||||
&& !self.credential_prompt_focus_pending
|
||||
|
||||
@@ -16,7 +16,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
if event.keystroke.key.as_str() == "escape" {
|
||||
if self.update_dialog_open {
|
||||
if self.update.dialog_open {
|
||||
self.close_update_dialog(cx);
|
||||
return true;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ impl NyaTermApp {
|
||||
self.close_about(cx);
|
||||
return true;
|
||||
}
|
||||
if self.translation_dialog.is_some() {
|
||||
if self.translation.dialog.is_some() {
|
||||
self.close_translation_dialog(cx);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ impl NyaTermApp {
|
||||
Vec::new()
|
||||
};
|
||||
let translation_providers: Vec<(String, String)> =
|
||||
available_translation_providers(&self.translation_settings)
|
||||
available_translation_providers(&self.translation.settings)
|
||||
.into_iter()
|
||||
.map(|(id, _)| {
|
||||
let label = self
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ impl NyaTermApp {
|
||||
if self.action_link_menu.is_some()
|
||||
|| self.terminal.menus.context_menu.is_some()
|
||||
|| self.terminal.selection.dragging
|
||||
|| self.translation_dialog.is_some()
|
||||
|| self.translation.dialog.is_some()
|
||||
{
|
||||
self.clear_action_link_tooltip(cx);
|
||||
return;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//! Translation dialog runtime.
|
||||
|
||||
mod state;
|
||||
mod translation_runtime;
|
||||
|
||||
pub(in crate::features) use state::TranslationFeatureState;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
//! Authoritative transient state for translation settings and jobs.
|
||||
|
||||
use std::sync::mpsc;
|
||||
|
||||
use nyaterm_core::{TranslateResult, TranslationSettings};
|
||||
|
||||
use crate::models::{TranslateInputField, TranslationDialogState, TranslationSecretDraft};
|
||||
|
||||
pub(super) struct TranslateJobResult {
|
||||
pub result: Result<TranslateResult, String>,
|
||||
}
|
||||
|
||||
pub(in crate::features) struct TranslationFeatureState {
|
||||
pub dialog: Option<TranslationDialogState>,
|
||||
pub(super) tx: mpsc::Sender<TranslateJobResult>,
|
||||
pub(super) rx: mpsc::Receiver<TranslateJobResult>,
|
||||
pub provider: String,
|
||||
pub settings: TranslationSettings,
|
||||
pub secret_draft: TranslationSecretDraft,
|
||||
pub target_language: String,
|
||||
pub input: String,
|
||||
pub result: Option<TranslateResult>,
|
||||
pub status: String,
|
||||
pub pending: bool,
|
||||
pub focused_field: TranslateInputField,
|
||||
}
|
||||
|
||||
impl TranslationFeatureState {
|
||||
pub(in crate::features) fn new(settings: TranslationSettings) -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let target_language = settings.target_language.clone();
|
||||
Self {
|
||||
dialog: None,
|
||||
tx,
|
||||
rx,
|
||||
provider: "google".to_string(),
|
||||
settings,
|
||||
secret_draft: TranslationSecretDraft::default(),
|
||||
target_language,
|
||||
input: String::new(),
|
||||
result: None,
|
||||
status: "Google translation ready".to_string(),
|
||||
pending: false,
|
||||
focused_field: TranslateInputField::Text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nyaterm_core::TranslationSettings;
|
||||
|
||||
use super::TranslationFeatureState;
|
||||
|
||||
#[test]
|
||||
fn translation_state_owns_job_channel_and_loaded_settings() {
|
||||
let settings = TranslationSettings {
|
||||
target_language: "ja".to_string(),
|
||||
..TranslationSettings::default()
|
||||
};
|
||||
|
||||
let state = TranslationFeatureState::new(settings.clone());
|
||||
|
||||
assert_eq!(state.settings, settings);
|
||||
assert_eq!(state.target_language, "ja");
|
||||
assert!(state.rx.try_recv().is_err());
|
||||
assert!(!state.pending);
|
||||
assert!(state.dialog.is_none());
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,12 @@ use gpui::{
|
||||
use nyaterm_core::ConnectionStore;
|
||||
|
||||
use crate::features::NyaTermApp;
|
||||
use crate::features::runtime_jobs::TranslateJobResult;
|
||||
use crate::http::translation::translate_text;
|
||||
use crate::models::{TranslateInputField, TranslationDialogState, TranslationSecretDraft};
|
||||
use crate::widgets::small_button;
|
||||
|
||||
use super::state::TranslateJobResult;
|
||||
|
||||
const TRANSLATE_EVENT_DRAIN_LIMIT: usize = 8;
|
||||
|
||||
impl NyaTermApp {
|
||||
@@ -18,24 +19,24 @@ impl NyaTermApp {
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.translate_pending {
|
||||
self.translate_status = "translation already running".to_string();
|
||||
if self.translation.pending {
|
||||
self.translation.status = "translation already running".to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if self.translate_input.trim().is_empty() {
|
||||
self.translate_status = "type text before translating".to_string();
|
||||
if self.translation.input.trim().is_empty() {
|
||||
self.translation.status = "type text before translating".to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
|
||||
self.translate_pending = true;
|
||||
self.translate_status = format!("translating with {}", self.translate_provider);
|
||||
let tx = self.translate_tx.clone();
|
||||
let provider = self.translate_provider.clone();
|
||||
let target_language = self.translate_target_language.clone();
|
||||
let text = self.translate_input.clone();
|
||||
let settings = self.translation_settings.clone();
|
||||
self.translation.pending = true;
|
||||
self.translation.status = format!("translating with {}", self.translation.provider);
|
||||
let tx = self.translation.tx.clone();
|
||||
let provider = self.translation.provider.clone();
|
||||
let target_language = self.translation.target_language.clone();
|
||||
let text = self.translation.input.clone();
|
||||
let settings = self.translation.settings.clone();
|
||||
std::thread::spawn(move || {
|
||||
let result = translate_text(&provider, &text, &target_language, &settings);
|
||||
let _ = tx.send(TranslateJobResult { result });
|
||||
@@ -46,10 +47,10 @@ impl NyaTermApp {
|
||||
pub(in crate::features) fn save_translation_settings(&mut self, cx: &mut Context<Self>) {
|
||||
let next = self.pending_translation_settings();
|
||||
if self.defer_settings_persistence(cx) {
|
||||
self.translation_settings = next;
|
||||
self.translation_secret_draft = TranslationSecretDraft::default();
|
||||
self.translate_target_language = self.translation_settings.target_language.clone();
|
||||
self.translate_status = "translation settings staged".to_string();
|
||||
self.translation.settings = next;
|
||||
self.translation.secret_draft = TranslationSecretDraft::default();
|
||||
self.translation.target_language = self.translation.settings.target_language.clone();
|
||||
self.translation.status = "translation settings staged".to_string();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,16 +61,17 @@ impl NyaTermApp {
|
||||
.and_then(|store| store.save_translation_settings(next))
|
||||
{
|
||||
Ok(saved) => {
|
||||
self.translation_settings = saved;
|
||||
self.translation_secret_draft = TranslationSecretDraft::default();
|
||||
self.translate_target_language = self.translation_settings.target_language.clone();
|
||||
self.translate_status = "translation settings saved".to_string();
|
||||
self.translation.settings = saved;
|
||||
self.translation.secret_draft = TranslationSecretDraft::default();
|
||||
self.translation.target_language =
|
||||
self.translation.settings.target_language.clone();
|
||||
self.translation.status = "translation settings saved".to_string();
|
||||
self.store_status.message = "translation settings saved".to_string();
|
||||
self.store_status.ready = true;
|
||||
}
|
||||
Err(error) => {
|
||||
self.translate_status = format!("translation settings save failed: {error}");
|
||||
self.store_status.message = self.translate_status.clone();
|
||||
self.translation.status = format!("translation settings save failed: {error}");
|
||||
self.store_status.message = self.translation.status.clone();
|
||||
self.store_status.ready = false;
|
||||
}
|
||||
}
|
||||
@@ -83,24 +85,24 @@ impl NyaTermApp {
|
||||
) {
|
||||
match provider {
|
||||
"deepl" => {
|
||||
self.translation_settings.deepl_api_key.clear();
|
||||
self.translation_secret_draft.deepl_api_key.clear();
|
||||
self.translation.settings.deepl_api_key.clear();
|
||||
self.translation.secret_draft.deepl_api_key.clear();
|
||||
}
|
||||
"baidu" => {
|
||||
self.translation_settings.baidu_app_key.clear();
|
||||
self.translation_secret_draft.baidu_app_key.clear();
|
||||
self.translation.settings.baidu_app_key.clear();
|
||||
self.translation.secret_draft.baidu_app_key.clear();
|
||||
}
|
||||
"ali" => {
|
||||
self.translation_settings.ali_app_key.clear();
|
||||
self.translation_secret_draft.ali_app_key.clear();
|
||||
self.translation.settings.ali_app_key.clear();
|
||||
self.translation.secret_draft.ali_app_key.clear();
|
||||
}
|
||||
"youdao" => {
|
||||
self.translation_settings.youdao_app_key.clear();
|
||||
self.translation_secret_draft.youdao_app_key.clear();
|
||||
self.translation.settings.youdao_app_key.clear();
|
||||
self.translation.secret_draft.youdao_app_key.clear();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.translate_status = format!("{provider} translation secret cleared; save to persist");
|
||||
self.translation.status = format!("{provider} translation secret cleared; save to persist");
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -111,9 +113,9 @@ impl NyaTermApp {
|
||||
text: String,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.translate_focused_field = field;
|
||||
self.translation.focused_field = field;
|
||||
*self.translate_input_value_mut() = text;
|
||||
self.translate_status = if field.is_settings_field() {
|
||||
self.translation.status = if field.is_settings_field() {
|
||||
"translation settings edited".to_string()
|
||||
} else {
|
||||
"translation input edited".to_string()
|
||||
@@ -122,16 +124,16 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
fn translate_input_value_mut(&mut self) -> &mut String {
|
||||
match self.translate_focused_field {
|
||||
TranslateInputField::TargetLanguage => &mut self.translate_target_language,
|
||||
TranslateInputField::Text => &mut self.translate_input,
|
||||
TranslateInputField::DeeplApiKey => &mut self.translation_secret_draft.deepl_api_key,
|
||||
TranslateInputField::BaiduAppId => &mut self.translation_settings.baidu_app_id,
|
||||
TranslateInputField::BaiduAppKey => &mut self.translation_secret_draft.baidu_app_key,
|
||||
TranslateInputField::AliAppId => &mut self.translation_settings.ali_app_id,
|
||||
TranslateInputField::AliAppKey => &mut self.translation_secret_draft.ali_app_key,
|
||||
TranslateInputField::YoudaoAppId => &mut self.translation_settings.youdao_app_id,
|
||||
TranslateInputField::YoudaoAppKey => &mut self.translation_secret_draft.youdao_app_key,
|
||||
match self.translation.focused_field {
|
||||
TranslateInputField::TargetLanguage => &mut self.translation.target_language,
|
||||
TranslateInputField::Text => &mut self.translation.input,
|
||||
TranslateInputField::DeeplApiKey => &mut self.translation.secret_draft.deepl_api_key,
|
||||
TranslateInputField::BaiduAppId => &mut self.translation.settings.baidu_app_id,
|
||||
TranslateInputField::BaiduAppKey => &mut self.translation.secret_draft.baidu_app_key,
|
||||
TranslateInputField::AliAppId => &mut self.translation.settings.ali_app_id,
|
||||
TranslateInputField::AliAppKey => &mut self.translation.secret_draft.ali_app_key,
|
||||
TranslateInputField::YoudaoAppId => &mut self.translation.settings.youdao_app_id,
|
||||
TranslateInputField::YoudaoAppKey => &mut self.translation.secret_draft.youdao_app_key,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,21 +147,21 @@ impl NyaTermApp {
|
||||
) {
|
||||
let text = text.trim().to_string();
|
||||
if text.is_empty() {
|
||||
self.translate_status = "no text to translate".to_string();
|
||||
self.translation.status = "no text to translate".to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
self.translation_dialog = Some(TranslationDialogState {
|
||||
self.translation.dialog = Some(TranslationDialogState {
|
||||
source_text: text.clone(),
|
||||
provider: provider.clone(),
|
||||
provider_label,
|
||||
});
|
||||
self.translate_provider = provider;
|
||||
self.translate_input = text;
|
||||
self.translate_result = None;
|
||||
self.translate_status = format!("translating with {}", self.translate_provider);
|
||||
self.translation.provider = provider;
|
||||
self.translation.input = text;
|
||||
self.translation.result = None;
|
||||
self.translation.status = format!("translating with {}", self.translation.provider);
|
||||
// Kick off immediately (Tauri TranslationDialog behavior).
|
||||
if !self.translate_pending {
|
||||
if !self.translation.pending {
|
||||
self.run_translation(window, cx);
|
||||
} else {
|
||||
cx.notify();
|
||||
@@ -167,7 +169,7 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn close_translation_dialog(&mut self, cx: &mut Context<Self>) {
|
||||
if self.translation_dialog.take().is_some() {
|
||||
if self.translation.dialog.take().is_some() {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -177,13 +179,13 @@ impl NyaTermApp {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let palette = self.theme_palette();
|
||||
let Some(dialog) = self.translation_dialog.clone() else {
|
||||
let Some(dialog) = self.translation.dialog.clone() else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
let provider_label = dialog.provider_label.clone();
|
||||
let source = dialog.source_text.clone();
|
||||
let pending = self.translate_pending;
|
||||
let status = self.translate_status.clone();
|
||||
let pending = self.translation.pending;
|
||||
let status = self.translation.status.clone();
|
||||
let title_label = self.tr("translation.title");
|
||||
let source_label = self.tr("translation.sourceText");
|
||||
let translated_label = self.tr("translation.translatedText");
|
||||
@@ -192,7 +194,7 @@ impl NyaTermApp {
|
||||
let copy_label = self.tr("translation.copy");
|
||||
let close_label = self.tr("translation.close");
|
||||
let copied_label = self.tr("translation.copied");
|
||||
let result = self.translate_result.clone();
|
||||
let result = self.translation.result.clone();
|
||||
let detected = result
|
||||
.as_ref()
|
||||
.map(|item| item.detected_language.clone())
|
||||
@@ -355,11 +357,11 @@ impl NyaTermApp {
|
||||
"translation-dialog-copy",
|
||||
copy_label,
|
||||
cx.listener(|this, _, _, cx| {
|
||||
if let Some(result) = this.translate_result.clone() {
|
||||
if let Some(result) = this.translation.result.clone() {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
result.translated,
|
||||
));
|
||||
this.translate_status = copied_label.to_string();
|
||||
this.translation.status = copied_label.to_string();
|
||||
cx.notify();
|
||||
}
|
||||
}),
|
||||
@@ -379,29 +381,29 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn drain_translate_events(&mut self) -> bool {
|
||||
if !self.translate_pending {
|
||||
if !self.translation.pending {
|
||||
return false;
|
||||
}
|
||||
let mut dirty = false;
|
||||
for _ in 0..TRANSLATE_EVENT_DRAIN_LIMIT {
|
||||
let Ok(event) = self.translate_rx.try_recv() else {
|
||||
let Ok(event) = self.translation.rx.try_recv() else {
|
||||
break;
|
||||
};
|
||||
dirty = true;
|
||||
self.translate_pending = false;
|
||||
self.translation.pending = false;
|
||||
match event.result {
|
||||
Ok(result) => {
|
||||
self.translate_status = format!(
|
||||
self.translation.status = format!(
|
||||
"translated {} character(s) from {}",
|
||||
result.original.chars().count(),
|
||||
result.detected_language
|
||||
);
|
||||
self.terminal.view.status = self.translate_status.clone();
|
||||
self.translate_result = Some(result);
|
||||
self.terminal.view.status = self.translation.status.clone();
|
||||
self.translation.result = Some(result);
|
||||
}
|
||||
Err(error) => {
|
||||
self.translate_status = format!("translation failed: {error}");
|
||||
self.terminal.view.status = self.translate_status.clone();
|
||||
self.translation.status = format!("translation failed: {error}");
|
||||
self.terminal.view.status = self.translation.status.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Native application update state and runtime.
|
||||
|
||||
mod state;
|
||||
mod update_runtime;
|
||||
|
||||
pub(in crate::features) use state::UpdateFeatureState;
|
||||
@@ -0,0 +1,48 @@
|
||||
//! Authoritative transient state for native update checks.
|
||||
|
||||
use std::sync::mpsc;
|
||||
|
||||
use nyaterm_core::NativeUpdateInfo;
|
||||
|
||||
pub(super) struct UpdateJobResult {
|
||||
pub result: Result<NativeUpdateInfo, String>,
|
||||
}
|
||||
|
||||
pub(in crate::features) struct UpdateFeatureState {
|
||||
pub(super) tx: mpsc::Sender<UpdateJobResult>,
|
||||
pub(super) rx: mpsc::Receiver<UpdateJobResult>,
|
||||
pub status: String,
|
||||
pub info: Option<NativeUpdateInfo>,
|
||||
pub pending: bool,
|
||||
pub dialog_open: bool,
|
||||
}
|
||||
|
||||
impl UpdateFeatureState {
|
||||
pub(in crate::features) fn new() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
Self {
|
||||
tx,
|
||||
rx,
|
||||
status: format!("Current version {}", env!("CARGO_PKG_VERSION")),
|
||||
info: None,
|
||||
pending: false,
|
||||
dialog_open: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::UpdateFeatureState;
|
||||
|
||||
#[test]
|
||||
fn update_state_owns_job_channel_and_initial_status() {
|
||||
let state = UpdateFeatureState::new();
|
||||
|
||||
assert!(state.status.contains(env!("CARGO_PKG_VERSION")));
|
||||
assert!(state.rx.try_recv().is_err());
|
||||
assert!(state.info.is_none());
|
||||
assert!(!state.pending);
|
||||
assert!(!state.dialog_open);
|
||||
}
|
||||
}
|
||||
+20
-18
@@ -1,31 +1,33 @@
|
||||
use gpui::Context;
|
||||
|
||||
use crate::features::{NyaTermApp, UpdateJobResult};
|
||||
use crate::features::NyaTermApp;
|
||||
use crate::http::update::check_native_update;
|
||||
|
||||
use super::state::UpdateJobResult;
|
||||
|
||||
const UPDATE_EVENT_DRAIN_LIMIT: usize = 4;
|
||||
|
||||
impl NyaTermApp {
|
||||
pub(in crate::features) fn open_update_dialog(&mut self, cx: &mut Context<Self>) {
|
||||
self.update_dialog_open = true;
|
||||
self.update.dialog_open = true;
|
||||
self.start_update_check(cx);
|
||||
}
|
||||
|
||||
pub(in crate::features) fn close_update_dialog(&mut self, cx: &mut Context<Self>) {
|
||||
self.update_dialog_open = false;
|
||||
self.update.dialog_open = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn start_update_check(&mut self, cx: &mut Context<Self>) {
|
||||
if self.update_pending {
|
||||
self.update_status = "update check already running".to_string();
|
||||
if self.update.pending {
|
||||
self.update.status = "update check already running".to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
self.update_pending = true;
|
||||
self.update_status = "checking GitHub releases...".to_string();
|
||||
self.update_info = None;
|
||||
let tx = self.update_tx.clone();
|
||||
self.update.pending = true;
|
||||
self.update.status = "checking GitHub releases...".to_string();
|
||||
self.update.info = None;
|
||||
let tx = self.update.tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let result = check_native_update();
|
||||
let _ = tx.send(UpdateJobResult { result });
|
||||
@@ -34,19 +36,19 @@ impl NyaTermApp {
|
||||
}
|
||||
|
||||
pub(in crate::features) fn drain_update_events(&mut self) -> bool {
|
||||
if !self.update_pending {
|
||||
if !self.update.pending {
|
||||
return false;
|
||||
}
|
||||
let mut dirty = false;
|
||||
for _ in 0..UPDATE_EVENT_DRAIN_LIMIT {
|
||||
let Ok(event) = self.update_rx.try_recv() else {
|
||||
let Ok(event) = self.update.rx.try_recv() else {
|
||||
break;
|
||||
};
|
||||
dirty = true;
|
||||
self.update_pending = false;
|
||||
self.update.pending = false;
|
||||
match event.result {
|
||||
Ok(info) => {
|
||||
self.update_status = if info.available {
|
||||
self.update.status = if info.available {
|
||||
format!(
|
||||
"update available: {} -> {}",
|
||||
info.current_version, info.latest_version
|
||||
@@ -54,13 +56,13 @@ impl NyaTermApp {
|
||||
} else {
|
||||
format!("NyaTerm is up to date ({})", info.current_version)
|
||||
};
|
||||
self.terminal.view.status = self.update_status.clone();
|
||||
self.update_info = Some(info);
|
||||
self.terminal.view.status = self.update.status.clone();
|
||||
self.update.info = Some(info);
|
||||
}
|
||||
Err(error) => {
|
||||
self.update_status = format!("update check failed: {error}");
|
||||
self.terminal.view.status = self.update_status.clone();
|
||||
self.update_info = None;
|
||||
self.update.status = format!("update check failed: {error}");
|
||||
self.terminal.view.status = self.update.status.clone();
|
||||
self.update.info = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ Last updated from the working tree on 2026-07-28.
|
||||
|
||||
| Metric | Current value | Notes |
|
||||
| --- | ---: | --- |
|
||||
| `NyaTermApp` fields | 229 | Counted from `features/app_state/mod.rs`; down from 585, still transitional. |
|
||||
| `NyaTermApp` fields | 213 | Counted from `features/app_state/mod.rs`; down from 585, still transitional. |
|
||||
| `impl NyaTermApp` blocks | 238 | Spread across 233 files under `crates/nyaterm-desktop/src`. |
|
||||
| `#[path = "..."]` declarations in desktop | 0 | Cleared. Every directory is a real module; the boundary script fails on any new occurrence. |
|
||||
| `use super::*` imports in desktop | 0 | Cleared in production and test modules; guarded crate-wide. |
|
||||
@@ -202,6 +202,15 @@ these as staged extraction candidates, not as formatting-only refactor targets.
|
||||
one composition-root field. Persisted `AppSettingsSummary` and
|
||||
`KeywordHighlightConfig` remain on `NyaTermApp`; their save/load formats and
|
||||
storage paths are unchanged.
|
||||
- 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
|
||||
channel together with pending/status/result and dialog state. Translation
|
||||
settings and the secret draft moved as one compatibility-sensitive unit,
|
||||
while their existing load/save, masking and fallback paths are unchanged.
|
||||
Native update runtime also moved out of the settings module into its own
|
||||
normal module tree, and both feature-specific job event types left the shared
|
||||
`runtime_jobs.rs` bucket.
|
||||
- Transfer state is grouped into `TransferFeatureState`. Seventy-eight fields
|
||||
turned out to be five separate things sharing one panel: the job `queue`, the
|
||||
SFTP `browser`, the file operation dialogs (`file_ops`), the built-in remote
|
||||
@@ -1345,14 +1354,15 @@ honest remaining list.
|
||||
compiler-confirmed final pass also removed `features/prelude.rs`, so modules
|
||||
cannot regain the same implicit dependency surface through a shared import
|
||||
bucket.
|
||||
3. Largely done. `NyaTermApp` is down from 585 fields to 229, across nine
|
||||
3. Largely done. `NyaTermApp` is down from 585 fields to 213, across eleven
|
||||
feature-state structs. The latest cohesive cuts moved sixteen terminal
|
||||
command-assistance and credential-prompt fields into
|
||||
`TerminalFeatureState::assist`, then seventeen transient settings fields
|
||||
into four `SettingsFeatureState` children. What is left is a long tail, and
|
||||
much of it is genuinely app-level (stores, runtime, services, persisted
|
||||
collections). Group by cohesion where a cluster exists; do not force the
|
||||
count down for its own sake.
|
||||
into four `SettingsFeatureState` children, then moved translation and native
|
||||
update channels, job state and dialogs into two authoritative feature-state
|
||||
owners. What is left is a long tail, and much of it is genuinely app-level
|
||||
(stores, runtime, services, persisted collections). Group by cohesion where
|
||||
a cluster exists; do not force the count down for its own sake.
|
||||
Method ownership is now moving too, which is what grouping the fields alone
|
||||
did not buy. The rule: if a method only reads and writes one feature state,
|
||||
it belongs on that state, and the `NyaTermApp` method becomes a forwarder
|
||||
|
||||
@@ -118,6 +118,21 @@ check_no_matches \
|
||||
'^[[:space:]]*pub\(in crate::features\)[[:space:]]+(search_engine_edit_index|search_engine_expanded_index|search_engine_icon_picker_index|search_engine_actions_index|search_engine_edit_field|search_engine_focus|keyword_highlight_expanded_id|keyword_highlight_edit_id|keyword_highlight_edit_field|keyword_highlight_focus|appearance_menu_open|appearance_ui_font_options|appearance_terminal_font_options|keybinding_recording_id|keybinding_pending_keys|keybinding_search_draft|keybindings_focus)[[:space:]]*:' \
|
||||
crates/nyaterm-desktop/src/features/app_state/mod.rs
|
||||
|
||||
check_no_matches \
|
||||
"translation state must stay grouped under TranslationFeatureState" \
|
||||
'^[[:space:]]*pub\(in crate::features\)[[:space:]]+(translation_dialog|translate_tx|translate_rx|translate_provider|translation_settings|translation_secret_draft|translate_target_language|translate_input|translate_result|translate_status|translate_pending|translate_focused_field)[[:space:]]*:' \
|
||||
crates/nyaterm-desktop/src/features/app_state/mod.rs
|
||||
|
||||
check_no_matches \
|
||||
"update state must stay grouped under UpdateFeatureState" \
|
||||
'^[[:space:]]*pub\(in crate::features\)[[:space:]]+(update_tx|update_rx|update_status|update_info|update_pending|update_dialog_open)[[:space:]]*:' \
|
||||
crates/nyaterm-desktop/src/features/app_state/mod.rs
|
||||
|
||||
check_no_matches \
|
||||
"translation and update job events must stay in their owning features" \
|
||||
'struct[[:space:]]+(TranslateJobResult|UpdateJobResult)' \
|
||||
crates/nyaterm-desktop/src/features/runtime_jobs.rs
|
||||
|
||||
# These low-frequency transport helpers have explicit imports at their call
|
||||
# sites. Keep them out of the shared feature prelude so new modules do not
|
||||
# acquire unrelated transport dependencies implicitly.
|
||||
|
||||
Reference in New Issue
Block a user