]> git.gir.st - VimFx.git/blob - extension/lib/modes.coffee
Add missing `return`s after `for` loops
[VimFx.git] / extension / lib / modes.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014, 2015.
4 # Copyright Wang Zhuochun 2014.
5 #
6 # This file is part of VimFx.
7 #
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.
12 #
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.
17 #
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/>.
20 ###
21
22 # This file defines VimFx’s modes, and their respective commands. The Normal
23 # mode commands are defined in commands.coffee, though.
24
25 { commands
26 , findStorage } = require('./commands')
27 defaults = require('./defaults')
28 help = require('./help')
29 hints = require('./hints')
30 translate = require('./l10n')
31 { rotateOverlappingMarkers } = require('./marker')
32 utils = require('./utils')
33
34 # Helper to create modes in a DRY way.
35 mode = (modeName, obj, commands) ->
36 obj.name = translate.bind(null, "mode.#{ modeName }")
37 obj.order = defaults.mode_order[modeName]
38 obj.commands = {}
39 for commandName, fn of commands
40 pref = "mode.#{ modeName }.#{ commandName }"
41 obj.commands[commandName] =
42 pref: defaults.BRANCH + pref
43 run: fn
44 category: defaults.categoryMap[pref]
45 description: translate.bind(null, pref)
46 order: defaults.command_order[pref]
47 exports[modeName] = obj
48
49
50
51 mode('normal', {
52 onEnter: ({ vim, storage }, options = {}) ->
53 if options.returnTo
54 storage.returnTo = options.returnTo
55 else if storage.returnTo
56 vim.enterMode(storage.returnTo)
57 storage.returnTo = null
58
59 onLeave: ({ vim }) ->
60 vim._run('clear_inputs')
61 help.removeHelp(vim.window)
62
63 onInput: (args, match) ->
64 { vim, storage, uiEvent } = args
65 { keyStr } = match
66
67 autoInsertMode = (match.focus != null)
68 if match.type == 'none' or
69 (autoInsertMode and not match.specialKeys['<force>'])
70 if storage.returnTo
71 vim.enterMode(storage.returnTo)
72 storage.returnTo = null
73 return false
74
75 if match.type == 'full'
76 match.command.run(args)
77
78 # If the command changed the mode, wait until coming back from that mode
79 # before switching to `storage.returnTo` if any (see `onEnter` above).
80 if storage.returnTo and vim.mode == 'normal'
81 vim.enterMode(storage.returnTo)
82 storage.returnTo = null
83
84 # At this point the match is either full, partial or part of a count. Then
85 # we always want to suppress, except for one case: The Escape key.
86 return true unless keyStr == '<escape>'
87
88 # Passing Escape through allows for stopping the loading of the page and
89 # closing many custom dialogs (and perhaps other things; Escape is a very
90 # commonly used key).
91 if uiEvent
92 # In browser UI the biggest reasons are allowing to reset the location bar
93 # when blurring it, and closing dialogs such as the “bookmark this page”
94 # dialog (<c-d>). However, an exception is made for the dev tools (<c-K>).
95 # There, trying to unfocus the dev tools using Escape would annoyingly
96 # open the split console.
97 return uiEvent.originalTarget.ownerGlobal.DevTools?
98 else
99 # In web pages content, an exception is made if we’re in autoInsertMode.
100 # That allows for blurring an input in a custom dialog without closing the
101 # dialog too.
102 return autoInsertMode
103
104 # Note that this special handling of Escape is only used in normal mode.
105 # There are two reasons we might suppress it in other modes. If some custom
106 # dialog of a website is open, we should be able to cancel hint markers on
107 # it without closing it. Secondly, otherwise cancelling hint markers on
108 # Google causes its search bar to be focused.
109
110 }, commands)
111
112
113
114 mode('hints', {
115 onEnter: ({ vim, storage }, markers, callback, count = 1) ->
116 storage.markers = markers
117 storage.callback = callback
118 storage.count = count
119 storage.numEnteredChars = 0
120
121 # Expose the storage so asynchronously computed markers can be set
122 # retroactively.
123 return storage
124
125 onLeave: ({ vim, storage }) ->
126 vim.window.setTimeout(hints.removeHints.bind(null, vim.window),
127 vim.options.hints_timeout)
128 for key of storage
129 storage[key] = null
130 return
131
132 onInput: (args, match) ->
133 { vim, storage } = args
134 { markers, callback } = storage
135
136 if match.type == 'full'
137 match.command.run(args)
138 else if match.unmodifiedKey in vim.options.hint_chars and markers.length > 0
139 matchedMarkers = []
140
141 for marker in markers when marker.hintIndex == storage.numEnteredChars
142 matched = marker.matchHintChar(match.unmodifiedKey)
143 marker.hide() unless matched
144 if marker.isMatched()
145 marker.markMatched(true)
146 matchedMarkers.push(marker)
147
148 if matchedMarkers.length > 0
149 again = callback(matchedMarkers[0], storage.count, match.keyStr)
150 storage.count--
151 if again
152 vim.window.setTimeout((->
153 marker.markMatched(false) for marker in matchedMarkers
154 return
155 ), vim.options.hints_timeout)
156 marker.reset() for marker in markers
157 storage.numEnteredChars = 0
158 else
159 vim.enterMode('normal')
160 else
161 storage.numEnteredChars++
162
163 return true
164
165 }, {
166 exit: ({ vim, storage }) ->
167 # The hints are removed automatically when leaving the mode, but after a
168 # timeout. When aborting the mode we should remove the hints immediately.
169 hints.removeHints(vim.window)
170 vim.enterMode('normal')
171
172 rotate_markers_forward: ({ storage }) ->
173 rotateOverlappingMarkers(storage.markers, true)
174
175 rotate_markers_backward: ({ storage }) ->
176 rotateOverlappingMarkers(storage.markers, false)
177
178 delete_hint_char: ({ storage }) ->
179 for marker in storage.markers
180 switch marker.hintIndex - storage.numEnteredChars
181 when 0 then marker.deleteHintChar()
182 when -1 then marker.show()
183 storage.numEnteredChars-- unless storage.numEnteredChars == 0
184
185 increase_count: ({ storage }) -> storage.count++
186 })
187
188
189
190 mode('ignore', {
191 onEnter: ({ vim, storage }, count = null) ->
192 storage.count = count
193
194 onLeave: ({ vim, storage }) ->
195 vim._run('blur_active_element') unless storage.count?
196
197 onInput: (args, match) ->
198 { vim, storage } = args
199 switch storage.count
200 when null
201 if match.type == 'full'
202 match.command.run(args)
203 return true
204 when 1
205 vim.enterMode('normal')
206 else
207 storage.count--
208 return false
209
210 }, {
211 exit: ({ vim }) -> vim.enterMode('normal')
212 unquote: ({ vim }) -> vim.enterMode('normal', {returnTo: 'ignore'})
213 })
214
215
216
217 mode('find', {
218 onEnter: ->
219
220 onLeave: ({ vim }) ->
221 findBar = vim.window.gBrowser.getFindBar()
222 findStorage.lastSearchString = findBar._findField.value
223
224 onInput: (args, match) ->
225 args.findBar = args.vim.window.gBrowser.getFindBar()
226 if match.type == 'full'
227 match.command.run(args)
228 return true
229 return false
230
231 }, {
232 exit: ({ findBar }) -> findBar.close()
233 })
Imprint / Impressum