use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use fuzzy_matcher::skim::SkimMatcherV2;
use iced::widget::Id;
use super::help::HelpEntry;
use super::preview::InspectedFile;
use super::theme::{PreviewStyle, ThemeColors};
pub(crate) use super::types::{
CachedRate, CalculatorResult, ClipboardData, CurrencyConversion, CurrencyConversionRequest,
CurrencyResult, DynamicPreview, EmojiEntry, FileBrowserEntry, Message, MultiCurrencyRequest,
MultiCurrencyResult, ScriptFilterResult, WebSearchActiveState,
};
use crate::{AddonsConfig, TextSnippet};
pub(super) type ContainerId = Id;
pub(super) type ScrollableId = Id;
pub(super) type TextInputId = Id;
pub(super) type SharedSelection = Arc<Mutex<Option<String>>>;
#[derive(Debug, Clone, Default)]
pub(super) struct CurrencyState {
pub result: Option<CurrencyResult>,
pub loading: bool,
pub error: Option<String>,
pub cache: HashMap<String, CachedRate>,
pub pending_request: Option<CurrencyConversionRequest>,
pub help: bool,
pub multi_result: Option<MultiCurrencyResult>,
pub multi_loading: bool,
pub pending_multi_request: Option<MultiCurrencyRequest>,
}
impl CurrencyState {
pub fn clear(&mut self) {
self.result = None;
self.loading = false;
self.error = None;
self.pending_request = None;
self.help = false;
self.multi_result = None;
self.multi_loading = false;
self.pending_multi_request = None;
}
pub fn clear_single(&mut self) {
self.result = None;
self.loading = false;
self.error = None;
self.pending_request = None;
}
pub fn clear_multi(&mut self) {
self.multi_result = None;
self.multi_loading = false;
self.pending_multi_request = None;
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct ScriptFilterState {
pub results: Option<ScriptFilterResult>,
pub loading: bool,
pub loading_name: Option<String>,
pub generation: u64,
pub action: Option<String>,
pub secondary_action: Option<String>,
pub help_message: Option<String>,
pub config_index: Option<usize>,
pub query: String,
pub preview_generation: u64,
pub preview_live_generation: Arc<AtomicU64>,
pub previews: HashMap<usize, DynamicPreview>,
pub preview_unsupported: HashSet<usize>,
pub preview_supported: HashSet<usize>,
pub preview_probing: HashSet<usize>,
}
impl ScriptFilterState {
pub fn clear(&mut self) {
self.results = None;
self.loading = false;
self.loading_name = None;
self.action = None;
self.secondary_action = None;
self.help_message = None;
self.config_index = None;
self.query.clear();
self.previews.clear();
self.bump_preview_generation();
}
pub fn bump_preview_generation(&mut self) {
self.preview_generation = self.preview_generation.wrapping_add(1);
self.preview_live_generation
.store(self.preview_generation, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct ClipboardSnapshot {
pub fetching: bool,
pub data: ClipboardData,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct PendingSelection {
pub action: String,
pub preparing_paste: bool,
}
#[derive(Debug, Clone, Default)]
pub(super) struct TextSnippetState {
pub items: Vec<TextSnippet>,
pub filtered: Vec<usize>,
pub active: bool,
pub loading: bool,
pub icon: Option<String>,
pub action: Option<String>,
pub secondary_action: Option<String>,
pub generation: u64,
pub file_cache: HashMap<String, Vec<TextSnippet>>,
pub source_index: Option<usize>,
pub session: u64,
pub placeholders: bool,
pub restore_clipboard: bool,
pub allow_shell: bool,
pub clipboard: ClipboardSnapshot,
pub clipboard_history: crate::ClipboardHistory,
pub previews: Vec<Option<String>>,
pub pending: Option<PendingSelection>,
pub error: Option<String>,
pub mru_map: HashMap<String, super::support::MruEntry>,
pub mru_path: Option<std::path::PathBuf>,
}
impl TextSnippetState {
pub fn clear(&mut self) {
self.items.clear();
self.filtered.clear();
self.active = false;
self.loading = false;
self.icon = None;
self.action = None;
self.secondary_action = None;
self.reset_session(None);
}
pub fn reset_session(&mut self, source_index: Option<usize>) {
self.source_index = source_index;
self.session = self.session.wrapping_add(1);
self.generation = self.generation.wrapping_add(1);
self.clipboard = ClipboardSnapshot::default();
self.previews.clear();
self.pending = None;
self.error = None;
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct ClipboardState {
pub active: bool,
pub loading: bool,
pub generation: u64,
pub session: u64,
pub query: String,
pub images_only: bool,
pub image_keyword: bool,
pub tab: String,
pub total: usize,
pub head: String,
pub scanned: usize,
pub items: Vec<super::clipboard::ClipItem>,
pub fresh: bool,
pub restarts: u32,
pub error: Option<String>,
pub follow: Option<String>,
pub previews: Vec<(String, Result<super::clipboard::ClipPreview, String>)>,
pub preview_inflight: Option<String>,
pub pending: bool,
}
impl ClipboardState {
pub fn clear(&mut self) {
let generation = self.generation.wrapping_add(1);
let session = self.session.wrapping_add(1);
*self = Self {
generation,
session,
..Self::default()
};
}
pub fn row_count(&self) -> usize {
if !self.active {
0
} else {
self.items.len().max(1)
}
}
pub fn preview(&self, id: &str) -> Option<&Result<super::clipboard::ClipPreview, String>> {
self.previews
.iter()
.find(|(cached, _)| cached == id)
.map(|(_, preview)| preview)
}
pub fn status(&self) -> String {
if let Some(error) = &self.error {
error.clone()
} else if self.images_only {
if self.loading {
"Searching clipboard images...".into()
} else if self.query.is_empty() {
"No images in clipboard history".into()
} else {
"No clipboard images match".into()
}
} else if self.loading {
if self.query.is_empty() {
"Loading clipboard history...".into()
} else {
"Searching clipboard history...".into()
}
} else if self.query.is_empty() {
"Clipboard history is empty".into()
} else {
"No clipboard entries match".into()
}
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct WebSearchState {
pub active: Option<WebSearchActiveState>,
}
impl WebSearchState {
pub fn clear(&mut self) {
self.active = None;
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct FileBrowserState {
pub entries: Vec<FileBrowserEntry>,
pub all_entries: Vec<FileBrowserEntry>,
pub active: bool,
pub show_hidden: bool,
pub sort: crate::FileBrowserSort,
pub sort_reverse: bool,
pub show: crate::FileBrowserShow,
pub hidden_match: Option<FileBrowserEntry>,
pub current_dir: String,
pub error: Option<String>,
pub cached_preview: Option<InspectedFile>,
pub hovered_row: Option<usize>,
pub pressed_row: Option<usize>,
pub dragging: bool,
pub preview_target: Option<String>,
pub preview_due: bool,
pub preview_command: Option<super::file_preview::FilePreviewCommand>,
pub preview_inflight: bool,
pub script_previews: Vec<(
super::file_preview::FilePreviewKey,
super::file_preview::FilePreviewResult,
)>,
}
impl FileBrowserState {
pub fn clear(&mut self) {
self.entries.clear();
self.all_entries.clear();
self.hidden_match = None;
self.active = false;
self.current_dir.clear();
self.error = None;
self.cached_preview = None;
self.hovered_row = None;
self.pressed_row = None;
self.dragging = false;
self.preview_target = None;
self.preview_due = false;
}
pub fn script_preview(&self, path: &str) -> Option<&super::file_preview::FilePreviewResult> {
self.script_previews
.iter()
.rev()
.find(|(key, _)| key.path == path)
.map(|(_, result)| result)
}
}
#[derive(Debug, Clone)]
pub(super) struct ViewState {
pub search_input_id: TextInputId,
pub scrollable_id: ScrollableId,
pub preview_scrollable_id: ScrollableId,
pub items_container_id: ContainerId,
pub generation: u64,
pub current_modifiers: iced::keyboard::Modifiers,
pub theme: ThemeColors,
pub font_sizes: crate::ui::FontSizes,
pub help_active: bool,
pub show_preview: bool,
pub show_launcher_preview: bool,
pub preview_style: PreviewStyle,
pub window_size: iced::Size,
pub default_script_shell: String,
}
impl ViewState {
pub fn new(theme: ThemeColors, font_sizes: crate::ui::FontSizes) -> Self {
Self {
search_input_id: TextInputId::unique(),
scrollable_id: ScrollableId::unique(),
preview_scrollable_id: ScrollableId::unique(),
items_container_id: ContainerId::unique(),
generation: 0,
current_modifiers: iced::keyboard::Modifiers::empty(),
preview_style: PreviewStyle::resolve(&theme, &crate::PreviewConfig::default(), None),
window_size: iced::Size::new(800.0, 600.0),
theme,
font_sizes,
help_active: false,
show_preview: true,
show_launcher_preview: false,
default_script_shell: "bash".to_string(),
}
}
pub fn refresh_ids(&mut self) {
self.scrollable_id = ScrollableId::unique();
self.preview_scrollable_id = ScrollableId::unique();
self.items_container_id = ContainerId::unique();
self.generation = self.generation.wrapping_add(1);
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct HistoryState {
pub items: Vec<String>,
pub index: Option<usize>,
pub saved_query: String,
pub search_in_progress: bool,
pub max_items: u32,
}
#[derive(Default)]
pub(super) struct EmojiState {
pub active: bool,
pub data: Vec<EmojiEntry>,
pub data_loading: bool,
pub filtered: Vec<usize>,
pub action: Option<String>,
pub secondary_action: Option<String>,
pub matcher: SkimMatcherV2,
}
impl EmojiState {
pub fn clear(&mut self) {
self.active = false;
self.filtered.clear();
self.action = None;
self.secondary_action = None;
}
}
#[derive(Default)]
pub(super) struct DevtoolsState {
pub active: bool,
pub rows: Vec<super::devtools::DevRow>,
pub loading: bool,
pub hash_generation: Arc<std::sync::atomic::AtomicU64>,
#[cfg(test)]
pub hash_command: Option<Vec<String>>,
}
impl DevtoolsState {
pub fn clear(&mut self) {
self.active = false;
self.rows.clear();
self.loading = false;
self.hash_generation
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
pub fn current_generation(&self) -> u64 {
self.hash_generation
.load(std::sync::atomic::Ordering::SeqCst)
}
}
#[derive(Debug, Clone)]
pub(super) enum FallbackAction {
WebSearch {
url_template: String,
},
ScriptFilter {
#[allow(dead_code)]
keyword: String,
command: String,
args: Vec<String>,
action: Option<String>,
#[allow(dead_code)]
secondary_action: Option<String>,
},
Terminal {
prefix: Vec<String>,
hold: bool,
},
}
#[derive(Debug, Clone)]
pub(super) struct ResolvedFallback {
pub name: String,
pub icon: Option<String>,
pub action: FallbackAction,
}
impl ResolvedFallback {
pub fn label(&self, query: &str) -> String {
match self.action {
FallbackAction::Terminal { .. } => format!("Run '{query}' in {}", self.name),
_ => format!("Search {} for '{query}'", self.name),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct KeywordSuggestion {
pub keyword: String,
pub subtitle: String,
pub icon: Option<String>,
}
#[derive(Debug, Default)]
pub(super) struct InFlightActions {
keys: HashSet<(&'static str, String)>,
}
impl InFlightActions {
pub fn begin(&mut self, provider: &'static str, target: &str) -> bool {
self.keys.insert((provider, target.to_string()))
}
pub fn finish(&mut self, provider: &'static str, target: &str) {
self.keys.remove(&(provider, target.to_string()));
}
pub fn is_pending(&self, provider: &'static str, target: &str) -> bool {
self.keys.contains(&(provider, target.to_string()))
}
}
pub(super) struct LauncherApp {
pub configs: Vec<crate::RaffiConfig>,
pub filtered_configs: Vec<usize>,
pub search_query: String,
pub select_1_query: Option<String>,
pub select_1_ready: bool,
pub selected_index: usize,
pub selected_item: SharedSelection,
pub icon_map: HashMap<String, String>,
pub desktop_icon_map: HashMap<String, String>,
pub no_icons: bool,
pub launcher_icon_paths: Vec<Option<String>>,
pub mru_map: HashMap<String, super::support::MruEntry>,
pub sort_mode: crate::SortMode,
pub addons: AddonsConfig,
pub query_class: super::app::QueryClass,
pub inline_result: Option<super::types::InlineResult>,
pub currency: CurrencyState,
pub script_filter: ScriptFilterState,
pub text_snippets: TextSnippetState,
pub hyprland: Option<Result<super::hyprland::Hyprland, String>>,
pub deferred_action: Arc<Mutex<Option<super::actions::DeferredAction>>>,
pub in_flight: InFlightActions,
pub inline_generation: u64,
pub inline_copy_error: Option<String>,
#[cfg(test)]
pub copier: Option<Vec<String>>,
pub program_exists: fn(&str) -> bool,
pub compositor: fn() -> Option<super::windows::Compositor>,
pub terminal_tab: fn(&[String], Option<&str>) -> Option<crate::terminal::TabTarget>,
pub web_search: WebSearchState,
pub file_browser: FileBrowserState,
pub drag: Option<super::dnd::DragSource>,
pub view: ViewState,
pub history: HistoryState,
pub emoji: EmojiState,
pub command_list: super::command_list::CommandListState,
pub vault: super::vault::VaultState,
pub slack: super::slack_mode::SlackState,
pub devtools: DevtoolsState,
pub clipboard: ClipboardState,
pub fallbacks: Vec<ResolvedFallback>,
pub main_view: super::main_view::MainViewState,
pub keyword_suggestions: Vec<KeywordSuggestion>,
pub help_entries: Vec<HelpEntry>,
pub help_filtered: Vec<usize>,
pub help_saved_query: Option<String>,
}