2 # Copyright Anton Khodakivskiy 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014, 2015, 2016.
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 # This file defines VimFx’s modes, and their respective commands. The Normal
23 # mode commands are defined in commands.coffee, though.
25 {commands, findStorage} = require('./commands')
26 defaults = require('./defaults')
27 help = require('./help')
28 hints = require('./hints')
29 translate = require('./l10n')
30 {rotateOverlappingMarkers} = require('./marker')
31 utils = require('./utils')
33 # Helper to create modes in a DRY way.
34 mode = (modeName, obj, commands = null) ->
35 obj.name = translate("mode.#{modeName}")
36 obj.order = defaults.mode_order[modeName]
38 for commandName, fn of commands
39 pref = "mode.#{modeName}.#{commandName}"
40 obj.commands[commandName] = {
41 pref: defaults.BRANCH + pref
43 category: defaults.categoryMap[pref]
44 description: translate(pref)
45 order: defaults.command_order[pref]
47 exports[modeName] = obj
52 onEnter: ({vim, storage}, options = {}) ->
54 storage.returnTo = options.returnTo
55 else if storage.returnTo
56 vim.enterMode(storage.returnTo)
57 storage.returnTo = null
60 vim._run('clear_inputs')
62 onInput: (args, match) ->
63 {vim, storage, uiEvent} = args
66 if match.type == 'none' or
67 (match.likelyConflict and not match.specialKeys['<force>'])
70 vim.enterMode(storage.returnTo)
71 storage.returnTo = null
72 # If you press `aa` (and `a` is a prefix key, but there’s no `aa`
73 # shortcut), don’t pass the second `a` to the page.
74 return not match.toplevel
76 if match.type == 'full'
77 match.command.run(args)
79 # If the command changed the mode, wait until coming back from that mode
80 # before switching to `storage.returnTo` if any (see `onEnter` above).
81 if storage.returnTo and vim.mode == 'normal'
82 vim.enterMode(storage.returnTo)
83 storage.returnTo = null
85 # At this point the match is either full, partial or part of a count. Then
86 # we always want to suppress, except for one case: The Escape key.
87 return true unless keyStr == '<escape>'
89 # Passing Escape through allows for stopping the loading of the page and
90 # closing many custom dialogs (and perhaps other things; Escape is a very
93 # In browser UI the biggest reasons are allowing to reset the location bar
94 # when blurring it, and closing dialogs such as the “bookmark this page”
95 # dialog (<c-d>). However, an exception is made for the devtools (<c-K>).
96 # There, trying to unfocus the devtools using Escape would annoyingly
97 # open the split console.
98 return uiEvent.originalTarget.ownerGlobal.DevTools?
100 # In web pages content, an exception is made if an element that VimFx
101 # cares about is focused. That allows for blurring an input in a custom
102 # dialog without closing the dialog too.
103 return vim.focusType != 'none'
105 # Note that this special handling of Escape is only used in Normal mode.
106 # There are two reasons we might suppress it in other modes. If some custom
107 # dialog of a website is open, we should be able to cancel hint markers on
108 # it without closing it. Secondly, otherwise cancelling hint markers on
109 # Google causes its search bar to be focused.
116 onEnter: ({vim, storage}, markers, callback, count = 1, sleep = -1) ->
117 storage.markers = markers
118 storage.markerMap = null
119 storage.callback = callback
120 storage.count = count
121 storage.numEnteredChars = 0
124 storage.clearInterval = utils.interval(vim.window, sleep, (next) ->
125 unless storage.markerMap
128 vim._send('getMarkableElementsMovements', null, (diffs) ->
129 for {dx, dy}, index in diffs when not (dx == 0 and dy == 0)
130 storage.markerMap[index].updatePosition(dx, dy)
135 # Expose the storage so asynchronously computed markers can be set
139 onLeave: ({vim, storage}) ->
140 # When clicking VimFx’s disable button in the Add-ons Manager, `hints` will
141 # have been `null`ed out when the timeout has passed.
142 vim.window.setTimeout(
143 (-> hints?.removeHints(vim.window)),
144 vim.options.hints_timeout
146 storage.clearInterval?()
151 onInput: (args, match) ->
152 {vim, storage} = args
153 {markers, callback} = storage
155 if match.type == 'full'
156 match.command.run(args)
157 else if match.unmodifiedKey in vim.options.hint_chars and markers.length > 0
160 for marker in markers when marker.hintIndex == storage.numEnteredChars
161 matched = marker.matchHintChar(match.unmodifiedKey)
162 marker.hide() unless matched
163 if marker.isMatched()
164 marker.markMatched(true)
165 matchedMarkers.push(marker)
167 if matchedMarkers.length > 0
168 again = callback(matchedMarkers[0], storage.count, match.keyStr)
171 vim.window.setTimeout((->
172 marker.markMatched(false) for marker in matchedMarkers
174 ), vim.options.hints_timeout)
175 marker.reset() for marker in markers
176 storage.numEnteredChars = 0
178 vim.enterMode('normal')
180 storage.numEnteredChars += 1
185 exit: ({vim, storage}) ->
186 # The hints are removed automatically when leaving the mode, but after a
187 # timeout. When aborting the mode we should remove the hints immediately.
188 hints.removeHints(vim.window)
189 vim.enterMode('normal')
191 rotate_markers_forward: ({storage}) ->
192 rotateOverlappingMarkers(storage.markers, true)
194 rotate_markers_backward: ({storage}) ->
195 rotateOverlappingMarkers(storage.markers, false)
197 delete_hint_char: ({storage}) ->
198 for marker in storage.markers
199 switch marker.hintIndex - storage.numEnteredChars
201 marker.deleteHintChar()
204 storage.numEnteredChars -= 1 unless storage.numEnteredChars == 0
206 increase_count: ({storage}) -> storage.count += 1
212 onEnter: ({vim, storage}, {count = null, type = null} = {}) ->
213 storage.count = count
215 # Keep last `.type` if no type was given. This is useful when returning to
216 # Ignore mode after runnning the `unquote` command.
220 storage.type ?= 'explicit'
222 onLeave: ({vim, storage}) ->
223 unless storage.count? or storage.type == 'focusType'
224 vim._run('blur_active_element')
226 onInput: (args, match) ->
227 {vim, storage} = args
230 if match.type == 'full'
231 match.command.run(args)
234 vim.enterMode('normal')
240 exit: ({vim, storage}) ->
242 vim.enterMode('normal')
244 vim.enterMode('normal', {returnTo: 'ignore'})
253 findBar = vim.window.gBrowser.getFindBar()
254 findStorage.lastSearchString = findBar._findField.value
256 onInput: (args, match) ->
257 args.findBar = args.vim.window.gBrowser.getFindBar()
258 if match.type == 'full'
259 match.command.run(args)
264 exit: ({findBar}) -> findBar.close()
270 onEnter: ({storage}, callback) ->
271 storage.callback = callback
273 onLeave: ({storage}) ->
274 storage.callback = null
276 onInput: (args, match) ->
277 {vim, storage} = args
278 storage.callback(match.keyStr)
279 vim.enterMode('normal')