2 # Copyright Simon Lydell 2015, 2016.
4 # This file is part of VimFx.
6 # VimFx is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # VimFx is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
20 # This file defines a top-level object to hold global state for VimFx. It keeps
21 # track of all `Vim` instances (vim.coffee), all options and all keyboard
22 # shortcuts. It can consume key presses according to its commands, and return
23 # the commands for UI presentation. There is only one `VimFx` instance.
25 notation = require('vim-like-key-notation')
26 prefs = require('./prefs')
27 utils = require('./utils')
28 Vim = require('./vim')
30 {EventEmitter} = utils
34 class VimFx extends EventEmitter
35 constructor: (@modes, @options) ->
40 @skipCreateKeyTrees = false
43 @on('modeChange', ({mode}) => @reset(mode))
47 '<late>': {single: true}
51 vim = new Vim(browser, this)
52 @vims.set(browser, vim)
53 # Calling `vim._start` will emit VimFx events. It might seem as if the logic
54 # of `vim._start` could be moved into the constructor, but splitting it like
55 # this allows to save the `vim` instance in `vimfx.vims` first, which in
56 # turn allows `vimfx.on(...)` listeners to use `vimfx.getCurrentVim()`.
59 # NOTE: This method is often called in event handlers. Many events may fire
60 # before a `vim` object has been created for the current tab yet (such as when
61 # the browser is starting up). Therefore always check if anything was
64 # return unless vim = @vimfx.getCurrentVim(@window)
65 getCurrentVim: (window) -> @vims.get(window.gBrowser.selectedBrowser)
67 reset: (mode = null) ->
68 # Modes without commands are returned by neither `.getGroupedCommands()` nor
69 # `createKeyTrees`. Fall back to an empty tree.
70 @currentKeyTree = @keyTrees[mode] ? {}
75 return if @skipCreateKeyTrees
76 {@keyTrees, @errors} = createKeyTrees(@getGroupedCommands(), @SPECIAL_KEYS)
78 stringifyKeyEvent: (event) ->
79 return notation.stringify(event, {
80 ignoreCtrlAlt: @options.ignore_ctrl_alt
81 ignoreKeyboardLayout: @options.ignore_keyboard_layout
82 translations: @options.translations
85 consumeKeyEvent: (event, vim) ->
87 return unless keyStr = @stringifyKeyEvent(event)
90 @reset(mode) if now - @lastInputTime >= @options.timeout
93 toplevel = (@currentKeyTree == @keyTrees[mode])
95 if toplevel and @options.keyValidator
96 unless @options.keyValidator(keyStr, mode)
105 when keyStr of @currentKeyTree and
106 not (toplevel and keyStr == '0' and @count != '')
107 next = @currentKeyTree[keyStr]
108 if next instanceof Leaf
110 {command, specialKeys} = next
112 @currentKeyTree = next
115 when @options.counts_enabled and toplevel and DIGIT.test(keyStr) and
116 not (keyStr == '0' and @count == '')
123 count = if @count == '' then undefined else Number(@count)
124 unmodifiedKey = notation.parse(keyStr).key
126 focusTypeKeys = @options["#{vim.focusType}_element_keys"]
130 keyStr in focusTypeKeys
132 vim.focusType != 'none'
136 @reset(mode) if type == 'full'
138 type, command, count, specialKeys, keyStr, unmodifiedKey, toplevel
140 discard: @reset.bind(this, mode)
143 getGroupedCommands: (options = {}) ->
145 for modeName, mode of @modes
146 if options.enabledOnly
147 usedSequences = getUsedSequences(@keyTrees[modeName])
148 for commandName, command of mode.commands
149 enabledSequences = null
150 if options.enabledOnly
151 enabledSequences = utils.removeDuplicates(
152 command._sequences.filter((sequence) ->
153 return (usedSequences[sequence] == command.pref)
156 continue if enabledSequences.length == 0
157 categories = modes[modeName] ?= {}
158 category = categories[command.category] ?= []
160 {command, enabledSequences, order: command.order, name: commandName}
164 for modeName, categories of modes
165 categoriesSorted = []
166 for categoryName, commands of categories
167 category = @options.categories[categoryName]
168 categoriesSorted.push({
171 order: category.order
172 commands: commands.sort(byOrder)
174 mode = @modes[modeName]
179 categories: categoriesSorted.sort(byOrder)
181 return modesSorted.sort(byOrder)
183 byOrder = (a, b) -> a.order - b.order
186 constructor: (@command, @originalSequence, @specialKeys) ->
188 createKeyTrees = (groupedCommands, specialKeysSpec) ->
192 pushError = (error, command) ->
193 (errors[command.pref] ?= []).push(error)
195 pushOverrideErrors = (command, originalSequence, tree) ->
196 {command: overridingCommand} = getFirstLeaf(tree)
199 subject: overridingCommand.description
200 context: originalSequence
202 pushError(error, command)
204 pushSpecialKeyError = (command, id, originalSequence, key) ->
206 id: "special_key.#{id}"
208 context: originalSequence
210 pushError(error, command)
212 for mode in groupedCommands
213 keyTrees[mode._name] = {}
214 for category in mode.categories then for {command} in category.commands
215 {shortcuts, errors: parseErrors} = parseShortcutPref(command.pref)
216 pushError(error, command) for error in parseErrors
217 command._sequences = []
219 for shortcut in shortcuts
220 [prefixKeys..., lastKey] = shortcut.normalized
221 tree = keyTrees[mode._name]
222 command._sequences.push(shortcut.original)
223 seenNonSpecialKey = false
227 for prefixKey, index in prefixKeys
228 if prefixKey of specialKeysSpec
230 pushSpecialKeyError(command, 'prefix_only', shortcut.original,
235 specialKeys[prefixKey] = true
237 else if not seenNonSpecialKey
238 for specialKey of specialKeys
239 options = specialKeysSpec[specialKey]
241 pushSpecialKeyError(command, 'single_only', shortcut.original,
246 seenNonSpecialKey = true
249 next = tree[prefixKey]
250 if next instanceof Leaf
251 pushOverrideErrors(command, shortcut.original, next)
257 tree = tree[prefixKey] = {}
260 if lastKey of specialKeysSpec
261 subject = if seenNonSpecialKey then lastKey else shortcut.original
262 pushSpecialKeyError(command, 'prefix_only', shortcut.original,
266 pushOverrideErrors(command, shortcut.original, tree[lastKey])
268 tree[lastKey] = new Leaf(command, shortcut.original, specialKeys)
270 return {keyTrees, errors}
272 parseShortcutPref = (pref) ->
276 # The shorcut prefs are read from root in order to support other extensions to
277 # extend VimFx with custom commands.
278 prefValue = prefs.root.get(pref).trim()
280 unless prefValue == ''
281 for sequence in prefValue.split(/\s+/)
284 for key in notation.parseSequence(sequence)
286 shortcut.push(notation.normalize(key))
288 throw error unless error.id?
292 shortcuts.push({normalized: shortcut, original: sequence}) unless errored
294 return {shortcuts, errors}
296 getFirstLeaf = (node) ->
297 if node instanceof Leaf
299 for key, value of node
300 return getFirstLeaf(value)
302 getLeaves = (node) ->
303 if node instanceof Leaf
306 for key, value of node
307 leaves.push(getLeaves(value)...)
310 getUsedSequences = (tree) ->
312 for leaf in getLeaves(tree)
313 usedSequences[leaf.originalSequence] = leaf.command.pref
316 module.exports = VimFx