""" Emoji plugin for the launcher. Provides quick access to emojis with search functionality. """ import json import os import subprocess import time from typing import Dict, List from collections import OrderedDict from fabric.utils import get_relative_path from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result import config.data as data class EmojiPlugin(PluginBase): """ Plugin for searching and copying emojis. """ def __init__(self): super().__init__() self.name = "emoji" self.display_name = "Emoji" self.description = "Search and copy emojis" self.emoji_data = {} self.emoji_path = get_relative_path("../../../config/json/emoji.json") # Use cache directory for recent emojis (save directly in cache dir) self.recent_emoji_path = os.path.join(data.CACHE_DIR, "recent_emoji.json") self.recent_emojis = OrderedDict() self.max_recent_emojis = 20 # Maximum number of recent emojis to track def initialize(self): """Initialize the emoji plugin.""" self.set_triggers(["emoji", "emoji ", ";", "; "]) self._load_emoji_data() self._load_recent_emojis() def cleanup(self): """Cleanup the emoji plugin.""" pass def _load_emoji_data(self): """Load emoji data from JSON file.""" try: if os.path.exists(self.emoji_path): with open(self.emoji_path, "r", encoding="utf-8") as f: self.emoji_data = json.load(f) else: print(f"Emoji file not found: {self.emoji_path}") except Exception as e: print(f"Error loading emoji data: {e}") def _load_recent_emojis(self): """Load recently used emojis from JSON file.""" try: if os.path.exists(self.recent_emoji_path): with open(self.recent_emoji_path, "r", encoding="utf-8") as f: recent_data = json.load(f) # Convert to OrderedDict to maintain order self.recent_emojis = OrderedDict(recent_data) else: # Create empty recent emojis file self.recent_emojis = OrderedDict() self._save_recent_emojis() except Exception as e: print(f"Error loading recent emoji data: {e}") self.recent_emojis = OrderedDict() def _save_recent_emojis(self): """Save recently used emojis to JSON file.""" try: # Ensure the cache directory exists os.makedirs(data.CACHE_DIR, exist_ok=True) with open(self.recent_emoji_path, "w", encoding="utf-8") as f: json.dump(dict(self.recent_emojis), f, ensure_ascii=False, indent=2) except Exception as e: print(f"Error saving recent emoji data: {e}") def _add_to_recent(self, emoji: str): """Add an emoji to the recent list.""" # Remove if already exists (to move it to front) if emoji in self.recent_emojis: del self.recent_emojis[emoji] # Add to front with current timestamp self.recent_emojis[emoji] = time.time() # Keep only the most recent emojis while len(self.recent_emojis) > self.max_recent_emojis: # Remove the oldest item self.recent_emojis.popitem(last=False) # Save to file self._save_recent_emojis() def _copy_to_clipboard(self, emoji: str): """Copy emoji to clipboard and track usage.""" try: # Try Wayland first try: subprocess.run(["wl-copy"], input=emoji.encode(), check=True) except subprocess.SubprocessError: # Fall back to X11 subprocess.run( ["xclip", "-selection", "clipboard"], input=emoji.encode(), check=True, ) # Track this emoji as recently used self._add_to_recent(emoji) except Exception as e: print(f"Failed to copy to clipboard: {e}") def query(self, query_string: str) -> List[Result]: """Search emojis based on query.""" results = [] query = query_string.lower().strip() # If no query, show recently used emojis if not query: if self.recent_emojis: # Show recent emojis in reverse order (most recent first) for emoji in reversed(list(self.recent_emojis.keys())): if emoji in self.emoji_data: emoji_info = self.emoji_data[emoji] results.append(self._create_emoji_result(emoji, emoji_info, 1.0)) else: # If no recent emojis, show some popular ones as fallback popular_emojis = ["😀", "👍", "❤️", "🎉", "🔥", "✨", "🚀", "🌈"] for emoji in popular_emojis: if emoji in self.emoji_data: emoji_info = self.emoji_data[emoji] results.append(self._create_emoji_result(emoji, emoji_info, 1.0)) return results # Search by name, group, or the emoji itself for emoji, info in self.emoji_data.items(): relevance = 0 name = info.get("name", "").lower() group = info.get("group", "").lower() slug = info.get("slug", "").lower() # Exact match with emoji if query == emoji: relevance = 1.0 # Name contains query elif query in name: relevance = 0.9 # Slug contains query elif query in slug: relevance = 0.8 # Group contains query elif query in group: relevance = 0.7 if relevance > 0: results.append(self._create_emoji_result(emoji, info, relevance)) # Sort by relevance results.sort(key=lambda x: x.relevance, reverse=True) return results[:20] # Limit to 20 results def _create_emoji_result(self, emoji: str, info: Dict, relevance: float) -> Result: """Create a Result object for an emoji.""" name = info.get("name", "") group = info.get("group", "") # Check if this is a recently used emoji is_recent = emoji in self.recent_emojis subtitle = f"{group}" + (" • Recently used" if is_recent else "") return Result( title=name, # Show only the name, not the emoji subtitle=subtitle, icon_markup=emoji, # Use the emoji itself as the icon action=lambda e=emoji: self._copy_to_clipboard(e), relevance=relevance, plugin_name=self.display_name, data={"emoji": emoji, "name": name, "group": group, "recent": is_recent}, )