From bc6bbdbe9dcc03df0349aefd53ebfedb11d95785 Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 14 Apr 2026 11:49:35 -0400 Subject: [PATCH] launcher: add ability to search files/folders in all tab fixes #2032 --- quickshell/Common/SettingsData.qml | 2 + quickshell/Common/settings/SettingsSpec.js | 2 + .../Modals/DankLauncherV2/Controller.qml | 246 +++++++++++------- quickshell/Modules/Settings/LauncherTab.qml | 66 ++++- quickshell/Services/DSearchService.qml | 3 +- .../translations/settings_search_index.json | 45 ++++ 6 files changed, 260 insertions(+), 104 deletions(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index d0cb9337..0790ea41 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -359,6 +359,8 @@ Singleton { property string dankLauncherV2BorderColor: "primary" property bool dankLauncherV2ShowFooter: true property bool dankLauncherV2UnloadOnClose: false + property bool dankLauncherV2IncludeFilesInAll: false + property bool dankLauncherV2IncludeFoldersInAll: false property string _legacyWeatherLocation: "New York, NY" property string _legacyWeatherCoordinates: "40.7128,-74.0060" diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 05e94032..a5f03f53 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -204,6 +204,8 @@ var SPEC = { dankLauncherV2BorderColor: { def: "primary" }, dankLauncherV2ShowFooter: { def: true }, dankLauncherV2UnloadOnClose: { def: false }, + dankLauncherV2IncludeFilesInAll: { def: false }, + dankLauncherV2IncludeFoldersInAll: { def: false }, useAutoLocation: { def: false }, weatherEnabled: { def: true }, diff --git a/quickshell/Modals/DankLauncherV2/Controller.qml b/quickshell/Modals/DankLauncherV2/Controller.qml index 4ed5dc97..67cb6060 100644 --- a/quickshell/Modals/DankLauncherV2/Controller.qml +++ b/quickshell/Modals/DankLauncherV2/Controller.qml @@ -352,7 +352,8 @@ Item { searchQuery = query; searchDebounce.restart(); - if (searchMode !== "plugins" && (searchMode === "files" || query.startsWith("/")) && query.length > 0) { + var filesInAll = searchMode === "all" && (SettingsData.dankLauncherV2IncludeFilesInAll || SettingsData.dankLauncherV2IncludeFoldersInAll); + if (searchMode !== "plugins" && (searchMode === "files" || query.startsWith("/") || filesInAll) && query.length > 0) { fileSearchDebounce.restart(); } } @@ -369,7 +370,8 @@ Item { searchMode = mode; modeChanged(mode); performSearch(); - if (mode === "files") { + var filesInAll = mode === "all" && (SettingsData.dankLauncherV2IncludeFilesInAll || SettingsData.dankLauncherV2IncludeFoldersInAll) && searchQuery.length > 0; + if (mode === "files" || filesInAll) { fileSearchDebounce.restart(); } } @@ -927,10 +929,22 @@ Item { if (!DSearchService.dsearchAvailable) return; var fileQuery = ""; + var effectiveType = fileSearchType || "all"; + var includeFiles = SettingsData.dankLauncherV2IncludeFilesInAll; + var includeFolders = SettingsData.dankLauncherV2IncludeFoldersInAll; + if (searchQuery.startsWith("/")) { fileQuery = searchQuery.substring(1).trim(); } else if (searchMode === "files") { fileQuery = searchQuery.trim(); + } else if (searchMode === "all" && (includeFiles || includeFolders)) { + fileQuery = searchQuery.trim(); + if (includeFiles && !includeFolders) + effectiveType = "file"; + else if (!includeFiles && includeFolders) + effectiveType = "dir"; + else + effectiveType = "all"; } else { return; } @@ -941,109 +955,129 @@ Item { } isFileSearching = true; - var params = { - limit: 20, - fuzzy: true, - sort: fileSearchSort || "score", - desc: true - }; - if (DSearchService.supportsTypeFilter) { - params.type = (fileSearchType && fileSearchType !== "all") ? fileSearchType : "all"; - } - if (fileSearchExt) { - params.ext = fileSearchExt; - } - if (fileSearchFolder) { - params.folder = fileSearchFolder; - } + var splitBothTypes = searchMode === "all" && includeFiles && includeFolders && DSearchService.supportsTypeFilter; + var queryTypes = splitBothTypes ? ["file", "dir"] : [effectiveType]; + var pending = queryTypes.length; + var aggregatedItems = []; - DSearchService.search(fileQuery, params, function (response) { - isFileSearching = false; - if (response.error) - return; - var fileItems = []; - var hits = response.result?.hits || []; + for (var t = 0; t < queryTypes.length; t++) { + var queryType = queryTypes[t]; + var params = { + limit: 20, + fuzzy: true, + sort: fileSearchSort || "score", + desc: true + }; - for (var i = 0; i < hits.length; i++) { - var hit = hits[i]; - var docTypes = hit.locations?.doc_type; - var isDir = docTypes ? !!docTypes["dir"] : false; - fileItems.push(transformFileResult({ - path: hit.id || "", - score: hit.score || 0, - is_dir: isDir - })); + if (DSearchService.supportsTypeFilter) { + params.type = (queryType && queryType !== "all") ? queryType : "all"; + } + if (fileSearchExt) { + params.ext = fileSearchExt; + } + if (fileSearchFolder) { + params.folder = fileSearchFolder; } - var fileSections = []; - var showType = fileSearchType || "all"; + DSearchService.search(fileQuery, params, function (response) { + pending--; + if (!response.error) { + var hits = response.result?.hits || []; + for (var i = 0; i < hits.length; i++) { + var hit = hits[i]; + var docTypes = hit.locations?.doc_type; + var isDir = docTypes ? !!docTypes["dir"] : false; + aggregatedItems.push(transformFileResult({ + path: hit.id || "", + score: hit.score || 0, + is_dir: isDir + })); + } + } + if (pending > 0) + return; - if (showType === "all" && DSearchService.supportsTypeFilter) { - var onlyFiles = []; - var onlyDirs = []; - for (var j = 0; j < fileItems.length; j++) { - if (fileItems[j].data?.is_dir) - onlyDirs.push(fileItems[j]); - else - onlyFiles.push(fileItems[j]); - } - if (onlyFiles.length > 0) { - fileSections.push({ - id: "files", - title: I18n.tr("Files"), - icon: "insert_drive_file", - priority: 4, - items: onlyFiles, - collapsed: collapsedSections["files"] || false, - flatStartIndex: 0 - }); - } - if (onlyDirs.length > 0) { - fileSections.push({ - id: "folders", - title: I18n.tr("Folders"), - icon: "folder", - priority: 4.1, - items: onlyDirs, - collapsed: collapsedSections["folders"] || false, - flatStartIndex: 0 - }); - } - } else { - var filesIcon = showType === "dir" ? "folder" : showType === "file" ? "insert_drive_file" : "folder"; - var filesTitle = showType === "dir" ? I18n.tr("Folders") : I18n.tr("Files"); - if (fileItems.length > 0) { - fileSections.push({ - id: "files", - title: filesTitle, - icon: filesIcon, - priority: 4, - items: fileItems, - collapsed: collapsedSections["files"] || false, - flatStartIndex: 0 - }); - } - } - - var newSections; - if (searchMode === "files") { - newSections = fileSections; - } else { - var existingNonFile = sections.filter(function (s) { - return s.id !== "files" && s.id !== "folders"; - }); - newSections = existingNonFile.concat(fileSections); - } - newSections.sort(function (a, b) { - return a.priority - b.priority; + isFileSearching = false; + _applyFileSearchResults(aggregatedItems, effectiveType); }); - _applyHighlights(newSections, searchQuery); - flatModel = Scorer.flattenSections(newSections); - sections = newSections; - selectedFlatIndex = getFirstItemIndex(); - updateSelectedItem(); + } + } + + function _applyFileSearchResults(fileItems, effectiveType) { + var fileSections = []; + var showType = effectiveType; + var order = SettingsData.launcherPluginOrder || []; + var filesOrderIdx = order.indexOf("__files"); + var foldersOrderIdx = order.indexOf("__folders"); + var filesPriority = filesOrderIdx !== -1 ? 2.6 + filesOrderIdx * 0.01 : 4; + var foldersPriority = foldersOrderIdx !== -1 ? 2.6 + foldersOrderIdx * 0.01 : 4.1; + + if (showType === "all" && DSearchService.supportsTypeFilter) { + var onlyFiles = []; + var onlyDirs = []; + for (var j = 0; j < fileItems.length; j++) { + if (fileItems[j].data?.is_dir) + onlyDirs.push(fileItems[j]); + else + onlyFiles.push(fileItems[j]); + } + if (onlyFiles.length > 0) { + fileSections.push({ + id: "files", + title: I18n.tr("Files"), + icon: "insert_drive_file", + priority: filesPriority, + items: onlyFiles, + collapsed: collapsedSections["files"] || false, + flatStartIndex: 0 + }); + } + if (onlyDirs.length > 0) { + fileSections.push({ + id: "folders", + title: I18n.tr("Folders"), + icon: "folder", + priority: foldersPriority, + items: onlyDirs, + collapsed: collapsedSections["folders"] || false, + flatStartIndex: 0 + }); + } + } else { + var filesIcon = showType === "dir" ? "folder" : showType === "file" ? "insert_drive_file" : "folder"; + var filesTitle = showType === "dir" ? I18n.tr("Folders") : I18n.tr("Files"); + var singlePriority = showType === "dir" ? foldersPriority : filesPriority; + if (fileItems.length > 0) { + fileSections.push({ + id: "files", + title: filesTitle, + icon: filesIcon, + priority: singlePriority, + items: fileItems, + collapsed: collapsedSections["files"] || false, + flatStartIndex: 0 + }); + } + } + + var newSections; + if (searchMode === "files") { + newSections = fileSections; + } else { + var existingNonFile = sections.filter(function (s) { + return s.id !== "files" && s.id !== "folders"; + }); + newSections = existingNonFile.concat(fileSections); + } + newSections.sort(function (a, b) { + return a.priority - b.priority; }); + _applyHighlights(newSections, searchQuery); + flatModel = Scorer.flattenSections(newSections); + sections = newSections; + selectedFlatIndex = getFirstItemIndex(); + updateSelectedItem(); } function searchApps(query) { @@ -1276,7 +1310,11 @@ Item { function buildDynamicSectionDefs(items) { var baseDefs = sectionDefinitions.slice(); var pluginSections = {}; - var basePriority = 2.6; + var order = SettingsData.launcherPluginOrder || []; + var orderMap = {}; + for (var k = 0; k < order.length; k++) + orderMap[order[k]] = k; + var unorderedPriority = 2.6 + order.length * 0.01; for (var i = 0; i < items.length; i++) { var section = items[i].section; @@ -1287,19 +1325,25 @@ Item { var pluginId = section.substring(7); var meta = getPluginMetadata(pluginId); var viewPref = getPluginViewPref(pluginId); + var orderIdx = orderMap[pluginId]; + var priority; + if (orderIdx !== undefined) { + priority = 2.6 + orderIdx * 0.01; + } else { + priority = unorderedPriority; + unorderedPriority += 0.01; + } pluginSections[section] = { id: section, title: meta.name, icon: meta.icon, - priority: basePriority, + priority: priority, defaultViewMode: viewPref.mode || "list" }; if (viewPref.mode) setPluginViewPreference(section, viewPref.mode, viewPref.enforced); - - basePriority += 0.01; } for (var sectionId in pluginSections) { diff --git a/quickshell/Modules/Settings/LauncherTab.qml b/quickshell/Modules/Settings/LauncherTab.qml index b88a977e..8705d420 100644 --- a/quickshell/Modules/Settings/LauncherTab.qml +++ b/quickshell/Modules/Settings/LauncherTab.qml @@ -606,6 +606,8 @@ Item { property var allLauncherPlugins: { SettingsData.launcherPluginVisibility; SettingsData.launcherPluginOrder; + SettingsData.dankLauncherV2IncludeFilesInAll; + SettingsData.dankLauncherV2IncludeFoldersInAll; var plugins = []; var builtIn = AppSearchService.getBuiltInLauncherPlugins() || {}; for (var pluginId in builtIn) { @@ -616,6 +618,7 @@ Item { icon: plugin.cornerIcon || "extension", iconType: "material", isBuiltIn: true, + isVirtual: false, trigger: AppSearchService.getBuiltInPluginTrigger(pluginId) || "" }); } @@ -629,9 +632,32 @@ Item { icon: rawIcon.startsWith("material:") ? rawIcon.substring(9) : rawIcon.startsWith("unicode:") ? rawIcon.substring(8) : rawIcon, iconType: rawIcon.startsWith("unicode:") ? "unicode" : "material", isBuiltIn: false, + isVirtual: false, trigger: PluginService.getPluginTrigger(pluginId) || "" }); } + if (SettingsData.dankLauncherV2IncludeFilesInAll) { + plugins.push({ + id: "__files", + name: I18n.tr("Files"), + icon: "insert_drive_file", + iconType: "material", + isBuiltIn: false, + isVirtual: true, + trigger: "/" + }); + } + if (SettingsData.dankLauncherV2IncludeFoldersInAll) { + plugins.push({ + id: "__folders", + name: I18n.tr("Folders"), + icon: "folder", + iconType: "material", + isBuiltIn: false, + isVirtual: true, + trigger: "/" + }); + } return SettingsData.getOrderedLauncherPlugins(plugins); } @@ -750,9 +776,27 @@ Item { anchors.right: parent.right anchors.rightMargin: Theme.spacingM anchors.verticalCenter: parent.verticalCenter - checked: SettingsData.getPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id) + checked: { + switch (visibilityDelegateItem.modelData.id) { + case "__files": + return SettingsData.dankLauncherV2IncludeFilesInAll; + case "__folders": + return SettingsData.dankLauncherV2IncludeFoldersInAll; + default: + return SettingsData.getPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id); + } + } onToggled: function (isChecked) { - SettingsData.setPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id, isChecked); + switch (visibilityDelegateItem.modelData.id) { + case "__files": + SettingsData.set("dankLauncherV2IncludeFilesInAll", isChecked); + break; + case "__folders": + SettingsData.set("dankLauncherV2IncludeFoldersInAll", isChecked); + break; + default: + SettingsData.setPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id, isChecked); + } } } } @@ -840,6 +884,24 @@ Item { checked: SettingsData.rememberLastQuery onToggled: checked => SettingsData.set("rememberLastQuery", checked) } + + SettingsToggleRow { + settingKey: "dankLauncherV2IncludeFilesInAll" + tags: ["launcher", "files", "dsearch", "all", "results", "indexed"] + text: I18n.tr("Include Files in All Tab") + description: I18n.tr("Merge indexed file results into the All tab (requires dsearch).") + checked: SettingsData.dankLauncherV2IncludeFilesInAll + onToggled: checked => SettingsData.set("dankLauncherV2IncludeFilesInAll", checked) + } + + SettingsToggleRow { + settingKey: "dankLauncherV2IncludeFoldersInAll" + tags: ["launcher", "folders", "dirs", "dsearch", "all", "results", "indexed"] + text: I18n.tr("Include Folders in All Tab") + description: I18n.tr("Merge indexed folder results into the All tab (requires dsearch).") + checked: SettingsData.dankLauncherV2IncludeFoldersInAll + onToggled: checked => SettingsData.set("dankLauncherV2IncludeFoldersInAll", checked) + } } SettingsCard { diff --git a/quickshell/Services/DSearchService.qml b/quickshell/Services/DSearchService.qml index 2c6da397..63efcb06 100644 --- a/quickshell/Services/DSearchService.qml +++ b/quickshell/Services/DSearchService.qml @@ -138,7 +138,8 @@ Singleton { } } - Proc.runCommand("dsearch-search", args, (stdout, exitCode) => { + const procId = "dsearch-search-" + (params?.type || "all"); + Proc.runCommand(procId, args, (stdout, exitCode) => { if (exitCode === 0) { try { const response = JSON.parse(stdout); diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 749fc4ae..a23662b6 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -1982,6 +1982,51 @@ ], "icon": "visibility_off" }, + { + "section": "dankLauncherV2IncludeFilesInAll", + "label": "Include Files in All Tab", + "tabIndex": 9, + "category": "Launcher", + "keywords": [ + "all", + "drawer", + "dsearch", + "file", + "files", + "include", + "indexed", + "launcher", + "menu", + "merge", + "results", + "start", + "tab" + ], + "description": "Merge indexed file results into the All tab (requires dsearch)." + }, + { + "section": "dankLauncherV2IncludeFoldersInAll", + "label": "Include Folders in All Tab", + "tabIndex": 9, + "category": "Launcher", + "keywords": [ + "all", + "dirs", + "drawer", + "dsearch", + "folder", + "folders", + "include", + "indexed", + "launcher", + "menu", + "merge", + "results", + "start", + "tab" + ], + "description": "Merge indexed folder results into the All tab (requires dsearch)." + }, { "section": "launcherLogoColorInvertOnMode", "label": "Invert on mode change",