From 577863b96997e7cad051678754b70c30bed8c243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tri=E1=BB=87u=20Kha?= Date: Mon, 30 Mar 2026 20:09:39 +0700 Subject: [PATCH] feat(danklauncher): add launcher history (#2086) * remember last search query * add launcherLastQuery SessionSpec * add rememberLastQuery option * Add remember last query for appdrawer * Add query history sessiondata * Complete and cleanup * Discard changes to quickshell/Modules/Settings/LauncherTab.qml * Cleanup logic * Add rememberLastQuery option * Add rememberLastQuery option description * Move setLauncherLastQuery above validation * Fix logic bug with empty query --- quickshell/Common/SessionData.qml | 39 +++++++++++++++++++ quickshell/Common/SettingsData.qml | 1 + quickshell/Common/settings/SessionSpec.js | 2 + quickshell/Common/settings/SettingsSpec.js | 1 + .../Modals/DankLauncherV2/Controller.qml | 35 +++++++++++++++++ .../DankLauncherV2/DankLauncherV2Modal.qml | 21 ++++++---- .../Modals/DankLauncherV2/LauncherContent.qml | 13 ++++++- .../Modules/AppDrawer/AppDrawerPopout.qml | 11 ++---- quickshell/Modules/Settings/LauncherTab.qml | 21 +++++----- 9 files changed, 119 insertions(+), 25 deletions(-) diff --git a/quickshell/Common/SessionData.qml b/quickshell/Common/SessionData.qml index af6d7960..a71c5124 100644 --- a/quickshell/Common/SessionData.qml +++ b/quickshell/Common/SessionData.qml @@ -132,6 +132,8 @@ Singleton { property string timeLocale: "" property string launcherLastMode: "all" + property string launcherLastQuery: "" + property var launcherQueryHistory: [] property string appDrawerLastMode: "apps" property string niriOverviewLastMode: "apps" property string settingsSidebarExpandedIds: "," @@ -1096,6 +1098,43 @@ Singleton { saveSettings(); } + function setLauncherLastQuery(query) { + launcherLastQuery = query; + saveSettings(); + } + + function addLauncherHistory(query) { + let q = query.trim(); + + setLauncherLastQuery(q); + + if (!q) + return; + + if (launcherQueryHistory.length > 0 && launcherQueryHistory[0] === q) { + return; + } + + let history = [...launcherQueryHistory]; + + let idx = history.indexOf(q); + if (idx !== -1) + history.splice(idx, 1); + + history.unshift(q); + if (history.length > 50) + history = history.slice(0, 50); + + launcherQueryHistory = history; + saveSettings(); + } + + function clearLauncherHistory() { + launcherLastQuery = ""; + launcherSearchHistory = []; + saveSettings(); + } + function setAppDrawerLastMode(mode) { appDrawerLastMode = mode; saveSettings(); diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 71fdd588..88e17fa1 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -338,6 +338,7 @@ Singleton { property bool sortAppsAlphabetically: false property int appLauncherGridColumns: 4 property bool spotlightCloseNiriOverview: true + property bool rememberLastQuery: false property var spotlightSectionViewModes: ({}) onSpotlightSectionViewModesChanged: saveSettings() property var appDrawerSectionViewModes: ({}) diff --git a/quickshell/Common/settings/SessionSpec.js b/quickshell/Common/settings/SessionSpec.js index ae1a9cce..3f5d91cf 100644 --- a/quickshell/Common/settings/SessionSpec.js +++ b/quickshell/Common/settings/SessionSpec.js @@ -83,6 +83,8 @@ var SPEC = { timeLocale: { def: "" }, launcherLastMode: { def: "all" }, + launcherLastQuery: { def: "" }, + launcherQueryHistory: { def: [] }, appDrawerLastMode: { def: "apps" }, niriOverviewLastMode: { def: "apps" }, diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 031d28b6..bea3782e 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -189,6 +189,7 @@ var SPEC = { sortAppsAlphabetically: { def: false }, appLauncherGridColumns: { def: 4 }, spotlightCloseNiriOverview: { def: true }, + rememberLastQuery: { def: false }, spotlightSectionViewModes: { def: {} }, appDrawerSectionViewModes: { def: {} }, niriOverviewOverlayEnabled: { def: true }, diff --git a/quickshell/Modals/DankLauncherV2/Controller.qml b/quickshell/Modals/DankLauncherV2/Controller.qml index 52d2135a..4ed5dc97 100644 --- a/quickshell/Modals/DankLauncherV2/Controller.qml +++ b/quickshell/Modals/DankLauncherV2/Controller.qml @@ -39,11 +39,14 @@ Item { signal itemExecuted signal searchCompleted signal modeChanged(string mode) + signal queryChanged(string query) signal viewModeChanged(string sectionId, string mode) signal searchQueryRequested(string query) onActiveChanged: { if (!active) { + SessionData.addLauncherHistory(searchQuery); + sections = []; flatModel = []; selectedItem = null; @@ -175,6 +178,33 @@ Item { } ] + property int historyIndex: -1 + property string typingBackup: "" + + function navigateHistory(direction) { + let history = SessionData.launcherQueryHistory; + if (history.length === 0) + return; + + if (historyIndex === -1) + typingBackup = searchQuery; + + let nextIndex = historyIndex + (direction === "up" ? 1 : -1); + if (nextIndex >= history.length) + nextIndex = history.length - 1; + if (nextIndex < -1) + nextIndex = -1; + + if (nextIndex === historyIndex) + return; + historyIndex = nextIndex; + + let targetText = (historyIndex === -1) ? typingBackup : history[historyIndex]; + + setSearchQuery(targetText); + searchQueryRequested(targetText); + } + property string fileSearchType: "all" property string fileSearchExt: "" property string fileSearchFolder: "" @@ -496,6 +526,8 @@ Item { } function performSearch() { + queryChanged(searchQuery); + var currentVersion = _searchVersion; isSearching = true; var shouldResetSelection = _queryDrivenSearch; @@ -1654,6 +1686,9 @@ Item { function executeItem(item) { if (!item) return; + + SessionData.addLauncherHistory(searchQuery); + if (item.type === "plugin_browse") { var browsePluginId = item.data?.pluginId; if (!browsePluginId) diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml index 0b0af1e1..1e886d00 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml @@ -97,8 +97,16 @@ Item { contentVisible = true; spotlightContent.searchField.forceActiveFocus(); + var targetQuery = ""; + + if (query) { + targetQuery = query; + } else if (SettingsData.rememberLastQuery) { + targetQuery = SessionData.launcherLastQuery || ""; + } + if (spotlightContent.searchField) { - spotlightContent.searchField.text = query; + spotlightContent.searchField.text = targetQuery; } if (spotlightContent.controller) { var targetMode = mode || SessionData.launcherLastMode || "all"; @@ -113,12 +121,10 @@ Item { spotlightContent.controller.collapsedSections = {}; spotlightContent.controller.selectedFlatIndex = 0; spotlightContent.controller.selectedItem = null; - if (query) { - spotlightContent.controller.setSearchQuery(query); - } else { - spotlightContent.controller.searchQuery = ""; - spotlightContent.controller.performSearch(); - } + spotlightContent.controller.historyIndex = -1; + spotlightContent.controller.searchQuery = targetQuery; + + spotlightContent.controller.performSearch(); } if (spotlightContent.resetScroll) { spotlightContent.resetScroll(); @@ -231,6 +237,7 @@ Item { Connections { target: spotlightContent?.controller ?? null + function onModeChanged(mode) { if (spotlightContent.controller.autoSwitchedToFiles) return; diff --git a/quickshell/Modals/DankLauncherV2/LauncherContent.qml b/quickshell/Modals/DankLauncherV2/LauncherContent.qml index 106a8484..8464112d 100644 --- a/quickshell/Modals/DankLauncherV2/LauncherContent.qml +++ b/quickshell/Modals/DankLauncherV2/LauncherContent.qml @@ -149,10 +149,18 @@ FocusScope { event.accepted = false; return; case Qt.Key_Down: - controller.selectNext(); + if (hasCtrl) { + controller.navigateHistory("down"); + } else { + controller.selectNext(); + } return; case Qt.Key_Up: - controller.selectPrevious(); + if (hasCtrl) { + controller.navigateHistory("up"); + } else { + controller.selectPrevious(); + } return; case Qt.Key_PageDown: controller.selectPageDown(8); @@ -763,6 +771,7 @@ FocusScope { } function onSearchQueryRequested(query) { searchField.text = query; + searchField.cursorPosition = query.length; } function onModeChanged() { extFilterField.text = ""; diff --git a/quickshell/Modules/AppDrawer/AppDrawerPopout.qml b/quickshell/Modules/AppDrawer/AppDrawerPopout.qml index 264cc036..d136ad4c 100644 --- a/quickshell/Modules/AppDrawer/AppDrawerPopout.qml +++ b/quickshell/Modules/AppDrawer/AppDrawerPopout.qml @@ -90,7 +90,7 @@ DankPopout { if (!lc) return; - const query = _pendingQuery; + const query = _pendingQuery || (SettingsData.rememberLastQuery ? SessionData.launcherLastQuery : "") || ""; const mode = _pendingMode || SessionData.appDrawerLastMode || "apps"; _pendingMode = ""; _pendingQuery = ""; @@ -102,12 +102,9 @@ DankPopout { if (lc.controller) { lc.controller.searchMode = mode; lc.controller.pluginFilter = ""; - lc.controller.searchQuery = ""; - if (query) { - lc.controller.setSearchQuery(query); - } else { - lc.controller.performSearch(); - } + lc.controller.searchQuery = query; + + lc.controller.performSearch(); } lc.resetScroll?.(); lc.actionPanel?.hide(); diff --git a/quickshell/Modules/Settings/LauncherTab.qml b/quickshell/Modules/Settings/LauncherTab.qml index ccdc1b45..b88a977e 100644 --- a/quickshell/Modules/Settings/LauncherTab.qml +++ b/quickshell/Modules/Settings/LauncherTab.qml @@ -831,6 +831,15 @@ Item { checked: SessionData.searchAppActions onToggled: checked => SessionData.setSearchAppActions(checked) } + + SettingsToggleRow { + settingKey: "rememberLastQuery" + tags: ["launcher", "remember", "last", "search", "query"] + text: I18n.tr("Remember Last Query") + description: I18n.tr("Autofill last remembered query when opened") + checked: SettingsData.rememberLastQuery + onToggled: checked => SettingsData.set("rememberLastQuery", checked) + } } SettingsCard { @@ -1189,17 +1198,11 @@ Item { if (diffMins < 1) return I18n.tr("Last launched just now"); if (diffMins < 60) - return diffMins === 1 - ? I18n.tr("Last launched %1 minute ago").arg(diffMins) - : I18n.tr("Last launched %1 minutes ago").arg(diffMins); + return diffMins === 1 ? I18n.tr("Last launched %1 minute ago").arg(diffMins) : I18n.tr("Last launched %1 minutes ago").arg(diffMins); if (diffHours < 24) - return diffHours === 1 - ? I18n.tr("Last launched %1 hour ago").arg(diffHours) - : I18n.tr("Last launched %1 hours ago").arg(diffHours); + return diffHours === 1 ? I18n.tr("Last launched %1 hour ago").arg(diffHours) : I18n.tr("Last launched %1 hours ago").arg(diffHours); if (diffDays < 7) - return diffDays === 1 - ? I18n.tr("Last launched %1 day ago").arg(diffDays) - : I18n.tr("Last launched %1 days ago").arg(diffDays); + return diffDays === 1 ? I18n.tr("Last launched %1 day ago").arg(diffDays) : I18n.tr("Last launched %1 days ago").arg(diffDays); return I18n.tr("Last launched %1").arg(date.toLocaleDateString()); } font.pixelSize: Theme.fontSizeSmall