2 # Copyright Simon Lydell 2015.
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 notation = require('vim-like-key-notation')
21 prefs = require('./prefs')
22 utils = require('./utils')
23 Vim = require('./vim')
28 class VimFx extends utils.EventEmitter
29 constructor: (@modes, @options) ->
31 @vimBucket = new utils.Bucket(((window) => new Vim(window, this)), this)
34 @on('modechange', ({mode}) => @reset(mode))
36 @on('bucket.get', (vim) =>
37 return if @currentVim == vim
39 @emit('currentVimChange', vim)
41 @customCommandCounter = 0
43 reset: (mode = null) ->
44 @currentKeyTree = if mode then @keyTrees[mode] else {}
48 getCurrentLocation: -> @currentVim?.window.location
51 {@keyTrees, @forceCommands, @errors} = createKeyTrees(@modes)
53 stringifyKeyEvent: (event) ->
54 return notation.stringify(event, {
55 ignoreKeyboardLayout: @options.ignore_keyboard_layout
56 translations: @options.translations
59 consumeKeyEvent: (event, vim) ->
61 return unless keyStr = @stringifyKeyEvent(event)
64 @reset(mode) if now - @lastInputTime >= @options.timeout
67 toplevel = (@currentKeyTree == @keyTrees[mode])
69 if toplevel and @options.keyValidator
70 unless @options.keyValidator(keyStr, mode)
78 when keyStr of @currentKeyTree
79 next = @currentKeyTree[keyStr]
80 if next instanceof Leaf
82 command = next.command
84 @currentKeyTree = next
87 when toplevel and DIGIT.test(keyStr) and
88 not (keyStr == '0' and @count == '')
95 count = if @count == '' then undefined else Number(@count)
96 force = if command then (command.pref of @forceCommands) else false
97 focus = @getFocusType(vim, event, keyStr)
98 @reset(mode) if type == 'full'
99 return {type, focus, command, count, force, keyStr, toplevel }
101 getFocusType: (vim, event, keyStr) ->
102 target = event.originalTarget
103 document = target.ownerDocument
105 { activatable_element_keys, adjustable_element_keys } = @options
107 when utils.isTextInputElement(target) or
108 utils.isContentEditable(target)
110 when (utils.isActivatable(target) and
111 keyStr in activatable_element_keys)
113 when (utils.isAdjustable(target) and
114 keyStr in adjustable_element_keys)
116 when vim.rootWindow.TabView.isVisible() or
117 document.fullscreenElement or document.mozFullScreenElement
122 getGroupedCommands: (options = {}) ->
124 for modeName, mode of @modes
125 if options.enabledOnly
126 usedSequences = getUsedSequences(@keyTrees[modeName])
127 for commandName, command of mode.commands
128 enabledSequences = null
129 if options.enabledOnly
130 enabledSequences = utils.removeDuplicates(
131 command._sequences.filter((sequence) ->
132 return (usedSequences[sequence] == command.pref)
135 continue if enabledSequences.length == 0
136 categories = modes[modeName] ?= {}
137 category = categories[command.category] ?= []
138 category.push({command, enabledSequences, order: command.order})
141 for modeName, categories of modes
142 categoriesSorted = []
143 for categoryName, commands of categories
144 category = @options.categories[categoryName]
145 categoriesSorted.push({
146 name: category.name()
148 order: category.order
149 commands: commands.sort(byOrder)
151 mode = @modes[modeName]
156 categories: categoriesSorted.sort(byOrder)
158 return modesSorted.sort(byOrder)
160 byOrder = (a, b) -> a.order - b.order
163 constructor: (@command, @originalSequence) ->
165 createKeyTrees = (modes) ->
170 pushError = (error, command) ->
171 (errors[command.pref] ?= []).push(error)
173 pushOverrideErrors = (command, tree) ->
174 for { command: overriddenCommand, originalSequence } in getLeaves(tree)
177 subject: command.description()
178 context: originalSequence
179 pushError(error, overriddenCommand)
182 pushForceKeyError = (command, originalSequence) ->
184 id: 'illegal_force_key'
186 context: originalSequence
187 pushError(error, command)
189 for modeName, { commands } of modes
190 keyTrees[modeName] = {}
191 for commandName, command of commands
192 { shortcuts, errors: parseErrors } = parseShortcutPref(command.pref)
193 pushError(error, command) for error in parseErrors
194 command._sequences = []
196 for shortcut in shortcuts
197 [ prefixKeys..., lastKey ] = shortcut.normalized
198 tree = keyTrees[modeName]
199 command._sequences.push(shortcut.original)
201 for prefixKey, index in prefixKeys
202 if prefixKey == FORCE_KEY
204 forceCommands[command.pref] = true
206 pushForceKeyError(command, shortcut.original)
210 next = tree[prefixKey]
211 if next instanceof Leaf
212 pushOverrideErrors(command, next)
213 tree = tree[prefixKey] = {}
217 tree = tree[prefixKey] = {}
219 if lastKey == FORCE_KEY
220 pushForceKeyError(command, shortcut.original)
223 pushOverrideErrors(command, tree[lastKey])
224 tree[lastKey] = new Leaf(command, shortcut.original)
226 return {keyTrees, forceCommands, errors}
228 parseShortcutPref = (pref) ->
232 # The shorcut prefs are read from root in order to support other extensions to
233 # extend VimFx with custom commands.
234 prefValue = prefs.root.get(pref).trim()
236 unless prefValue == ''
237 for sequence in prefValue.split(/\s+/)
240 for key in notation.parseSequence(sequence)
242 shortcut.push(notation.normalize(key))
244 throw error unless error.id?
248 shortcuts.push({normalized: shortcut, original: sequence}) unless errored
250 return {shortcuts, errors}
252 getLeaves = (node) ->
253 if node instanceof Leaf
256 for key, value of node
257 leaves.push(getLeaves(value)...)
260 getUsedSequences = (tree) ->
262 for leaf in getLeaves(tree)
263 usedSequences[leaf.originalSequence] = leaf.command.pref
266 module.exports = VimFx