]> git.gir.st - VimFx.git/blob - extension/lib/modes.coffee
Fix count for the `I` command
[VimFx.git] / extension / lib / modes.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014, 2015, 2016.
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, 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')
32
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]
37 obj.commands = {}
38 for commandName, fn of commands
39 pref = "mode.#{modeName}.#{commandName}"
40 obj.commands[commandName] = {
41 pref: defaults.BRANCH + pref
42 run: fn
43 category: defaults.categoryMap[pref]
44 description: translate(pref)
45 order: defaults.command_order[pref]
46 }
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
62 onInput: (args, match) ->
63 {vim, storage, uiEvent} = args
64 {keyStr} = match
65
66 autoInsertMode = (match.focus != null)
67 if match.type == 'none' or
68 (autoInsertMode and not match.specialKeys['<force>'])
69 match.discard()
70 if storage.returnTo
71 vim.enterMode(storage.returnTo)
72 storage.returnTo = null
73 # If you press `aa` (and `a` is a prefix key, but there’s no `aa`
74 # shortcut), don’t pass the second `a` to the page.
75 return not match.toplevel
76
77 if match.type == 'full'
78 match.command.run(args)
79
80 # If the command changed the mode, wait until coming back from that mode
81 # before switching to `storage.returnTo` if any (see `onEnter` above).
82 if storage.returnTo and vim.mode == 'normal'
83 vim.enterMode(storage.returnTo)
84 storage.returnTo = null
85
86 # At this point the match is either full, partial or part of a count. Then
87 # we always want to suppress, except for one case: The Escape key.
88 return true unless keyStr == '<escape>'
89
90 # Passing Escape through allows for stopping the loading of the page and
91 # closing many custom dialogs (and perhaps other things; Escape is a very
92 # commonly used key).
93 if uiEvent
94 # In browser UI the biggest reasons are allowing to reset the location bar
95 # when blurring it, and closing dialogs such as the “bookmark this page”
96 # dialog (<c-d>). However, an exception is made for the devtools (<c-K>).
97 # There, trying to unfocus the devtools using Escape would annoyingly
98 # open the split console.
99 return uiEvent.originalTarget.ownerGlobal.DevTools?
100 else
101 # In web pages content, an exception is made if we’re in autoInsertMode.
102 # That allows for blurring an input in a custom dialog without closing the
103 # dialog too.
104 return autoInsertMode
105
106 # Note that this special handling of Escape is only used in Normal mode.
107 # There are two reasons we might suppress it in other modes. If some custom
108 # dialog of a website is open, we should be able to cancel hint markers on
109 # it without closing it. Secondly, otherwise cancelling hint markers on
110 # Google causes its search bar to be focused.
111
112 }, commands)
113
114
115
116 mode('hints', {
117 onEnter: ({vim, storage}, markers, callback, count = 1, sleep = -1) ->
118 storage.markers = markers
119 storage.markerMap = null
120 storage.callback = callback
121 storage.count = count
122 storage.numEnteredChars = 0
123
124 if sleep >= 0
125 storage.clearInterval = utils.interval(vim.window, sleep, (next) ->
126 unless storage.markerMap
127 next()
128 return
129 vim._send('getMarkableElementsMovements', null, (diffs) ->
130 for {dx, dy}, index in diffs when not (dx == 0 and dy == 0)
131 storage.markerMap[index].updatePosition(dx, dy)
132 next()
133 )
134 )
135
136 # Expose the storage so asynchronously computed markers can be set
137 # retroactively.
138 return storage
139
140 onLeave: ({vim, storage}) ->
141 vim.window.setTimeout((-> hints.removeHints(vim.window)),
142 vim.options.hints_timeout)
143 storage.clearInterval?()
144 for key of storage
145 storage[key] = null
146 return
147
148 onInput: (args, match) ->
149 {vim, storage} = args
150 {markers, callback} = storage
151
152 if match.type == 'full'
153 match.command.run(args)
154 else if match.unmodifiedKey in vim.options.hint_chars and markers.length > 0
155 matchedMarkers = []
156
157 for marker in markers when marker.hintIndex == storage.numEnteredChars
158 matched = marker.matchHintChar(match.unmodifiedKey)
159 marker.hide() unless matched
160 if marker.isMatched()
161 marker.markMatched(true)
162 matchedMarkers.push(marker)
163
164 if matchedMarkers.length > 0
165 again = callback(matchedMarkers[0], storage.count, match.keyStr)
166 storage.count -= 1
167 if again
168 vim.window.setTimeout((->
169 marker.markMatched(false) for marker in matchedMarkers
170 return
171 ), vim.options.hints_timeout)
172 marker.reset() for marker in markers
173 storage.numEnteredChars = 0
174 else
175 vim.enterMode('normal')
176 else
177 storage.numEnteredChars += 1
178
179 return true
180
181 }, {
182 exit: ({vim, storage}) ->
183 # The hints are removed automatically when leaving the mode, but after a
184 # timeout. When aborting the mode we should remove the hints immediately.
185 hints.removeHints(vim.window)
186 vim.enterMode('normal')
187
188 rotate_markers_forward: ({storage}) ->
189 rotateOverlappingMarkers(storage.markers, true)
190
191 rotate_markers_backward: ({storage}) ->
192 rotateOverlappingMarkers(storage.markers, false)
193
194 delete_hint_char: ({storage}) ->
195 for marker in storage.markers
196 switch marker.hintIndex - storage.numEnteredChars
197 when 0
198 marker.deleteHintChar()
199 when -1
200 marker.show()
201 storage.numEnteredChars -= 1 unless storage.numEnteredChars == 0
202
203 increase_count: ({storage}) -> storage.count += 1
204 })
205
206
207
208 mode('ignore', {
209 onEnter: ({vim, storage}, count = null) ->
210 storage.count = count
211
212 onLeave: ({vim, storage}) ->
213 vim._run('blur_active_element') unless storage.count?
214
215 onInput: (args, match) ->
216 {vim, storage} = args
217 switch storage.count
218 when null
219 if match.type == 'full'
220 match.command.run(args)
221 return true
222 when 1
223 vim.enterMode('normal')
224 else
225 storage.count -= 1
226 return false
227
228 }, {
229 exit: ({vim}) -> vim.enterMode('normal')
230 unquote: ({vim}) -> vim.enterMode('normal', {returnTo: 'ignore'})
231 })
232
233
234
235 mode('find', {
236 onEnter: ->
237
238 onLeave: ({vim}) ->
239 findBar = vim.window.gBrowser.getFindBar()
240 findStorage.lastSearchString = findBar._findField.value
241
242 onInput: (args, match) ->
243 args.findBar = args.vim.window.gBrowser.getFindBar()
244 if match.type == 'full'
245 match.command.run(args)
246 return true
247 return false
248
249 }, {
250 exit: ({findBar}) -> findBar.close()
251 })
252
253
254
255 mode('marks', {
256 onEnter: ({storage}, callback) ->
257 storage.callback = callback
258
259 onLeave: ({storage}) ->
260 storage.callback = null
261
262 onInput: (args, match) ->
263 {vim, storage} = args
264 storage.callback(match.keyStr)
265 vim.enterMode('normal')
266 return true
267 })
Imprint / Impressum