...

Source file src/codeberg.org/tslocum/cbind/key.go

Documentation: codeberg.org/tslocum/cbind

     1  package cbind
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  
     7  	"github.com/gdamore/tcell/v3"
     8  )
     9  
    10  // Modifier labels
    11  const (
    12  	LabelCtrl  = "ctrl"
    13  	LabelAlt   = "alt"
    14  	LabelMeta  = "meta"
    15  	LabelShift = "shift"
    16  )
    17  
    18  // ErrInvalidKeyEvent is the error returned when encoding or decoding a key event fails.
    19  var ErrInvalidKeyEvent = errors.New("invalid key event")
    20  
    21  // UnifyEnterKeys is a flag that determines whether or not KPEnter (keypad
    22  // enter) key events are interpreted as Enter key events. When enabled, Ctrl+J
    23  // key events are also interpreted as Enter key events.
    24  var UnifyEnterKeys = true
    25  
    26  var fullKeyNames = map[string]string{
    27  	"backspace2": "Backspace",
    28  	"pgup":       "PageUp",
    29  	"pgdn":       "PageDown",
    30  	"esc":        "Escape",
    31  }
    32  
    33  // Decode decodes a string as a key or combination of keys.
    34  func Decode(s string) (mod tcell.ModMask, key tcell.Key, str string, err error) {
    35  	if len(s) == 0 {
    36  		return 0, 0, "", ErrInvalidKeyEvent
    37  	}
    38  
    39  	// Special case for plus rune decoding
    40  	if s[len(s)-1:] == "+" {
    41  		key = tcell.KeyRune
    42  		str = "+"
    43  
    44  		if len(s) == 1 {
    45  			return mod, key, str, nil
    46  		} else if len(s) == 2 {
    47  			return 0, 0, "", ErrInvalidKeyEvent
    48  		} else {
    49  			s = s[:len(s)-2]
    50  		}
    51  	}
    52  
    53  	split := strings.Split(s, "+")
    54  DECODEPIECE:
    55  	for _, piece := range split {
    56  		// Decode modifiers
    57  		pieceLower := strings.ToLower(piece)
    58  		switch pieceLower {
    59  		case LabelCtrl:
    60  			mod |= tcell.ModCtrl
    61  			continue
    62  		case LabelAlt:
    63  			mod |= tcell.ModAlt
    64  			continue
    65  		case LabelMeta:
    66  			mod |= tcell.ModMeta
    67  			continue
    68  		case LabelShift:
    69  			mod |= tcell.ModShift
    70  			continue
    71  		}
    72  
    73  		// Decode key
    74  		for shortKey, fullKey := range fullKeyNames {
    75  			if pieceLower == strings.ToLower(fullKey) {
    76  				pieceLower = shortKey
    77  				break
    78  			}
    79  		}
    80  		switch pieceLower {
    81  		case "backspace":
    82  			key = tcell.KeyBackspace2
    83  			continue
    84  		case "space", "spacebar":
    85  			key = tcell.KeyRune
    86  			str = " "
    87  			continue
    88  		}
    89  		for k, keyName := range tcell.KeyNames {
    90  			if pieceLower == strings.ToLower(strings.ReplaceAll(keyName, "-", "+")) {
    91  				key = k
    92  				if key < 0x80 {
    93  					str = string(rune(k))
    94  				}
    95  				continue DECODEPIECE
    96  			}
    97  		}
    98  
    99  		// Decode rune
   100  		if len(piece) > 1 {
   101  			return 0, 0, "", ErrInvalidKeyEvent
   102  		}
   103  
   104  		key = tcell.KeyRune
   105  		str = string(rune(piece[0]))
   106  	}
   107  
   108  	// Normalize Ctrl+A-Z to lowercase
   109  	if mod&tcell.ModCtrl != 0 && key == tcell.KeyRune {
   110  		str = strings.ToLower(str)
   111  	}
   112  
   113  	return mod, key, str, nil
   114  }
   115  
   116  // Encode encodes a key or combination of keys a string.
   117  func Encode(mod tcell.ModMask, key tcell.Key, str string) (string, error) {
   118  	var b strings.Builder
   119  	var wrote bool
   120  
   121  	if mod&tcell.ModCtrl != 0 {
   122  		if key == tcell.KeyBackspace || key == tcell.KeyTab || key == tcell.KeyEnter {
   123  			mod ^= tcell.ModCtrl
   124  		} else {
   125  			// Convert KeyCtrlA-Z to rune format.
   126  			if key >= tcell.KeyCtrlA && key <= tcell.KeyCtrlZ {
   127  				mod |= tcell.ModCtrl
   128  				str = string(rune('a' + (key - tcell.KeyCtrlA)))
   129  				key = tcell.KeyRune
   130  			}
   131  		}
   132  	}
   133  
   134  	if key != tcell.KeyRune {
   135  		if UnifyEnterKeys && key == tcell.KeyCtrlJ {
   136  			key = tcell.KeyEnter
   137  		} else if key < 0x80 {
   138  			str = string(rune(key))
   139  		}
   140  	}
   141  
   142  	// Encode modifiers
   143  	if mod&tcell.ModCtrl != 0 {
   144  		b.WriteString(upperFirst(LabelCtrl))
   145  		wrote = true
   146  	}
   147  	if mod&tcell.ModAlt != 0 {
   148  		if wrote {
   149  			b.WriteRune('+')
   150  		}
   151  		b.WriteString(upperFirst(LabelAlt))
   152  		wrote = true
   153  	}
   154  	if mod&tcell.ModMeta != 0 {
   155  		if wrote {
   156  			b.WriteRune('+')
   157  		}
   158  		b.WriteString(upperFirst(LabelMeta))
   159  		wrote = true
   160  	}
   161  	if mod&tcell.ModShift != 0 {
   162  		if wrote {
   163  			b.WriteRune('+')
   164  		}
   165  		b.WriteString(upperFirst(LabelShift))
   166  		wrote = true
   167  	}
   168  
   169  	if key == tcell.KeyRune && str == " " {
   170  		if wrote {
   171  			b.WriteRune('+')
   172  		}
   173  		b.WriteString("Space")
   174  	} else if key != tcell.KeyRune {
   175  		// Encode key
   176  		keyName := tcell.KeyNames[key]
   177  		if keyName == "" {
   178  			return "", ErrInvalidKeyEvent
   179  		}
   180  		keyName = strings.ReplaceAll(keyName, "-", "+")
   181  		fullKeyName := fullKeyNames[strings.ToLower(keyName)]
   182  		if fullKeyName != "" {
   183  			keyName = fullKeyName
   184  		}
   185  
   186  		if wrote {
   187  			b.WriteRune('+')
   188  		}
   189  		b.WriteString(keyName)
   190  	} else {
   191  		// Encode rune
   192  		if wrote {
   193  			b.WriteRune('+')
   194  		}
   195  		b.WriteString(str)
   196  	}
   197  
   198  	return b.String(), nil
   199  }
   200  
   201  func upperFirst(s string) string {
   202  	if len(s) <= 1 {
   203  		return strings.ToUpper(s)
   204  	}
   205  	return strings.ToUpper(s[:1]) + s[1:]
   206  }
   207  

View as plain text