import json import os import subprocess import time from collections import OrderedDict from typing import Dict, List import config.data as data from fabric.utils import get_relative_path from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result 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/assets/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(["em"]) 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}, )