src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/ui/key.go (about)

     1  package ui
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"src.elv.sh/pkg/parse"
     9  	"src.elv.sh/pkg/persistent/hash"
    10  )
    11  
    12  // Key represents a single keyboard input, typically assembled from a escape
    13  // sequence.
    14  type Key struct {
    15  	Rune rune
    16  	Mod  Mod
    17  }
    18  
    19  // K constructs a new Key.
    20  func K(r rune, mods ...Mod) Key {
    21  	var mod Mod
    22  	for _, m := range mods {
    23  		mod |= m
    24  	}
    25  	return Key{r, mod}
    26  }
    27  
    28  // Default is used in the key binding table to indicate a default binding.
    29  var DefaultKey = Key{DefaultBindingRune, 0}
    30  
    31  // Mod represents a modifier key.
    32  type Mod byte
    33  
    34  // Values for Mod.
    35  const (
    36  	// Shift is the shift modifier. It is only applied to special keys (e.g.
    37  	// Shift-F1). For instance 'A' and '@' which are typically entered with the
    38  	// shift key pressed, are not considered to be shift-modified.
    39  	Shift Mod = 1 << iota
    40  	// Alt is the alt modifier, traditionally known as the meta modifier.
    41  	Alt
    42  	Ctrl
    43  )
    44  
    45  const functionKeyOffset = 1000
    46  
    47  // Special negative runes to represent function keys, used in the Rune field
    48  // of the Key struct. This also has a few function names that are aliases for
    49  // simple runes. See keyNames below for mapping these values to strings.
    50  const (
    51  	// DefaultBindingRune is a special value to represent a default binding.
    52  	DefaultBindingRune rune = iota - functionKeyOffset
    53  
    54  	F1
    55  	F2
    56  	F3
    57  	F4
    58  	F5
    59  	F6
    60  	F7
    61  	F8
    62  	F9
    63  	F10
    64  	F11
    65  	F12
    66  
    67  	Up
    68  	Down
    69  	Right
    70  	Left
    71  
    72  	Home
    73  	Insert
    74  	Delete
    75  	End
    76  	PageUp
    77  	PageDown
    78  
    79  	// Function key names that are aliases for their ASCII representation.
    80  	Tab       = '\t'
    81  	Enter     = '\n'
    82  	Backspace = 0x7f
    83  )
    84  
    85  // keyNames maps runes, whether simple or function, to symbolic key names.
    86  var keyNames = map[rune]string{
    87  	DefaultBindingRune: "Default",
    88  	F1:                 "F1",
    89  	F2:                 "F2",
    90  	F3:                 "F3",
    91  	F4:                 "F4",
    92  	F5:                 "F5",
    93  	F6:                 "F6",
    94  	F7:                 "F7",
    95  	F8:                 "F8",
    96  	F9:                 "F9",
    97  	F10:                "F10",
    98  	F11:                "F11",
    99  	F12:                "F12",
   100  	Up:                 "Up",
   101  	Down:               "Down",
   102  	Right:              "Right",
   103  	Left:               "Left",
   104  	Home:               "Home",
   105  	Insert:             "Insert",
   106  	Delete:             "Delete",
   107  	End:                "End",
   108  	PageUp:             "PageUp",
   109  	PageDown:           "PageDown",
   110  	Tab:                "Tab",
   111  	Enter:              "Enter",
   112  	Backspace:          "Backspace",
   113  }
   114  
   115  func (k Key) Kind() string {
   116  	return "edit:key"
   117  }
   118  
   119  func (k Key) Equal(other any) bool {
   120  	return k == other
   121  }
   122  
   123  func (k Key) Hash() uint32 {
   124  	return hash.DJB(uint32(k.Rune), uint32(k.Mod))
   125  }
   126  
   127  func (k Key) Repr(int) string {
   128  	return "(edit:key " + parse.Quote(k.String()) + ")"
   129  }
   130  
   131  func (k Key) String() string {
   132  	var b bytes.Buffer
   133  
   134  	if k.Mod&Ctrl != 0 {
   135  		b.WriteString("Ctrl-")
   136  	}
   137  	if k.Mod&Alt != 0 {
   138  		b.WriteString("Alt-")
   139  	}
   140  	if k.Mod&Shift != 0 {
   141  		b.WriteString("Shift-")
   142  	}
   143  
   144  	if name, ok := keyNames[k.Rune]; ok {
   145  		b.WriteString(name)
   146  	} else {
   147  		if k.Rune >= 0 {
   148  			b.WriteRune(k.Rune)
   149  		} else {
   150  			fmt.Fprintf(&b, "(bad function key %d)", k.Rune)
   151  		}
   152  	}
   153  
   154  	return b.String()
   155  }
   156  
   157  // modifierByName maps a name to an modifier. It is used for parsing keys where
   158  // the modifier string is first turned to lower case, so that all of C, c,
   159  // CTRL, Ctrl and ctrl can represent the Ctrl modifier.
   160  var modifierByName = map[string]Mod{
   161  	"S": Shift, "Shift": Shift,
   162  	"A": Alt, "Alt": Alt,
   163  	"M": Alt, "Meta": Alt,
   164  	"C": Ctrl, "Ctrl": Ctrl,
   165  }
   166  
   167  // ParseKey parses a symbolic key. The syntax is:
   168  //
   169  //	Key = { Mod ('+' | '-') } BareKey
   170  //
   171  //	BareKey = FunctionKeyName | SingleRune
   172  func ParseKey(s string) (Key, error) {
   173  	var k Key
   174  
   175  	// Parse modifiers.
   176  	for {
   177  		i := strings.IndexAny(s, "+-")
   178  		if i == -1 {
   179  			break
   180  		}
   181  		modname := s[:i]
   182  		if mod, ok := modifierByName[modname]; ok {
   183  			k.Mod |= mod
   184  			s = s[i+1:]
   185  		} else {
   186  			return Key{}, fmt.Errorf("bad modifier: %s", parse.Quote(modname))
   187  		}
   188  	}
   189  
   190  	if len(s) == 1 {
   191  		k.Rune = rune(s[0])
   192  		if k.Rune < 0x20 {
   193  			if k.Mod&Ctrl != 0 {
   194  				//lint:ignore ST1005 We want this error to begin with "Ctrl" rather than "ctrl"
   195  				// since the user has to use the capitalized form when creating a key binding.
   196  				return Key{}, fmt.Errorf("Ctrl modifier with literal control char: %q", k.Rune)
   197  			}
   198  			// Convert literal control char to the equivalent canonical form,
   199  			// e.g. "\e" to Ctrl-'[' and "\t" to Ctrl-I.
   200  			k.Mod |= Ctrl
   201  			k.Rune += 0x40
   202  		}
   203  		// TODO(xiaq): The following assumptions about keys with Ctrl are not
   204  		// checked with all terminals.
   205  		if k.Mod&Ctrl != 0 {
   206  			// Keys with Ctrl as one of the modifiers and a single ASCII letter
   207  			// as the base rune do not distinguish between cases. So we
   208  			// normalize the base rune to upper case.
   209  			if 'a' <= k.Rune && k.Rune <= 'z' {
   210  				k.Rune += 'A' - 'a'
   211  			}
   212  			// Normalize Ctrl-I to Tab, Ctrl-J to Enter, and Ctrl-? to Backspace.
   213  			if k.Rune == 'I' {
   214  				k.Mod &= ^Ctrl
   215  				k.Rune = Tab
   216  			} else if k.Rune == 'J' {
   217  				k.Mod &= ^Ctrl
   218  				k.Rune = Enter
   219  			}
   220  		}
   221  		return k, nil
   222  	}
   223  
   224  	// Is this is a symbolic key name, such as `Enter`, we recognize?
   225  	for r, name := range keyNames {
   226  		if s == name {
   227  			k.Rune = r
   228  			return k, nil
   229  		}
   230  	}
   231  
   232  	return Key{}, fmt.Errorf("bad key: %s", parse.Quote(s))
   233  }
   234  
   235  // Keys implements sort.Interface.
   236  type Keys []Key
   237  
   238  func (ks Keys) Len() int      { return len(ks) }
   239  func (ks Keys) Swap(i, j int) { ks[i], ks[j] = ks[j], ks[i] }
   240  func (ks Keys) Less(i, j int) bool {
   241  	return ks[i].Mod < ks[j].Mod ||
   242  		(ks[i].Mod == ks[j].Mod && ks[i].Rune < ks[j].Rune)
   243  }