sublime_dsl 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +136 -0
- data/Rakefile +248 -0
- data/SYNTAX.md +927 -0
- data/bin/subdsl +4 -0
- data/lib/sublime_dsl/cli/export.rb +134 -0
- data/lib/sublime_dsl/cli/import.rb +143 -0
- data/lib/sublime_dsl/cli.rb +125 -0
- data/lib/sublime_dsl/core_ext/enumerable.rb +24 -0
- data/lib/sublime_dsl/core_ext/string.rb +129 -0
- data/lib/sublime_dsl/core_ext.rb +4 -0
- data/lib/sublime_dsl/sublime_text/command.rb +157 -0
- data/lib/sublime_dsl/sublime_text/command_set.rb +112 -0
- data/lib/sublime_dsl/sublime_text/keyboard.rb +659 -0
- data/lib/sublime_dsl/sublime_text/keymap/dsl_reader.rb +194 -0
- data/lib/sublime_dsl/sublime_text/keymap.rb +385 -0
- data/lib/sublime_dsl/sublime_text/macro.rb +91 -0
- data/lib/sublime_dsl/sublime_text/menu.rb +237 -0
- data/lib/sublime_dsl/sublime_text/mouse.rb +149 -0
- data/lib/sublime_dsl/sublime_text/mousemap.rb +185 -0
- data/lib/sublime_dsl/sublime_text/package/dsl_reader.rb +91 -0
- data/lib/sublime_dsl/sublime_text/package/exporter.rb +138 -0
- data/lib/sublime_dsl/sublime_text/package/importer.rb +127 -0
- data/lib/sublime_dsl/sublime_text/package/reader.rb +102 -0
- data/lib/sublime_dsl/sublime_text/package/writer.rb +112 -0
- data/lib/sublime_dsl/sublime_text/package.rb +96 -0
- data/lib/sublime_dsl/sublime_text/setting_set.rb +123 -0
- data/lib/sublime_dsl/sublime_text.rb +48 -0
- data/lib/sublime_dsl/textmate/custom_base_name.rb +45 -0
- data/lib/sublime_dsl/textmate/grammar/dsl_reader.rb +383 -0
- data/lib/sublime_dsl/textmate/grammar/dsl_writer.rb +178 -0
- data/lib/sublime_dsl/textmate/grammar/plist_reader.rb +163 -0
- data/lib/sublime_dsl/textmate/grammar/plist_writer.rb +153 -0
- data/lib/sublime_dsl/textmate/grammar.rb +252 -0
- data/lib/sublime_dsl/textmate/plist.rb +141 -0
- data/lib/sublime_dsl/textmate/preference.rb +301 -0
- data/lib/sublime_dsl/textmate/snippet.rb +437 -0
- data/lib/sublime_dsl/textmate/theme/dsl_reader.rb +87 -0
- data/lib/sublime_dsl/textmate/theme/item.rb +74 -0
- data/lib/sublime_dsl/textmate/theme/plist_writer.rb +53 -0
- data/lib/sublime_dsl/textmate/theme.rb +364 -0
- data/lib/sublime_dsl/textmate.rb +9 -0
- data/lib/sublime_dsl/tools/blank_slate.rb +49 -0
- data/lib/sublime_dsl/tools/console.rb +74 -0
- data/lib/sublime_dsl/tools/helpers.rb +152 -0
- data/lib/sublime_dsl/tools/regexp_wannabe.rb +154 -0
- data/lib/sublime_dsl/tools/stable_inspect.rb +20 -0
- data/lib/sublime_dsl/tools/value_equality.rb +37 -0
- data/lib/sublime_dsl/tools/xml.rb +66 -0
- data/lib/sublime_dsl/tools.rb +66 -0
- data/lib/sublime_dsl.rb +23 -0
- metadata +145 -0
@@ -0,0 +1,659 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module SublimeDSL
|
4
|
+
module SublimeText
|
5
|
+
|
6
|
+
##
|
7
|
+
# A keyboard.
|
8
|
+
|
9
|
+
class Keyboard
|
10
|
+
|
11
|
+
SUBLIME_MODIFIERS = %w(shift ctrl alt super)
|
12
|
+
|
13
|
+
SUBLIME_KEYS = %w(
|
14
|
+
escape f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18 f19 f20 sysreq pause
|
15
|
+
up down right left
|
16
|
+
insert home end pageup pagedown backspace delete
|
17
|
+
tab enter context_menu
|
18
|
+
keypad0 keypad1 keypad2 keypad3 keypad4 keypad5 keypad6 keypad7 keypad8 keypad9
|
19
|
+
keypad_period keypad_divide keypad_multiply keypad_minus keypad_plus keypad_enter
|
20
|
+
clear break
|
21
|
+
browser_back browser_forward browser_refresh browser_stop
|
22
|
+
browser_search browser_favorites browser_home
|
23
|
+
|
24
|
+
` 0 1 2 3 4 5 6 7 8 9 - = +
|
25
|
+
a b c d e f g h i j k l m n o p q r s t u v w x y z
|
26
|
+
[ ] \\ ; ' , . /
|
27
|
+
space
|
28
|
+
)
|
29
|
+
|
30
|
+
SUBLIME_ALIAS_MAP = {
|
31
|
+
'forward_slash' => '/',
|
32
|
+
'backquote' => '`',
|
33
|
+
'equals' => '=',
|
34
|
+
'plus' => '+',
|
35
|
+
'minus' => '-'
|
36
|
+
}
|
37
|
+
|
38
|
+
SUBLIME_ALIAS_RE = /^(#{SUBLIME_ALIAS_MAP.keys.join('|')})$/
|
39
|
+
|
40
|
+
@defining_sublime = false
|
41
|
+
|
42
|
+
# The standard Sublime Text keyboard.
|
43
|
+
def self.sublime
|
44
|
+
@sublime ||= begin
|
45
|
+
kb = Keyboard.new('Sublime Text')
|
46
|
+
SUBLIME_MODIFIERS.each { |name| kb.add_modifier name }
|
47
|
+
SUBLIME_KEYS.each { |name| kb.add_key name }
|
48
|
+
# map_char and map_key call this method while @sublime is not yet set
|
49
|
+
unless @defining_sublime
|
50
|
+
@defining_sublime = true
|
51
|
+
# FIXME: space => key_event nil, but generates a key_event when modified
|
52
|
+
kb.map_char 'space' => ' ', key_event: nil
|
53
|
+
@defining_sublime = false
|
54
|
+
end
|
55
|
+
kb
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.get(name, root)
|
60
|
+
file = nil
|
61
|
+
Dir.chdir(root) do
|
62
|
+
files = Dir['**/*.keyboard.rb']
|
63
|
+
file = SublimeText.order_config(files).last
|
64
|
+
file or raise Error, "file '#{name}.keyboard.rb' not found"
|
65
|
+
file = File.expand_path(file)
|
66
|
+
end
|
67
|
+
DSLReader.new(file)._keyboard
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :name, :os
|
71
|
+
attr_reader :modifiers, :keys # Key instances
|
72
|
+
attr_reader :keystrokes_hash # normalized spec => KeyStroke or CharStroke instance
|
73
|
+
|
74
|
+
def initialize(name)
|
75
|
+
@name = name
|
76
|
+
@os = nil
|
77
|
+
@modifiers = []
|
78
|
+
@keys = []
|
79
|
+
@keystrokes_hash = {}
|
80
|
+
# Vintage generic character
|
81
|
+
add_keystroke CharStroke.new('<character>')
|
82
|
+
end
|
83
|
+
|
84
|
+
def os=(value)
|
85
|
+
case value.to_s.downcase
|
86
|
+
when 'windows' then @os = 'Windows'
|
87
|
+
when 'osx' then @os = 'OSX'
|
88
|
+
when 'linux' then @os = 'Linux'
|
89
|
+
else raise Error, "invalid OS value: #{value}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def modifier(name)
|
94
|
+
modifiers.find { |k| k.name == name }
|
95
|
+
end
|
96
|
+
|
97
|
+
def key(name)
|
98
|
+
keys.find { |k| k.name == name }
|
99
|
+
end
|
100
|
+
|
101
|
+
def add_modifier(name)
|
102
|
+
m = Key.new(name)
|
103
|
+
m.st_name = name if SUBLIME_MODIFIERS.include?(name)
|
104
|
+
@modifiers << m
|
105
|
+
end
|
106
|
+
|
107
|
+
def map_modifier(name, st_name)
|
108
|
+
m = modifier(name) or raise Error, "unknown modifier: '#{name}'"
|
109
|
+
SUBLIME_MODIFIERS.include?(st_name) or raise Error, "invalid ST modifier: #{st_name}"
|
110
|
+
m.st_name = st_name
|
111
|
+
end
|
112
|
+
|
113
|
+
# Create a Key +name+ and adds the corresponding KeyStroke to this keyboard.
|
114
|
+
#
|
115
|
+
# * If +name+ is one character long, the KeyStroke generates a chr_event +name+
|
116
|
+
# and no key_event.
|
117
|
+
#
|
118
|
+
# * Otherwise, the KeyStroke generates no chr_event, and a key_event +name+
|
119
|
+
# if +name+ is a Sublime Text key name.
|
120
|
+
|
121
|
+
def add_key(name)
|
122
|
+
k = Key.new(name)
|
123
|
+
k.st_name = name if SUBLIME_KEYS.include?(name)
|
124
|
+
@keys << k
|
125
|
+
ks = KeyStroke.new([], k)
|
126
|
+
if name.length == 1
|
127
|
+
ks.chr_event = name
|
128
|
+
else
|
129
|
+
ks.key_event = k.st_name
|
130
|
+
end
|
131
|
+
add_keystroke ks
|
132
|
+
|
133
|
+
k
|
134
|
+
end
|
135
|
+
|
136
|
+
# Assigns the key_event for the keystroke +spec+.
|
137
|
+
# +st_spec+ must be a valid ST keystroke, or nil if
|
138
|
+
# the keystroke is not seen by ST.
|
139
|
+
|
140
|
+
def map_key(spec, st_spec)
|
141
|
+
ks = ensure_keystroke(spec)
|
142
|
+
if st_spec.nil?
|
143
|
+
ks.key_event = nil
|
144
|
+
else
|
145
|
+
st_ks = Keyboard.sublime.ensure_keystroke(st_spec)
|
146
|
+
st_spec = st_ks.to_spec
|
147
|
+
if ks.modifiers.empty?
|
148
|
+
ks.key.st_name = st_spec
|
149
|
+
ks.key_event = st_spec if st_spec.length > 1
|
150
|
+
else
|
151
|
+
ks.key_event = st_spec
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Map a keystroke to a chr_event.
|
157
|
+
# Optionally sets the key_event.
|
158
|
+
def map_char(options = {})
|
159
|
+
ks_name = options.keys.first
|
160
|
+
ks = ensure_keystroke(ks_name)
|
161
|
+
ks.chr_event = options[ks_name]
|
162
|
+
if options.has_key?(:key_event)
|
163
|
+
st_spec = options[:key_event]
|
164
|
+
if st_spec
|
165
|
+
st_ks = Keyboard.sublime.ensure_keystroke(st_spec)
|
166
|
+
st_spec = st_ks.to_spec
|
167
|
+
end
|
168
|
+
ks.key_event = st_spec
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def add_keystroke(ks)
|
173
|
+
@keystrokes_hash[ks.to_spec] = ks
|
174
|
+
end
|
175
|
+
|
176
|
+
def keystrokes
|
177
|
+
keystrokes_hash.values
|
178
|
+
end
|
179
|
+
|
180
|
+
def keystroke_for_sublime_spec(st_spec)
|
181
|
+
st_ks = Keyboard.sublime.ensure_keystroke(st_spec)
|
182
|
+
st_spec = st_ks.to_spec # standardize
|
183
|
+
|
184
|
+
# return the first one with a key event = the passed spec
|
185
|
+
this_ks = keystrokes.find { |ks| ks.key_event == st_spec }
|
186
|
+
return this_ks if this_ks
|
187
|
+
|
188
|
+
# if one char, no problem
|
189
|
+
return ensure_keystroke(st_spec) if st_spec.length == 1 || st_spec == '<character>'
|
190
|
+
|
191
|
+
# not (yet?) registered: find a keystroke with the same key
|
192
|
+
base_ks = keystrokes.find { |ks| ks.key && ks.key.st_name == st_ks.key.name }
|
193
|
+
if base_ks
|
194
|
+
this_spec = st_ks.modifiers.map(&:name).join('+') << '+' << base_ks.key.name
|
195
|
+
this_ks = ensure_keystroke(this_spec)
|
196
|
+
return this_ks
|
197
|
+
end
|
198
|
+
|
199
|
+
NullStroke.new(st_spec)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns a KeyStroke or CharStroke for +spec+, and adds it to
|
203
|
+
# the registered keystrokes if not already there. Raises an
|
204
|
+
# exception if +spec+ is not valid.
|
205
|
+
#
|
206
|
+
# * If +spec+ is one character long, returns a KeyStroke or CharStroke
|
207
|
+
# if found, otherwise creates a new CharStroke.
|
208
|
+
#
|
209
|
+
# * If +spec+ is more than one character long, the key has to exist,
|
210
|
+
# as well as the modifiers if any, otherwise an exception is raised.
|
211
|
+
# If the corresponding KeyStroke is not found, it will be created
|
212
|
+
# and registered.
|
213
|
+
|
214
|
+
def ensure_keystroke(spec)
|
215
|
+
|
216
|
+
# split the specification
|
217
|
+
# ctrl++ -> ['ctrl', '+']
|
218
|
+
# ctrl+num+ -> ['ctrl', 'num+']
|
219
|
+
*modifier_names, key_name = spec.split(/\+(?!$)/)
|
220
|
+
|
221
|
+
# normalize the key name
|
222
|
+
self == Keyboard.sublime and
|
223
|
+
key_name.sub! SUBLIME_ALIAS_RE, SUBLIME_ALIAS_MAP
|
224
|
+
|
225
|
+
# check & reorder the modifiers
|
226
|
+
unless modifier_names.empty?
|
227
|
+
sorted = []
|
228
|
+
modifier_names.each do |name|
|
229
|
+
i = modifiers_index_hash[name] or
|
230
|
+
raise Error, "invalid modifier #{name.inspect} for keyboard #{self.name}"
|
231
|
+
sorted[i] = name
|
232
|
+
end
|
233
|
+
modifier_names = sorted.compact
|
234
|
+
end
|
235
|
+
|
236
|
+
# if there is a registered keystroke for this spec, return it
|
237
|
+
std_spec = [*modifier_names, key_name].join('+')
|
238
|
+
ks = keystrokes_hash[std_spec]
|
239
|
+
return ks if ks
|
240
|
+
|
241
|
+
# shift + character is not ok
|
242
|
+
modifiers = modifier_names.map { |n| modifier(n) }
|
243
|
+
modifiers.map(&:st_name) == ['shift'] && key_name.length == 1 and
|
244
|
+
raise Error, "#{spec.to_source(true)} is invalid: specify the corresponding character"
|
245
|
+
|
246
|
+
key = key(key_name)
|
247
|
+
|
248
|
+
# The ST keyboard accepts any character as a valid key
|
249
|
+
if self == Keyboard.sublime && key.nil?
|
250
|
+
key_name.length == 1 or raise Error, "invalid key name in #{spec.to_source(true)}"
|
251
|
+
key = add_key(key_name)
|
252
|
+
return keystrokes_hash[key_name] if modifiers.empty?
|
253
|
+
end
|
254
|
+
|
255
|
+
if key
|
256
|
+
# registered key
|
257
|
+
ks = KeyStroke.new(modifiers, key)
|
258
|
+
assign_default_key_event ks
|
259
|
+
else
|
260
|
+
# unregistered: has to be a character
|
261
|
+
key_name.length == 1 or
|
262
|
+
raise Error, "#{spec.inspect}: key #{key_name.inspect} is undefined"
|
263
|
+
modifier_names.empty? or
|
264
|
+
raise Error, "#{spec.inspect}: #{key_name.inspect} is not a key"
|
265
|
+
ks = CharStroke.new(key_name)
|
266
|
+
end
|
267
|
+
|
268
|
+
add_keystroke ks
|
269
|
+
|
270
|
+
ks
|
271
|
+
end
|
272
|
+
|
273
|
+
# Assign the default key_event of a new keystroke (before its registration).
|
274
|
+
# It will be the modified key_event of a less specific keystroke.
|
275
|
+
#
|
276
|
+
# For instance, if we register 'shift+ctrl+keypad5', and 'shift+keypad5' has
|
277
|
+
# key event 'clear', this assigns 'ctrl+clear'. If there are several
|
278
|
+
# possibilities, the one(s) with the most modifiers are selected.
|
279
|
+
# If there are ex-aequo, the order of precedence is the order of registration
|
280
|
+
# of the modifiers.
|
281
|
+
|
282
|
+
def assign_default_key_event(keystroke)
|
283
|
+
|
284
|
+
# the ST keyboard: just register
|
285
|
+
if self == Keyboard.sublime
|
286
|
+
spec = keystroke.to_spec
|
287
|
+
keystroke.key_event = spec if spec.length > 1
|
288
|
+
return
|
289
|
+
end
|
290
|
+
|
291
|
+
# we always have modifiers, as all non-modified keys are already registered
|
292
|
+
keystroke.modifiers.empty? and raise Error, "bug: #{keystroke} is not registered"
|
293
|
+
|
294
|
+
# if the key has a ST name, assume the ST equivalent
|
295
|
+
if keystroke.key.st_name
|
296
|
+
spec = keystroke.modifiers.map(&:st_name).join('+') << '+' << keystroke.key.st_name
|
297
|
+
keystroke.key_event = spec
|
298
|
+
return
|
299
|
+
end
|
300
|
+
|
301
|
+
# the candidates are the registered keystrokes for that key with all
|
302
|
+
# modifiers included in the passed modifiers (so at least the keystroke
|
303
|
+
# for the key itself)
|
304
|
+
candidates = keystrokes_hash.values.select do |ks|
|
305
|
+
ks.key == keystroke.key &&
|
306
|
+
ks.modifiers - keystroke.modifiers == []
|
307
|
+
end
|
308
|
+
candidates.empty? and raise Error, "bug: nothing registered for #{keystroke}"
|
309
|
+
|
310
|
+
if candidates.length > 1
|
311
|
+
|
312
|
+
# select the one(s) with the most modifiers
|
313
|
+
max = 0
|
314
|
+
candidates.each do |ks|
|
315
|
+
max = ks.modifiers.length if ks.modifiers.length > max
|
316
|
+
end
|
317
|
+
candidates.reject! { |ks| ks.modifiers.length < max }
|
318
|
+
|
319
|
+
# apply modifier priority:
|
320
|
+
# create the bit mask for each keystroke,
|
321
|
+
# and then select the lowest one
|
322
|
+
if candidates.length > 1
|
323
|
+
sort_array =
|
324
|
+
candidates.map do |ks|
|
325
|
+
mask = 0
|
326
|
+
ks.modifiers.each do |m|
|
327
|
+
mask += (1 << modifiers_index_hash[m.name])
|
328
|
+
end
|
329
|
+
[ks, mask]
|
330
|
+
end
|
331
|
+
candidates = sort_array.sort_by(&:last).map(&:first)
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
|
336
|
+
# select the reference keystroke
|
337
|
+
ref = candidates.first
|
338
|
+
if ref.key_event.nil?
|
339
|
+
keystroke.key_event = nil
|
340
|
+
return
|
341
|
+
end
|
342
|
+
|
343
|
+
# apply the modifier delta versus the reference
|
344
|
+
delta = keystroke.modifiers - ref.modifiers
|
345
|
+
spec = delta.map(&:st_name).join('+') << '+' << ref.key_event
|
346
|
+
keystroke.key_event = Keyboard.sublime.ensure_keystroke(spec).to_spec
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
def modifiers_index_hash
|
351
|
+
@modifiers_index_hash ||= begin
|
352
|
+
h = {}
|
353
|
+
modifiers.each_with_index { |m, i| h[m.name] = i }
|
354
|
+
h
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
##
|
360
|
+
# A physical key or modifier on the keyboard.
|
361
|
+
|
362
|
+
class Key
|
363
|
+
|
364
|
+
attr_reader :name
|
365
|
+
attr_accessor :st_name
|
366
|
+
|
367
|
+
def initialize(name)
|
368
|
+
@name = name
|
369
|
+
@st_name = nil
|
370
|
+
end
|
371
|
+
|
372
|
+
def to_s
|
373
|
+
name
|
374
|
+
end
|
375
|
+
|
376
|
+
def eql?(other)
|
377
|
+
other.is_a?(Key) && other.name == self.name
|
378
|
+
end
|
379
|
+
|
380
|
+
alias == eql?
|
381
|
+
|
382
|
+
def hash
|
383
|
+
name.hash
|
384
|
+
end
|
385
|
+
|
386
|
+
end
|
387
|
+
|
388
|
+
|
389
|
+
##
|
390
|
+
# A keystroke: modifiers + key.
|
391
|
+
|
392
|
+
class KeyStroke
|
393
|
+
|
394
|
+
attr_reader :key, :modifiers
|
395
|
+
attr_accessor :key_event
|
396
|
+
attr_accessor :chr_event
|
397
|
+
attr_accessor :chr_dead
|
398
|
+
attr_accessor :os_action
|
399
|
+
|
400
|
+
def initialize(modifiers, key)
|
401
|
+
@modifiers = modifiers
|
402
|
+
@key = key
|
403
|
+
@key_event = nil
|
404
|
+
@chr_event = nil
|
405
|
+
@chr_dead = false
|
406
|
+
@os_action = nil
|
407
|
+
end
|
408
|
+
|
409
|
+
def type
|
410
|
+
:key
|
411
|
+
end
|
412
|
+
|
413
|
+
def to_spec
|
414
|
+
(modifiers.dup << key).map(&:to_s).join('+')
|
415
|
+
end
|
416
|
+
|
417
|
+
alias to_s to_spec
|
418
|
+
|
419
|
+
def inspect
|
420
|
+
s = "<#KeyStroke #{to_spec}"
|
421
|
+
s << " key_event=#{key_event.inspect}"
|
422
|
+
s << " chr_event=#{chr_event.inspect}"
|
423
|
+
s << " dead=true" if chr_dead
|
424
|
+
s << " os_action=#{os_action.inspect}" if os_action
|
425
|
+
s
|
426
|
+
end
|
427
|
+
|
428
|
+
include Tools::ValueEquality
|
429
|
+
|
430
|
+
def <=>(other)
|
431
|
+
c = self.key <=> other.key
|
432
|
+
c = self.modifiers <=> other.modifiers if c == 0
|
433
|
+
c
|
434
|
+
end
|
435
|
+
|
436
|
+
end
|
437
|
+
|
438
|
+
##
|
439
|
+
# A character: characters are supposed to be available on any keyboard.
|
440
|
+
|
441
|
+
class CharStroke
|
442
|
+
|
443
|
+
def initialize(char)
|
444
|
+
@char = char
|
445
|
+
end
|
446
|
+
|
447
|
+
def type
|
448
|
+
:char
|
449
|
+
end
|
450
|
+
|
451
|
+
def key
|
452
|
+
nil
|
453
|
+
end
|
454
|
+
|
455
|
+
def modifiers
|
456
|
+
[]
|
457
|
+
end
|
458
|
+
|
459
|
+
def key_event
|
460
|
+
nil
|
461
|
+
end
|
462
|
+
|
463
|
+
def chr_event
|
464
|
+
@char
|
465
|
+
end
|
466
|
+
|
467
|
+
def chr_dead
|
468
|
+
nil
|
469
|
+
end
|
470
|
+
|
471
|
+
def os_action
|
472
|
+
nil
|
473
|
+
end
|
474
|
+
|
475
|
+
def to_spec
|
476
|
+
@char
|
477
|
+
end
|
478
|
+
|
479
|
+
alias to_s to_spec
|
480
|
+
|
481
|
+
def inspect
|
482
|
+
"<#CharStroke char=#{@char}>"
|
483
|
+
end
|
484
|
+
|
485
|
+
end
|
486
|
+
|
487
|
+
|
488
|
+
##
|
489
|
+
# A ST keystroke that has no equivalent on this keyboard.
|
490
|
+
|
491
|
+
class NullStroke
|
492
|
+
|
493
|
+
def initialize(st_spec)
|
494
|
+
@key_event = st_spec
|
495
|
+
end
|
496
|
+
|
497
|
+
def type
|
498
|
+
:null
|
499
|
+
end
|
500
|
+
|
501
|
+
def key
|
502
|
+
nil
|
503
|
+
end
|
504
|
+
|
505
|
+
def modifiers
|
506
|
+
[]
|
507
|
+
end
|
508
|
+
|
509
|
+
def key_event
|
510
|
+
@key_event
|
511
|
+
end
|
512
|
+
|
513
|
+
def chr_event
|
514
|
+
nil
|
515
|
+
end
|
516
|
+
|
517
|
+
def chr_dead
|
518
|
+
nil
|
519
|
+
end
|
520
|
+
|
521
|
+
def os_action
|
522
|
+
nil
|
523
|
+
end
|
524
|
+
|
525
|
+
def to_spec
|
526
|
+
nil
|
527
|
+
end
|
528
|
+
|
529
|
+
def inspect
|
530
|
+
"<#NullStroke key_event=#{@key_event}>"
|
531
|
+
end
|
532
|
+
|
533
|
+
end
|
534
|
+
|
535
|
+
|
536
|
+
class DSLReader
|
537
|
+
|
538
|
+
attr_reader :_keyboard
|
539
|
+
|
540
|
+
def initialize(file)
|
541
|
+
@_keyboard = nil
|
542
|
+
@in_definition = false
|
543
|
+
instance_eval ::File.read(file, encoding: 'utf-8'), file
|
544
|
+
end
|
545
|
+
|
546
|
+
def method_missing(sym, *args, &block)
|
547
|
+
raise Error, "'#{sym}' is not a keyboard DSL statement"
|
548
|
+
end
|
549
|
+
|
550
|
+
def keyboard(name)
|
551
|
+
@_keyboard and raise Error, 'only one keyboard definition per file'
|
552
|
+
@in_definition and raise Error, "'keyboard' blocks cannot be nested"
|
553
|
+
@_keyboard = Keyboard.new(name)
|
554
|
+
@in_definition = true
|
555
|
+
yield self
|
556
|
+
@in_definition = false
|
557
|
+
end
|
558
|
+
|
559
|
+
def os(value)
|
560
|
+
ensure_context __method__
|
561
|
+
_keyboard.os = value
|
562
|
+
end
|
563
|
+
|
564
|
+
def add_modifiers(spec)
|
565
|
+
ensure_context __method__
|
566
|
+
spec.split(/\s/).each { |name| _keyboard.add_modifier name }
|
567
|
+
end
|
568
|
+
|
569
|
+
def map_modifier(options={})
|
570
|
+
ensure_context __method__
|
571
|
+
name = options.keys.first
|
572
|
+
name or raise Error, 'missing argument'
|
573
|
+
st_name = options.delete(name)
|
574
|
+
options.empty? or warn "extraneous arguments ignored: #{options.inspect}"
|
575
|
+
_keyboard.map_modifier name, st_name
|
576
|
+
end
|
577
|
+
|
578
|
+
def add_keys(spec, options={})
|
579
|
+
ensure_context __method__
|
580
|
+
keys = parse_key_list(spec)
|
581
|
+
|
582
|
+
st_spec = options.delete(:st_keys)
|
583
|
+
if st_spec
|
584
|
+
st_keys = parse_key_list(st_spec)
|
585
|
+
keys.length == st_keys.length or
|
586
|
+
raise Error, "st_keys: got #{st_keys.length} keys, expected #{keys.length}"
|
587
|
+
end
|
588
|
+
options.empty? or warn "extraneous arguments ignored: #{options.inspect}"
|
589
|
+
|
590
|
+
keys.each_with_index do |name, i|
|
591
|
+
_keyboard.add_key name
|
592
|
+
if st_spec
|
593
|
+
_keyboard.map_key name, st_keys[i]
|
594
|
+
# done automatically when registering key events of new keystrokes:
|
595
|
+
# elsif Keyboard.sublime.key(name)
|
596
|
+
# _keyboard.map_key name, name
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
end
|
601
|
+
|
602
|
+
def map_key(options={})
|
603
|
+
ensure_context __method__
|
604
|
+
name = options.keys.first
|
605
|
+
name or raise Error, 'missing argument'
|
606
|
+
st_name = options.delete(name)
|
607
|
+
options.empty? or warn "extraneous arguments ignored: #{options.inspect}"
|
608
|
+
_keyboard.map_key name, st_name
|
609
|
+
end
|
610
|
+
|
611
|
+
def map_char(options={})
|
612
|
+
spec = options.keys.first
|
613
|
+
spec or raise Error, 'missing argument'
|
614
|
+
char = options.delete(spec)
|
615
|
+
dead = options.delete(:dead)
|
616
|
+
options.empty? or warn "extraneous arguments ignored: #{options.inspect}"
|
617
|
+
char.length == 1 or raise Error, "map_dead: expected a character, got #{char.inspect}"
|
618
|
+
ks = _keyboard.ensure_keystroke(spec)
|
619
|
+
ks.chr_event = char
|
620
|
+
ks.chr_dead = true if dead
|
621
|
+
end
|
622
|
+
|
623
|
+
def os_action(options={})
|
624
|
+
spec = options.keys.first
|
625
|
+
spec or raise Error, 'missing argument'
|
626
|
+
action = options.delete(spec)
|
627
|
+
key_event = options.delete(:key_event)
|
628
|
+
options.empty? or warn "extraneous arguments ignored: #{options.inspect}"
|
629
|
+
ks = _keyboard.ensure_keystroke(spec)
|
630
|
+
ks.os_action = action
|
631
|
+
ks.key_event = nil unless key_event
|
632
|
+
end
|
633
|
+
|
634
|
+
private
|
635
|
+
|
636
|
+
def ensure_context(method)
|
637
|
+
_keyboard or raise Error, "'#{method}' is invalid outside of a 'keyboard' block"
|
638
|
+
end
|
639
|
+
|
640
|
+
def parse_key_list(string)
|
641
|
+
specs = string.split(/\s/)
|
642
|
+
specs.flat_map do |spec|
|
643
|
+
if spec =~ /^([a-z]*)(\d+)-\1(\d+)$/
|
644
|
+
stem = $1
|
645
|
+
start = $2.to_i
|
646
|
+
stop = $3.to_i
|
647
|
+
(start..stop).map { |i| "#{stem}#{i}" }
|
648
|
+
else
|
649
|
+
spec
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
end
|
655
|
+
|
656
|
+
end
|
657
|
+
|
658
|
+
end
|
659
|
+
end
|