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