Skip to main content

App

Struct App 

Source
pub struct App {
Show 48 fields pub repo: Repository, pub repo_name: String, pub workdir: PathBuf, pub config: Config, pub workspace: Option<WorkspaceState>, pub workspace_active_stale: bool, pub worktrees: Vec<WorktreeInfo>, pub list_state: TableState, pub view: View, pub status: String, pub delete_branch_on_remove: bool, pub open_menu_selected: LinkTarget, pub create_form: CreateForm, pub create_failure: Option<String>, pub branch_types: Vec<BranchType>, pub report: Option<BootstrapReport>, pub help_scroll: u16, pub help_x_scroll: u16, pub help_max_scroll: u16, pub help_max_x_scroll: u16, pub sidebar: SidebarState, pub pending_g: bool, pub pending_chord: Vec<KeyStroke>, pub keymap: Keymap, pub modal_keymap: ModalKeymap, pub theme: Theme, pub filter: FilterState, pub picker_mode: bool, pub picker_result: Option<PathBuf>, pub picker_should_exit: bool, pub should_quit: bool, pub confirm: ConfirmModal, pub delete_failure: Option<String>, pub spinner: Spinner, pub github: GitHubFetch, pub palette: PaletteState, pub trust_mode: TrustMode, pub tasks: TaskRunner, pub last_auto_refresh_at: Instant, pub command_logs: CommandLogs, pub config_panel: ConfigPanel, pub pty_overlay: Option<PtyOverlay>, pub exec_picker: ExecPicker, pub clean_overlay: CleanOverlay, pub should_exit_to: Option<PathBuf>, pub edit_original_branch: Option<String>, pub edit_original_path: Option<PathBuf>, pub edit_failure: Option<String>, /* private fields */
}

Fields§

§repo: Repository§repo_name: String§workdir: PathBuf§config: Config§workspace: Option<WorkspaceState>

Workspace-mode state (issue #36); None in single-repo mode.

§workspace_active_stale: bool

Set when the selected row’s repo could not be activated in workspace mode (moved / deleted / corrupt since listing). While true, repo/workdir/ config still point at the previously active repo, so repo-mutating actions are blocked to avoid a wrong-target write (#304). Always false in single-repo mode and once a selection activates cleanly.

§worktrees: Vec<WorktreeInfo>§list_state: TableState§view: View§status: String§delete_branch_on_remove: bool§open_menu_selected: LinkTarget§create_form: CreateForm

Create-worktree overlay state (extracted per #123). Holds field focus, type index, and the issue/slug input buffers.

§create_failure: Option<String>

Last asynchronous create failure shown inside the Create modal.

§branch_types: Vec<BranchType>

Branch types displayed in the create-form picker. Resolved once at startup from Config::resolved_branch_types so the picker honours any [[branch_types]] override in .gwm.toml without re-reading the file on every key event.

§report: Option<BootstrapReport>§help_scroll: u16

Keybindings (help) overlay scroll offset, in rows. Reset to 0 every time the overlay opens; clamped to help_max_scroll (#217).

§help_x_scroll: u16

Keybindings (help) overlay horizontal scroll offset, in columns (#222).

§help_max_scroll: u16

Maximum help scroll offset, republished by [super::ui::draw_help] each frame as content_rows.saturating_sub(viewport_rows) so the offset can never scroll past the last line into the void.

§help_max_x_scroll: u16

Maximum horizontal help scroll offset, republished by the renderer.

§sidebar: SidebarState

Sidebar (git preview) panel state (extracted per #127). Owns the visibility / focus flags, the scroll offset + max bound, and the cached pre-rendered sections keyed by the selected worktree’s path. The cache prevents re-shelling git log / git status on every TUI redraw — they only run when the selection actually changes (via SidebarState::on_navigation) or on explicit refresh (SidebarState::invalidate). The renderer publishes sidebar.max_scroll every frame against the actual rendered Recent Commits height; SidebarState::scroll_down clamps against it.

§pending_g: bool§pending_chord: Vec<KeyStroke>

Generic pending-keys buffer for the configurable keymap (issue #87). Empty most of the time; populated with the strokes seen so far whenever the user is partway through a chord that is a prefix of a bound binding (e.g. after the first g of the default g g → Top).

§keymap: Keymap

Resolved keymap for this TUI session. Built from [Config::tui.keys] at construction time and never mutated thereafter — the user has to relaunch gwm to pick up a config change, mirroring how every other knob in [tui] behaves.

§modal_keymap: ModalKeymap

Resolved contextual keymap for modals / overlays (issue #219). Built from the [tui.keys.modal.<context>] sub-tables at construction time alongside Self::keymap; consulted by the modal routing in src/tui/mod.rs to turn a keystroke into a typed ModalAction.

§theme: Theme

Resolved colour theme for this TUI session (issue #33). Built from [theme] in .gwm.toml at construction time. Threaded through draw_* calls so user overrides reach every visual signal. Same hot-reload-on-relaunch contract as the keymap.

§filter: FilterState§picker_mode: bool§picker_result: Option<PathBuf>§picker_should_exit: bool

Event-loop exit signal for picker mode. Driven by picker_confirm (only when a worktree is actually selected) and picker_cancel (Esc from inside the filter bar, where a blanket break would clash with the regular TUI’s clear-filter behaviour). Keeps the loop running on Enter-with-no-match so the user can back-space and refine the filter instead of being kicked out with exit code 1.

§should_quit: bool

Event-loop exit signal for Action::Quit fired from a path that cannot itself break the loop (issue #32: the command palette routes accepted actions through run_action, which returns Result<()> and has no break channel). Set by run_action when it sees Action::Quit; checked at the top of every event-loop iteration alongside picker_should_exit.

§confirm: ConfirmModal

Safety countdown state for the confirm overlay (issue #30, extracted per #125). Holds the timer anchor and exposes the pure state-machine API; this App keeps the side-effecting wrappers below that compose the status messages and call worktree::remove.

§delete_failure: Option<String>

Last delete-worktree failure shown inside the confirm modal (issue #257). Kept on App, not ConfirmModal, because it is the outcome of the async worktree deletion side effect rather than countdown state.

§spinner: Spinner

Animated loader for overlays (issue #187). Advanced by the event loop’s 200ms poll tick while the confirm countdown is armed and read by the renderer; pure state lives in super::state::spinner::Spinner.

§github: GitHubFetch

GitHub fetch state slice — owns the cached link for the currently selected worktree’s branch, the repo slug parsed from origin, and the per-target gh issue view / gh pr view fetch state (extracted per #128, part 6/6 of the App god-struct decomposition #102). The orchestrator methods below (refresh_link, refresh_github_status, apply_issue_fetch_result, apply_pr_fetch_result) are thin wrappers that compose the status-bar copy + drive the actual gh shell-outs; the pure state machine lives on GitHubFetch.

§palette: PaletteState

Command palette overlay state (issue #32). Opened by Action::CommandPalette (default : binding). The pure state machine — buffer, fuzzy-matched candidates, highlight cursor — lives on PaletteState; this App owns the view transition and routes the accepted Action back through the normal dispatcher so palette and keymap fire identical side effects.

§trust_mode: TrustMode

TOFU trust mode for this TUI session (issue #95). Resolved at the CLI entrypoint from --allow-bootstrap / --deny-bootstrap / GWM_ALLOW_BOOTSTRAP=1 and threaded down via tui::run(mode). Used by check_trust_for_bootstrap to gate submit_create and bootstrap_selected — same security policy as the CLI, no bypass via the TUI. Default Prompt (preserves the safe default when callers construct App directly, e.g. tests that don’t care about the gate).

§tasks: TaskRunner

Generic off-thread task spine (issue #231; GitHub fetch folded in by #255): coalescing + per-key generation late-drop for slow one-shot ops — the worktree list refresh and the gh issue/pr view fetches. Public for the same reason github is — the state-machine tests claim a generation directly without spawning an OS thread.

§last_auto_refresh_at: Instant

Last point at which the periodic TUI worktree refresh was armed. Tests set this directly to simulate elapsed time without sleeping.

§command_logs: CommandLogs

Command Logs overlay state (issue #226): the scroll cursor plus an owned snapshot of the crate::command_log global, so the modal renders off App state rather than locking the global mid-frame.

§config_panel: ConfigPanel

Configuration panel overlay state (issue #232): the scroll cursor plus the resolved-row snapshot, filled by Self::enter_config_panel.

§pty_overlay: Option<PtyOverlay>

Live PTY overlay state (issue #35). Some while a lazygit or native terminal PTY session is open; None at all other times. Managed by Self::open_pty_overlay / Self::close_pty_overlay.

§exec_picker: ExecPicker

Exec profile picker overlay state (issue #325). Populated by Self::enter_exec_picker from [exec.profiles.*]; on Enter the run loop resolves the highlight to an argv and spawns a PTY overlay ([PtyKind::Exec]) in the selected worktree’s directory.

§clean_overlay: CleanOverlay

Clean overlay state (issue #325). Holds the gated reclaim scan of the selected worktree, the [clean.profiles.*] picker, and a dedicated safety countdown. Filled by Self::enter_clean_overlay; the run loop fires crate::clean::delete_reclaim when the countdown elapses.

§should_exit_to: Option<PathBuf>

Set by Action::ExitToWorktree (#290): the path the main loop should print to stdout just before quitting so the shell wrapper (cd "$(gwm)") can change directory. None → plain quit.

§edit_original_branch: Option<String>

The selected worktree’s branch name captured when the rename modal (View::Edit, #290) opens — the <old> in git branch -m <old> <new>. None while the modal is closed.

§edit_original_path: Option<PathBuf>

The selected worktree’s on-disk path captured when the rename modal opens — the source for git worktree move <old_path> <new_path>.

§edit_failure: Option<String>

Last rename failure, surfaced inside the Edit modal (mirrors Self::create_failure) so the user can correct and retry without losing the form. Cleared when the modal reopens.

Implementations§

Source§

impl App

Source

pub fn new() -> Result<Self>

Source

pub fn new_at(start: Option<&Path>) -> Result<Self>

Source

pub fn new_at_layered( start: Option<&Path>, global_path: Option<&Path>, ) -> Result<Self>

Injectable variant of Self::new_at (issue #194): global_path is the user-level global config layered under the repo’s .gwm.toml (None = repo-only, no environment read). Tests pass None so App construction never depends on the runner’s real ~/.config/gwm/config.toml. new_at delegates with the real global_config_path(), so runtime behaviour is unchanged.

Source

pub fn new_workspace_at_layered( root: &Path, global_path: Option<&Path>, ) -> Result<Self>

Workspace-mode constructor (issue #36): open the TUI over every git repo one level below root, merging their worktree listings into one repo-tagged table. Anchors the session on the first repo (alphabetical) for keymap/theme resolution and the event-loop channels, then swaps the merged list and per-row repo map in. Errors with GwmError::EmptyWorkspace when no repo sits directly under root.

Source

pub fn is_workspace(&self) -> bool

True when the TUI is in workspace mode (issue #36).

Source

pub fn row_repo_name(&self, raw_index: usize) -> Option<&str>

Display name of the repo owning raw worktree row raw_index (the index into Self::worktrees, not the filtered view). None in single-repo mode or for an out-of-range index. Drives the TUI REPO column.

Source

pub fn sync_active_repo(&mut self)

Align the active repo (repo/repo_name/workdir/config) with the selected worktree’s repo (issue #36). A no-op in single-repo mode and when the selection still belongs to the active repo, so the event loop can call it every frame cheaply. On the repo actually changing it re-opens the git2::Repository from the target workdir and invalidates the sidebar preview; an open failure keeps the current repo and reports on the status bar rather than panicking mid-render.

Source

pub fn with_trust_mode(self, mode: TrustMode) -> Self

Builder-style setter for trust_mode. The TUI entrypoint (tui::run) calls this after construction to thread through the CLI flags / env resolution; tests can use it directly to exercise each variant of the gate.

Source

pub fn check_trust_for_bootstrap(&self) -> Result<Option<String>>

Silent TOFU gate for the TUI’s bootstrap call sites (submit_create, bootstrap_selected). Returns:

  • Ok(None) — caller is cleared to invoke bootstrap::run.
  • Ok(Some(msg)) — caller MUST NOT run bootstrap; show msg to the user (e.g. assign to self.status). Untrusted configs and TrustMode::Deny both land here — the TUI alternate-screen can’t host a stdin prompt today, so we refuse with a hint pointing the user at the CLI gate (gwm bootstrap from another terminal).
  • Err(e) — ledger I/O / config read error propagated verbatim.
Source

pub fn new_picker_at(start: Option<&Path>) -> Result<Self>

Constructor for gwm switch: same App, but picker mode is on and the fuzzy filter bar is open from the first frame so the user can start narrowing right away. Everything else (worktree list, sidebar, vim motions) behaves identically; only the event-loop interpretation of Enter / n / d / b changes.

Source

pub fn new_picker_at_layered( start: Option<&Path>, global_path: Option<&Path>, ) -> Result<Self>

Injectable variant of Self::new_picker_at (issue #196): mirrors Self::new_at_layered so picker-mode tests never read the runner’s real ~/.config/gwm/config.toml. new_picker_at delegates with the real global_config_path().

Source

pub fn refresh(&mut self) -> Result<()>

Synchronous worktree list refresh. Kept for internal post-mutation callers (create / delete / report-close) that need the list fresh before the next render; the user-initiated f / r key path goes through the off-thread Self::request_refresh instead (issue #231). Both converge on Self::apply_refreshed_worktrees so the two paths can never drift on the post-list bookkeeping.

Source

pub fn request_refresh(&mut self)

Off-thread worktree list refresh for the f / r key (issue #231): spawn a worker that re-lists the worktrees and posts the result back to the event loop, so a large repo / slow filesystem no longer freezes the TUI. Coalesces onto an in-flight run (a second press while loading is a no-op) and seeds the loader label + spinner. The result is applied by Self::drain_task_results.

Source

pub fn maybe_auto_refresh(&mut self, now: Instant) -> bool

Periodic worktree-list refresh for the TUI event loop. Returns true only when a new async refresh task was actually started. 0 disables the feature, and an in-flight refresh coalesces so the renderer is never blocked by repeated relist attempts.

Source

pub fn request_sync(&mut self)

Off-thread gwm sync of the selected worktree for the S key (issue #258): fetch + rebase its branch onto upstream on a worker thread, so a slow network fetch / rebase does not freeze the event loop. Coalesces onto an in-flight sync (a second S while one runs is a no-op, so two rebases never race). The outcome is applied by Self::drain_task_results, which reports it and refreshes the list so the new ahead/behind state shows. Default strategy is rebase (the repo convention); a --merge variant is deferred (see #258).

Source

pub fn drain_task_results(&mut self) -> bool

Apply every background task result that has arrived since the last call (issue #231; GitHub fetch results folded in by #255), draining the channel without blocking. Each result goes through TaskRunner::complete, so a result whose per-key generation was bumped mid-flight is dropped (#138 guard, generalised) — this is what makes a stale GitHub worker lose to a fresh one in the retry race.

A failed refresh surfaces on the status bar and leaves the list intact — what used to be a fatal refresh()? that tore down the event loop is now a graceful message. A GitHub result is stamped into the per-key cache via complete_{issue,pr} (pure writes now that the drop decision lives on the spine); once nothing GitHub-side is left loading, the aggregate outcome is re-reported on the status bar — the same end state drain_github_results produced pre-#255. Returns true if at least one result was applied, so the loop can force a redraw.

Source

pub fn is_task_loading(&self) -> bool

true while any background task is in flight (issue #231) — drives the statusbar spinner alongside Self::is_github_loading.

Source

pub fn is_create_worktree_loading(&self) -> bool

true while the create-worktree worker is in flight (issue #276).

Source

pub fn is_delete_worktree_loading(&self) -> bool

true while the delete-worktree worker is in flight (issue #257).

Source

pub fn can_quit_now(&self) -> bool

true when a requested quit can safely leave the event loop now. Mutating spine workers keep running until their result is drained so sync / bootstrap / delete-worktree are not abandoned mid-operation.

Source

pub fn defer_quit_for_mutating_task(&mut self)

Surface why a requested quit is being held. The event loop keeps ticking/draining while this status is visible.

Source

pub fn task_result_sender(&self) -> Sender<TaskMsg>

A clone of the task channel sender background workers report over (issue #231; GitHub fetch workers too since #255). Exposed so the async-apply path (Self::drain_task_results) can be driven deterministically in tests — inject a TaskMsg exactly as a worker would, then drain — without spawning an OS thread or a real gh.

Source

pub fn invalidate_sidebar_cache(&mut self)

Drop the cached sidebar content. Call on any change that may have altered what the sidebar shows: worktree list refresh, filter narrowing, etc. Pure delegate over SidebarState::invalidate; navigation-driven invalidation goes through Self::on_navigation which also resets the scroll offset.

Source

pub fn on_navigation(&mut self)

Selection-change reaction: drop the sidebar’s scroll back to the top, invalidate its cached preview, and resolve the link cache against the freshly selected worktree. Collapses the verbatim sidebar.scroll = 0; invalidate_sidebar_cache(); refresh_link(); triple that was repeated across next, prev, first, last pre-extraction (issue #127, part of #102). The first two pieces live on SidebarState::on_navigation; the link refresh is orchestrator-shaped (it touches self.link / self.link_slug / self.issue_state / self.pr_state via Self::refresh_link) so it stays here. Every navigation entry point now goes through this single call so the triple cannot drift back into duplicated literals.

Source

pub fn next(&mut self)

Source

pub fn prev(&mut self)

Source

pub fn first(&mut self)

Source

pub fn last(&mut self)

Source

pub fn handle_g(&mut self)

Drive the two-keystroke gg motion. First press arms it, second jumps to top.

Compatibility shim — kept so the existing tests in tests/tui_app_tests.rs::handle_g_motion_tracks_pending_then_jumps_to_first and the not-yet-migrated event-loop branch keep working verbatim. The implementation routes through Self::dispatch_key so the legacy and generic paths cannot drift on the chord semantics.

Source

pub fn cancel_pending_motion(&mut self)

Drop any in-flight chord prefix. Called by the legacy event-loop branch on any non-g keystroke (pre-#87 contract). New call sites that route through Self::dispatch_key don’t need it — dispatch_key already clears the buffer on NoMatch.

Source

pub fn pending_chord_is_empty(&self) -> bool

True iff no chord prefix is currently armed. Surface for tests and for the help / status-bar code that may want to show a “waiting for next key” hint once chord support is wired up.

Source

pub fn dispatch_key(&mut self, key: KeyEvent) -> Option<Action>

Drive a raw KeyEvent through the keymap.

Returns Some(action) when the buffer (current pending chord + this stroke) matches a binding — caller fires the action and the buffer is left cleared. Returns None when the buffer is now a strict prefix of a longer binding (caller waits for the next keystroke) or when the stroke matches nothing at all (caller drops it).

Vim-style fallback: if appending the stroke to a non-empty buffer produces a NoMatch, the buffer is cleared and the stroke is re-tried on its own. This mirrors the historical g j behaviour where the stray g is forgotten and j still navigates down.

Source

pub fn key_matches_action(&self, key: KeyEvent, action: Action) -> bool

Source

pub fn resolve_modal( &self, ctx: KeyContext, key: KeyEvent, ) -> Option<ModalAction>

Resolve a keystroke against the contextual modal keymap (issue #219). Returns the ModalAction bound to key in ctx, or None when nothing in that context binds it — the modal routing then applies its text-input / default fallback (digits, free-text, sub-state guards).

Source

pub fn open_command_palette(&mut self)

Open the command palette overlay. Transitions the active view to View::CommandPalette and arms the pure state machine on self.palette with a fresh empty buffer. Status bar shows a short hint so the user knows what to type.

Source

pub fn close_command_palette(&mut self)

Close the palette without firing anything. Called on Esc from inside the overlay. Returns the view to View::List and drops the buffer.

Source

pub fn palette_push_char(&mut self, c: char)

Append a character to the palette input buffer. The pure state machine re-runs its fuzzy match and resets the highlight to 0.

Source

pub fn palette_pop_char(&mut self)

Remove the trailing character from the palette input buffer.

Source

pub fn palette_cycle_down(&mut self)

Move the palette highlight one row down (wraps at the end).

Source

pub fn palette_cycle_up(&mut self)

Move the palette highlight one row up (wraps at the start).

Source

pub fn accept_command_palette(&mut self) -> Option<Action>

Accept the highlighted entry. Returns the resolved Action and drops the palette overlay; the caller (event loop) routes the action through the same dispatcher branch as a keystroke so palette + key fire identical side effects.

When the input buffer matches nothing the palette stays open and None is returned — the user can backspace and retry without losing context.

Source

pub fn toggle_sidebar(&mut self)

Source

pub fn cycle_sidebar_mode(&mut self)

Cycle the sidebar preview mode between Commits and Stashes (issue #34). Drives the pure-state cycle on SidebarState plus the status-bar copy: orchestrator-shaped because the status bar is owned by App, not by the sub-struct.

Source

pub fn cycle_sidebar_layout(&mut self)

Cycle the sidebar orientation auto → side-by-side → stacked (issue #188). Orchestrator-shaped for the status-bar copy, like Self::cycle_sidebar_mode.

Source

pub fn toggle_sidebar_position(&mut self)

Flip the side-by-side sidebar position left ↔ right (issue #188).

Source

pub fn toggle_focus(&mut self)

Source

pub fn focus_worktrees(&mut self)

Direct-focus the worktree table (issue #217, 1). Orchestrator-shaped for the status-bar copy, like the sidebar toggles.

Source

pub fn focus_status(&mut self)

Direct-focus the status (sidebar) pane (issue #217, 2). Opens the sidebar if needed and moves focus onto it.

Source

pub fn hint_context(&self) -> HintContext

The live UI context driving the statusbar chip + help subtitle (issue #217). An open modal / overlay wins over the pane focus (issue #217 review P2): when the create form is up, the statusbar must advertise the form’s keys, not the worktrees pane’s n new — pressing n there types text. Only View::List falls through to the pane context (Picker in gwm switch, Status when the sidebar holds focus, else Worktrees).

Source

pub fn pane_hint_context(&self) -> HintContext

The underlying list-view pane context (issue #217), ignoring any open overlay. Drives the help overlay’s subtitle + picker-section gating: ? documents the keys for the pane you were on, so it must NOT collapse to the Help context that Self::hint_context returns while the overlay is up.

Source

pub fn is_github_loading(&self) -> bool

true while a GitHub issue / PR fetch for the current link is inflight (issue #217) — drives the statusbar loading spinner.

Source

pub fn sidebar_scroll_down(&mut self)

Source

pub fn sidebar_scroll_up(&mut self)

Source

pub fn enter_help(&mut self)

Open the Keybindings (help) overlay from the top (#217). Resetting the scroll offset here keeps re-opens predictable.

Source

pub fn enter_command_logs(&mut self)

Open the Command Logs overlay (issue #226). Snapshots the global command log into owned state and resets the scroll cursor so a previously-scrolled session starts fresh at the top. The renderer republishes max_scroll against the live viewport.

Source

pub fn enter_config_panel(&mut self)

Open the Configuration panel (issue #232). Resolves the effective config — the user-level global deep-merged under the repo .gwm.toml, with per-row source attribution — into owned state, then resets the scroll cursor so a re-open starts fresh at the top. The reads are cheap local TOML parses; on failure the panel still opens (empty) with the error on the statusbar rather than refusing to open.

Source

pub fn push_key_capture(&mut self, key: KeyEvent)

Feed a raw key event into the in-progress Keys-tab capture (issue #294), normalising it to a KeyStroke first. No-op when no capture is armed.

Source

pub fn handle_capture_key(&mut self, key: KeyEvent)

Drive a key through an armed Keys-tab capture (issue #294). The event loop owns no logic — it just routes here when a capture is armed, mirroring handle_create_key / handle_link_prompt_key. Controls (resolved through the config.edit context so a rebind shows through):

  • cancel (def Esc) aborts the capture;
  • submit (def Enter) commits a multi-stroke global chord;
  • Backspace drops the last stroke of a global chord;
  • any other key is captured — a single-stroke modal verb auto-commits on the first one, a global chord accumulates until submit.

Esc / Enter / Backspace stay reserved controls in both modes and are never themselves captured (a modal verb can’t be bound to them via the UI — hand-edit .gwm.toml), matching the documented capture controls and the hard-coded escape-hatch policy.

Source

pub fn commit_key_capture(&mut self)

Commit the in-progress Keys-tab capture (issue #294): write the captured chord as a TOML array to the selected target’s [tui.keys] / [tui.keys.modal.<context>] key in the active layer, then reload the config + both keymaps so the rebind is live immediately. An empty capture writes [] (unbind). Validation (conflict / prefix-collision) happens in the writer’s validate-before-write gate; on failure the file and the live keymaps are left untouched and the error is surfaced on the statusbar.

Source

pub fn open_pty_overlay(&mut self, pty: PtyOverlay)

Open the PTY overlay: store pty and switch to View::Pty.

Source

pub fn close_pty_overlay(&mut self)

Close the PTY overlay: kill the child process, drop the state, and return to View::List. Safe to call when no overlay is open.

Source

pub fn destructive_overlay_open(&self) -> bool

true while a destructive overlay — the exec picker or the clean report — is open (issue #325). The run loop suspends maybe_auto_refresh and sync_active_repo while one is up, so the worktree list (and thus the live selection / active repo) cannot reshuffle under an armed reclaim or a pending exec run. This closes the drift class at its source (Codex #333 review); the per-overlay open-time snapshots stay as defence in depth against an already-in-flight refresh landing its result.

Source

pub fn enter_exec_picker(&mut self)

Open the exec profile picker (issue #325). Populates it from [exec.profiles.*] and switches to View::ExecPicker. Refuses (status-bar message, no transition) when nothing is selected or no exec profiles are configured — there is nothing to pick.

Source

pub fn handle_exec_picker_key(&mut self, key: KeyEvent) -> ExecPickerKey

Handle a key inside the exec picker overlay (issue #325). The testable handler owns the highlight movement; the run loop owns the two side effects (resolve + spawn, or close). Keys resolve through KeyContext::ExecPicker so they honour [tui.keys.modal.exec].

Source

pub fn exec_picker_resolve(&mut self) -> Option<(Vec<String>, PathBuf)>

Resolve the highlighted exec profile to an (argv, cwd) pair for the run loop to spawn in a PTY overlay (issue #325). None (with a status-bar message) when nothing is selected or the profile fails to resolve — e.g. an empty command array. The argv is the frozen [exec.profiles.<name>].command verbatim (no shell), matching the 1.0 exec contract; the run loop spawns argv[0] directly.

Source

pub fn close_exec_picker(&mut self)

Close the exec picker without running anything (issue #325). Returns to View::List.

Source

pub fn enter_clean_overlay(&mut self)

Open the clean overlay (issue #325). Populates the [clean.profiles] picker, scans the selected worktree through the safety gate (crate::clean::scan_worktree_safe), and switches to View::CleanReport. Refuses (status-bar message, no transition) when nothing is selected. A scan that finds nothing safe still opens — the report says so.

Source

pub fn clean_overlay_next(&mut self)

Cycle the clean profile picker forward and re-scan, but ONLY when the highlight actually moved (issue #325 / Codex #333). A no-op move (only the (default) choice) must not re-scan — that would reset the ConfirmModal and silently disarm a pending reclaim while the status bar still reads armed.

Source

pub fn clean_overlay_prev(&mut self)

Cycle the clean profile picker backward and re-scan, only when the highlight actually moved (issue #325 / Codex #333).

Source

pub fn clean_countdown_total(&self) -> Duration

Total duration of the clean safety countdown. Unlike the delete-confirm modal, clean has no delete_branch_on_remove gate — it reads [tui] confirm_countdown_secs directly. Duration::ZERO ⇒ classic single-keystroke confirm.

Source

pub fn clean_confirm_press(&mut self, now: Instant) -> ConfirmKeyAction

Handle the clean confirm key. Arms / disarms / fires the countdown via the dedicated CleanOverlay modal. Nothing-to-reclaim is a no-op guard so the user cannot arm a delete that would free zero bytes.

Source

pub fn tick_clean_countdown(&mut self, now: Instant) -> CountdownTickOutcome

Tick the clean safety countdown. Called from the event loop on every poll-timeout iteration while the overlay is open.

Source

pub fn clean_countdown_progress(&self, now: Instant) -> f64

Clean countdown progress in [0.0, 1.0] for the UI gauge.

Source

pub fn clean_countdown_remaining_secs(&self, now: Instant) -> u64

Seconds remaining (rounded up) on the clean countdown, for the UI label.

Source

pub fn clean_overlay_delete(&mut self)

Delete the gated reclaim of the current clean snapshot (issue #325) and return to the list. The snapshot was already filtered to the git-ignored, untracked artifacts by crate::clean::scan_worktree_safe, so this only removes what the CLI gwm clean --yes would. Reports the freed size (or the failure) on the status bar.

Source

pub fn close_clean_overlay(&mut self)

Close the clean overlay, disarming the countdown, and return to View::List (issue #325).

Source

pub fn activate_selected_setting(&mut self)

Activate the selected Settings field (issue #279): cycle a choice field to its next value (writing + applying live), or arm the numeric input buffer for a Uint field. No-op on the read-only All tab.

Source

pub fn commit_settings_edit(&mut self)

Commit the in-progress numeric edit (issue #279): write the buffered value to the selected field and apply it live. Clearing the buffer reads as 0 (see ConfigPanel::take_edit).

Source

pub fn apply_setting(&mut self, field: SettingField, value: &str)

Persist field = value into the active layer’s TOML file and apply the change live (issue #279). The write targets the per-project .gwm.toml or the user-global config.toml per the panel’s layer selector; on success the config is reloaded, the theme re-resolved, the sidebar position re-seeded and the resolved-rows snapshot refreshed so the All tab and the source attribution track the edit. Every fallible step routes its error to the status line — no unwrap on this path.

Source

pub fn command_logs_transcript(&self) -> String

Render the Command Logs transcript as plain text for the clipboard (issue #279, y): newest-first, mirroring the overlay’s layout ($ argv, the outcome line, then the full captured output — not the tail-capped view), entries separated by a blank line. Pure + owned so the format is unit-testable without a clipboard. Empty when no commands have run.

Source

pub fn help_scroll_down(&mut self)

Scroll the help overlay down one row, clamped to the renderer-published help_max_scroll so it never scrolls past the last line.

Source

pub fn help_scroll_up(&mut self)

Scroll the help overlay up one row, clamped at the top.

Source

pub fn help_scroll_right(&mut self)

Source

pub fn help_scroll_left(&mut self)

Source

pub fn launch_lazygit(&mut self) -> Option<PathBuf>

Path to launch lazygit on, or None if nothing selected or lazygit is missing. The caller drives the actual TUI suspension/restoration around the spawn.

Retained for callers that still want the legacy “lazygit only” path; new code should go through Self::prepare_git_tui, which honours the configurable [git_tui] block (issue #75).

Source

pub fn prepare_git_tui(&mut self) -> Option<LauncherPlan>

Build the LauncherPlan for the l keybinding. Reads [git_tui] from .gwm.toml (default lazygit -p {path} fullscreen=true) and expands the {path} placeholder against the selected worktree. Returns None (and sets a status hint) when nothing is selected or the template is malformed.

Source

pub fn prepare_review(&mut self) -> Option<LauncherPlan>

Build the LauncherPlan for the R keybinding. Implements the full review contract from issue #75:

  1. [review] must resolve to a concrete launcher (command set, or tool = "<preset>" matched).
  2. The selected worktree must carry a branch name.
  3. The review base is resolved via the documented chain (upstream → gwm-base[review].default_base"dev""main").
  4. When skip_when_no_changes is on (default), a zero git rev-list --count {base}..HEAD short-circuits with a status-bar hint naming the base.
  5. The template is expanded; {diff} lazily materialises a tempfile so unused placeholders never spawn git diff.
Source

pub fn selected(&self) -> Option<&WorktreeInfo>

Source

pub fn copy_path_to_status(&mut self)

Source

pub fn open_selected_in_finder(&mut self)

Reveal the selected worktree’s directory in the OS file manager. macOS: open, Linux: xdg-open, Windows: explorer. Used by resolve_open_target when the config picks mode = "finder", and by the event loop directly to spawn the opener.

Source

pub fn yank_selected_path(&self) -> Option<PathBuf>

Return the path that the Y: yank-path key should push into the system clipboard, or None when nothing is selected. Pure — the shell-out is handled by the event loop.

Source

pub fn yank_selected_branch(&self) -> Option<String>

Return the branch name for the y: yank-branch-name key (#290).

Source

pub fn yank_selected_worktree_name(&self) -> Option<String>

Return the worktree slug/name for the w: yank-worktree-name key (#290).

Source

pub fn exit_to_worktree(&mut self)

Signal the event loop to print the selected worktree path to stdout before quitting (e: exit-to-worktree, #290). The loop checks should_exit_to after can_quit_now to emit the path.

Source

pub fn request_pull(&mut self)

Request an off-thread git pull of the selected worktree’s branch (#290). Coalesces if a pull is already in flight, and refuses to start while a different mutating task (sync / bootstrap / push / rename / create / delete) runs in the same worktree (Codex review on PR #292).

Source

pub fn request_push(&mut self)

Request an off-thread git push of the selected worktree’s branch (#290). Coalesces if a push is already in flight, and refuses to start while a different mutating task runs in the same worktree (Codex review on PR #292).

Source

pub fn enter_edit_worktree(&mut self)

Open the rename modal for the selected worktree (c, #290). Reuses the Create form (Type / Issue / Desc) pre-filled by parsing the current branch name, so renaming is symmetric with creating. A branch that does not match the <type>/#<issue>-<desc> pattern can’t be decomposed into the form, so the modal refuses to open and explains why.

Source

pub fn is_edit_worktree_loading(&self) -> bool

true while the async rename worker is in flight (#290). The run loop swallows input in View::Edit while this holds, mirroring create.

Source

pub fn cancel_edit_worktree(&mut self)

Cancel the rename modal (Esc): drop the captured original branch/path and return to the list without touching git.

Source

pub fn submit_edit_worktree(&mut self) -> Result<()>

Submit the rename from the View::Edit modal (#290). Composes the new branch name + worktree path from the form, then spawns an off-thread worker that renames the local branch (git branch -m), the remote branch when it exists (git push origin :<old> <new>:<new> + re-track), and moves the worktree directory (git worktree move). A no-op rename (nothing changed) just closes the modal.

Source

pub fn open_in_mux_pane(&mut self)

Open the selected worktree in a new multiplexer pane/tab (t, #290). Detects tmux / zellij at runtime via environment variables; prints a status message when no supported multiplexer is active.

Source

pub fn resolve_open_target(&self) -> Option<OpenTarget>

Resolve what the o key should do for the currently selected worktree. Returns None when nothing is selected (the event loop surfaces a status message in that case). The exact command is resolved once here (config override > env var > hardcoded fallback) so the event loop never has to reason about precedence.

Source

pub fn toggle_delete_branch(&mut self)

Source

pub fn enter_create(&mut self)

Source

pub fn create_next_field(&mut self)

Source

pub fn create_prev_field(&mut self)

Source

pub fn create_next_type(&mut self)

Source

pub fn create_prev_type(&mut self)

Source

pub fn create_push_char(&mut self, c: char)

Source

pub fn create_pop_char(&mut self)

Source

pub fn handle_create_key(&mut self, key: KeyEvent) -> CreateKey

Handle one key in the create overlay and report what the run loop must do next. Extracted from the inline View::Create match (issue #217) so the input path — typing, type cycling, submit/cancel — is unit-testable rather than only reachable through a live terminal.

h / l mirror the / horizontal type selector, but only when the Type field is focused; on a text field they are literal input so the letters are never swallowed.

Source

pub fn submit_create(&mut self) -> Result<()>

Source

pub fn enter_confirm_delete(&mut self)

Source

pub fn confirm_delete(&mut self) -> Result<()>

Source

pub fn confirm_countdown_total(&self) -> Duration

Total duration of the safety countdown for the current modal state. Duration::ZERO means “no countdown — classic modal”.

Source

pub fn confirm_is_countdown_mode(&self) -> bool

True when the confirm overlay should render the countdown variant (progress bar + footer “y arm / y again to cancel”). False for the classic single-keystroke confirm.

Source

pub fn confirm_press_y(&mut self, now: Instant) -> ConfirmKeyAction

Handle a y / Enter press inside the confirm overlay. Delegates to ConfirmModal::press_y and composes the status-bar message based on the returned action.

Source

pub fn confirm_dismiss(&mut self)

Handle the dismissal keys (n / Esc) inside the confirm overlay. Always disarms the countdown and returns to the list.

Source

pub fn tick_confirm_countdown(&mut self, now: Instant) -> CountdownTickOutcome

Tick the countdown forward. Called from the event loop on every poll-timeout iteration (every 200ms).

Source

pub fn confirm_countdown_progress(&self, now: Instant) -> f64

Countdown progress in [0.0, 1.0]. 0.0 when not armed, 1.0 once elapsed. Used by the UI to draw the gauge.

Source

pub fn confirm_countdown_remaining_secs(&self, now: Instant) -> u64

Seconds remaining (rounded up to the next whole second) for the UI label. 0 when not armed or when the countdown has elapsed.

Source

pub fn enter_filter(&mut self)

Open the inline filter bar. The existing query is preserved so the user can refine an already-sticky filter; Esc is the way to start fresh. Disarms any pending gg motion so /g doesn’t half-trigger it.

Forces focus back onto the list: opening / is an intent to narrow the list, and the post-Enter contract is “navigation returns to the table”. Leaving the sidebar focused would make j / k scroll it instead of walking the filtered worktrees after the filter sticks.

Source

pub fn exit_filter_keep(&mut self)

Close the filter bar but keep the query: Enter confirms the current match set and returns the cursor to list navigation.

Source

pub fn exit_filter_cancel(&mut self)

Close the filter bar and clear the query: Esc returns to the full list.

Source

pub fn filter_push_char(&mut self, c: char)

Source

pub fn filter_pop_char(&mut self)

Source

pub fn filtered_indices(&mut self) -> &[usize]

Indices into self.worktrees, in display order:

  • empty query: identity (every worktree in source order).
  • non-empty: only worktrees whose name matches the query via nucleo_matcher, ranked by descending score (nucleo intrinsically ranks exact/substring/prefix matches above subsequence matches).

Score ties are broken by original index so output is stable.

Memoised on FilterState since #124 / #104: the per-frame render path calls this 3–5× (table height, visible rows, title hint, footer counter, selection resolver), but the result only changes when the query OR the worktrees vec changes. The cache holds the previous result and the worktrees length it was computed against; any buffer mutation (push_char / pop_char / set_query / clear), an explicit filter.invalidate(), or a length change invalidates it. App::refresh calls invalidate after replacing worktrees so a same-length-different-contents refresh is also caught.

Source

pub fn reselect_by_path(&mut self, path: &Path)

Source

pub fn picker_confirm(&mut self)

Commit the highlighted worktree as the picker’s result. The event loop breaks once picker_should_exit flips so run_picker can surface the path to the CLI caller, which prints it on stdout for cd "$(gwm switch)".

Outside picker mode the call is inert. When picker mode is on but nothing is selected (e.g. the filter narrowed the list to zero matches), the loop stays open and a status hint asks the user to refine — addresses Copilot’s PR #53 review: Enter on an empty match set used to break with None, which read as “cancel” instead of “nothing to pick”.

Source

pub fn picker_cancel(&mut self)

Esc-equivalent for picker mode: leave without recording a path. The regular TUI uses Esc to clear an active filter, which conflicts with the picker footer’s esc:cancel contract; this method exists so the event loop can route Esc-during-filter to a clean picker cancel.

Source

pub fn bootstrap_selected(&mut self)

Re-read the link for the currently selected worktree’s branch. Also re-resolves the repo slug from the origin remote, and resets any previously cached GitHub fetch state since it would refer to a different (issue, pr) tuple now. Delegates to GitHubFetch::refresh_link for the pure state mutation; the branch resolution still lives here because it depends on App’s selected() + repo.head() fallback.

Source

pub fn current_slug(&self) -> Option<&str>

Source

pub fn issue_fetch_state(&self) -> &GitHubFetchState<IssueStatus>

Read the cached issue fetch state for the currently-linked issue. Returns &GitHubFetchState::Idle when no issue is linked (or when the linked issue has never been fetched) — the cache is per-number (post-#138), so reading “the” state means resolving via self.github.link.issue first.

Source

pub fn pr_fetch_state(&self) -> &GitHubFetchState<PrStatus>

PR-side counterpart to Self::issue_fetch_state.

Source

pub fn refresh_github_status(&mut self)

Kick off the issue/PR fetch. Called from the event loop when the user presses F (refresh GitHub status). Each gh issue view / gh pr view shell-out runs off-thread on the shared async-task spine (issue #255, migrated from #217’s dedicated channel): the App checks the per-key cache, claims a generation from TaskRunner::request, marks the cache Loading, and spawns a worker tagged with that generation. The worker reports a TaskMsg::Github{Issue,Pr} back; Self::drain_task_results applies it only if the generation is still authoritative — so a stale worker from a previous fetch loses the retry race to a fresh one.

The PR auto-detection (gh pr list, issue #181) stays synchronous: it mutates link which the very next render needs, and it is a single cheap call rather than the two view shell-outs the spinner is for.

This call path is the explicit user-initiated refresh, so it flushes the cache + drops any in-flight worker via Self::invalidate_github first — the user just asked for fresh data, a cache short-circuit here would be a bug.

Source

pub fn report_github_refresh_status(&mut self)

Compute the post-refresh status line message based on the actual outcome of the issue / PR fetches. PR #68 Copilot review caught that always printing “refreshed” misled users when one of the fetches had failed.

Source

pub fn apply_issue_fetch_result(&mut self, r: Result<IssueStatus, String>)

Source

pub fn apply_pr_fetch_result(&mut self, r: Result<PrStatus, String>)

Source

pub fn enter_open_menu(&mut self)

Source

pub fn exit_open_menu(&mut self)

Source

pub fn open_menu_toggle_selection(&mut self)

Source

pub fn open_menu_pick(&mut self, target: LinkTarget) -> Option<String>

Pick a target from the open menu. Returns the URL to open, or None when the link is missing (the status bar carries the explanation).

Highlighted row in the ChooseTarget picker (for the renderer).

Testable key handler for the link prompt (issue #217), mirroring App::handle_create_key. The picker / digit-buffer mutations and the per-stage status copy stay here; the loop only acts on the returned LinkPromptKey for the two genuine side effects (submit shell-out, view transition).

Auto Trait Implementations§

§

impl !RefUnwindSafe for App

§

impl !Sync for App

§

impl !UnwindSafe for App

§

impl Freeze for App

§

impl Send for App

§

impl Unpin for App

§

impl UnsafeUnpin 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> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
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.