aboutsummaryrefslogtreecommitdiffstats
path: root/lib/keyboard_utils.coffee
blob: 5ba47bff2ea60ceb00a069c744d35c29193fc854 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
mapKeyRegistry = {}
# NOTE: "?" here for the tests.
Utils?.monitorChromeStorage "mapKeyRegistry", (value) => mapKeyRegistry = value

KeyboardUtils =
  keyCodes:
    { ESC: 27, backspace: 8, deleteKey: 46, enter: 13, ctrlEnter: 10, space: 32, shiftKey: 16, ctrlKey: 17, f1: 112,
    f12: 123, tab: 9, downArrow: 40, upArrow: 38 }

  keyNames:
    { 37: "left", 38: "up", 39: "right", 40: "down", 32: "space", 8: "backspace" }

  # This is a mapping of the incorrect keyIdentifiers generated by Webkit on Windows during keydown events to
  # the correct identifiers, which are correctly generated on Mac. We require this mapping to properly handle
  # these keys on Windows. See https://bugs.webkit.org/show_bug.cgi?id=19906 for more details.
  keyIdentifierCorrectionMap:
    "U+00C0": ["U+0060", "U+007E"] # `~
    "U+00BD": ["U+002D", "U+005F"] # -_
    "U+00BB": ["U+003D", "U+002B"] # =+
    "U+00DB": ["U+005B", "U+007B"] # [{
    "U+00DD": ["U+005D", "U+007D"] # ]}
    "U+00DC": ["U+005C", "U+007C"] # \|
    "U+00BA": ["U+003B", "U+003A"] # ;:
    "U+00DE": ["U+0027", "U+0022"] # '"
    "U+00BC": ["U+002C", "U+003C"] # ,<
    "U+00BE": ["U+002E", "U+003E"] # .>
    "U+00BF": ["U+002F", "U+003F"] # /?

  init: ->
    if (navigator.userAgent.indexOf("Mac") != -1)
      @platform = "Mac"
    else if (navigator.userAgent.indexOf("Linux") != -1)
      @platform = "Linux"
    else
      @platform = "Windows"

  # We are migrating from using event.keyIdentifier to using event.key.  For some period of time, we must
  # support both.  This wrapper can be removed once Chrome 52 is considered too old to support.
  getKeyChar: (event) ->
    # We favor using event.keyIdentifier due to Chromium's currently (Chrome 51) incorrect implementataion of
    # event.key; see #2147.
    if event.keyIdentifier?
      @getKeyCharUsingKeyIdentifier event
    else
      @getKeyCharUsingKey event

  getKeyCharUsingKey: (event) ->
    if event.keyCode of @keyNames
      @keyNames[event.keyCode]
    # It appears that event.key is not always defined (see #2453).
    else if not event.key?
      ""
    else if event.key.length == 1
      event.key
    else if event.key.length == 2 and "F1" <= event.key <= "F9"
      event.key.toLowerCase() # F1 to F9.
    else if event.key.length == 3 and "F10" <= event.key <= "F12"
      event.key.toLowerCase() # F10 to F12.
    else
      ""

  getKeyCharUsingKeyIdentifier: (event) ->
    # Handle named keys.
    keyCode = event.keyCode
    if keyCode
      if keyCode of @keyNames
        return @keyNames[keyCode]
      # Function keys.
      if @keyCodes.f1 <= keyCode <= @keyCodes.f12
        return "f" + (1 + keyCode - keyCodes.f1)

    keyIdentifier = event.keyIdentifier

    # Not a letter.
    if not keyIdentifier.startsWith "U+"
      return ""

    # On Windows, the keyIdentifiers for non-letter keys are incorrect. See
    # https://bugs.webkit.org/show_bug.cgi?id=19906 for more details.
    if ((@platform == "Windows" || @platform == "Linux") && @keyIdentifierCorrectionMap[keyIdentifier])
      correctedIdentifiers = @keyIdentifierCorrectionMap[keyIdentifier]
      keyIdentifier = if event.shiftKey then correctedIdentifiers[1] else correctedIdentifiers[0]
    unicodeKeyInHex = "0x" + keyIdentifier.substring(2)
    character = String.fromCharCode(parseInt(unicodeKeyInHex)).toLowerCase()
    if event.shiftKey then character.toUpperCase() else character

  isPrimaryModifierKey: (event) -> if (@platform == "Mac") then event.metaKey else event.ctrlKey

  isEscape: do ->

    # TODO(smblott) Change this to use event.key.
    (event) ->
      event.keyCode == @keyCodes.ESC || do =>
        keyChar = @getKeyCharString event
        # <c-[> is mapped to Escape in Vim by default.
        keyChar == "<c-[>"

  # TODO. This is probably a poor way of detecting printable characters.  However, it shouldn't incorrectly
  # identify any of chrome's own keyboard shortcuts as printable.
  isPrintable: (event) ->
    return false if event.metaKey or event.ctrlKey or event.altKey
    keyChar =
      if event.type == "keypress"
        String.fromCharCode event.charCode
      else
        @getKeyChar event
    keyChar.length == 1

  # Return the Vimium key representation for this keyboard event. Return a falsy value (the empty string or
  # undefined) when no Vimium representation is appropriate.
  getKeyCharString: (event) ->
    switch event.type
      when "keypress"
        # Ignore modifier keys by themselves.
        if 31 < event.keyCode
          String.fromCharCode event.charCode

      when "keydown"
        if keyChar = @getKeyChar event
          modifiers = []

          keyChar = keyChar.toUpperCase() if event.shiftKey and keyChar.length == 1
          # These must be in alphabetical order (to match the sorted modifier order in Commands.normalizeKey).
          modifiers.push "a" if event.altKey
          modifiers.push "c" if event.ctrlKey
          modifiers.push "m" if event.metaKey

          keyChar = [modifiers..., keyChar].join "-"
          keyChar = "<#{keyChar}>" if 1 < keyChar.length
          keyChar = mapKeyRegistry[keyChar] ? keyChar
          keyChar

KeyboardUtils.init()

root = exports ? window
root.KeyboardUtils = KeyboardUtils
# TODO(philc): A lot of code uses this keyCodes hash... maybe we shouldn't export it as a global.
root.keyCodes = KeyboardUtils.keyCodes