class SublimeDSL::SublimeText::Keyboard

A keyboard.

Constants

SUBLIME_ALIAS_MAP
SUBLIME_ALIAS_RE
SUBLIME_KEYS
SUBLIME_MODIFIERS

Attributes

keys[R]
keystrokes_hash[R]
modifiers[R]
name[R]
os[R]

Public Class Methods

get(name, root) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 62
def self.get(name, root)
  file = nil
  Dir.chdir(root) do
    files = Dir['**/*.keyboard.rb']
    file = SublimeText.order_config(files).last
    file or raise Error, "file '#{name}.keyboard.rb' not found"
    file = File.expand_path(file)
  end
  DSLReader.new(file)._keyboard
end
new(name) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 77
def initialize(name)
  @name = name
  @os = nil
  @modifiers = []
  @keys = []
  @keystrokes_hash = {}
  # Vintage generic character
  add_keystroke CharStroke.new('<character>')
end
sublime() click to toggle source

The standard Sublime Text keyboard.

# File lib/sublime_dsl/sublime_text/keyboard.rb, line 43
def self.sublime
  @sublime ||= begin
    kb = Keyboard.new('Sublime Text')
    SUBLIME_MODIFIERS.each { |name| kb.add_modifier name }
    SUBLIME_KEYS.each { |name| kb.add_key name }
    # map_char and map_key call this method while @sublime is not yet set
    unless @defining_sublime
      @defining_sublime = true
      # FIXME: space => key_event nil, but generates a key_event when modified,
      # because it's a character
      # => put it in SUBLIME_ALIAS_MAP, but remember it was named 'space' when
      # generating its to_s ('ctrl+ ' will generate problems...)
      kb.map_char 'space' => ' ', key_event: nil
      @defining_sublime = false
    end
    kb
  end
end

Public Instance Methods

add_key(name) click to toggle source

Create a Key name and adds the corresponding KeyStroke to this keyboard.

  • If name is one character long, the KeyStroke generates a chr_event name and no key_event.

  • Otherwise, the KeyStroke generates no chr_event, and a key_event name if name is a Sublime Text key name.

# File lib/sublime_dsl/sublime_text/keyboard.rb, line 124
def add_key(name)
  k = Key.new(name)
  k.st_name = name if SUBLIME_KEYS.include?(name)
  @keys << k
  ks = KeyStroke.new([], k)
  if name.length == 1
    ks.chr_event = name
  else
    ks.key_event = k.st_name
  end
  add_keystroke ks

  k
end
add_keystroke(ks) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 175
def add_keystroke(ks)
  @keystrokes_hash[ks.to_spec] = ks
end
add_modifier(name) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 104
def add_modifier(name)
  m = Key.new(name)
  m.st_name = name if SUBLIME_MODIFIERS.include?(name)
  @modifiers << m
end
assign_default_key_event(keystroke) click to toggle source

Assign the default key_event of a new keystroke (before its registration). It will be the modified key_event of a less specific keystroke.

For instance, if we register 'shift+ctrl+keypad5', and 'shift+keypad5' has key event 'clear', this assigns 'ctrl+clear'. If there are several possibilities, the one(s) with the most modifiers are selected. If there are ex-aequo, the order of precedence is the order of registration of the modifiers.

# File lib/sublime_dsl/sublime_text/keyboard.rb, line 285
def assign_default_key_event(keystroke)

  # the ST keyboard: just register
  if self == Keyboard.sublime
    spec = keystroke.to_spec
    keystroke.key_event = spec if spec.length > 1
    return
  end

  # we always have modifiers, as all non-modified keys are already registered
  keystroke.modifiers.empty? and raise Error, "bug: #{keystroke} is not registered"

  # if the key has a ST name, assume the ST equivalent
  if keystroke.key.st_name
    spec = keystroke.modifiers.map(&:st_name).join('+') << '+' << keystroke.key.st_name
    keystroke.key_event = spec
    return
  end

  # the candidates are the registered keystrokes for that key with all
  # modifiers included in the passed modifiers (so at least the keystroke
  # for the key itself)
  candidates = keystrokes_hash.values.select do |ks|
    ks.key == keystroke.key &&
      ks.modifiers - keystroke.modifiers == []
  end
  candidates.empty? and raise Error, "bug: nothing registered for #{keystroke}"

  if candidates.length > 1

    # select the one(s) with the most modifiers
    max = 0
    candidates.each do |ks|
      max = ks.modifiers.length if ks.modifiers.length > max
    end
    candidates.reject! { |ks| ks.modifiers.length < max }

    # apply modifier priority:
    # create the bit mask for each keystroke,
    # and then select the lowest one
    if candidates.length > 1
      sort_array =
        candidates.map do |ks|
          mask = 0
          ks.modifiers.each do |m|
            mask += (1 << modifiers_index_hash[m.name])
          end
          [ks, mask]
        end
      candidates = sort_array.sort_by(&:last).map(&:first)
    end

  end

  # select the reference keystroke
  ref = candidates.first
  if ref.key_event.nil?
    keystroke.key_event = nil
    return
  end

  # apply the modifier delta versus the reference
  delta = keystroke.modifiers - ref.modifiers
  spec = delta.map(&:st_name).join('+') << '+' << ref.key_event
  keystroke.key_event = Keyboard.sublime.ensure_keystroke(spec).to_spec

end
ensure_keystroke(spec) click to toggle source

Returns a KeyStroke or CharStroke for spec, and adds it to the registered keystrokes if not already there. Raises an exception if spec is not valid.

  • If spec is one character long, returns a KeyStroke or CharStroke if found, otherwise creates a new CharStroke.

  • If spec is more than one character long, the key has to exist, as well as the modifiers if any, otherwise an exception is raised. If the corresponding KeyStroke is not found, it will be created and registered.

# File lib/sublime_dsl/sublime_text/keyboard.rb, line 217
def ensure_keystroke(spec)

  # split the specification
  #   ctrl++ -> ['ctrl', '+']
  #   ctrl+num+ -> ['ctrl', 'num+']
  *modifier_names, key_name = spec.split(/\+(?!$)/)

  # normalize the key name
  self == Keyboard.sublime and
    key_name.sub! SUBLIME_ALIAS_RE, SUBLIME_ALIAS_MAP

  # check & reorder the modifiers
  unless modifier_names.empty?
    sorted = []
    modifier_names.each do |name|
      i = modifiers_index_hash[name] or
        raise Error, "invalid modifier #{name.inspect} for keyboard #{self.name}"
      sorted[i] = name
    end
    modifier_names = sorted.compact
  end

  # if there is a registered keystroke for this spec, return it
  std_spec = [*modifier_names, key_name].join('+')
  ks = keystrokes_hash[std_spec]
  return ks if ks

  # shift + character is not ok
  modifiers = modifier_names.map { |n| modifier(n) }
  modifiers.map(&:st_name) == ['shift'] && key_name.length == 1 and
    raise Error, "#{spec.to_source(true)} is invalid: specify the corresponding character"

  key = key(key_name)

  # The ST keyboard accepts any character as a valid key
  if self == Keyboard.sublime && key.nil?
    key_name.length == 1 or raise Error, "invalid key name in #{spec.to_source(true)}"
    key = add_key(key_name)
    return keystrokes_hash[key_name] if modifiers.empty?
  end

  if key
    # registered key
    ks = KeyStroke.new(modifiers, key)
    assign_default_key_event ks
  else
    # unregistered: has to be a character
    key_name.length == 1 or
      raise Error, "#{spec.inspect}: key #{key_name.inspect} is undefined"
    modifier_names.empty? or
      raise Error, "#{spec.inspect}: #{key_name.inspect} is not a key"
    ks = CharStroke.new(key_name)
  end

  add_keystroke ks

  ks
end
key(name) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 100
def key(name)
  keys.find { |k| k.name == name }
end
keystroke_for_sublime_spec(st_spec) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 183
def keystroke_for_sublime_spec(st_spec)
  st_ks = Keyboard.sublime.ensure_keystroke(st_spec)
  st_spec = st_ks.to_spec   # standardize

  # return the first one with a key event = the passed spec
  this_ks = keystrokes.find { |ks| ks.key_event == st_spec }
  return this_ks if this_ks

  # if one char, no problem
  return ensure_keystroke(st_spec) if st_spec.length == 1 || st_spec == '<character>'

  # not (yet?) registered: find a keystroke with the same key
  base_ks = keystrokes.find { |ks| ks.key && ks.key.st_name == st_ks.key.name }
  if base_ks
    this_spec = st_ks.modifiers.map(&:name).join('+') << '+' << base_ks.key.name
    this_ks = ensure_keystroke(this_spec)
    return this_ks
  end

  NullStroke.new(st_spec)
end
keystrokes() click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 179
def keystrokes
  keystrokes_hash.values
end
map_char(options = {}) click to toggle source

Map a keystroke to a chr_event. Optionally sets the key_event.

# File lib/sublime_dsl/sublime_text/keyboard.rb, line 161
def map_char(options = {})
  ks_name = options.keys.first
  ks = ensure_keystroke(ks_name)
  ks.chr_event = options[ks_name]
  if options.has_key?(:key_event)
    st_spec = options[:key_event]
    if st_spec
      st_ks = Keyboard.sublime.ensure_keystroke(st_spec)
      st_spec = st_ks.to_spec
    end
    ks.key_event = st_spec
  end
end
map_key(spec, st_spec) click to toggle source

Assigns the key_event for the keystroke spec. st_spec must be a valid ST keystroke, or nil if the keystroke is not seen by ST.

# File lib/sublime_dsl/sublime_text/keyboard.rb, line 143
def map_key(spec, st_spec)
  ks = ensure_keystroke(spec)
  if st_spec.nil?
    ks.key_event = nil
  else
    st_ks = Keyboard.sublime.ensure_keystroke(st_spec)
    st_spec = st_ks.to_spec
    if ks.modifiers.empty?
      ks.key.st_name = st_spec
      ks.key_event = st_spec if st_spec.length > 1
    else
      ks.key_event = st_spec
    end
  end
end
map_modifier(name, st_name) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 110
def map_modifier(name, st_name)
  m = modifier(name) or raise Error, "unknown modifier: '#{name}'"
  SUBLIME_MODIFIERS.include?(st_name) or raise Error, "invalid ST modifier: #{st_name}"
  m.st_name = st_name
end
modifier(name) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 96
def modifier(name)
  modifiers.find { |k| k.name == name }
end
modifiers_index_hash() click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 353
def modifiers_index_hash
  @modifiers_index_hash ||= begin
    h = {}
    modifiers.each_with_index { |m, i| h[m.name] = i }
    h
  end
end
os=(value) click to toggle source
# File lib/sublime_dsl/sublime_text/keyboard.rb, line 87
def os=(value)
  case value.to_s.downcase
  when 'windows' then @os = 'Windows'
  when 'osx' then @os = 'OSX'
  when 'linux' then @os = 'Linux'
  else raise Error, "invalid OS value: #{value}"
  end
end