///|
/// Replacement chord suggestions are deterministic and never mutate a keymap.
fn candidate_keys() -> Array[String] {
[
"Ctrl+Shift+P", "Ctrl+Shift+O", "Ctrl+Shift+L", "Ctrl+Shift+K", "Ctrl+Alt+P",
"Ctrl+Alt+O", "Ctrl+Alt+L", "Ctrl+Alt+K", "Alt+Shift+P", "Alt+Shift+O", "Alt+Shift+L",
"Alt+Shift+K", "Ctrl+Shift+1", "Ctrl+Shift+2", "Ctrl+Shift+3", "Ctrl+Shift+4",
"Ctrl+Alt+1", "Ctrl+Alt+2", "Ctrl+Alt+3", "Ctrl+Alt+4",
]
}
///|
fn candidate_is_free(
keymap : Keymap,
binding : Binding,
candidate : KeySequence,
) -> Bool {
for other in keymap.bindings {
if other.id != binding.id &&
other.enabled &&
other.keys.canonical == candidate.canonical &&
contexts_overlap(keymap, binding.context, other.context) &&
platforms_overlap(binding.platform, other.platform) {
return false
}
}
for marker in keymap.reserved {
let (_, reserved_key) = split_once(marker, ":")
if reserved_key == candidate.canonical {
return false
}
}
true
}
///|
/// Suggest up to `limit` free alternatives for one binding.
pub fn suggest_for(
keymap : Keymap,
binding : Binding,
limit? : Int = 3,
) -> Array[Suggestion] {
let result : Array[Suggestion] = []
let maximum = if limit < 0 { 0 } else { limit }
for raw in candidate_keys() {
if result.length() >= maximum {
break
}
match parse_keys(raw) {
Err(_) => ()
Ok(candidate) =>
if candidate.canonical != binding.keys.canonical &&
candidate_is_free(keymap, binding, candidate) {
let confidence = if candidate.modifier_count ==
binding.keys.modifier_count {
90
} else {
70
}
result.push({
binding_id: binding.id,
command: binding.command,
current: binding.keys.canonical,
replacement: candidate.canonical,
reason: "free in the same context and platform scope",
confidence,
})
}
}
}
result
}
///|
/// Suggest alternatives for every binding carrying a finding.
pub fn suggest_all(
keymap : Keymap,
analysis : Analysis,
limit? : Int = 3,
) -> Array[Suggestion] {
let result : Array[Suggestion] = []
let seen : Array[String] = []
for finding in analysis.findings {
if finding.kind == ExactConflict ||
finding.kind == PrefixConflict ||
finding.kind == ReservedShortcut ||
finding.kind == AccessibilityRisk {
for binding in keymap.bindings {
if binding.id == finding.primary_id && !array_contains(seen, binding.id) {
seen.push(binding.id)
for suggestion in suggest_for(keymap, binding, limit~) {
result.push(suggestion)
}
}
}
}
}
result
}
///|
/// Render suggestions as a simple Markdown checklist.
pub fn suggestions_to_markdown(suggestions : Array[Suggestion]) -> String {
let lines : Array[String] = ["## Suggested replacements", ""]
if suggestions.length() == 0 {
lines.push("No replacement is needed.")
}
for suggestion in suggestions {
lines.push(
"- `" +
suggestion.binding_id +
"` (`" +
suggestion.current +
"`) → `" +
suggestion.replacement +
"` — " +
suggestion.reason +
" (confidence " +
suggestion.confidence.to_string() +
")",
)
}
lines.join("\n")
}