Commit f36736932d
f36736932de274a2d437c25707091b56f82b1f4e
parent: 9a9fb2ac00
Verified · cmc
cmc <hello@cleberg.net> · 2026-10-06 00:11 UTC
Fix duplicate bindings
C-x b in the Emacs preset and SPC b b in Doom kept their old Quick Open
bindings under the new buffer switch, so C-x b never switched buffers.
Menu and palette key labels now leave out bindings an always-applying
binding takes first, and a test catches a preset binding the same keys
twice.
Layout: unified · split
Sources/OrgCore/Keymap/Keymap.swift
+10 −2
| @@ -101,8 +101,7 @@ public struct Keymap: Sendable, Equatable { |
| 101 | 101 | public func keyLabels(for command: String, modes: [String?] = [nil], limit: Int = 2) -> [String] { |
| 102 | 102 | var found: [(keys: [KeyChord], rank: [Int])] = [] |
| 103 | 103 | for (order, mode) in modes.enumerated() { |
| 104 | | for binding in bindings.reversed() where binding.mode == mode && binding.command == command |
| 105 | | && candidates(for: binding.keys, mode: mode).contains(binding) { |
| 104 | for binding in bindings.reversed() where binding.mode == mode && binding.command == command && isLive(binding) { |
| 106 | 105 | found.append((binding.keys, [order, binding.when == nil ? 0 : 1, binding.keys.count, found.count])) |
| 107 | 106 | } |
| 108 | 107 | } |
| @@ -114,6 +113,15 @@ public struct Keymap: Sendable, Equatable { |
| 114 | 113 | .map { $0 } |
| 115 | 114 | } |
| 116 | 115 | |
| 116 | /// Whether `binding` can run: no binding that always applies takes its keys first. |
| 117 | func isLive(_ binding: KeyBinding) -> Bool { |
| 118 | for candidate in candidates(for: binding.keys, mode: binding.mode) { |
| 119 | if candidate == binding { return true } |
| 120 | if candidate.when == nil { return false } |
| 121 | } |
| 122 | return false |
| 123 | } |
| 124 | |
| 117 | 125 | /// The first live binding of `command`, for showing its keys. |
| 118 | 126 | public func keys(for command: String, mode: String? = nil) -> [KeyChord]? { |
| 119 | 127 | bindings.reversed().first { $0.mode == mode && $0.command == command && candidates(for: $0.keys, mode: mode).contains($0) }?.keys |
Sources/OrgCore/Keymap/Presets.swift
−7
| @@ -87,10 +87,6 @@ public enum KeymapPreset: String, Sendable, CaseIterable { |
| 87 | 87 | command = "app.quick-open" |
| 88 | 88 | mode = "normal" |
| 89 | 89 | [[bind]] |
| 90 | | keys = "SPC b b" |
| 91 | | command = "app.quick-open" |
| 92 | | mode = "normal" |
| 93 | | [[bind]] |
| 94 | 90 | keys = "SPC f s" |
| 95 | 91 | command = "app.save" |
| 96 | 92 | mode = "normal" |
| @@ -576,9 +572,6 @@ public enum KeymapPreset: String, Sendable, CaseIterable { |
| 576 | 572 | keys = "C-x C-f" |
| 577 | 573 | command = "app.quick-open" |
| 578 | 574 | [[bind]] |
| 579 | | keys = "C-x b" |
| 580 | | command = "app.quick-open" |
| 581 | | [[bind]] |
| 582 | 575 | keys = "M-x" |
| 583 | 576 | command = "app.palette" |
| 584 | 577 | [[bind]] |
Tests/OrgCoreTests/KeymapTests.swift
+21
| @@ -98,6 +98,27 @@ struct KeymapTests { |
| 98 | 98 | #expect(doom.keyLabels(for: "app.buffer.kill", modes: ["normal"], limit: 3) == ["C-x k", "SPC b d", "SPC b k"]) |
| 99 | 99 | #expect(doom.keyLabels(for: "app.buffer.next", modes: ["normal"]) == ["] b", "C-x <right>"]) |
| 100 | 100 | #expect(emacs.keyLabels(for: "app.buffer.switch") == ["C-x C-b", "C-x b"]) |
| 101 | #expect(!emacs.keyLabels(for: "app.quick-open").contains("C-x b")) |
| 102 | } |
| 103 | |
| 104 | /// Two bindings of the same keys in the same state, both always applying: the earlier one |
| 105 | /// can never run. |
| 106 | @Test func presetsBindEachKeyOnce() { |
| 107 | for preset in KeymapPreset.allCases { |
| 108 | var seen: [String: String] = [:] |
| 109 | for binding in preset.keymap.bindings where binding.when == nil && !binding.unbinds { |
| 110 | let key = "\(binding.mode ?? "-") \(KeySequence.format(binding.keys))" |
| 111 | #expect(seen[key] == nil, "\(preset): \(key) is \(seen[key] ?? "") and \(binding.command)") |
| 112 | seen[key] = binding.command |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | @Test func shadowedBindingsShowNoKeys() throws { |
| 118 | var problems: [String] = [] |
| 119 | let map = Keymap.layered([KeymapPreset.emacs.keymap, try Keymap(toml: "[[bind]]\nkeys = \"C-c C-t\"\ncommand = \"app.save\"\n", problems: &problems)]) |
| 120 | #expect(!map.keyLabels(for: "org.todo.cycle").contains("C-c C-t")) |
| 121 | #expect(map.keyLabels(for: "app.save").contains("C-c C-t")) |
| 101 | 122 | } |
| 102 | 123 | |
| 103 | 124 | @Test func dispatchesPrefixes() { |