wtfutil/wtf · error
Key is already mapped to a keyboard command: %s
Error message
Key is already mapped to a keyboard command: %s
What it means
SetKeyboardChar registers a single-character keyboard shortcut on a keyboard-enabled widget. It panics if the requested char already exists in the widget's charMap, because one key can only map to one command. This is a deliberate programming-error guard: duplicate keybindings for the same widget are treated as a setup bug rather than silently overwritten.
Source
Thrown at view/keyboard_widget.go:141
path = widget.settings.Type
}
url := "https://wtfutil.com/modules/" + path
utils.OpenFile(url)
}
// SetKeyboardChar sets a character/function combination that responds to key presses
// Example:
//
// widget.SetKeyboardChar("d", widget.deleteSelectedItem)
func (widget *KeyboardWidget) SetKeyboardChar(char string, fn func(), helpText string) {
if char == "" {
return
}
// Check to ensure that the key trying to be used isn't already being used for something
if _, ok := widget.charMap[char]; ok {
panic(fmt.Sprintf("Key is already mapped to a keyboard command: %s\n", char))
}
widget.charMap[char] = fn
widget.charHelp = append(widget.charHelp, helpItem{char, helpText})
}
// SetKeyboardKey sets a tcell.Key/function combination that responds to key presses
// Example:
//
// widget.SetKeyboardKey(tcell.KeyCtrlD, widget.deleteSelectedItem)
func (widget *KeyboardWidget) SetKeyboardKey(key tcell.Key, fn func(), helpText string) {
widget.keyMap[key] = fn
widget.keyHelp = append(widget.keyHelp, helpItem{tcell.KeyNames[key], helpText})
if len(tcell.KeyNames[key]) > widget.maxKey {
widget.maxKey = len(tcell.KeyNames[key])
}
}View on GitHub (pinned to bb838c1ccb)
Solutions
- Find the second registration of the duplicate char in the widget's keyboard initialization and remove it or pick a different char
- Check that initializeCommonKeyboardControls is not re-registering keys already bound by InitializeHelpTextKeyboardControl / InitializeRefreshKeyboardControls
- Ensure widget keyboard controls are initialized only once per widget instance (no double init on reload)
- If collisions should be allowed, change code to overwrite instead of panic (widget.charMap[char] = fn after removing the existence check)
Example fix
// before
widget.InitializeHelpTextKeyboardControl("h", "help")
widget.initializeCommonKeyboardControls()
// after (initializeCommonKeyboardControls also binds "h")
if _, taken := widget.charMap["h"]; !taken {
widget.InitializeHelpTextKeyboardControl("h", "help")
}
widget.initializeCommonKeyboardControls() Defensive patterns
Strategy: validation
Validate before calling
func safeSetKey(w *view.KeyboardWidget, char string, fn func(), help string) {
if char == "" {
return
}
if _, taken := w.CharMap()[char]; taken {
log.Printf("key %q already mapped, skipping", char)
return
}
w.SetKeyboardChar(char, fn, help)
} Try / catch
defer func() {
if r := recover(); r != nil {
if s, ok := r.(string); ok && strings.Contains(s, "already mapped") {
log.Printf("duplicate keybinding skipped: %s", s)
return
}
panic(r)
}
}()
widget.SetKeyboardChar("h", fn, "help") Prevention
- Keep a single place (initializeCommonKeyboardControls) where all default keybindings are registered
- Before adding a new shortcut, grep the widget's init chain for the chosen char
- Only call the keyboard initialization functions once per widget instance lifecycle
- In tests, create a fresh widget instead of re-initializing controls on a shared instance
When it happens
Trigger: Calling SetKeyboardChar twice with the same char on the same widget instance — typically in a widget's InitializeHelpTextKeyboardControl, InitializeRefreshKeyboardControl, or initializeCommonKeyboardControls chain where the same key (e.g. "h", "r", or the anonymous function registrations) is bound more than once.
Common situations: A widget author copies initialization code and accidentally re-binds a key already bound by initializeCommonKeyboardControls (e.g. re-binding "r" that refresh already uses); user config maps a key that collides with a built-in binding registered earlier in the chain; tests (Test_HelpText) re-initializing controls on the same widget object.
Related errors
- failed to initialize Azure session: %w
- invalid base URL: %w
- could not create client: %w
- unable to get the league id for provided league '%s'
- no such league
AI-assisted analysis of wtfutil/wtf@bb838c1ccb (2026-09-03).
Data as JSON: /api/errors/51bf5b8ae5489c91.
Report an issue: GitHub.