From 99a5721fe8d5b30c1578e74d3c440b4095c1dec0 Mon Sep 17 00:00:00 2001 From: Marcus Ramberg Date: Sun, 11 Jan 2026 22:14:45 +0000 Subject: [PATCH] settings: extract tab headings for search (#1333) * settings: extract tab headings for search * fix pre-commit --------- Co-authored-by: bbedward --- .gitignore | 1 + .../translations/extract_settings_index.py | 187 ++++++- .../translations/settings_search_index.json | 527 +++++++++++++++++- 3 files changed, 660 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index 0cf9077e..e34d7768 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,4 @@ bin/ .envrc .direnv/ quickshell/dms-plugins +__pycache__ diff --git a/quickshell/translations/extract_settings_index.py b/quickshell/translations/extract_settings_index.py index 8cec299a..bd75242b 100755 --- a/quickshell/translations/extract_settings_index.py +++ b/quickshell/translations/extract_settings_index.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -import os import re import json +from collections import Counter from pathlib import Path ABBREVIATIONS = { @@ -153,11 +153,49 @@ SEARCHABLE_COMPONENTS = [ "SettingsToggleCard", ] +STOPWORDS = { + "the", + "and", + "for", + "with", + "from", + "this", + "that", + "are", + "was", + "will", + "can", + "has", + "have", + "been", + "when", + "your", + "use", + "used", + "using", + "instead", + "like", + "such", + "also", + "only", + "which", + "each", + "other", + "some", + "into", + "than", + "then", + "them", + "these", + "those", +} + + def enrich_keywords(label, description, category, existing_tags): keywords = set(existing_tags) label_lower = label.lower() - label_words = re.split(r'[\s\-_&/]+', label_lower) + label_words = re.split(r"[\s\-_&/]+", label_lower) keywords.update(w for w in label_words if len(w) > 2) for term, aliases in ABBREVIATIONS.items(): @@ -166,7 +204,7 @@ def enrich_keywords(label, description, category, existing_tags): if description: desc_lower = description.lower() - desc_words = re.split(r'[\s\-_&/,.]+', desc_lower) + desc_words = re.split(r"[\s\-_&/,.]+", desc_lower) keywords.update(w for w in desc_words if len(w) > 3 and w.isalpha()) for term, aliases in ABBREVIATIONS.items(): if term in desc_lower: @@ -176,17 +214,13 @@ def enrich_keywords(label, description, category, existing_tags): keywords.update(CATEGORY_KEYWORDS[category]) cat_lower = category.lower() - cat_words = re.split(r'[\s\-_&/]+', cat_lower) + cat_words = re.split(r"[\s\-_&/]+", cat_lower) keywords.update(w for w in cat_words if len(w) > 2) - stopwords = {'the', 'and', 'for', 'with', 'from', 'this', 'that', 'are', 'was', - 'will', 'can', 'has', 'have', 'been', 'when', 'your', 'use', 'used', - 'using', 'instead', 'like', 'such', 'also', 'only', 'which', 'each', - 'other', 'some', 'into', 'than', 'then', 'them', 'these', 'those'} - keywords = {k for k in keywords if k not in stopwords and len(k) > 1} - + keywords = {k for k in keywords if k not in STOPWORDS and len(k) > 1} return sorted(keywords) + def extract_i18n_string(value): match = re.search(r'I18n\.tr\(["\']([^"\']+)["\']', value) if match: @@ -196,38 +230,42 @@ def extract_i18n_string(value): return match.group(1) return None + def extract_tags(value): - match = re.search(r'\[([^\]]+)\]', value) + match = re.search(r"\[([^\]]+)\]", value) if not match: return [] content = match.group(1) tags = re.findall(r'["\']([^"\']+)["\']', content) return tags + def parse_component_block(content, start_pos, component_name): brace_count = 0 started = False block_start = start_pos for i in range(start_pos, len(content)): - if content[i] == '{': + if content[i] == "{": if not started: block_start = i started = True brace_count += 1 - elif content[i] == '}': + elif content[i] == "}": brace_count -= 1 if started and brace_count == 0: - return content[block_start:i+1] + return content[block_start : i + 1] return "" + def extract_property(block, prop_name): - pattern = rf'{prop_name}\s*:\s*([^\n]+)' + pattern = rf"{prop_name}\s*:\s*([^\n]+)" match = re.search(pattern, block) if match: return match.group(1).strip() return None + def find_settings_components(content, filename): results = [] tab_index = TAB_INDEX_MAP.get(filename, -1) @@ -236,7 +274,7 @@ def find_settings_components(content, filename): return results for component in SEARCHABLE_COMPONENTS: - pattern = rf'\b{component}\s*\{{' + pattern = rf"\b{component}\s*\{{" for match in re.finditer(pattern, content): block = parse_component_block(content, match.start(), component) if not block: @@ -244,7 +282,7 @@ def find_settings_components(content, filename): setting_key = extract_property(block, "settingKey") if setting_key: - setting_key = setting_key.strip('"\'') + setting_key = setting_key.strip("\"'") if not setting_key: continue @@ -263,7 +301,7 @@ def find_settings_components(content, filename): icon_raw = extract_property(block, "iconName") icon = None if icon_raw: - icon = icon_raw.strip('"\'') + icon = icon_raw.strip("\"'") if icon.startswith("{") or "?" in icon: icon = None @@ -321,6 +359,95 @@ def find_settings_components(content, filename): return results + +def parse_tabs_from_sidebar(sidebar_file): + with open(sidebar_file, "r", encoding="utf-8") as f: + content = f.read() + + pattern = r'"text"\s*:\s*I18n\.tr\("([^"]+)"(?:,\s*"[^"]+")?\).*?"icon"\s*:\s*"([^"]+)".*?"tabIndex"\s*:\s*(\d+)' + tabs = [] + + for match in re.finditer(pattern, content, re.DOTALL): + label, icon, tab_idx = match.group(1), match.group(2), int(match.group(3)) + + before_text = content[: match.start()] + parent_match = re.search( + r'"text"\s*:\s*I18n\.tr\("([^"]+)"\)[^{]*"children"[^[]*\[[^{]*$', + before_text, + ) + parent = parent_match.group(1) if parent_match else None + + cond = None + after_pos = match.end() + snippet = content[match.start() : min(after_pos + 200, len(content))] + for qml_cond, key in [ + ("shortcutsOnly", "keybindsAvailable"), + ("soundsOnly", "soundsAvailable"), + ("cupsOnly", "cupsAvailable"), + ("dmsOnly", "dmsConnected"), + ("hyprlandNiriOnly", "isHyprlandOrNiri"), + ("clipboardOnly", "dmsConnected"), + ]: + if f'"{qml_cond}": true' in snippet: + cond = key + break + + tabs.append( + { + "tabIndex": tab_idx, + "label": label, + "icon": icon, + "parent": parent, + "conditionKey": cond, + } + ) + + return tabs + + +def generate_tab_entries(sidebar_file): + tabs = parse_tabs_from_sidebar(sidebar_file) + + label_counts = Counter([t["label"] for t in tabs]) + + entries = [] + for tab in tabs: + label = ( + f"{tab['parent']}: {tab['label']}" + if label_counts[tab["label"]] > 1 and tab["parent"] + else tab["label"] + ) + category = TAB_CATEGORY_MAP.get(tab["tabIndex"], "Settings") + + keywords = enrich_keywords(tab["label"], None, category, []) + + if tab["parent"]: + parent_keywords = [ + w for w in re.split(r"[\s\-_&/]+", tab["parent"].lower()) if len(w) > 2 + ] + keywords = sorted( + set( + keywords + + parent_keywords + + [k for p in parent_keywords for k in ABBREVIATIONS.get(p, [])] + ) + ) + + entry = { + "section": f"_tab_{tab['tabIndex']}", + "label": label, + "tabIndex": tab["tabIndex"], + "category": category, + "keywords": keywords, + "icon": tab["icon"], + } + if tab["conditionKey"]: + entry["conditionKey"] = tab["conditionKey"] + entries.append(entry) + + return entries + + def extract_settings_index(root_dir): settings_dir = Path(root_dir) / "Modules" / "Settings" all_entries = [] @@ -330,7 +457,7 @@ def extract_settings_index(root_dir): if not qml_file.name.endswith("Tab.qml"): continue - with open(qml_file, 'r', encoding='utf-8') as f: + with open(qml_file, "r", encoding="utf-8") as f: content = f.read() entries = find_settings_components(content, qml_file.name) @@ -342,29 +469,37 @@ def extract_settings_index(root_dir): return all_entries + def main(): script_dir = Path(__file__).parent root_dir = script_dir.parent + sidebar_file = root_dir / "Modals" / "Settings" / "SettingsSidebar.qml" print("Extracting settings search index...") - entries = extract_settings_index(root_dir) + settings_entries = extract_settings_index(root_dir) + tab_entries = generate_tab_entries(sidebar_file) - entries.sort(key=lambda x: (x["tabIndex"], x["label"])) + all_entries = tab_entries + settings_entries + + all_entries.sort(key=lambda x: (x["tabIndex"], x["label"])) output_path = script_dir / "settings_search_index.json" - with open(output_path, 'w', encoding='utf-8') as f: - json.dump(entries, f, indent=2, ensure_ascii=False) + with open(output_path, "w", encoding="utf-8") as f: + json.dump(all_entries, f, indent=2, ensure_ascii=False) - print(f"Found {len(entries)} searchable settings") + print(f"Found {len(settings_entries)} searchable settings") + print(f"Found {len(tab_entries)} tab entries") + print(f"Total: {len(all_entries)} entries") print(f"Output: {output_path}") conditions = set() - for entry in entries: + for entry in all_entries: if "conditionKey" in entry: conditions.add(entry["conditionKey"]) if conditions: print(f"Condition keys found: {', '.join(sorted(conditions))}") -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 3b88bc83..e6c88d97 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -295,6 +295,20 @@ ], "description": "Set different wallpapers for each connected monitor" }, + { + "section": "_tab_0", + "label": "Personalization", + "tabIndex": 0, + "category": "Personalization", + "keywords": [ + "appearance", + "custom", + "customize", + "personal", + "personalization" + ], + "icon": "palette" + }, { "section": "wallpaperTransition", "label": "Transition Effect", @@ -494,6 +508,22 @@ ], "description": "Display seconds in the clock" }, + { + "section": "_tab_1", + "label": "Time & Weather", + "tabIndex": 1, + "category": "Time & Weather", + "keywords": [ + "climate", + "clock", + "date", + "forecast", + "temperature", + "time", + "weather" + ], + "icon": "schedule" + }, { "section": "timeFormat", "label": "Time Format", @@ -576,6 +606,23 @@ "icon": "cloud", "description": "Show weather information in top bar and control center" }, + { + "section": "_tab_2", + "label": "Keyboard Shortcuts", + "tabIndex": 2, + "category": "Keyboard Shortcuts", + "keywords": [ + "bindings", + "hotkey", + "hotkeys", + "keybinds", + "keyboard", + "keys", + "shortcuts" + ], + "icon": "keyboard", + "conditionKey": "keybindsAvailable" + }, { "section": "barConfigurations", "label": "Bar Configurations", @@ -614,6 +661,21 @@ ], "icon": "rounded_corner" }, + { + "section": "_tab_3", + "label": "Dank Bar", + "tabIndex": 3, + "category": "Dank Bar", + "keywords": [ + "bar", + "dank", + "panel", + "statusbar", + "taskbar", + "topbar" + ], + "icon": "toolbar" + }, { "section": "barDisplay", "label": "Display Assignment", @@ -1022,6 +1084,25 @@ "description": "Show workspace index numbers in the top bar workspace switcher", "conditionKey": "isNiri" }, + { + "section": "_tab_4", + "label": "Workspaces & Widgets", + "tabIndex": 4, + "category": "Workspaces", + "keywords": [ + "components", + "desktop", + "desktops", + "modules", + "spaces", + "virtual", + "virtual desktops", + "widgets", + "workspace", + "workspaces" + ], + "icon": "dashboard" + }, { "section": "dockAutoHide", "label": "Auto-hide Dock", @@ -1080,6 +1161,24 @@ "icon": "border_style", "description": "Add a border around the dock" }, + { + "section": "_tab_5", + "label": "Dock & Launcher", + "tabIndex": 5, + "category": "Dock", + "keywords": [ + "app drawer", + "app menu", + "applications", + "dock", + "launcher", + "launcher bar", + "panel", + "start menu", + "taskbar" + ], + "icon": "apps" + }, { "section": "dockPosition", "label": "Dock Position", @@ -1297,6 +1396,38 @@ ], "icon": "opacity" }, + { + "section": "_tab_7", + "label": "Network", + "tabIndex": 7, + "category": "Network", + "keywords": [ + "connection", + "connectivity", + "ethernet", + "internet", + "network", + "online", + "wi-fi", + "wifi", + "wireless" + ], + "icon": "wifi", + "conditionKey": "dmsConnected" + }, + { + "section": "_tab_8", + "label": "System", + "tabIndex": 8, + "category": "System", + "keywords": [ + "linux", + "os", + "system" + ], + "icon": "computer", + "conditionKey": "cupsAvailable" + }, { "section": "launcherLogoBrightness", "label": "Brightness", @@ -1421,6 +1552,23 @@ ], "icon": "terminal" }, + { + "section": "_tab_9", + "label": "Launcher", + "tabIndex": 9, + "category": "Launcher", + "keywords": [ + "app drawer", + "app menu", + "applications", + "drawer", + "launcher", + "menu", + "start", + "start menu" + ], + "icon": "grid_view" + }, { "section": "launcherLogo", "label": "Launcher Button Logo", @@ -2531,6 +2679,26 @@ ], "description": "Force terminal applications to always use dark color schemes" }, + { + "section": "_tab_10", + "label": "Theme & Colors", + "tabIndex": 10, + "category": "Theme & Colors", + "keywords": [ + "appearance", + "colors", + "colour", + "colours", + "hue", + "look", + "palette", + "scheme", + "style", + "theme", + "tint" + ], + "icon": "format_paint" + }, { "section": "themeColor", "label": "Theme Color", @@ -3153,6 +3321,54 @@ "icon": "lock", "description": "If the field is hidden, it will appear as soon as a key is pressed." }, + { + "section": "lockScreenNotificationMode", + "label": "Notification Display", + "tabIndex": 11, + "category": "Lock Screen", + "keywords": [ + "alert", + "control", + "display", + "information", + "lock", + "lockscreen", + "login", + "monitor", + "notif", + "notification", + "notifications", + "output", + "password", + "privacy", + "screen", + "security", + "shown", + "what" + ], + "description": "Control what notification information is shown on the lock screen" + }, + { + "section": "_tab_11", + "label": "Power & Security", + "tabIndex": 11, + "category": "Lock Screen", + "keywords": [ + "hibernate", + "lock", + "login", + "password", + "power", + "reboot", + "restart", + "screen", + "security", + "shutdown", + "sleep", + "suspend" + ], + "icon": "security" + }, { "section": "lockScreenShowPasswordField", "label": "Show Password Field", @@ -3268,6 +3484,35 @@ "time" ] }, + { + "section": "_tab_12", + "label": "Plugins", + "tabIndex": 12, + "category": "Plugins", + "keywords": [ + "addon", + "addons", + "extend", + "extensions", + "plugins", + "widgets" + ], + "icon": "extension" + }, + { + "section": "_tab_13", + "label": "About", + "tabIndex": 13, + "category": "About", + "keywords": [ + "about", + "credits", + "help", + "info", + "version" + ], + "icon": "info" + }, { "section": "animationSpeed", "label": "Animation Speed", @@ -3422,6 +3667,22 @@ "icon": "text_fields", "description": "Select the font family for UI text" }, + { + "section": "_tab_14", + "label": "Typography & Motion", + "tabIndex": 14, + "category": "Typography & Motion", + "keywords": [ + "animation", + "font", + "fonts", + "motion", + "text", + "typeface", + "typography" + ], + "icon": "text_fields" + }, { "section": "soundsEnabled", "label": "Enable System Sounds", @@ -3507,6 +3768,20 @@ ], "description": "Select system sound theme" }, + { + "section": "_tab_15", + "label": "Sounds", + "tabIndex": 15, + "category": "Sounds", + "keywords": [ + "audio", + "effects", + "sfx", + "sounds" + ], + "icon": "volume_up", + "conditionKey": "soundsAvailable" + }, { "section": "systemSounds", "label": "System Sounds", @@ -3569,6 +3844,22 @@ ], "description": "Play sound when volume is adjusted" }, + { + "section": "_tab_16", + "label": "Media Player", + "tabIndex": 16, + "category": "Media Player", + "keywords": [ + "audio", + "media", + "mpris", + "music", + "playback", + "player", + "spotify" + ], + "icon": "music_note" + }, { "section": "mediaPlayer", "label": "Media Player Settings", @@ -3614,6 +3905,28 @@ ], "description": "Scroll wheel behavior on media widget" }, + { + "section": "notificationCompactMode", + "label": "Compact", + "tabIndex": 17, + "category": "Notifications", + "keywords": [ + "alert", + "alerts", + "cards", + "compact", + "display", + "messages", + "mode", + "notif", + "notification", + "notifications", + "size", + "smaller", + "toast" + ], + "description": "Use smaller notification cards" + }, { "section": "notificationHistorySaveCritical", "label": "Critical Priority", @@ -3888,35 +4201,6 @@ ], "description": "Timeout for normal priority notifications" }, - { - "section": "lockScreenNotificationMode", - "label": "Notification Display", - "tabIndex": 17, - "category": "Notifications", - "keywords": [ - "alert", - "alerts", - "control", - "display", - "information", - "lock", - "lockscreen", - "login", - "messages", - "monitor", - "notif", - "notification", - "notifications", - "output", - "privacy", - "screen", - "security", - "shown", - "toast", - "what" - ], - "description": "Control what notification information is shown on the lock screen" - }, { "section": "notificationOverlayEnabled", "label": "Notification Overlay", @@ -3991,6 +4275,22 @@ "icon": "timer", "description": "Timeout for low priority notifications" }, + { + "section": "_tab_17", + "label": "Notifications", + "tabIndex": 17, + "category": "Notifications", + "keywords": [ + "alert", + "alerts", + "messages", + "notif", + "notifications", + "notifs", + "toast" + ], + "icon": "notifications" + }, { "section": "notificationPopupPosition", "label": "Popup Position", @@ -4015,6 +4315,25 @@ ], "description": "Choose where notification popups appear on screen" }, + { + "section": "_tab_18", + "label": "On-screen Displays", + "tabIndex": 18, + "category": "On-screen Displays", + "keywords": [ + "displays", + "indicator", + "monitor", + "monitors", + "osd", + "output", + "outputs", + "popup", + "screen", + "screens" + ], + "icon": "tune" + }, { "section": "osd", "label": "On-screen Displays", @@ -4061,6 +4380,23 @@ ], "icon": "find_replace" }, + { + "section": "_tab_19", + "label": "Running Apps", + "tabIndex": 19, + "category": "Running Apps", + "keywords": [ + "active", + "apps", + "open", + "running", + "taskbar", + "tasks", + "windows" + ], + "icon": "apps", + "conditionKey": "isHyprlandOrNiri" + }, { "section": "runningApps", "label": "Running Apps Settings", @@ -4084,6 +4420,20 @@ "icon": "apps", "description": "Show only apps running in current workspace" }, + { + "section": "_tab_20", + "label": "System Updater", + "tabIndex": 20, + "category": "System Updater", + "keywords": [ + "packages", + "system", + "updater", + "updates", + "upgrade" + ], + "icon": "refresh" + }, { "section": "systemUpdater", "label": "System Updater", @@ -4394,6 +4744,23 @@ "timeout" ] }, + { + "section": "_tab_21", + "label": "Power & Sleep", + "tabIndex": 21, + "category": "Power & Sleep", + "keywords": [ + "energy", + "hibernate", + "power", + "reboot", + "restart", + "shutdown", + "sleep", + "suspend" + ], + "icon": "power_settings_new" + }, { "section": "powerConfirmation", "label": "Power Action Confirmation", @@ -4536,6 +4903,23 @@ ], "description": "Display power menu actions in a grid instead of a list" }, + { + "section": "_tab_22", + "label": "Widgets", + "tabIndex": 22, + "category": "Dank Bar", + "keywords": [ + "bar", + "components", + "dank", + "modules", + "panel", + "statusbar", + "topbar", + "widgets" + ], + "icon": "widgets" + }, { "section": "disabled", "label": "Advanced", @@ -4600,6 +4984,24 @@ "icon": "settings", "description": "Clear all history when server starts" }, + { + "section": "_tab_23", + "label": "Clipboard", + "tabIndex": 23, + "category": "System", + "keywords": [ + "clipboard", + "cliphist", + "copy", + "history", + "linux", + "os", + "paste", + "system" + ], + "icon": "content_paste", + "conditionKey": "dmsConnected" + }, { "section": "maxHistory", "label": "History Settings", @@ -4645,6 +5047,23 @@ ], "description": "Maximum size per clipboard entry" }, + { + "section": "_tab_24", + "label": "Displays", + "tabIndex": 24, + "category": "Displays", + "keywords": [ + "displays", + "monitor", + "monitors", + "output", + "outputs", + "resolution", + "screen", + "screens" + ], + "icon": "monitor" + }, { "section": "nightModeHighTemperature", "label": "Day Temperature", @@ -4670,6 +5089,25 @@ ], "description": "Color temperature for day time" }, + { + "section": "_tab_25", + "label": "Gamma Control", + "tabIndex": 25, + "category": "Displays", + "keywords": [ + "blue light", + "color temperature", + "control", + "displays", + "gamma", + "monitor", + "night light", + "redshift", + "resolution", + "screen" + ], + "icon": "brightness_6" + }, { "section": "nightModeTemperature", "label": "Night Temperature", @@ -4696,5 +5134,36 @@ "warm" ], "description": "Color temperature for night mode" + }, + { + "section": "_tab_26", + "label": "Widgets", + "tabIndex": 26, + "category": "Displays", + "keywords": [ + "components", + "displays", + "modules", + "monitor", + "resolution", + "screen", + "widgets" + ], + "icon": "widgets" + }, + { + "section": "_tab_27", + "label": "Desktop Widgets", + "tabIndex": 27, + "category": "Desktop Widgets", + "keywords": [ + "components", + "conky", + "desktop", + "desktop clock", + "modules", + "widgets" + ], + "icon": "widgets" } ]