...

Source file src/code.rocketnine.space/tslocum/cbind/key.go

Documentation: code.rocketnine.space/tslocum/cbind

     1  package cbind
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  	"unicode"
     7  
     8  	"github.com/gdamore/tcell/v2"
     9  )
    10  
    11  // Modifier labels
    12  const (
    13  	LabelCtrl  = "ctrl"
    14  	LabelAlt   = "alt"
    15  	LabelMeta  = "meta"
    16  	LabelShift = "shift"
    17  )
    18  
    19  // ErrInvalidKeyEvent is the error returned when encoding or decoding a key event fails.
    20  var ErrInvalidKeyEvent = errors.New("invalid key event")
    21  
    22  // UnifyEnterKeys is a flag that determines whether or not KPEnter (keypad
    23  // enter) key events are interpreted as Enter key events. When enabled, Ctrl+J
    24  // key events are also interpreted as Enter key events.
    25  var UnifyEnterKeys = true
    26  
    27  var fullKeyNames = map[string]string{
    28  	"backspace2": "Backspace",
    29  	"pgup":       "PageUp",
    30  	"pgdn":       "PageDown",
    31  	"esc":        "Escape",
    32  }
    33  
    34  var ctrlKeys = map[rune]tcell.Key{
    35  	' ':  tcell.KeyCtrlSpace,
    36  	'a':  tcell.KeyCtrlA,
    37  	'b':  tcell.KeyCtrlB,
    38  	'c':  tcell.KeyCtrlC,
    39  	'd':  tcell.KeyCtrlD,
    40  	'e':  tcell.KeyCtrlE,
    41  	'f':  tcell.KeyCtrlF,
    42  	'g':  tcell.KeyCtrlG,
    43  	'h':  tcell.KeyCtrlH,
    44  	'i':  tcell.KeyCtrlI,
    45  	'j':  tcell.KeyCtrlJ,
    46  	'k':  tcell.KeyCtrlK,
    47  	'l':  tcell.KeyCtrlL,
    48  	'm':  tcell.KeyCtrlM,
    49  	'n':  tcell.KeyCtrlN,
    50  	'o':  tcell.KeyCtrlO,
    51  	'p':  tcell.KeyCtrlP,
    52  	'q':  tcell.KeyCtrlQ,
    53  	'r':  tcell.KeyCtrlR,
    54  	's':  tcell.KeyCtrlS,
    55  	't':  tcell.KeyCtrlT,
    56  	'u':  tcell.KeyCtrlU,
    57  	'v':  tcell.KeyCtrlV,
    58  	'w':  tcell.KeyCtrlW,
    59  	'x':  tcell.KeyCtrlX,
    60  	'y':  tcell.KeyCtrlY,
    61  	'z':  tcell.KeyCtrlZ,
    62  	'\\': tcell.KeyCtrlBackslash,
    63  	']':  tcell.KeyCtrlRightSq,
    64  	'^':  tcell.KeyCtrlCarat,
    65  	'_':  tcell.KeyCtrlUnderscore,
    66  }
    67  
    68  // Decode decodes a string as a key or combination of keys.
    69  func Decode(s string) (mod tcell.ModMask, key tcell.Key, ch rune, err error) {
    70  	if len(s) == 0 {
    71  		return 0, 0, 0, ErrInvalidKeyEvent
    72  	}
    73  
    74  	// Special case for plus rune decoding
    75  	if s[len(s)-1:] == "+" {
    76  		key = tcell.KeyRune
    77  		ch = '+'
    78  
    79  		if len(s) == 1 {
    80  			return mod, key, ch, nil
    81  		} else if len(s) == 2 {
    82  			return 0, 0, 0, ErrInvalidKeyEvent
    83  		} else {
    84  			s = s[:len(s)-2]
    85  		}
    86  	}
    87  
    88  	split := strings.Split(s, "+")
    89  DECODEPIECE:
    90  	for _, piece := range split {
    91  		// Decode modifiers
    92  		pieceLower := strings.ToLower(piece)
    93  		switch pieceLower {
    94  		case LabelCtrl:
    95  			mod |= tcell.ModCtrl
    96  			continue
    97  		case LabelAlt:
    98  			mod |= tcell.ModAlt
    99  			continue
   100  		case LabelMeta:
   101  			mod |= tcell.ModMeta
   102  			continue
   103  		case LabelShift:
   104  			mod |= tcell.ModShift
   105  			continue
   106  		}
   107  
   108  		// Decode key
   109  		for shortKey, fullKey := range fullKeyNames {
   110  			if pieceLower == strings.ToLower(fullKey) {
   111  				pieceLower = shortKey
   112  				break
   113  			}
   114  		}
   115  		switch pieceLower {
   116  		case "backspace":
   117  			key = tcell.KeyBackspace2
   118  			continue
   119  		case "space", "spacebar":
   120  			key = tcell.KeyRune
   121  			ch = ' '
   122  			continue
   123  		}
   124  		for k, keyName := range tcell.KeyNames {
   125  			if pieceLower == strings.ToLower(strings.ReplaceAll(keyName, "-", "+")) {
   126  				key = k
   127  				if key < 0x80 {
   128  					ch = rune(k)
   129  				}
   130  				continue DECODEPIECE
   131  			}
   132  		}
   133  
   134  		// Decode rune
   135  		if len(piece) > 1 {
   136  			return 0, 0, 0, ErrInvalidKeyEvent
   137  		}
   138  
   139  		key = tcell.KeyRune
   140  		ch = rune(piece[0])
   141  	}
   142  
   143  	if mod&tcell.ModCtrl != 0 {
   144  		k, ok := ctrlKeys[unicode.ToLower(ch)]
   145  		if ok {
   146  			key = k
   147  			if UnifyEnterKeys && key == ctrlKeys['j'] {
   148  				key = tcell.KeyEnter
   149  			} else if key < 0x80 {
   150  				ch = rune(key)
   151  			}
   152  		}
   153  	}
   154  
   155  	return mod, key, ch, nil
   156  }
   157  
   158  // Encode encodes a key or combination of keys a string.
   159  func Encode(mod tcell.ModMask, key tcell.Key, ch rune) (string, error) {
   160  	var b strings.Builder
   161  	var wrote bool
   162  
   163  	if mod&tcell.ModCtrl != 0 {
   164  		if key == tcell.KeyBackspace || key == tcell.KeyTab || key == tcell.KeyEnter {
   165  			mod ^= tcell.ModCtrl
   166  		} else {
   167  			for _, ctrlKey := range ctrlKeys {
   168  				if key == ctrlKey {
   169  					mod ^= tcell.ModCtrl
   170  					break
   171  				}
   172  			}
   173  		}
   174  	}
   175  
   176  	if key != tcell.KeyRune {
   177  		if UnifyEnterKeys && key == ctrlKeys['j'] {
   178  			key = tcell.KeyEnter
   179  		} else if key < 0x80 {
   180  			ch = rune(key)
   181  		}
   182  	}
   183  
   184  	// Encode modifiers
   185  	if mod&tcell.ModCtrl != 0 {
   186  		b.WriteString(upperFirst(LabelCtrl))
   187  		wrote = true
   188  	}
   189  	if mod&tcell.ModAlt != 0 {
   190  		if wrote {
   191  			b.WriteRune('+')
   192  		}
   193  		b.WriteString(upperFirst(LabelAlt))
   194  		wrote = true
   195  	}
   196  	if mod&tcell.ModMeta != 0 {
   197  		if wrote {
   198  			b.WriteRune('+')
   199  		}
   200  		b.WriteString(upperFirst(LabelMeta))
   201  		wrote = true
   202  	}
   203  	if mod&tcell.ModShift != 0 {
   204  		if wrote {
   205  			b.WriteRune('+')
   206  		}
   207  		b.WriteString(upperFirst(LabelShift))
   208  		wrote = true
   209  	}
   210  
   211  	if key == tcell.KeyRune && ch == ' ' {
   212  		if wrote {
   213  			b.WriteRune('+')
   214  		}
   215  		b.WriteString("Space")
   216  	} else if key != tcell.KeyRune {
   217  		// Encode key
   218  		keyName := tcell.KeyNames[key]
   219  		if keyName == "" {
   220  			return "", ErrInvalidKeyEvent
   221  		}
   222  		keyName = strings.ReplaceAll(keyName, "-", "+")
   223  		fullKeyName := fullKeyNames[strings.ToLower(keyName)]
   224  		if fullKeyName != "" {
   225  			keyName = fullKeyName
   226  		}
   227  
   228  		if wrote {
   229  			b.WriteRune('+')
   230  		}
   231  		b.WriteString(keyName)
   232  	} else {
   233  		// Encode rune
   234  		if wrote {
   235  			b.WriteRune('+')
   236  		}
   237  		b.WriteRune(ch)
   238  	}
   239  
   240  	return b.String(), nil
   241  }
   242  
   243  func upperFirst(s string) string {
   244  	if len(s) <= 1 {
   245  		return strings.ToUpper(s)
   246  	}
   247  	return strings.ToUpper(s[:1]) + s[1:]
   248  }
   249  

View as plain text