← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


commit 89f160402415dfa0447ad9d229424c03ce39e184
parent ae7e996e35301e0daf31ec67608f9968f4867bfb
Author: Michael Camilleri <[email protected]>
Date:   Sat, 18 Apr 2026 06:19:09 +0900

Add additional keyboard shortcuts

This commit adds support for Home/End/Page-Up/Page-Down. It does this
using the existing keyboard shortcut mechanism.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

Diffstat

MListless/Extensions/ItemListView+Logic.swift | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MListless/Helpers/ItemListTypes.swift | 2++
MListlessMac/Views/ItemListView.swift | 4++++
MListlessiOS/Helpers/KeyCommandBridge.swift | 28++++++++++++++++++++++++++++
MListlessiOS/Views/ItemListView.swift | 6+++++-

5 files changed, 105 insertions(+), 1 deletion(-)

diff --git a/Listless/Extensions/ItemListView+Logic.swift b/Listless/Extensions/ItemListView+Logic.swift

@@ -355,6 +355,72 @@ extension ItemListViewProtocol {
         return .handled
     }
 
+    func navigateToFirst() -> KeyPress.Result {
+        guard focusedField == .scrollView else {
+            return .ignored
+        }
+
+        let displayOrder = allItemsInDisplayOrder
+        guard let first = displayOrder.first else {
+            return .handled
+        }
+        fState.selectedItemID = first.id
+        return .handled
+    }
+
+    func navigateToLast() -> KeyPress.Result {
+        guard focusedField == .scrollView else {
+            return .ignored
+        }
+
+        let displayOrder = allItemsInDisplayOrder
+        guard let last = displayOrder.last else {
+            return .handled
+        }
+        fState.selectedItemID = last.id
+        return .handled
+    }
+
+    func navigatePageUp() -> KeyPress.Result {
+        guard focusedField == .scrollView else {
+            return .ignored
+        }
+
+        let displayOrder = allItemsInDisplayOrder
+        guard !displayOrder.isEmpty else { return .handled }
+
+        guard let currentID = fState.selectedItemID,
+            let currentIndex = displayOrder.firstIndex(where: { $0.id == currentID })
+        else {
+            fState.selectedItemID = displayOrder.first?.id
+            return .handled
+        }
+
+        let targetIndex = max(0, currentIndex - pageNavigationSize)
+        fState.selectedItemID = displayOrder[targetIndex].id
+        return .handled
+    }
+
+    func navigatePageDown() -> KeyPress.Result {
+        guard focusedField == .scrollView else {
+            return .ignored
+        }
+
+        let displayOrder = allItemsInDisplayOrder
+        guard !displayOrder.isEmpty else { return .handled }
+
+        guard let currentID = fState.selectedItemID,
+            let currentIndex = displayOrder.firstIndex(where: { $0.id == currentID })
+        else {
+            fState.selectedItemID = displayOrder.first?.id
+            return .handled
+        }
+
+        let targetIndex = min(displayOrder.count - 1, currentIndex + pageNavigationSize)
+        fState.selectedItemID = displayOrder[targetIndex].id
+        return .handled
+    }
+
     func navigateUpExtend() -> KeyPress.Result {
         guard focusedField == .scrollView else {
             return .ignored

diff --git a/Listless/Helpers/ItemListTypes.swift b/Listless/Helpers/ItemListTypes.swift

@@ -5,6 +5,8 @@ enum FocusField: Hashable {
     case scrollView
 }
 
+let pageNavigationSize = 10
+
 enum DragState: Equatable {
     case idle
     case dragging(id: UUID, order: [UUID])

diff --git a/ListlessMac/Views/ItemListView.swift b/ListlessMac/Views/ItemListView.swift

@@ -426,6 +426,10 @@ struct ItemListView: View, ItemListViewProtocol {
             ShortcutKey(key: .downArrow): navigateDown,
             ShortcutKey(key: .upArrow, modifiers: .shift): navigateUpExtend,
             ShortcutKey(key: .downArrow, modifiers: .shift): navigateDownExtend,
+            ShortcutKey(key: .home): navigateToFirst,
+            ShortcutKey(key: .end): navigateToLast,
+            ShortcutKey(key: .pageUp): navigatePageUp,
+            ShortcutKey(key: .pageDown): navigatePageDown,
             ShortcutKey(key: .return): focusSelectedItem,
         ])
         .onAppear {

diff --git a/ListlessiOS/Helpers/KeyCommandBridge.swift b/ListlessiOS/Helpers/KeyCommandBridge.swift

@@ -18,6 +18,10 @@ struct KeyCommandBridge: UIViewRepresentable {
     let onSpace: () -> Void
     let onReturn: () -> Void
     let onDelete: () -> Void
+    let onHome: () -> Void
+    let onEnd: () -> Void
+    let onPageUp: () -> Void
+    let onPageDown: () -> Void
 
     func makeUIView(context: Context) -> KeyCaptureView {
         let view = KeyCaptureView()
@@ -26,6 +30,10 @@ struct KeyCommandBridge: UIViewRepresentable {
         view.onSpace = onSpace
         view.onReturn = onReturn
         view.onDelete = onDelete
+        view.onHome = onHome
+        view.onEnd = onEnd
+        view.onPageUp = onPageUp
+        view.onPageDown = onPageDown
         view.isActive = isActive
         return view
     }
@@ -36,6 +44,10 @@ struct KeyCommandBridge: UIViewRepresentable {
         view.onSpace = onSpace
         view.onReturn = onReturn
         view.onDelete = onDelete
+        view.onHome = onHome
+        view.onEnd = onEnd
+        view.onPageUp = onPageUp
+        view.onPageDown = onPageDown
         view.isActive = isActive
 
         if isActive && !view.isFirstResponder {
@@ -53,6 +65,10 @@ struct KeyCommandBridge: UIViewRepresentable {
         var onSpace: (() -> Void)?
         var onReturn: (() -> Void)?
         var onDelete: (() -> Void)?
+        var onHome: (() -> Void)?
+        var onEnd: (() -> Void)?
+        var onPageUp: (() -> Void)?
+        var onPageDown: (() -> Void)?
 
         override var canBecomeFirstResponder: Bool { true }
 
@@ -66,6 +82,10 @@ struct KeyCommandBridge: UIViewRepresentable {
                 " ",
                 "\r",
                 "\u{8}",
+                UIKeyCommand.inputHome,
+                UIKeyCommand.inputEnd,
+                UIKeyCommand.inputPageUp,
+                UIKeyCommand.inputPageDown,
             ].map { input in
                 let cmd = UIKeyCommand(
                     input: input,
@@ -89,6 +109,14 @@ struct KeyCommandBridge: UIViewRepresentable {
                 onReturn?()
             case "\u{8}":
                 onDelete?()
+            case UIKeyCommand.inputHome:
+                onHome?()
+            case UIKeyCommand.inputEnd:
+                onEnd?()
+            case UIKeyCommand.inputPageUp:
+                onPageUp?()
+            case UIKeyCommand.inputPageDown:
+                onPageDown?()
             default:
                 break
             }

diff --git a/ListlessiOS/Views/ItemListView.swift b/ListlessiOS/Views/ItemListView.swift

@@ -464,7 +464,11 @@ struct ItemListView: View, ItemListViewProtocol {
                     onDown: { _ = navigateDown() },
                     onSpace: { _ = toggleSelectedItem() },
                     onReturn: { _ = focusSelectedItem() },
-                    onDelete: { _ = deleteSelectedItemWithUndo() }
+                    onDelete: { _ = deleteSelectedItemWithUndo() },
+                    onHome: { _ = navigateToFirst() },
+                    onEnd: { _ = navigateToLast() },
+                    onPageUp: { _ = navigatePageUp() },
+                    onPageDown: { _ = navigatePageDown() }
                 )
             }
             .onAppear {