2 # Copyright Anton Khodakivskiy 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014.
4 # Copyright Wang Zhuochun 2014.
6 # This file is part of VimFx.
8 # VimFx is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # VimFx is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
22 utils = require('./utils')
23 { injectHints } = require('./hints')
24 { rotateOverlappingMarkers } = require('./marker')
25 { updateToolbarButton } = require('./button')
27 , searchForMatchingCommand
30 , findStorage } = require('./commands')
32 { interfaces: Ci } = Components
34 XULDocument = Ci.nsIDOMXULDocument
37 onEnter: (vim, storage) ->
39 storage.commands ?= {}
41 onLeave: (vim, storage) ->
42 storage.keys.length = 0
44 onInput: (vim, storage, keyStr, event) ->
45 target = event.originalTarget
47 utils.isTextInputElement(target) or
48 utils.isContentEditable(target) or
49 (utils.isActivatable(target) and keyStr == '<enter>') or
50 (utils.isAdjustable(target) and keyStr in [
51 '<arrowup>', '<arrowdown>', '<arrowleft>', '<arrowright>'
54 vim.rootWindow.TabView.isVisible()
56 storage.keys.push(keyStr)
58 { match, exact, command, count } = searchForMatchingCommand(storage.keys)
60 if vim.state.blacklistedKeys and
61 storage.keys.join('') in vim.state.blacklistedKeys
66 if autoInsertMode and command != escapeCommand
71 command.func(vim, event, count)
72 storage.keys.length = 0
74 # Esc key is not suppressed, and passed to the browser in normal mode.
76 # - It allows for stopping the loading of the page.
77 # - It allows for closing many custom dialogs (and perhaps other things
78 # -- Esc is a very commonly used key).
79 # - It is not passed if Esc is used for `command_Esc` and we’re blurring
80 # an element. That allows for blurring an input in a custom dialog
81 # without closing the dialog too.
82 # - There are two reasons we might suppress it in other modes. If some
83 # custom dialog of a website is open, we should be able to cancel hint
84 # markers on it without closing it. Secondly, otherwise cancelling hint
85 # markers on Google causes its search bar to be focused.
86 # - It may only be suppressed in web pages, not in browser chrome. That
87 # allows for reseting the location bar when blurring it, and closing
88 # dialogs such as the “bookmark this page” dialog (<c-d>).
89 document = event.originalTarget.ownerDocument
90 inBrowserChrome = (document instanceof XULDocument)
91 if keyStr == '<escape>' and (not autoInsertMode or inBrowserChrome)
97 storage.keys.length = 0 unless /\d/.test(keyStr)
104 onEnter: (vim, storage, count = null) ->
105 storage.count = count
106 updateToolbarButton(vim.rootWindow, {insertMode: true})
108 updateToolbarButton(vim.rootWindow, {insertMode: false})
109 utils.blurActiveElement(vim.window)
110 onInput: (vim, storage, keyStr) ->
113 if @commands['exit'].match(keyStr)
114 vim.enterMode('normal')
117 vim.enterMode('normal')
128 findBar = vim.rootWindow.gBrowser.getFindBar()
129 findStorage.lastSearchString = findBar._findField.value
131 onInput: (vim, storage, keyStr) ->
132 findBar = vim.rootWindow.gBrowser.getFindBar()
133 if @commands['exit'].match(keyStr)
139 exit: ['<escape>', '<enter>']
142 onEnter: (vim, storage, filter, callback) ->
143 [ markers, container ] = injectHints(vim.rootWindow, vim.window, filter)
144 if markers.length > 0
145 storage.markers = markers
146 storage.container = container
147 storage.callback = callback
148 storage.numEnteredChars = 0
150 vim.enterMode('normal')
152 onLeave: (vim, storage) ->
153 { container } = storage
154 vim.rootWindow.setTimeout((->
160 onInput: (vim, storage, keyStr, event) ->
161 { markers, callback } = storage
164 when @commands['exit'].match(keyStr)
165 # Remove the hints immediately.
166 storage.container?.remove()
167 vim.enterMode('normal')
170 when @commands['rotate_markers_forward'].match(keyStr)
171 rotateOverlappingMarkers(markers, true)
172 when @commands['rotate_markers_backward'].match(keyStr)
173 rotateOverlappingMarkers(markers, false)
175 when @commands['delete_hint_char'].match(keyStr)
176 for marker in markers
177 switch marker.hintIndex - storage.numEnteredChars
178 when 0 then marker.deleteHintChar()
179 when -1 then marker.show()
180 storage.numEnteredChars-- unless storage.numEnteredChars == 0
183 if keyStr not in utils.getHintChars()
186 for marker in markers when marker.hintIndex == storage.numEnteredChars
187 match = marker.matchHintChar(keyStr)
188 marker.hide() unless match
189 if marker.isMatched()
190 marker.markMatched(true)
191 matchedMarkers.push(marker)
192 if matchedMarkers.length > 0
193 again = callback(matchedMarkers[0])
195 vim.rootWindow.setTimeout((->
196 marker.markMatched(false) for marker in matchedMarkers
198 marker.reset() for marker in markers
199 storage.numEnteredChars = 0
201 vim.enterMode('normal')
203 storage.numEnteredChars++
211 rotate_markers_forward: ['<space>']
212 rotate_markers_backward: ['<s-space>']
213 delete_hint_char: ['<backspace>']
215 for modeName of exports
216 mode = exports[modeName]
217 continue if Array.isArray(mode.commands)
218 for commandName of mode.commands
219 name = "mode_#{ modeName }_#{ commandName }"
220 keys = mode.commands[commandName].map((key) -> [key])
221 mode.commands[commandName] = new Command(null, name, null, keys)