Repository: oliverschwendener/ueli
Pull request #1324 (2025-01-18 12:30:30)

feat: add keyboard shortcuts for actions
This is an alternative implementation for the problem mentioned in #1323.

|Windows|macOS|Linux|
|---|---|---|
|![image](https://github.com/user-attachments/assets/34ade5fd-8fd2-4f1c-b67a-debd494432c1)|<img width="609" alt="image" src="https://github.com/user-attachments/assets/85a52ee8-aeca-4e0a-8276-608c972a0ef1" />|TBD|

This is currently just a POC and is work in progress. A lot of stuff needs to be tested/cleaned up.


Patch:
diff --git a/src/common/Core/Hotkey.ts b/src/common/Core/Hotkey.ts
index 8f4b6f2d..2e785ad4 100644
--- a/src/common/Core/Hotkey.ts
+++ b/src/common/Core/Hotkey.ts
@@ -15,7 +15,7 @@ const isValidKeyCode = (keyCode: string): boolean => {
 };
 
 const isValidModifier = (modifier: string): boolean =>
-    /^(Command|Cmd|Control|Ctrl|CommandOrControl|CmdOrCtrl|Alt|Option|AltGr|Shift|Super|Meta)$/.test(modifier);
+    /^(Command|Cmd|Control|Ctrl|CommandOrControl|CmdOrCtrl|Alt|Option|Shift|Super|Meta)$/.test(modifier);
 
 const hasDuplicates = (modifiers: string[]): boolean => modifiers.length !== new Set(modifiers).size;
 
diff --git a/src/common/Core/SearchResultItemAction.ts b/src/common/Core/SearchResultItemAction.ts
index 457fff2c..ec6c5316 100644
--- a/src/common/Core/SearchResultItemAction.ts
+++ b/src/common/Core/SearchResultItemAction.ts
@@ -39,4 +39,12 @@ export type SearchResultItemAction = {
      * in the settings is disabled, this option will be ignored.
      */
     hideWindowAfterInvocation?: boolean;
+
+    /**
+     * The keyboard shortcut that can be used to invoke the action, in the following format: `<KEY>` or `<MODIFIER>+<KEY>`,
+     * for example: `Ctrl+C`, `Shift+ArrowUp` or `Enter`. Currently supported modifiers are: `Ctrl`, `Cmd`, `Shift` and
+     * `Alt`. If the action is the default action this property will be ignored and overwritten with
+     * `Enter`.
+     */
+    keyboardShortcut?: string;
 };
diff --git a/src/common/Core/SearchResultItemActionUtility/createAddToFavoritesAction.ts b/src/common/Core/SearchResultItemActionUtility/createAddToFavoritesAction.ts
index e5fbcdba..5c6b9fcf 100644
--- a/src/common/Core/SearchResultItemActionUtility/createAddToFavoritesAction.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createAddToFavoritesAction.ts
@@ -3,7 +3,13 @@ import type { SearchResultItemAction } from "@common/Core";
 /**
  * Creates an action to add the given SearchResultItem to the favorites by its ID.
  */
-export const createAddToFavoritesAction = ({ id }: { id: string }): SearchResultItemAction => ({
+export const createAddToFavoritesAction = ({
+    id,
+    keyboardShortcut,
+}: {
+    id: string;
+    keyboardShortcut?: string;
+}): SearchResultItemAction => ({
     argument: JSON.stringify({ action: "Add", id }),
     description: "Add to favorites",
     descriptionTranslation: {
@@ -12,4 +18,5 @@ export const createAddToFavoritesAction = ({ id }: { id: string }): SearchResult
     },
     handlerId: "Favorites",
     fluentIcon: "StarRegular",
+    keyboardShortcut,
 });
diff --git a/src/common/Core/SearchResultItemActionUtility/createCopyToClipboardAction.ts b/src/common/Core/SearchResultItemActionUtility/createCopyToClipboardAction.ts
index e63121cf..438b21e7 100644
--- a/src/common/Core/SearchResultItemActionUtility/createCopyToClipboardAction.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createCopyToClipboardAction.ts
@@ -7,14 +7,17 @@ export const createCopyToClipboardAction = ({
     textToCopy,
     description,
     descriptionTranslation,
+    keyboardShortcut,
 }: {
     textToCopy: string;
     description: string;
     descriptionTranslation?: { key: string; namespace: string };
+    keyboardShortcut?: string;
 }): SearchResultItemAction => ({
     argument: textToCopy,
     description,
     descriptionTranslation,
     handlerId: "copyToClipboard",
     fluentIcon: "CopyRegular",
+    keyboardShortcut,
 });
diff --git a/src/common/Core/SearchResultItemActionUtility/createExcludeFromSearchResultsAction.ts b/src/common/Core/SearchResultItemActionUtility/createExcludeFromSearchResultsAction.ts
index ba76ebe1..cfc3fce8 100644
--- a/src/common/Core/SearchResultItemActionUtility/createExcludeFromSearchResultsAction.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createExcludeFromSearchResultsAction.ts
@@ -3,7 +3,13 @@ import type { SearchResultItemAction } from "@common/Core";
 /**
  * Creates an action to exclude the given SearchResultItem from the search results by its ID.
  */
-export const createExcludeFromSearchResultsAction = ({ id }: { id: string }): SearchResultItemAction => ({
+export const createExcludeFromSearchResultsAction = ({
+    id,
+    keyboardShortcut,
+}: {
+    id: string;
+    keyboardShortcut?: string;
+}): SearchResultItemAction => ({
     argument: id,
     description: "Exclude from search results",
     descriptionTranslation: {
@@ -12,4 +18,5 @@ export const createExcludeFromSearchResultsAction = ({ id }: { id: string }): Se
     },
     handlerId: "excludeFromSearchResults",
     fluentIcon: "EyeOffRegular",
+    keyboardShortcut,
 });
diff --git a/src/common/Core/SearchResultItemActionUtility/createOpenFileAction.ts b/src/common/Core/SearchResultItemActionUtility/createOpenFileAction.ts
index 12d8d2f2..ff7fa72e 100644
--- a/src/common/Core/SearchResultItemActionUtility/createOpenFileAction.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createOpenFileAction.ts
@@ -7,10 +7,12 @@ export const createOpenFileAction = ({
     filePath,
     description,
     descriptionTranslation,
+    keyboardShortcut,
 }: {
     filePath: string;
     description: string;
     descriptionTranslation?: { key: string; namespace: string };
+    keyboardShortcut?: string;
 }): SearchResultItemAction => ({
     argument: filePath,
     description,
@@ -18,4 +20,5 @@ export const createOpenFileAction = ({
     handlerId: "OpenFilePath",
     fluentIcon: "OpenRegular",
     hideWindowAfterInvocation: true,
+    keyboardShortcut,
 });
diff --git a/src/common/Core/SearchResultItemActionUtility/createRemoveFromFavoritesAction.ts b/src/common/Core/SearchResultItemActionUtility/createRemoveFromFavoritesAction.ts
index ea9c785a..dcc4b6cb 100644
--- a/src/common/Core/SearchResultItemActionUtility/createRemoveFromFavoritesAction.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createRemoveFromFavoritesAction.ts
@@ -3,7 +3,13 @@ import type { SearchResultItemAction } from "@common/Core";
 /**
  * Creates an action to remove the given SearchResultItem from the favorites by its ID.
  */
-export const createRemoveFromFavoritesAction = ({ id }: { id: string }): SearchResultItemAction => ({
+export const createRemoveFromFavoritesAction = ({
+    id,
+    keyboardShortcut,
+}: {
+    id: string;
+    keyboardShortcut?: string;
+}): SearchResultItemAction => ({
     argument: JSON.stringify({ action: "Remove", id }),
     description: "Remove from favorites",
     descriptionTranslation: {
@@ -12,4 +18,5 @@ export const createRemoveFromFavoritesAction = ({ id }: { id: string }): SearchR
     },
     handlerId: "Favorites",
     fluentIcon: "StarOffRegular",
+    keyboardShortcut,
 });
diff --git a/src/common/Core/SearchResultItemActionUtility/createShowItemInFileExplorerAction.ts b/src/common/Core/SearchResultItemActionUtility/createShowItemInFileExplorerAction.ts
index 5297802d..daf8c9e1 100644
--- a/src/common/Core/SearchResultItemActionUtility/createShowItemInFileExplorerAction.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createShowItemInFileExplorerAction.ts
@@ -3,7 +3,13 @@ import type { SearchResultItemAction } from "@common/Core";
 /**
  * Creates an action to show the given file in the default file browser.
  */
-export const createShowItemInFileExplorerAction = ({ filePath }: { filePath: string }): SearchResultItemAction => ({
+export const createShowItemInFileExplorerAction = ({
+    filePath,
+    keyboardShortcut,
+}: {
+    filePath: string;
+    keyboardShortcut?: string;
+}): SearchResultItemAction => ({
     argument: filePath,
     description: "Show in file explorer",
     descriptionTranslation: {
@@ -13,4 +19,5 @@ export const createShowItemInFileExplorerAction = ({ filePath }: { filePath: str
     handlerId: "ShowItemInFileExplorer",
     fluentIcon: "DocumentFolderRegular",
     hideWindowAfterInvocation: true,
+    keyboardShortcut,
 });
diff --git a/src/main/Extensions/ApplicationSearch/ApplicationSearch.ts b/src/main/Extensions/ApplicationSearch/ApplicationSearch.ts
index c7918322..2adf85af 100644
--- a/src/main/Extensions/ApplicationSearch/ApplicationSearch.ts
+++ b/src/main/Extensions/ApplicationSearch/ApplicationSearch.ts
@@ -67,8 +67,8 @@ export class ApplicationSearch implements Extension {
             "en-US": {
                 extensionName: "Application Search",
                 searchResultItemDescription: "Application",
-                openApplication: "Open application",
-                openApplicationAsAdministrator: "Open application as administrator",
+                openApplication: "Open",
+                openApplicationAsAdministrator: "Open as admin",
                 copyFilePathToClipboard: "Copy file path to clipboard",
                 advanced: "Advanced",
             },
diff --git a/src/main/Extensions/ApplicationSearch/Linux/LinuxApplication.ts b/src/main/Extensions/ApplicationSearch/Linux/LinuxApplication.ts
index 826e128f..f5b8a1ab 100644
--- a/src/main/Extensions/ApplicationSearch/Linux/LinuxApplication.ts
+++ b/src/main/Extensions/ApplicationSearch/Linux/LinuxApplication.ts
@@ -30,7 +30,7 @@ export class LinuxApplication implements Application {
                 },
             }),
             additionalActions: [
-                createShowItemInFileExplorerAction({ filePath: this.filePath }),
+                createShowItemInFileExplorerAction({ filePath: this.filePath, keyboardShortcut: "Ctrl+O" }),
                 createCopyToClipboardAction({
                     textToCopy: this.filePath,
                     description: "Copy file path to clipboard",
@@ -38,6 +38,7 @@ export class LinuxApplication implements Application {
                         key: "copyFilePathToClipboard",
                         namespace: "extension[ApplicationSearch]",
                     },
+                    keyboardShortcut: "Ctrl+C",
                 }),
             ],
         };
diff --git a/src/main/Extensions/ApplicationSearch/Windows/WindowsApplication.ts b/src/main/Extensions/ApplicationSearch/Windows/WindowsApplication.ts
index 36cb63df..28c99ee6 100644
--- a/src/main/Extensions/ApplicationSearch/Windows/WindowsApplication.ts
+++ b/src/main/Extensions/ApplicationSearch/Windows/WindowsApplication.ts
@@ -43,8 +43,9 @@ export class WindowsApplication implements Application {
                     fluentIcon: "ShieldPersonRegular",
                     hideWindowAfterInvocation: true,
                     handlerId: "WindowsOpenAsAdministrator",
+                    keyboardShortcut: "Shift+Enter",
                 },
-                createShowItemInFileExplorerAction({ filePath: this.filePath }),
+                createShowItemInFileExplorerAction({ filePath: this.filePath, keyboardShortcut: "Ctrl+O" }),
                 createCopyToClipboardAction({
                     textToCopy: this.filePath,
                     description: "Copy file path to clipboard",
@@ -52,6 +53,7 @@ export class WindowsApplication implements Application {
                         key: "copyFilePathToClipboard",
                         namespace: "extension[ApplicationSearch]",
                     },
+                    keyboardShortcut: "Ctrl+C",
                 }),
             ],
         };
diff --git a/src/main/Extensions/ApplicationSearch/macOS/MacOsApplication.ts b/src/main/Extensions/ApplicationSearch/macOS/MacOsApplication.ts
index 3c48d6a9..060eac0d 100644
--- a/src/main/Extensions/ApplicationSearch/macOS/MacOsApplication.ts
+++ b/src/main/Extensions/ApplicationSearch/macOS/MacOsApplication.ts
@@ -33,7 +33,7 @@ export class MacOsApplication implements Application {
                 },
             }),
             additionalActions: [
-                createShowItemInFileExplorerAction({ filePath: this.filePath }),
+                createShowItemInFileExplorerAction({ filePath: this.filePath, keyboardShortcut: "Cmd+O" }),
                 createCopyToClipboardAction({
                     textToCopy: this.filePath,
                     description: "Copy file path to clipboard",
@@ -41,6 +41,7 @@ export class MacOsApplication implements Application {
                         key: "copyFilePathToClipboard",
                         namespace: "extension[ApplicationSearch]",
                     },
+                    keyboardShortcut: "Cmd+C",
                 }),
             ],
         };
diff --git a/src/main/Extensions/BrowserBookmarks/BrowserBookmarks.ts b/src/main/Extensions/BrowserBookmarks/BrowserBookmarks.ts
index ce01a38d..5759beab 100644
--- a/src/main/Extensions/BrowserBookmarks/BrowserBookmarks.ts
+++ b/src/main/Extensions/BrowserBookmarks/BrowserBookmarks.ts
@@ -54,6 +54,12 @@ export class BrowserBookmarks implements Extension {
         const { t } = this.translator.createT(this.getI18nResources());
         const browsers = this.getCurrentlyConfiguredBrowsers();
 
+        const keyboardShortcuts: Record<OperatingSystem, Record<string, string>> = {
+            Linux: { copyUrlToClipboard: "Ctrl+C" },
+            macOS: { copyUrlToClipboard: "Cmd+C" },
+            Windows: { copyUrlToClipboard: "Ctrl+C" },
+        };
+
         const toSearchResultItem = (browserBookmark: BrowserBookmark, browserName: Browser) => {
             return {
                 description: t("searchResultItemDescription", { browserName }),
@@ -71,6 +77,7 @@ export class BrowserBookmarks implements Extension {
                             key: "copyUrlToClipboard",
                             namespace: "extension[BrowserBookmarks]",
                         },
+                        keyboardShortcut: keyboardShortcuts[this.operatingSystem].copyUrlToClipboard,
                     }),
                 ],
             };
diff --git a/src/main/Extensions/SimpleFileSearch/SimpleFileSearchExtension.ts b/src/main/Extensions/SimpleFileSearch/SimpleFileSearchExtension.ts
index 2a32c0ef..da5091b9 100644
--- a/src/main/Extensions/SimpleFileSearch/SimpleFileSearchExtension.ts
+++ b/src/main/Extensions/SimpleFileSearch/SimpleFileSearchExtension.ts
@@ -53,6 +53,12 @@ export class SimpleFileSearchExtension implements Extension {
 
         const searchResultItems: SearchResultItem[] = [];
 
+        const keyboardShortcuts: Record<OperatingSystem, Record<"showItemInFileExplorer", string>> = {
+            Linux: { showItemInFileExplorer: "Ctrl+O" },
+            macOS: { showItemInFileExplorer: "Cmd+O" },
+            Windows: { showItemInFileExplorer: "Ctrl+O" },
+        };
+
         for (const folderSettingId of Object.keys(filePathsGroupedByFolderSettingId)) {
             const folderSetting = folderSettings.find(({ id }) => id === folderSettingId);
 
@@ -70,7 +76,12 @@ export class SimpleFileSearchExtension implements Extension {
                         filePath,
                         description: types[filePath] === "folder" ? t("openFolder") : t("openFile"),
                     }),
-                    additionalActions: [createShowItemInFileExplorerAction({ filePath })],
+                    additionalActions: [
+                        createShowItemInFileExplorerAction({
+                            filePath,
+                            keyboardShortcut: keyboardShortcuts[this.operatingSystem].showItemInFileExplorer,
+                        }),
+                    ],
                 });
             }
         }
diff --git a/src/renderer/Core/Components/KeyboardShortcut.tsx b/src/renderer/Core/Components/KeyboardShortcut.tsx
index 6fbbc22d..a77c27cd 100644
--- a/src/renderer/Core/Components/KeyboardShortcut.tsx
+++ b/src/renderer/Core/Components/KeyboardShortcut.tsx
@@ -1,12 +1,21 @@
 import { tokens } from "@fluentui/react-components";
+import type { CSSProperties } from "react";
 
 type KeyboardShortcutProps = {
     shortcut: string;
+    style?: CSSProperties;
 };
 
-export const KeyboardShortcut = ({ shortcut }: KeyboardShortcutProps) => {
+export const KeyboardShortcut = ({ shortcut, style }: KeyboardShortcutProps) => {
+    shortcut = shortcut.replace("Ctrl", "^");
+    shortcut = shortcut.replace("Cmd", "⌘");
+    shortcut = shortcut.replace("Shift", "⇧");
+    shortcut = shortcut.replace("Alt", "⌥");
+    shortcut = shortcut.replace("Enter", "↵");
+    shortcut = shortcut.replace("Backspace", "⌫");
+
     return (
-        <div style={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 2, paddingTop: 2 }}>
+        <div style={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 2, ...style }}>
             {shortcut.split("+").map((part) => (
                 <kbd
                     key={`keyboard-shortcut-part-${part}`}
@@ -14,6 +23,7 @@ export const KeyboardShortcut = ({ shortcut }: KeyboardShortcutProps) => {
                         backgroundColor: tokens.colorNeutralBackground5,
                         borderRadius: tokens.borderRadiusMedium,
                         padding: "0px 5px",
+                        fontSize: tokens.fontSizeBase200,
                     }}
                 >
                     {part}
diff --git a/src/renderer/Core/I18n/getCoreResources.ts b/src/renderer/Core/I18n/getCoreResources.ts
index 6c444f9e..a33d4a16 100644
--- a/src/renderer/Core/I18n/getCoreResources.ts
+++ b/src/renderer/Core/I18n/getCoreResources.ts
@@ -54,6 +54,7 @@ export const getCoreResources = (): { namespace: string; resources: Resources<Tr
                     hotkeyBinding: "Hotkey binding",
                     validHotkeyBinding: "Valid hotkey",
                     invalidHotkeyBinding: "Invalid hotkey",
+                    invalidHotkeyBindingAltGr: "Use Ctrl+Alt instead of AltGr",
                     saveHotkeyBinding: "Save",
                     hotkeyBindingMoreInfo: "More info",
                     language: "Language",
@@ -80,6 +81,7 @@ export const getCoreResources = (): { namespace: string; resources: Resources<Tr
                     hotkeyBinding: "Tastenkombination",
                     validHotkeyBinding: "Gültige Tastenkombination",
                     invalidHotkeyBinding: "Ungültige Tastenkombination",
+                    invalidHotkeyBindingAltGr: "Benutze Ctrl+Alt anstatt AltGr",
                     saveHotkeyBinding: "Speichern",
                     hotkeyBindingMoreInfo: "Mehr Informationen",
                     language: "Sprache",
diff --git a/src/renderer/Core/Search/ActionsMenu/ActionsMenu.tsx b/src/renderer/Core/Search/ActionsMenu/ActionsMenu.tsx
index 43dc3a15..9c28dba6 100644
--- a/src/renderer/Core/Search/ActionsMenu/ActionsMenu.tsx
+++ b/src/renderer/Core/Search/ActionsMenu/ActionsMenu.tsx
@@ -1,5 +1,5 @@
 import { KeyboardShortcut } from "@Core/Components";
-import { type SearchResultItem, type SearchResultItemAction } from "@common/Core";
+import { type SearchResultItemAction } from "@common/Core";
 import {
     Button,
     Menu,
@@ -7,6 +7,7 @@ import {
     MenuList,
     MenuPopover,
     MenuTrigger,
+    Text,
     Toast,
     ToastTitle,
     Toaster,
@@ -18,11 +19,9 @@ import { MoreVerticalFilled } from "@fluentui/react-icons";
 import { useEffect, type Ref } from "react";
 import { useTranslation } from "react-i18next";
 import { FluentIcon } from "../FluentIcon";
-import { getActions } from "./getActions";
 
 type AdditionalActionsProps = {
-    searchResultItem?: SearchResultItem;
-    favorites: string[];
+    actions: SearchResultItemAction[];
     invokeAction: (action: SearchResultItemAction) => void;
     additionalActionsButtonRef: Ref<HTMLButtonElement>;
     open: boolean;
@@ -31,8 +30,7 @@ type AdditionalActionsProps = {
 };
 
 export const ActionsMenu = ({
-    searchResultItem,
-    favorites,
+    actions,
     invokeAction,
     additionalActionsButtonRef,
     open,
@@ -44,8 +42,6 @@ export const ActionsMenu = ({
     const toasterId = useId("copiedToClipboardToasterId");
     const { dispatchToast } = useToastController(toasterId);
 
-    const actions = searchResultItem ? getActions(searchResultItem, favorites) : [];
-
     useEffect(() => {
         const copiedToClipboardHandler = () =>
             dispatchToast(
@@ -73,7 +69,7 @@ export const ActionsMenu = ({
                         content={
                             <div style={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 8 }}>
                                 {t("actions", { ns: "general" })}
-                                <KeyboardShortcut shortcut={keyboardShortcut} />
+                                <KeyboardShortcut shortcut={keyboardShortcut} style={{ paddingTop: 2 }} />
                             </div>
                         }
                         relationship="label"
@@ -94,13 +90,30 @@ export const ActionsMenu = ({
                             <MenuItem
                                 key={`additional-action-${action.argument}-${action.handlerId}`}
                                 onClick={() => invokeAction(action)}
-                                icon={action.fluentIcon ? <FluentIcon icon={action.fluentIcon} /> : undefined}
+                                icon={
+                                    action.fluentIcon ? (
+                                        <FluentIcon fontSize={16} icon={action.fluentIcon} />
+                                    ) : undefined
+                                }
                             >
-                                {action.descriptionTranslation
-                                    ? t(action.descriptionTranslation.key, {
-                                          ns: action.descriptionTranslation.namespace,
-                                      })
-                                    : action.description}
+                                <div
+                                    style={{
+                                        display: "flex",
+                                        justifyContent: "space-between",
+                                        alignItems: "center",
+                                        gap: 8,
+                                        width: "100%",
+                                    }}
+                                >
+                                    <Text weight="medium" size={200} wrap={false}>
+                                        {action.descriptionTranslation
+                                            ? t(action.descriptionTranslation.key, {
+                                                  ns: action.descriptionTranslation.namespace,
+                                              })
+                                            : action.description}
+                                    </Text>
+                                    {action.keyboardShortcut && <KeyboardShortcut shortcut={action.keyboardShortcut} />}
+                                </div>
                             </MenuItem>
                         ))}
                     </MenuList>
diff --git a/src/renderer/Core/Search/ActionsMenu/getActions.ts b/src/renderer/Core/Search/ActionsMenu/getActions.ts
deleted file mode 100644
index 4a14acaa..00000000
--- a/src/renderer/Core/Search/ActionsMenu/getActions.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import {
-    createAddToFavoritesAction,
-    createExcludeFromSearchResultsAction,
-    createRemoveFromFavoritesAction,
-    type SearchResultItem,
-    type SearchResultItemAction,
-} from "@common/Core";
-
-export const getActions = (searchResultItem: SearchResultItem, favorites: string[]): SearchResultItemAction[] => {
-    const defaultAdditionalActions = [
-        favorites.includes(searchResultItem.id)
-            ? createRemoveFromFavoritesAction({ id: searchResultItem.id })
-            : createAddToFavoritesAction(searchResultItem),
-        createExcludeFromSearchResultsAction(searchResultItem),
-    ];
-
-    return [searchResultItem.defaultAction, ...(searchResultItem.additionalActions ?? []), ...defaultAdditionalActions];
-};
diff --git a/src/renderer/Core/Search/FluentIcon.tsx b/src/renderer/Core/Search/FluentIcon.tsx
index 5919f0a9..935859f2 100644
--- a/src/renderer/Core/Search/FluentIcon.tsx
+++ b/src/renderer/Core/Search/FluentIcon.tsx
@@ -19,24 +19,24 @@ import {
 } from "@fluentui/react-icons";
 import type { ReactElement } from "react";
 
-export const FluentIcon = ({ icon }: { icon: FluentIconString }) => {
+export const FluentIcon = ({ icon, fontSize }: { fontSize?: number; icon: FluentIconString }) => {
     const icons: Record<FluentIconString, ReactElement> = {
-        AppsAddInRegular: <AppsAddInRegular />,
-        ArrowClockwiseRegular: <ArrowClockwiseRegular />,
-        ArrowSquareUpRightRegular: <ArrowSquareUpRightRegular />,
-        CheckmarkCircleRegular: <CheckmarkCircleRegular />,
-        CopyRegular: <CopyRegular />,
-        DeleteDismissRegular: <DeleteDismissRegular />,
-        DismissCircleRegular: <DismissCircleRegular />,
-        DocumentFolderRegular: <DocumentFolderRegular />,
-        EyeOffRegular: <EyeOffRegular />,
-        OpenRegular: <OpenRegular />,
-        SettingsRegular: <SettingsRegular />,
-        ShieldPersonRegular: <ShieldPersonRegular />,
-        StarOffRegular: <StarOffRegular />,
-        StarRegular: <StarRegular />,
-        ToggleMultipleRegular: <ToggleMultipleRegular />,
-        WindowConsoleRegular: <WindowConsoleRegular />,
+        AppsAddInRegular: <AppsAddInRegular fontSize={fontSize} />,
+        ArrowClockwiseRegular: <ArrowClockwiseRegular fontSize={fontSize} />,
+        ArrowSquareUpRightRegular: <ArrowSquareUpRightRegular fontSize={fontSize} />,
+        CheckmarkCircleRegular: <CheckmarkCircleRegular fontSize={fontSize} />,
+        CopyRegular: <CopyRegular fontSize={fontSize} />,
+        DeleteDismissRegular: <DeleteDismissRegular fontSize={fontSize} />,
+        DismissCircleRegular: <DismissCircleRegular fontSize={fontSize} />,
+        DocumentFolderRegular: <DocumentFolderRegular fontSize={fontSize} />,
+        EyeOffRegular: <EyeOffRegular fontSize={fontSize} />,
+        OpenRegular: <OpenRegular fontSize={fontSize} />,
+        SettingsRegular: <SettingsRegular fontSize={fontSize} />,
+        ShieldPersonRegular: <ShieldPersonRegular fontSize={fontSize} />,
+        StarOffRegular: <StarOffRegular fontSize={fontSize} />,
+        StarRegular: <StarRegular fontSize={fontSize} />,
+        ToggleMultipleRegular: <ToggleMultipleRegular fontSize={fontSize} />,
+        WindowConsoleRegular: <WindowConsoleRegular fontSize={fontSize} />,
     };
 
     return icons[icon] ?? <>Unknown Icon</>;
diff --git a/src/renderer/Core/Search/Helpers/KeyboardShortcut.ts b/src/renderer/Core/Search/Helpers/KeyboardShortcut.ts
new file mode 100644
index 00000000..d592ecc2
--- /dev/null
+++ b/src/renderer/Core/Search/Helpers/KeyboardShortcut.ts
@@ -0,0 +1,4 @@
+export type KeyboardShortcut = {
+    modifier?: string;
+    key: string;
+};
diff --git a/src/renderer/Core/Search/Helpers/deserializeKeyboardShortcut.ts b/src/renderer/Core/Search/Helpers/deserializeKeyboardShortcut.ts
new file mode 100644
index 00000000..ddcdd0d5
--- /dev/null
+++ b/src/renderer/Core/Search/Helpers/deserializeKeyboardShortcut.ts
@@ -0,0 +1,10 @@
+import type { KeyboardShortcut } from "./KeyboardShortcut";
+
+export const deserializeKeyboardShortcut = (keyboardShortcut: string): KeyboardShortcut => {
+    const [modifier, key] = keyboardShortcut.split("+");
+
+    return {
+        modifier: key ? modifier : undefined,
+        key: key ? key : modifier,
+    };
+};
diff --git a/src/renderer/Core/Search/Helpers/getActions.ts b/src/renderer/Core/Search/Helpers/getActions.ts
new file mode 100644
index 00000000..654663b0
--- /dev/null
+++ b/src/renderer/Core/Search/Helpers/getActions.ts
@@ -0,0 +1,35 @@
+import {
+    createAddToFavoritesAction,
+    createExcludeFromSearchResultsAction,
+    createRemoveFromFavoritesAction,
+    type SearchResultItem,
+    type SearchResultItemAction,
+} from "@common/Core";
+
+export const getActions = (
+    searchResultItem: SearchResultItem,
+    favorites: string[],
+    keyboardShortcuts: Record<"addToFavorites" | "excludeFromSearchResults", string>,
+): SearchResultItemAction[] => {
+    const defaultAdditionalActions = [
+        favorites.includes(searchResultItem.id)
+            ? createRemoveFromFavoritesAction({
+                  id: searchResultItem.id,
+                  keyboardShortcut: keyboardShortcuts.addToFavorites,
+              })
+            : createAddToFavoritesAction({
+                  id: searchResultItem.id,
+                  keyboardShortcut: keyboardShortcuts.addToFavorites,
+              }),
+        createExcludeFromSearchResultsAction({
+            id: searchResultItem.id,
+            keyboardShortcut: keyboardShortcuts.excludeFromSearchResults,
+        }),
+    ];
+
+    return [
+        { ...searchResultItem.defaultAction, keyboardShortcut: "Enter" },
+        ...(searchResultItem.additionalActions ?? []),
+        ...defaultAdditionalActions,
+    ];
+};
diff --git a/src/renderer/Core/Search/Helpers/getSearchResultItemActionByKeyboardshortcut.ts b/src/renderer/Core/Search/Helpers/getSearchResultItemActionByKeyboardshortcut.ts
new file mode 100644
index 00000000..87d3eb9e
--- /dev/null
+++ b/src/renderer/Core/Search/Helpers/getSearchResultItemActionByKeyboardshortcut.ts
@@ -0,0 +1,18 @@
+import type { SearchResultItemAction } from "@common/Core";
+import type { KeyboardEvent } from "react";
+import { keyboardEventMatchesKeyboardShortcut } from "./keyboardEventMatchesKeyboardShortcut";
+
+export const getSearchResultItemActionByKeyboardshortcut = (
+    keyboardEvent: KeyboardEvent,
+    actions: SearchResultItemAction[],
+): SearchResultItemAction | undefined => {
+    for (const action of actions) {
+        if (action.keyboardShortcut) {
+            if (keyboardEventMatchesKeyboardShortcut(keyboardEvent, action.keyboardShortcut)) {
+                return action;
+            }
+        }
+    }
+
+    return undefined;
+};
diff --git a/src/renderer/Core/Search/Helpers/index.ts b/src/renderer/Core/Search/Helpers/index.ts
index 595c4846..57d1d858 100644
--- a/src/renderer/Core/Search/Helpers/index.ts
+++ b/src/renderer/Core/Search/Helpers/index.ts
@@ -1,1 +1,5 @@
 export * from "./elementIsVisible";
+export * from "./getActions";
+export * from "./getNextSearchResultItemId";
+export * from "./getPreviousSearchResultItemId";
+export * from "./getSearchResultItemActionByKeyboardshortcut";
diff --git a/src/renderer/Core/Search/Helpers/keyboardEventMatchesKeyboardShortcut.ts b/src/renderer/Core/Search/Helpers/keyboardEventMatchesKeyboardShortcut.ts
new file mode 100644
index 00000000..42ac50ae
--- /dev/null
+++ b/src/renderer/Core/Search/Helpers/keyboardEventMatchesKeyboardShortcut.ts
@@ -0,0 +1,16 @@
+import type { KeyboardEvent } from "react";
+import { deserializeKeyboardShortcut } from "./deserializeKeyboardShortcut";
+
+export const keyboardEventMatchesKeyboardShortcut = (keyboardEvent: KeyboardEvent, keyboardShortcut: string) => {
+    const { modifier, key } = deserializeKeyboardShortcut(keyboardShortcut);
+
+    const conditions: (() => boolean)[] = [
+        () => keyboardEvent.key.toLowerCase() === key.toLowerCase(),
+        () => !!keyboardEvent.ctrlKey === (modifier === "Ctrl"),
+        () => !!keyboardEvent.metaKey === (modifier === "Cmd"),
+        () => !!keyboardEvent.shiftKey === (modifier === "Shift"),
+        () => !!keyboardEvent.altKey === (modifier === "Alt"),
+    ];
+
+    return conditions.every((c) => c());
+};
diff --git a/src/renderer/Core/Search/KeyboardEventHandler.ts b/src/renderer/Core/Search/KeyboardEventHandler.ts
index 759cb11c..4edaef0e 100644
--- a/src/renderer/Core/Search/KeyboardEventHandler.ts
+++ b/src/renderer/Core/Search/KeyboardEventHandler.ts
@@ -1,6 +1,10 @@
 import type { KeyboardEvent } from "react";
 
+export type KeyboardEventHandlerReturnType = {
+    shouldInvokeAction: boolean;
+    action: () => void;
+};
+
 export type KeyboardEventHandler = {
-    needsToInvokeListener: (keyboardEvent: KeyboardEvent<HTMLElement>) => boolean;
-    listener: () => void;
+    check: (keyboardEvent: KeyboardEvent) => KeyboardEventHandlerReturnType;
 };
diff --git a/src/renderer/Core/Search/Search.tsx b/src/renderer/Core/Search/Search.tsx
index 697428f4..5a49365e 100644
--- a/src/renderer/Core/Search/Search.tsx
+++ b/src/renderer/Core/Search/Search.tsx
@@ -9,6 +9,7 @@ import { BaseLayout } from "../BaseLayout";
 import { Footer } from "../Footer";
 import { ActionsMenu } from "./ActionsMenu";
 import { ConfirmationDialog } from "./ConfirmationDialog";
+import { getSearchResultItemActionByKeyboardshortcut } from "./Helpers";
 import type { KeyboardEventHandler } from "./KeyboardEventHandler";
 import { RescanIndicator } from "./RescanIndicator";
 import { SearchBar } from "./SearchBar";
@@ -49,49 +50,71 @@ export const Search = ({
         searchResultItems,
         excludedSearchResultItemIds,
         favoriteSearchResultItemIds,
+        operatingSystem,
     });
 
     const searchHistory = useSearchHistoryController();
     const containerRef = useRef<HTMLDivElement>(null);
     const additionalActionsButtonRef = useRef<HTMLButtonElement>(null);
 
-    const openSettings = () => window.ContextBridge.openSettings();
-
     const handleUserInputKeyDownEvent = (keyboardEvent: ReactKeyboardEvent<HTMLElement>) => {
         const eventHandlers: KeyboardEventHandler[] = [
             {
-                listener: () => window.ContextBridge.ipcRenderer.send("escapePressed"),
-                needsToInvokeListener: (keyboardEvent) => keyboardEvent.key === "Escape",
+                check: (keyboardEvent) => ({
+                    shouldInvokeAction: keyboardEvent.key === "Escape",
+                    action: () => window.ContextBridge.ipcRenderer.send("escapePressed"),
+                }),
             },
             {
-                listener: () => selectedItemId.previous(),
-                needsToInvokeListener: (keyboardEvent) => keyboardEvent.key === "ArrowUp",
+                check: (keyboardEvent) => ({
+                    shouldInvokeAction: keyboardEvent.key === "ArrowUp",
+                    action: () => selectedItemId.previous(),
+                }),
             },
             {
-                listener: () => selectedItemId.previous(),
-                needsToInvokeListener: (keyboardEvent) => keyboardEvent.ctrlKey && keyboardEvent.key === "p",
+                check: (keyboardEvent) => ({
+                    shouldInvokeAction: keyboardEvent.ctrlKey && keyboardEvent.key === "p",
+                    action: () => selectedItemId.previous(),
+                }),
             },
             {
-                listener: () => selectedItemId.next(),
-                needsToInvokeListener: (keyboardEvent) => keyboardEvent.key === "ArrowDown",
+                check: (keyboardEvent) => ({
+                    shouldInvokeAction: keyboardEvent.key === "ArrowDown",
+                    action: () => selectedItemId.next(),
+                }),
             },
             {
-                listener: () => selectedItemId.next(),
-                needsToInvokeListener: (keyboardEvent) => keyboardEvent.ctrlKey && keyboardEvent.key === "n",
+                check: (keyboardEvent) => ({
+                    shouldInvokeAction: keyboardEvent.ctrlKey && keyboardEvent.key === "n",
+                    action: () => selectedItemId.next(),
+                }),
             },
             {
-                listener: async () => {
-                    searchHistory.add(searchTerm.value);
-                    await invokeSelectedSearchResultItem();
+                check: (keyboardEvent) => {
+                    const searchResultItemAction = getSearchResultItemActionByKeyboardshortcut(
+                        keyboardEvent,
+                        searchResult.currentActions(),
+                    );
+
+                    return {
+                        shouldInvokeAction: searchResultItemAction !== undefined,
+                        action: () => {
+                            if (searchResultItemAction !== undefined) {
+                                invokeAction(searchResultItemAction);
+                            }
+                        },
+                    };
                 },
-                needsToInvokeListener: (keyboardEvent) => keyboardEvent.key === "Enter",
             },
         ];
 
         for (const eventHandler of eventHandlers) {
-            if (eventHandler.needsToInvokeListener(keyboardEvent)) {
+            const { shouldInvokeAction, action } = eventHandler.check(keyboardEvent);
+
+            if (shouldInvokeAction) {
                 keyboardEvent.preventDefault();
-                eventHandler.listener();
+                action();
+                break;
             }
         }
     };
@@ -147,7 +170,7 @@ export const Search = ({
                     : event.key === "," && event.ctrlKey,
             action: (event) => {
                 event.preventDefault();
-                openSettings();
+                window.ContextBridge.openSettings();
             },
         },
         {
@@ -328,14 +351,17 @@ export const Search = ({
                             content={
                                 <div style={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 8 }}>
                                     {t("settings", { ns: "general" })}
-                                    <KeyboardShortcut shortcut={keyboardShortcuts["openSettings"][operatingSystem]} />
+                                    <KeyboardShortcut
+                                        shortcut={keyboardShortcuts["openSettings"][operatingSystem]}
+                                        style={{ paddingTop: 2 }}
+                                    />
                                 </div>
                             }
                             relationship="label"
                         >
                             <Button
                                 className="non-draggable-area"
-                                onClick={openSettings}
+                                onClick={() => window.ContextBridge.openSettings()}
                                 size="small"
                                 appearance="subtle"
                                 icon={<Settings16Regular />}
@@ -362,8 +388,7 @@ export const Search = ({
                         ) : null}
                         <Divider appearance="subtle" vertical />
                         <ActionsMenu
-                            searchResultItem={searchResult.current()}
-                            favorites={favoriteSearchResultItemIds}
+                            actions={searchResult.currentActions()}
                             invokeAction={invokeAction}
                             additionalActionsButtonRef={additionalActionsButtonRef}
                             open={additionalActionsMenuIsOpen}
diff --git a/src/renderer/Core/Search/SearchViewController.ts b/src/renderer/Core/Search/SearchViewController.ts
index 42a2ea19..01391dc2 100644
--- a/src/renderer/Core/Search/SearchViewController.ts
+++ b/src/renderer/Core/Search/SearchViewController.ts
@@ -1,9 +1,8 @@
 import { useSetting } from "@Core/Hooks";
-import type { SearchResultItem, SearchResultItemAction } from "@common/Core";
+import type { OperatingSystem, SearchResultItem, SearchResultItemAction } from "@common/Core";
 import type { SearchEngineId } from "@common/Core/Search";
 import { useRef, useState } from "react";
-import { getNextSearchResultItemId } from "./Helpers/getNextSearchResultItemId";
-import { getPreviousSearchResultItemId } from "./Helpers/getPreviousSearchResultItemId";
+import { getActions, getNextSearchResultItemId, getPreviousSearchResultItemId } from "./Helpers";
 import { getSearchResult } from "./Helpers/getSearchResult";
 
 type ViewModel = {
@@ -16,6 +15,7 @@ type SearchViewControllerProps = {
     searchResultItems: SearchResultItem[];
     excludedSearchResultItemIds: string[];
     favoriteSearchResultItemIds: string[];
+    operatingSystem: OperatingSystem;
 };
 
 const collectSearchResultItems = (searchResult: Record<string, SearchResultItem[]>) => {
@@ -32,6 +32,7 @@ export const useSearchViewController = ({
     searchResultItems,
     excludedSearchResultItemIds,
     favoriteSearchResultItemIds,
+    operatingSystem,
 }: SearchViewControllerProps) => {
     const [viewModel, setViewModel] = useState<ViewModel>({
         searchResult: {},
@@ -39,6 +40,21 @@ export const useSearchViewController = ({
         selectedItemId: "",
     });
 
+    const keyboardShortcuts: Record<OperatingSystem, Record<"addToFavorites" | "excludeFromSearchResults", string>> = {
+        Linux: {
+            addToFavorites: "Ctrl+F",
+            excludeFromSearchResults: "Ctrl+Backspace",
+        },
+        macOS: {
+            addToFavorites: "Cmd+F",
+            excludeFromSearchResults: "Cmd+Backspace",
+        },
+        Windows: {
+            addToFavorites: "Ctrl+F",
+            excludeFromSearchResults: "Ctrl+Backspace",
+        },
+    };
+
     const userInputRef = useRef<HTMLInputElement>(null);
 
     const setSearchTerm = (searchTerm: string) => setViewModel({ ...viewModel, searchTerm });
@@ -61,6 +77,13 @@ export const useSearchViewController = ({
     const getSelectedSearchResultItem = (): SearchResultItem | undefined =>
         collectSearchResultItems(viewModel.searchResult).find((s) => s.id === viewModel.selectedItemId);
 
+    const getSelectedSearchResultItemActions = (): SearchResultItemAction[] => {
+        const selectedSearchResultItem = getSelectedSearchResultItem();
+        return selectedSearchResultItem
+            ? getActions(selectedSearchResultItem, favoriteSearchResultItemIds, keyboardShortcuts[operatingSystem])
+            : [];
+    };
+
     const { value: fuzziness } = useSetting({ key: "searchEngine.fuzziness", defaultValue: 0.5 });
     const { value: maxSearchResultItems } = useSetting({ key: "searchEngine.maxResultLength", defaultValue: 50 });
     const { value: searchEngineId } = useSetting<SearchEngineId>({ key: "searchEngine.id", defaultValue: "fuzzysort" });
@@ -130,6 +153,7 @@ export const useSearchViewController = ({
             value: viewModel.searchResult,
             set: setSearchResult,
             current: () => getSelectedSearchResultItem(),
+            currentActions: () => getSelectedSearchResultItemActions(),
         },
         userInput: {
             ref: userInputRef,
diff --git a/src/renderer/Core/Settings/Pages/General/HotKey/HotkeyBinding.tsx b/src/renderer/Core/Settings/Pages/General/HotKey/HotkeyBinding.tsx
index 40b322ca..d53c4e5f 100644
--- a/src/renderer/Core/Settings/Pages/General/HotKey/HotkeyBinding.tsx
+++ b/src/renderer/Core/Settings/Pages/General/HotKey/HotkeyBinding.tsx
@@ -17,14 +17,16 @@ export const HotKeyBinding = ({ hotkeyEnabled }: HotkeyBindingProps) => {
 
     const [temporaryHotkey, setTemporaryHotkey] = useState<string>(hotkey);
 
+    const invalidHotkeyBinding = temporaryHotkey.includes("AltGr")
+        ? t("invalidHotkeyBindingAltGr")
+        : t("invalidHotkeyBinding");
+
     return (
         <Setting
             label={t("hotkeyBinding")}
             control={
                 <Field
-                    validationMessage={
-                        isValidHotkey(temporaryHotkey) ? t("validHotkeyBinding") : t("invalidHotkeyBinding")
-                    }
+                    validationMessage={isValidHotkey(temporaryHotkey) ? t("validHotkeyBinding") : invalidHotkeyBinding}
                     validationState={isValidHotkey(temporaryHotkey) ? "success" : "error"}
                 >
                     <Input


Test patch:
diff --git a/src/common/Core/SearchResultItemActionUtility/createAddToFavoritesAction.test.ts b/src/common/Core/SearchResultItemActionUtility/createAddToFavoritesAction.test.ts
index b0094244..cc9789d8 100644
--- a/src/common/Core/SearchResultItemActionUtility/createAddToFavoritesAction.test.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createAddToFavoritesAction.test.ts
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
 
 describe(createAddToFavoritesAction, () => {
     it("should create an 'add to favorites' action", () => {
-        const actual = createAddToFavoritesAction({ id: "id_1" });
+        const actual = createAddToFavoritesAction({ id: "id_1", keyboardShortcut: "keyboard-shortcut-1" });
 
         const expected = <SearchResultItemAction>{
             argument: JSON.stringify({ action: "Add", id: "id_1" }),
@@ -14,6 +14,7 @@ describe(createAddToFavoritesAction, () => {
             },
             handlerId: "Favorites",
             fluentIcon: "StarRegular",
+            keyboardShortcut: "keyboard-shortcut-1",
         };
 
         expect(actual).toEqual(expected);
diff --git a/src/common/Core/SearchResultItemActionUtility/createCopyToClipboardAction.test.ts b/src/common/Core/SearchResultItemActionUtility/createCopyToClipboardAction.test.ts
index 87585712..797705b3 100644
--- a/src/common/Core/SearchResultItemActionUtility/createCopyToClipboardAction.test.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createCopyToClipboardAction.test.ts
@@ -10,6 +10,7 @@ describe(createCopyToClipboardAction, () => {
                 key: "translation key",
                 namespace: "translation namespace",
             },
+            keyboardShortcut: "test-keyboard-shortcut",
         });
 
         const expected = <SearchResultItemAction>{
@@ -21,6 +22,7 @@ describe(createCopyToClipboardAction, () => {
             },
             fluentIcon: "CopyRegular",
             handlerId: "copyToClipboard",
+            keyboardShortcut: "test-keyboard-shortcut",
         };
 
         expect(actual).toEqual(expected);
diff --git a/src/common/Core/SearchResultItemActionUtility/createExcludeFromSearchResultsAction.test.ts b/src/common/Core/SearchResultItemActionUtility/createExcludeFromSearchResultsAction.test.ts
index c32d1a59..b7c43ca4 100644
--- a/src/common/Core/SearchResultItemActionUtility/createExcludeFromSearchResultsAction.test.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createExcludeFromSearchResultsAction.test.ts
@@ -3,7 +3,10 @@ import { describe, expect, it } from "vitest";
 
 describe(createExcludeFromSearchResultsAction, () => {
     it("should create an 'exclude from search results' action", () => {
-        const actual = createExcludeFromSearchResultsAction({ id: "test id" });
+        const actual = createExcludeFromSearchResultsAction({
+            id: "test id",
+            keyboardShortcut: "test-keyboard-shortcut",
+        });
 
         const expected = <SearchResultItemAction>{
             argument: "test id",
@@ -14,6 +17,7 @@ describe(createExcludeFromSearchResultsAction, () => {
             },
             fluentIcon: "EyeOffRegular",
             handlerId: "excludeFromSearchResults",
+            keyboardShortcut: "test-keyboard-shortcut",
         };
 
         expect(actual).toEqual(expected);
diff --git a/src/common/Core/SearchResultItemActionUtility/createOpenFileAction.test.ts b/src/common/Core/SearchResultItemActionUtility/createOpenFileAction.test.ts
index 4f607e87..4bb7511b 100644
--- a/src/common/Core/SearchResultItemActionUtility/createOpenFileAction.test.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createOpenFileAction.test.ts
@@ -10,6 +10,7 @@ describe(createOpenFileAction, () => {
                 key: "test translation key",
                 namespace: "test namespace",
             },
+            keyboardShortcut: "test keyboard shortcut",
         });
 
         const expected = <SearchResultItemAction>{
@@ -22,6 +23,7 @@ describe(createOpenFileAction, () => {
             handlerId: "OpenFilePath",
             fluentIcon: "OpenRegular",
             hideWindowAfterInvocation: true,
+            keyboardShortcut: "test keyboard shortcut",
         };
 
         expect(actual).toEqual(expected);
diff --git a/src/common/Core/SearchResultItemActionUtility/createRemoveFromFavoritesAction.test.ts b/src/common/Core/SearchResultItemActionUtility/createRemoveFromFavoritesAction.test.ts
index f6aead96..e203ca03 100644
--- a/src/common/Core/SearchResultItemActionUtility/createRemoveFromFavoritesAction.test.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createRemoveFromFavoritesAction.test.ts
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
 
 describe(createRemoveFromFavoritesAction, () => {
     it("should create a 'remove from favorites' action", () => {
-        const actual = createRemoveFromFavoritesAction({ id: "id_1" });
+        const actual = createRemoveFromFavoritesAction({ id: "id_1", keyboardShortcut: "test-keyboard-shortcut" });
 
         const expected = <SearchResultItemAction>{
             argument: JSON.stringify({ action: "Remove", id: "id_1" }),
@@ -14,6 +14,7 @@ describe(createRemoveFromFavoritesAction, () => {
             },
             handlerId: "Favorites",
             fluentIcon: "StarOffRegular",
+            keyboardShortcut: "test-keyboard-shortcut",
         };
 
         expect(actual).toEqual(expected);
diff --git a/src/common/Core/SearchResultItemActionUtility/createShowItemInFileExplorerAction.test.ts b/src/common/Core/SearchResultItemActionUtility/createShowItemInFileExplorerAction.test.ts
index ab0babe1..ec528e5f 100644
--- a/src/common/Core/SearchResultItemActionUtility/createShowItemInFileExplorerAction.test.ts
+++ b/src/common/Core/SearchResultItemActionUtility/createShowItemInFileExplorerAction.test.ts
@@ -5,6 +5,7 @@ describe(createShowItemInFileExplorerAction, () => {
     it("should create an 'show item in file explorer' action", () => {
         const actual = createShowItemInFileExplorerAction({
             filePath: "this is a file path",
+            keyboardShortcut: "test keyboard shortcut",
         });
 
         const expected = <SearchResultItemAction>{
@@ -17,6 +18,7 @@ describe(createShowItemInFileExplorerAction, () => {
             },
             fluentIcon: "DocumentFolderRegular",
             hideWindowAfterInvocation: true,
+            keyboardShortcut: "test keyboard shortcut",
         };
 
         expect(actual).toEqual(expected);
diff --git a/src/renderer/Core/Search/Helpers/deserializeKeyboardShortcut.test.ts b/src/renderer/Core/Search/Helpers/deserializeKeyboardShortcut.test.ts
new file mode 100644
index 00000000..ee9aec76
--- /dev/null
+++ b/src/renderer/Core/Search/Helpers/deserializeKeyboardShortcut.test.ts
@@ -0,0 +1,37 @@
+import { describe, expect, it } from "vitest";
+import { deserializeKeyboardShortcut } from "./deserializeKeyboardShortcut";
+import type { KeyboardShortcut } from "./KeyboardShortcut";
+
+describe(deserializeKeyboardShortcut, () => {
+    const testDeserializeKeyboardShortcut = ({
+        expected,
+        keyboardShortcut,
+    }: {
+        expected: KeyboardShortcut;
+        keyboardShortcut: string;
+    }) => expect(deserializeKeyboardShortcut(keyboardShortcut)).toEqual(expected);
+
+    it("should deserialize keyboard shortcut with modifier", () => {
+        testDeserializeKeyboardShortcut({
+            expected: { modifier: "Ctrl", key: "A" },
+            keyboardShortcut: "Ctrl+A",
+        });
+
+        testDeserializeKeyboardShortcut({
+            expected: { modifier: "Alt", key: "N" },
+            keyboardShortcut: "Alt+N",
+        });
+
+        testDeserializeKeyboardShortcut({
+            expected: { modifier: "Cmd", key: "ArrowRight" },
+            keyboardShortcut: "Cmd+ArrowRight",
+        });
+    });
+
+    it("should deserialize keyboard shortcut without modifier", () => {
+        testDeserializeKeyboardShortcut({
+            expected: { modifier: undefined, key: "Enter" },
+            keyboardShortcut: "Enter",
+        });
+    });
+});
diff --git a/src/renderer/Core/Search/ActionsMenu/getActions.test.ts b/src/renderer/Core/Search/Helpers/getActions.test.ts
similarity index 72%
rename from src/renderer/Core/Search/ActionsMenu/getActions.test.ts
rename to src/renderer/Core/Search/Helpers/getActions.test.ts
index 6577c1e1..5561ea3a 100644
--- a/src/renderer/Core/Search/ActionsMenu/getActions.test.ts
+++ b/src/renderer/Core/Search/Helpers/getActions.test.ts
@@ -9,6 +9,11 @@ import { describe, expect, it } from "vitest";
 import { getActions } from "./getActions";
 
 describe(getActions, () => {
+    const keyboardShortcuts = {
+        addToFavorites: "Ctrl+F",
+        excludeFromSearchResults: "Ctrl+Backspace",
+    };
+
     it("should include an 'add to favorites' action when favorites don't include the current item", () => {
         const searchResultItem = <SearchResultItem>{
             id: "id1",
@@ -19,11 +24,11 @@ describe(getActions, () => {
             additionalActions: [{ argument: "additional argument", handlerId: "additional handler id" }],
         };
 
-        expect(getActions(searchResultItem, [])).toEqual(<SearchResultItemAction[]>[
-            { argument: "default action argument", handlerId: "default handler id" },
+        expect(getActions(searchResultItem, [], keyboardShortcuts)).toEqual(<SearchResultItemAction[]>[
+            { argument: "default action argument", handlerId: "default handler id", keyboardShortcut: "Enter" },
             { argument: "additional argument", handlerId: "additional handler id" },
-            createAddToFavoritesAction({ id: searchResultItem.id }),
-            createExcludeFromSearchResultsAction({ id: searchResultItem.id }),
+            createAddToFavoritesAction({ id: searchResultItem.id, keyboardShortcut: "Ctrl+F" }),
+            createExcludeFromSearchResultsAction({ id: searchResultItem.id, keyboardShortcut: "Ctrl+Backspace" }),
         ]);
     });
 
@@ -36,10 +41,10 @@ describe(getActions, () => {
             },
         };
 
-        expect(getActions(searchResultItem, ["id1"])).toEqual(<SearchResultItemAction[]>[
-            { argument: "default action argument", handlerId: "default handler id" },
-            createRemoveFromFavoritesAction({ id: searchResultItem.id }),
-            createExcludeFromSearchResultsAction({ id: searchResultItem.id }),
+        expect(getActions(searchResultItem, ["id1"], keyboardShortcuts)).toEqual(<SearchResultItemAction[]>[
+            { argument: "default action argument", handlerId: "default handler id", keyboardShortcut: "Enter" },
+            createRemoveFromFavoritesAction({ id: searchResultItem.id, keyboardShortcut: "Ctrl+F" }),
+            createExcludeFromSearchResultsAction({ id: searchResultItem.id, keyboardShortcut: "Ctrl+Backspace" }),
         ]);
     });
 });
diff --git a/src/renderer/Core/Search/Helpers/keyboardEventMatchesKeyboardShortcut.test.ts b/src/renderer/Core/Search/Helpers/keyboardEventMatchesKeyboardShortcut.test.ts
new file mode 100644
index 00000000..4102e88a
--- /dev/null
+++ b/src/renderer/Core/Search/Helpers/keyboardEventMatchesKeyboardShortcut.test.ts
@@ -0,0 +1,85 @@
+import type { KeyboardEvent } from "react";
+import { describe, expect, it } from "vitest";
+import { keyboardEventMatchesKeyboardShortcut } from "./keyboardEventMatchesKeyboardShortcut";
+
+describe(keyboardEventMatchesKeyboardShortcut, () => {
+    const test = ({
+        expected,
+        keyboardEvent,
+        keyboardShortcut,
+    }: {
+        expected: boolean;
+        keyboardEvent: KeyboardEvent;
+        keyboardShortcut: string;
+    }) => expect(keyboardEventMatchesKeyboardShortcut(keyboardEvent, keyboardShortcut)).toBe(expected);
+
+    it("should return true when expected", () => {
+        test({
+            expected: true,
+            keyboardEvent: <KeyboardEvent>{ key: "A", ctrlKey: true },
+            keyboardShortcut: "Ctrl+a",
+        });
+
+        test({
+            expected: true,
+            keyboardEvent: <KeyboardEvent>{ key: "a", ctrlKey: true },
+            keyboardShortcut: "Ctrl+A",
+        });
+
+        test({
+            expected: true,
+            keyboardEvent: <KeyboardEvent>{ key: "n", shiftKey: true },
+            keyboardShortcut: "Shift+N",
+        });
+
+        test({
+            expected: true,
+            keyboardEvent: <KeyboardEvent>{ key: "ArrowUp", altKey: true },
+            keyboardShortcut: "Alt+ArrowUp",
+        });
+
+        test({
+            expected: true,
+            keyboardEvent: <KeyboardEvent>{ key: "Enter", metaKey: true },
+            keyboardShortcut: "Cmd+Enter",
+        });
+
+        test({
+            expected: true,
+            keyboardEvent: <KeyboardEvent>{ key: "Enter" },
+            keyboardShortcut: "Enter",
+        });
+    });
+
+    it("should return false when expected", () => {
+        test({
+            expected: false,
+            keyboardEvent: <KeyboardEvent>{ key: "a", ctrlKey: false },
+            keyboardShortcut: "Ctrl+a",
+        });
+
+        test({
+            expected: false,
+            keyboardEvent: <KeyboardEvent>{ key: "a", ctrlKey: true },
+            keyboardShortcut: "Shift+A",
+        });
+
+        test({
+            expected: false,
+            keyboardEvent: <KeyboardEvent>{ key: "a", shiftKey: true, ctrlKey: true },
+            keyboardShortcut: "Shift+A",
+        });
+
+        test({
+            expected: false,
+            keyboardEvent: <KeyboardEvent>{ key: "ArrowUp" },
+            keyboardShortcut: "Enter",
+        });
+
+        test({
+            expected: false,
+            keyboardEvent: <KeyboardEvent>{ key: "Enter", shiftKey: true },
+            keyboardShortcut: "Enter",
+        });
+    });
+});