Skip to main content

App

Struct App 

Source
pub struct App {
Show 16 fields pub screen: Screen, pub top_page: TopPage, pub running: bool, pub status_center: StatusCenter, pub search: SearchState, pub reload: ReloadState, pub conflict: ConflictState, pub tags: TagState, pub history: ConnectionHistory, pub vault: VaultState, pub update: UpdateState, pub bw_session: Option<String>, pub file_browser_state: FileBrowserState, pub file_browser_session: Option<FileBrowserSession>, pub demo_mode: bool, pub jump: Option<JumpState>, /* private fields */
}
Expand description

Main application state.

Fields§

§screen: Screen

Currently rendered screen identifier; navigation only, never carries state heaps.

§top_page: TopPage

Top-level page (Hosts, Tunnels, Containers). Selected by Tab/Shift+Tab in the navigation bar. Independent of screen, which tracks overlays.

§running: bool

App lifecycle flag; flip to false to exit the event loop.

§status_center: StatusCenter

Toast queue, sticky messages, status routing.

§search: SearchState

Host-list incremental search query and matched hits.

§reload: ReloadState

Reload-from-disk state when ~/.ssh/config changes externally.

§conflict: ConflictState

Conflict detection when an external edit clashes with our pending write.

§tags: TagState

Tag library and per-host tag mappings.

§history: ConnectionHistory

Connection history persisted to ~/.purple/history.

§vault: VaultState

Vault SSH certificate cache and signing run state.

§update: UpdateState

Self-update polling and badge state.

§bw_session: Option<String>

askpass session token; not Keys-tab state.

§file_browser_state: FileBrowserState

Persistent per-host last-visited paths; always present.

§file_browser_session: Option<FileBrowserSession>

Per-host overlay session; Some when the file browser is open.

§demo_mode: bool

Demo mode: all mutations are in-memory only, no disk writes.

§jump: Option<JumpState>

Jump state. Some when the jump bar is open.

Implementations§

Source§

impl App

Source

pub fn clear_form_mtime(&mut self)

Clear form mtime state (call on form cancel or successful submit).

Source

pub fn capture_form_mtime(&mut self)

Capture config and Include file mtimes when opening a host form.

Source

pub fn capture_provider_form_mtime(&mut self)

Capture ~/.purple/providers mtime when opening a provider form.

Source

pub fn capture_form_baseline(&mut self)

Capture a baseline snapshot of the host form for dirty-check on Esc.

Source

pub fn host_form_is_dirty(&self) -> bool

Check if the host form has been modified since baseline was captured.

Source

pub fn close_host_form(&mut self)

Tear down host form state and return to the host list. Flush runs last because flush_pending_vault_write no-ops while a form is open.

Source

pub fn close_host_form_after_save(&mut self, target_alias: &str)

Close the host form and select the just-saved host. Use after a successful submit.

Source

pub fn close_provider_form(&mut self)

Tear down provider form state and return to the providers list. Same shape as close_host_form; provider forms have no per-save selection.

Source

pub fn close_tunnel_form(&mut self, return_to: Screen)

Tear down tunnel form state and return to the caller’s screen. The return target varies (host detail overlay, tunnels overview, picker), so the caller passes it.

Source

pub fn close_snippet_form(&mut self, target_aliases: Vec<String>)

Tear down snippet form state and return to the snippet picker for the given targets. Snippet forms intentionally skip clear_form_mtime; no mtime is captured on snippet form open.

Source

pub fn open_host_add_form(&mut self)

Open a blank host add form. Mirror is close_host_form.

Source

pub fn open_host_pattern_add_form(&mut self)

Open a blank pattern add form. Shares Screen::AddHost; the form constructor distinguishes pattern vs host entries internally.

Source

pub fn open_host_edit_form( &mut self, host: HostEntry, stale_hint: Option<String>, ) -> bool

Open the host edit form for host. Returns false (without changing screen) if the host lives in an Include file or its raw entry cannot be located. The caller computes stale_hint because it is derived from handler-local provider-display logic.

Source

pub fn open_host_pattern_edit_form(&mut self, pattern: &PatternEntry)

Open an edit form for an existing pattern entry.

Source

pub fn open_tunnel_add_form(&mut self, alias: String)

Open a blank tunnel add form scoped to alias. The alias is set on the screen variant so submit/cancel return to the right host context.

Source

pub fn open_tunnel_edit_form( &mut self, alias: String, rule: &TunnelRule, editing: usize, )

Open an edit form for an existing tunnel rule. editing is the index into tunnels.list that the save path mutates.

Source

pub fn open_snippet_add_form(&mut self, target_aliases: Vec<String>)

Open a blank snippet add form scoped to the given target aliases. No mtime capture (snippet forms have no mtime tracking).

Source

pub fn open_snippet_edit_form( &mut self, snippet: &Snippet, target_aliases: Vec<String>, editing: usize, )

Open an edit form for an existing snippet. editing is the index into the snippet store that the save path mutates.

Source

pub fn open_provider_form(&mut self, id: ProviderConfigId)

Open a provider form for id, populating defaults for new configs or existing data for edits. When id.label is Some("") the form opens in label-entry mode so the user types the label first.

Source

pub fn capture_tunnel_form_baseline(&mut self)

Capture a baseline snapshot of the tunnel form for dirty-check on Esc.

Source

pub fn tunnel_form_is_dirty(&self) -> bool

Check if the tunnel form has been modified since baseline was captured.

Source

pub fn capture_snippet_form_baseline(&mut self)

Capture a baseline snapshot of the snippet form for dirty-check on Esc.

Source

pub fn snippet_form_is_dirty(&self) -> bool

Check if the snippet form has been modified since baseline was captured.

Source

pub fn capture_provider_form_baseline(&mut self)

Capture a baseline snapshot of the provider form for dirty-check on Esc.

Source

pub fn provider_form_is_dirty(&self) -> bool

Check if the provider form has been modified since baseline was captured.

Source

pub fn config_changed_since_form_open(&self) -> bool

Check if config or any Include file/directory has changed since the form was opened.

Source

pub fn provider_config_changed_since_form_open(&self) -> bool

Check if ~/.purple/providers has changed since the provider form was opened.

Source§

impl App

Source

pub fn apply_sort(&mut self)

Rebuild the display list based on the current sort mode and group_by toggle.

Source

pub fn select_first_host(&mut self)

Select the first selectable item in the display list (always skips headers).

Source§

impl App

Source

pub fn clear_group_filter(&mut self)

Clear group filter (Esc from filtered mode).

Source§

impl App

Source

pub fn add_host_from_form(&mut self) -> Result<String, String>

Source

pub fn edit_host_from_form(&mut self, old_alias: &str) -> Result<String, String>

Edit an existing host from the current form. Returns status message.

Source

pub fn select_host_by_alias(&mut self, alias: &str)

Select a host in the display list (or filtered list) by alias.

Source

pub fn apply_sync_result( &mut self, provider: &str, hosts: Vec<ProviderHost>, partial: bool, ) -> (String, bool, usize, usize, usize, usize)

Apply sync results from a background provider fetch. Returns (message, is_error, server_count, added, updated, stale). Caller must remove from syncing_providers.

provider is the full ProviderConfigId display string (do for bare, do:work for labeled). We look up by exact id so multi-config providers route to the correct section.

Source

pub fn clear_stale_group_tag(&mut self) -> bool

Clear group-by-tag if the tag no longer exists in any host. Returns true if the tag was cleared.

Source§

impl App

Source

pub fn close_password_picker(&mut self)

Close the password picker overlay.

Source

pub fn close_key_picker(&mut self)

Close the key picker overlay.

Source

pub fn close_proxyjump_picker(&mut self)

Close the ProxyJump picker overlay.

Source

pub fn close_vault_role_picker(&mut self)

Close the Vault SSH role picker overlay.

Source

pub fn close_region_picker(&mut self)

Close the provider region picker overlay.

Source

pub fn open_password_picker(&mut self)

Open the password picker overlay focused on the first source.

Source

pub fn open_key_picker(&mut self)

Open the key picker overlay. Rescans ~/.ssh first so the list reflects keys added since the form was opened, then selects the first key when at least one was discovered.

Source

pub fn open_proxyjump_picker(&mut self)

Open the ProxyJump picker overlay. The opening cursor lands on the first host row rather than the first list entry, so separator/header rows above the host list do not steal initial focus.

Source

pub fn open_vault_role_picker(&mut self)

Open the Vault SSH role picker. The caller is responsible for guarding against an empty candidate list; this method assumes at least one role and selects the first.

Source

pub fn open_region_picker(&mut self)

Open the provider region picker overlay with the cursor on the first row. Region picker uses a cursor: usize rather than a ratatui ListState because its rows are a synthetic flat array.

Source§

impl App

Enter search mode.

Source

pub fn start_search_with(&mut self, query: &str)

Start search with an initial query (for positional arg).

Cancel search mode and restore normal view.

Source

pub fn apply_filter(&mut self)

Apply the current search query to filter hosts.

Source

pub fn filtered_snippet_indices(&self) -> Vec<usize>

Return indices of snippets matching the search query.

Source§

impl App

Source

pub fn set_screen(&mut self, screen: Screen)

Transition to a new screen. Logs the transition at debug level for support-bundle traceability. Callers should prefer this over direct app.screen = ... assignment.

Source

pub fn cycle_top_page_next(&mut self)

Cycle to the next top page. Logs the transition so Tab-cycle confusion (“I keep landing on the wrong page after Tab”) leaves a breadcrumb in ~/.purple/purple.log. Callers should prefer this over direct app.top_page = app.top_page.next() assignment.

Source

pub fn cycle_top_page_prev(&mut self)

Cycle to the previous top page. See cycle_top_page_next.

Source

pub fn selected_host_index(&self) -> Option<usize>

Get the host index from the currently selected display list item.

Source

pub fn selected_host(&self) -> Option<&HostEntry>

Get the currently selected host entry.

Source

pub fn selected_pattern(&self) -> Option<&PatternEntry>

Get the currently selected pattern entry (if a pattern is selected).

Source

pub fn is_pattern_selected(&self) -> bool

Check if the currently selected item is a pattern.

Source

pub fn select_prev(&mut self)

Move selection up, skipping group headers.

Source

pub fn select_next(&mut self)

Move selection down, skipping group headers.

Source

pub fn page_down_host(&mut self)

Page down in the host list, skipping group headers when ungrouped.

Source

pub fn page_up_host(&mut self)

Page up in the host list, skipping group headers.

Source

pub fn scan_keys(&mut self)

Source

pub fn select_prev_key(&mut self)

Move key list selection up.

Source

pub fn select_next_key(&mut self)

Move key list selection down.

Source

pub fn select_prev_picker_key(&mut self)

Move key picker selection up.

Source

pub fn select_next_picker_key(&mut self)

Move key picker selection down.

Source

pub fn select_prev_password_source(&mut self)

Move password picker selection up.

Source

pub fn select_next_password_source(&mut self)

Move password picker selection down.

Source

pub fn proxyjump_candidates(&self) -> Vec<ProxyJumpCandidate>

Get hosts available as ProxyJump targets (excludes the host being edited), ranked so likely jump hosts appear on top. Ranking combines three signals: usage count as ProxyJump on other hosts, alias or hostname matching a jump-host keyword (jump, bastion, gateway, proxy, gw), and sharing the last two domain labels with the hostname of the host being edited. Items with a non-zero score are grouped in a “suggested” section above a visual Separator. The remaining items are listed alphabetically below. If no item scores, the full list is alphabetical with no separator.

Source

pub fn proxyjump_first_host_index(&self) -> Option<usize>

Find the first selectable (non-separator) index in the ProxyJump picker, or None if the list has no hosts.

Source

pub fn select_prev_proxyjump(&mut self)

Move proxyjump picker selection up, skipping separators.

Source

pub fn select_next_proxyjump(&mut self)

Move proxyjump picker selection down, skipping separators.

Source

pub fn vault_role_candidates(&self) -> Vec<String>

Collect unique Vault SSH roles from all hosts and providers, sorted.

Source

pub fn select_prev_vault_role(&mut self)

Move vault role picker selection up.

Source

pub fn select_next_vault_role(&mut self)

Move vault role picker selection down.

Source

pub fn collect_unique_tags(&self) -> Vec<String>

Collect all unique tags from hosts, sorted alphabetically.

Source

pub fn open_bulk_tag_editor(&mut self) -> bool

Open the bulk tag editor for every host currently in multi_select. Returns false (and leaves the screen untouched) when the selection is empty or contains only pattern entries — callers can then fall back to single-host tag editing or show a status message.

Hosts that live in an Include file are still listed in aliases but get surfaced via skipped_included. bulk_tag_apply honours that split so included hosts are never mutated in place.

Source

pub fn bulk_tag_editor_next(&mut self)

Move bulk tag editor selection down.

Source

pub fn bulk_tag_editor_prev(&mut self)

Move bulk tag editor selection up.

Source

pub fn bulk_tag_editor_cycle_current(&mut self)

Cycle the action on the currently selected row: LeaveAddToAllRemoveFromAllLeave.

Source

pub fn bulk_tag_editor_commit_new_tag(&mut self)

Append a freshly typed tag to the row list. The new row is marked AddToAll so the user’s intent (“add this new tag to all selected hosts”) is preserved without a second keystroke. No-op for empty input or duplicate tag names.

Source

pub fn bulk_tag_apply(&mut self) -> Result<BulkTagApplyResult, String>

Apply all pending actions from the bulk tag editor. Leaves the config untouched (and returns an error) if the write fails so the user can retry without losing state. On success, hosts are reloaded (which clears multi_select).

Source

pub fn open_tag_picker(&mut self)

Open the tag picker overlay.

Source

pub fn select_prev_tag(&mut self)

Move tag picker selection up.

Source

pub fn select_next_tag(&mut self)

Move tag picker selection down.

Source

pub fn refresh_tunnel_list(&mut self, alias: &str)

Load tunnel directives for a host alias. Uses find_tunnel_directives for Include-aware, multi-pattern host lookup.

Source

pub fn select_prev_tunnel(&mut self)

Move tunnel list selection up.

Source

pub fn select_next_tunnel(&mut self)

Move tunnel list selection down.

Source

pub fn select_prev_snippet(&mut self)

Move snippet picker selection up.

Source

pub fn select_next_snippet(&mut self)

Move snippet picker selection down.

Source

pub fn select_next_skipping_headers(&mut self)

Poll active tunnels for exit status. Returns messages for any that exited. Move selection to the next non-header item.

Source

pub fn select_prev_skipping_headers(&mut self)

Move selection to the previous non-header item.

Source§

impl App

Source

pub fn new(config: SshConfigFile) -> Self

Source

pub fn record_key_use(&mut self, alias: &str, now: u64)

Record an SSH session against alias in the activity log. Appends in memory and flushes to ~/.purple/key_activity.json. Failures during flush are logged at debug level only; an activity-log write failure must never interrupt the user’s connect flow. Caller passes now; production call sites pass key_activity::now_secs().

Source

pub fn snapshot_alias_set(&self) -> HashSet<String>

Snapshot the alias of every host currently loaded. Used as the “before” set for queue_new_aliases_since after a reload that may have added or removed hosts.

Source

pub fn queue_new_aliases_since(&mut self, before_aliases: &HashSet<String>)

Push aliases that are in the current host list but were NOT in before_aliases to the auto-fetch queue. Sync handlers and external-config-edit detection use this so only freshly introduced hosts trigger an initial docker ps. pre-existing cache-missing hosts are explicitly left alone.

Source

pub fn reload_hosts(&mut self)

Reload hosts from config.

Source

pub fn refresh_cert_cache(&mut self, alias: &str)

Synchronously re-check a host’s Vault SSH certificate and update vault.cert_cache with fresh status + on-disk mtime.

Every sign path (V-key bulk sign, host form submit, connect-time ensure_vault_ssh_if_needed, CLI) funnels through this helper so the detail panel never lies about cert state after a successful sign.

No-op in demo mode. If the host is missing, has no resolvable vault role, or the cert path cannot be resolved, any stale entry for the alias is removed to avoid showing ghost status.

Source

pub fn is_form_open(&self) -> bool

Check whether a form screen is currently open (host or provider forms).

Source

pub fn open_jump(&mut self, mode: JumpMode)

Open the unified jump in the given mode. Loads recents from disk and seeds the empty-query view. Recomputes hits.

Source

pub fn recompute_jump_hits(&mut self)

Recompute the jump bar hit list against the current query. Pulls candidates from every live source and ranks them with nucleo-matcher. Preserves the previously-selected hit’s identity across the recompute so mid-typing arrow-key navigation does not jump back to row 0.

Source

pub fn record_jump_hit(&mut self, hit: &JumpHit)

Persist a jump dispatch to the on-disk MRU log. Best-effort; a write error logs and is otherwise swallowed so user navigation is never blocked by a recents-file failure. Takes &mut self so the type system reflects that this performs I/O and mutates persistent state, even though jump::save_recents only needs &File.

Source

pub fn flush_pending_vault_write(&mut self) -> bool

Flush a deferred vault config write if one is pending and no form is open. Returns true if a write was performed.

Source

pub fn post_init(&mut self)

Run once after App::new: queue the upgrade toast if the user just upgraded past their last-seen version, otherwise seed the preference so the next launch is silent.

Source

pub fn notify(&mut self, text: impl Into<String>)

User action feedback. Success toast, length-proportional timeout.

Source

pub fn notify_error(&mut self, text: impl Into<String>)

User action error. Error toast, sticky by default, queued.

Source

pub fn notify_background(&mut self, text: impl Into<String>)

Background event. Info footer, suppressed if sticky active.

Source

pub fn notify_background_error(&mut self, text: impl Into<String>)

Background error. Sticky toast, bypasses sticky suppression.

Source

pub fn notify_warning(&mut self, text: impl Into<String>)

Caution / degraded state → Warning toast (length-proportional timeout, queued). For: precondition violations (“Nothing to undo.”), validation hints (“Project ID can’t be empty.”), empty-state notices (“No stale hosts.”), stale-host warnings, deprecated config detected, partial sync results. Warnings are NOT sticky; the user acknowledges them by continuing to interact.

Use notify_error only for system-level failures (I/O, network, subprocess) that require explicit acknowledgement. Use notify_warning for everything that is “this can’t happen given current state” or “you forgot something”.

Source

pub fn notify_progress(&mut self, text: impl Into<String>)

Long-running progress. Footer sticky, never expires automatically.

Source

pub fn notify_sticky_error(&mut self, text: impl Into<String>)

Sticky error. Footer sticky, never expires automatically.

Source

pub fn notify_info(&mut self, text: impl Into<String>)

Explicit info. Footer, 4s timeout, not suppressed by sticky.

Source

pub fn tick_status(&mut self)

Tick the footer status message timer. Uses wall-clock time. Sticky/Progress messages never expire automatically.

Stays on App (not moved to StatusCenter) because expiry is suppressed while any provider sync is in flight, which requires reading self.providers.syncing.

Source

pub fn tick_toast(&mut self)

Shim. Routes to StatusCenter::tick_toast.

Source

pub fn check_config_changed(&mut self)

Check if config or any Include file has changed externally and reload if so. Skips reload when the user is in a form (AddHost/EditHost) to avoid overwriting in-memory config while the user is editing.

Source

pub fn check_keys_changed(&mut self)

Detect external changes to ~/.ssh/ keys and refresh self.keys.list when something has moved. Mirrors check_config_changed for the keys tab so users see new key files (or deletions, or rotations) without pressing R. Cheap: a single dir stat plus one stat per tracked key. Called from the 4-second throttle in handle_tick.

Skips during demo mode (the demo seeds a fixed key list and never reads from disk) and when a form is open that could be mutating the same data.

Source

pub fn external_config_changed(&self) -> bool

Non-mutating check: has the on-disk config (or any tracked Include) been modified since self.reload.last_modified was captured? Used by async write paths (e.g. the Vault SSH bulk-sign completion handler) to refuse writing when an external editor changed the file underneath us. overwriting those edits would silently discard user work. The backup-on-write mechanism in SshConfigFile::write() would still recover them, but detecting the conflict BEFORE writing is strictly better than after.

Source

pub fn update_last_modified(&mut self)

Update the last_modified timestamp (call after writing config).

Source

pub fn has_any_vault_role(&self) -> bool

Returns true if any host or provider has a vault role configured.

Source

pub fn poll_tunnels(&mut self) -> Vec<(String, String, bool)>

Poll active tunnels for exit. Returns (alias, message, is_error) tuples.

Source

pub fn refresh_tunnel_bind_ports(&mut self)

Recompute the lsof poller’s bind-port list from the current active map plus each host’s directives in the SSH config. Called after every tunnel start/stop. The poller picks up the new list on its next iteration.

Trait Implementations§

Source§

impl Drop for App

Kill active tunnel processes when App is dropped (e.g. on panic).

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

§

impl !Freeze for App

§

impl !RefUnwindSafe for App

§

impl Send for App

§

impl !Sync for App

§

impl Unpin for App

§

impl UnsafeUnpin for App

§

impl !UnwindSafe for App

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V