Class: Fatty::KeyEvent

Inherits:
Object
  • Object
show all
Defined in:
lib/fatty/key_event.rb

Overview

The KeyEvent class is a simple class to store a key with its modifiers. KeyMap will map a KeyEvent to an action (for a given context), and the KeyDecoder class is responsible for turning raw key inputs from curses into a KeyEvent. The CURSES_TO_EVENT constant is an initial map from known curses key codes to KeyEvents. These can be overriden for different terminals in Fatty::Config.keydefs.

Constant Summary collapse

CTRL_PUNCT =
{
  27 => :'[',   # ESC is also C-[
  28 => :'\\',  # C-\
  29 => :']',   # C-]
  30 => :'^',   # C-^
  31 => :'/',   # C-_
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key:, text: nil, raw: nil, ctrl: false, meta: false, shift: false) ⇒ KeyEvent

Returns a new instance of KeyEvent.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/fatty/key_event.rb', line 28

def initialize(key:, text: nil, raw: nil, ctrl: false, meta: false, shift: false)
  # If the decoder gives us a control character as an integer (1..26),
  # canonicalize it to ctrl+letter so keymaps and display behave nicely.
  if key.is_a?(Integer)
    if CTRL_CODE_TO_LETTER.key?(key)
      key = CTRL_CODE_TO_LETTER[key]
      ctrl = true
    elsif CTRL_PUNCT.key?(key)
      key = CTRL_PUNCT[key]
      ctrl = true
    end
  end
  @key = key           # Symbol or named key
  @raw = raw           # the key (or keys for escape sequences) as returned by Curses
  @ctrl = ctrl
  @meta = meta
  @shift = shift
  # invariant: chorded keys do not self-insert
  @text = ctrl || meta ? nil : text
end

Instance Attribute Details

#ctrlObject (readonly)

Returns the value of attribute ctrl.



26
27
28
# File 'lib/fatty/key_event.rb', line 26

def ctrl
  @ctrl
end

#keyObject (readonly)

Returns the value of attribute key.



26
27
28
# File 'lib/fatty/key_event.rb', line 26

def key
  @key
end

#metaObject (readonly)

Returns the value of attribute meta.



26
27
28
# File 'lib/fatty/key_event.rb', line 26

def meta
  @meta
end

#rawObject (readonly)

Returns the value of attribute raw.



26
27
28
# File 'lib/fatty/key_event.rb', line 26

def raw
  @raw
end

#shiftObject (readonly)

Returns the value of attribute shift.



26
27
28
# File 'lib/fatty/key_event.rb', line 26

def shift
  @shift
end

#textObject (readonly)

Returns the value of attribute text.



26
27
28
# File 'lib/fatty/key_event.rb', line 26

def text
  @text
end

Class Method Details

.key_to_str(key: "<?>", ctrl: false, meta: false, shift: false) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/fatty/key_event.rb', line 49

def self.key_to_str(key: "<?>", ctrl: false, meta: false, shift: false)
  mods = []
  mods << "C" if ctrl
  mods << "M" if meta
  mods << "S" if shift
  key_str = key.to_s
  mods.empty? ? key_str : "#{mods.join('-')}-#{key_str}"
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



62
63
64
65
66
67
68
# File 'lib/fatty/key_event.rb', line 62

def ==(other)
  other.is_a?(KeyEvent) &&
    key == other.key &&
    ctrl == other.ctrl &&
    meta == other.meta &&
    shift == other.shift
end

#action_owner_name(owner) ⇒ Object



174
175
176
177
178
179
180
181
182
# File 'lib/fatty/key_event.rb', line 174

def action_owner_name(owner)
  name =
    if owner.respond_to?(:name) && owner.name
      owner.name
    else
      owner.to_s
    end
  name.split("::").last
end

#action_target_text(action) ⇒ Object



165
166
167
168
169
170
171
172
# File 'lib/fatty/key_event.rb', line 165

def action_target_text(action)
  return "" unless action

  defn = Fatty::Actions.lookup(action)
  return "  (unregistered action)" unless defn

  "  (#{defn[:on]}: #{action_owner_name(defn[:owner])}##{defn[:method]})"
end

#binding_action(binding) ⇒ Object



143
144
145
146
147
148
149
150
# File 'lib/fatty/key_event.rb', line 143

def binding_action(binding)
  case binding
  when Array
    [binding[0], binding[1..] || []]
  else
    [binding, []]
  end
end

#binding_text(binding) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/fatty/key_event.rb', line 152

def binding_text(binding)
  case binding
  when nil
    "(none)"
  when Array
    action = binding[0]
    args = binding[1..] || []
    args.empty? ? action.inspect : "#{action.inspect} #{args.inspect}"
  else
    binding.inspect
  end
end

#bindingsObject



87
88
89
# File 'lib/fatty/key_event.rb', line 87

def bindings
  Fatty::KeyMap.active.bindings_for(self) || {}
end

#bindings_textObject



133
134
135
136
137
138
139
140
141
# File 'lib/fatty/key_event.rb', line 133

def bindings_text
  return "(none)" if bindings.empty?

  text = bindings.map { |context, binding|
    action, _args = binding_action(binding)
    "    context: #{context}: #{binding_text(binding)}#{action_target_text(action)}"
  }.join("\n")
  text
end

#bytes_from_raw(value) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/fatty/key_event.rb', line 112

def bytes_from_raw(value)
  case value
  when Array
    value.flat_map { |item| bytes_from_raw(item) }
  when String
    value.bytes
  when Integer
    [value]
  else
    []
  end
end

#codeObject



99
100
101
102
103
104
105
106
# File 'lib/fatty/key_event.rb', line 99

def code
  case raw
  when Integer
    raw
  when Array
    raw.reverse.find { |item| item.is_a?(Integer) }
  end
end

#coded?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/fatty/key_event.rb', line 79

def coded?
  key.is_a?(Symbol)
end

#colorObject



184
185
186
187
188
189
190
191
192
# File 'lib/fatty/key_event.rb', line 184

def color
  if uncoded?
    :oops
  elsif unbound?
    :warn
  else
    :good
  end
end

#colorize(text) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/fatty/key_event.rb', line 270

def colorize(text)
  case color
  when :good
    Rainbow(text).green
  when :warn
    Rainbow(text).yellow
  when :oops
    Rainbow(text).red
  else
    text
  end
end

#ctrl?Boolean

Returns:

  • (Boolean)


204
205
206
# File 'lib/fatty/key_event.rb', line 204

def ctrl?
  @ctrl
end

#hashObject



71
72
73
# File 'lib/fatty/key_event.rb', line 71

def hash
  [key, ctrl, meta, shift].hash
end

#key_nameObject



95
96
97
# File 'lib/fatty/key_event.rb', line 95

def key_name
  coded? ? to_s : "(none)"
end

#meta?Boolean

Returns:

  • (Boolean)


208
209
210
# File 'lib/fatty/key_event.rb', line 208

def meta?
  @meta
end

#modifier_textObject



125
126
127
128
129
130
131
# File 'lib/fatty/key_event.rb', line 125

def modifier_text
  mods = []
  mods << "ctrl" if ctrl?
  mods << "meta" if meta?
  mods << "shift" if shift?
  mods.empty? ? "(none)" : mods.join(", ")
end

#printable?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/fatty/key_event.rb', line 75

def printable?
  text && !text.empty? && text != "\n" && text != "\r"
end

#raw_bytesObject



108
109
110
# File 'lib/fatty/key_event.rb', line 108

def raw_bytes
  bytes_from_raw(raw)
end

#reportObject



216
217
218
219
220
221
222
223
224
# File 'lib/fatty/key_event.rb', line 216

def report
  if uncoded?
    report_for_uncoded
  elsif unbound?
    report_for_unbound
  else
    report_for_bound
  end
end

#report_for_boundObject



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/fatty/key_event.rb', line 253

def report_for_bound
  rule = colorize("=" * 55) + "\n"
  out = +""
  out << rule
  out << colorize("Key report #{status_string}:\n")
  # out << "  TERM:      #{terminal_name}\n"
  out << "  code:      #{code.inspect}\n"
  out << "  raw:       #{raw.inspect}\n"
  out << "  bytes:     #{raw_bytes.inspect}\n"
  out << "  key:       #{key.inspect}\n"
  out << "  name:      #{key_name}\n"
  out << "  text:      #{text.inspect}\n"
  out << "  modifiers: #{modifier_text}\n"
  out << "  bindings:\n#{bindings_text}\n"
  out << "\n"
end

#report_for_unboundObject



238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/fatty/key_event.rb', line 238

def report_for_unbound
  rule = colorize("=" * 55) + "\n"
  out = +""
  out << rule
  out << colorize("Key report #{status_string}:\n")
  # out << "  TERM:      #{terminal_name}\n"
  out << "  code:      #{code.inspect}\n"
  out << "  raw:       #{raw.inspect}\n"
  out << "  bytes:     #{raw_bytes.inspect}\n"
  out << "  key:       #{key.inspect}\n"
  out << "  name:      #{key_name}\n"
  out << "  text:      #{text.inspect}\n"
  out << "  modifiers: #{modifier_text}\n"
end

#report_for_uncodedObject



226
227
228
229
230
231
232
233
234
235
236
# File 'lib/fatty/key_event.rb', line 226

def report_for_uncoded
  rule = colorize("=" * 55) + "\n"
  out = +""
  out << rule
  out << colorize("Key report #{status_string}:\n")
  out << "  code:      #{code.inspect}\n"
  out << "  raw:       #{raw.inspect}\n"
  out << "  bytes:     #{raw_bytes.inspect}\n"
  out << "  key:       #{key.inspect}\n"
  out << "\n"
end

#shift?Boolean

Returns:

  • (Boolean)


212
213
214
# File 'lib/fatty/key_event.rb', line 212

def shift?
  @shift
end

#status_stringObject



194
195
196
197
198
199
200
201
202
# File 'lib/fatty/key_event.rb', line 194

def status_string
  if uncoded?
    "(UNCODED)"
  elsif unbound?
    "(UNBOUND)"
  else
    "(BOUND)"
  end
end

#suggested_keybindingObject



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/fatty/key_event.rb', line 314

def suggested_keybinding
  out = <<~TEXT

    No action is bound to #{key_name}.

    Suggested keybindings.yml entry:

    ............>8 snip here 8<....................
    #{yaml_desc}
    ............>8 snip here 8<....................
  TEXT

  out << <<~TEXT

  TEXT
  out
end

#suggested_keydef(terminal_name) ⇒ Object



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/fatty/key_event.rb', line 293

def suggested_keydef(terminal_name)
  return "" unless code

  <<~TEXT

    No key name is associated with keycode #{code}.

    Suggested keydefs.yml entry:

    ............>8 snip here 8<....................
    #{terminal_name}:
      map:
        #{code}:
          key: key_name
          shift: <true/false>
          ctrl: <true/false>
          meta: <true/false>
    ............>8 snip here 8<....................
  TEXT
end

#suggested_snippet(terminal_name) ⇒ Object



283
284
285
286
287
288
289
290
291
# File 'lib/fatty/key_event.rb', line 283

def suggested_snippet(terminal_name)
  if uncoded?
    suggested_keydef(terminal_name)
  elsif unbound?
    suggested_keybinding
  else
    ''
  end
end

#to_sObject



58
59
60
# File 'lib/fatty/key_event.rb', line 58

def to_s
  self.class.key_to_str(key:, ctrl:, meta:, shift:)
end

#unbound?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/fatty/key_event.rb', line 91

def unbound?
  coded? && bindings.empty?
end

#uncoded?Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/fatty/key_event.rb', line 83

def uncoded?
  !key.is_a?(Symbol)
end

#yaml_descObject



332
333
334
335
336
337
338
339
340
# File 'lib/fatty/key_event.rb', line 332

def yaml_desc
  lines = ["# #{key_name}", "- key: #{key}"]
  lines << "  shift: true" if shift?
  lines << "  ctrl: true" if ctrl?
  lines << "  meta: true" if meta?
  lines << "  context: <valid_context>"
  lines << "  action: <action_name>"
  lines.join("\n")
end