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

  1. Find the second registration of the duplicate char in the widget's keyboard initialization and remove it or pick a different char
  2. Check that initializeCommonKeyboardControls is not re-registering keys already bound by InitializeHelpTextKeyboardControl / InitializeRefreshKeyboardControls
  3. Ensure widget keyboard controls are initialized only once per widget instance (no double init on reload)
  4. 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

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


AI-assisted analysis of wtfutil/wtf@bb838c1ccb (2026-09-03). Data as JSON: /api/errors/51bf5b8ae5489c91. Report an issue: GitHub.