]> git.gir.st - VimFx.git/blob - extension/lib/modes.coffee
Merge pull request #512 from akhodakivskiy/multi-process
[VimFx.git] / extension / lib / modes.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014.
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, isFrameEvent } = args
65 { keyStr } = match
66
67 autoInsertMode = (match.focus != null)
68 if match.type == 'none' or (autoInsertMode and not match.force)
69 if storage.returnTo
70 vim.enterMode(storage.returnTo)
71 storage.returnTo = null
72 return false
73
74 if match.type == 'full'
75 { command } = match
76 # Rely on the default `<tab>` behavior, since it allows web pages to
77 # provide tab completion, for example, inside text inputs.
78 args._skipMoveFocus = (
79 match.toplevel and
80 ((command.run == commands.focus_previous and keyStr == '<tab>') or
81 (command.run == commands.focus_next and keyStr == '<s-tab>'))
82 )
83 command.run(args)
84
85 # If the command changed the mode, wait until coming back from that mode
86 # before switching to `storage.returnTo` if any (see `onEnter` above).
87 if storage.returnTo and vim.mode == 'normal'
88 vim.enterMode(storage.returnTo)
89 storage.returnTo = null
90
91 # At this point the match is either full, partial or part of a count. Then
92 # we always want to suppress, except for one case: The Escape key.
93 #
94 # - It allows for stopping the loading of the page.
95 # - It allows for closing many custom dialogs (and perhaps other things
96 # -- Esc is a very commonly used key).
97 # - It is not passed if Esc is used for `command.esc` and we’re blurring
98 # an element. That allows for blurring an input in a custom dialog
99 # without closing the dialog too.
100 # - There are two reasons we might suppress it in other modes. If some
101 # custom dialog of a website is open, we should be able to cancel hint
102 # markers on it without closing it. Secondly, otherwise cancelling hint
103 # markers on Google causes its search bar to be focused.
104 # - It may only be suppressed in web pages, not in browser chrome. That
105 # allows for reseting the location bar when blurring it, and closing
106 # dialogs such as the “bookmark this page” dialog (<c-d>).
107 return not (keyStr == '<escape>' and not autoInsertMode and isFrameEvent)
108
109 }, commands)
110
111
112
113 mode('hints', {
114 onEnter: ({ vim, storage }, wrappers, viewport, callback, count = 1) ->
115 [ markers, container ] = hints.injectHints(
116 vim.window, wrappers, viewport, vim.options
117 )
118 if markers.length > 0
119 storage.markers = markers
120 storage.container = container
121 storage.callback = callback
122 storage.count = count
123 storage.numEnteredChars = 0
124 else
125 vim.enterMode('normal')
126
127 onLeave: ({ vim, storage }) ->
128 { container } = storage
129 vim.window.setTimeout((->
130 container?.remove()
131 ), vim.options.hints_timeout)
132 for key of storage
133 storage[key] = null
134
135 onInput: (args, match) ->
136 { vim, storage } = args
137 { markers, callback } = storage
138
139 if match.type == 'full'
140 match.command.run(args)
141 else if match.unmodifiedKey in vim.options.hint_chars
142 matchedMarkers = []
143
144 for marker in markers when marker.hintIndex == storage.numEnteredChars
145 matched = marker.matchHintChar(match.unmodifiedKey)
146 marker.hide() unless matched
147 if marker.isMatched()
148 marker.markMatched(true)
149 matchedMarkers.push(marker)
150
151 if matchedMarkers.length > 0
152 again = callback(matchedMarkers[0], storage.count, match.keyStr)
153 storage.count--
154 if again
155 vim.window.setTimeout((->
156 marker.markMatched(false) for marker in matchedMarkers
157 ), vim.options.hints_timeout)
158 marker.reset() for marker in markers
159 storage.numEnteredChars = 0
160 else
161 vim.enterMode('normal')
162 else
163 storage.numEnteredChars++
164
165 return true
166
167 }, {
168 exit: ({ vim, storage }) ->
169 # The hints are removed automatically when leaving the mode, but after a
170 # timeout. When aborting the mode we should remove the hints immediately.
171 storage.container?.remove()
172 vim.enterMode('normal')
173
174 rotate_markers_forward: ({ storage }) ->
175 rotateOverlappingMarkers(storage.markers, true)
176
177 rotate_markers_backward: ({ storage }) ->
178 rotateOverlappingMarkers(storage.markers, false)
179
180 delete_hint_char: ({ storage }) ->
181 for marker in storage.markers
182 switch marker.hintIndex - storage.numEnteredChars
183 when 0 then marker.deleteHintChar()
184 when -1 then marker.show()
185 storage.numEnteredChars-- unless storage.numEnteredChars == 0
186
187 increase_count: ({ storage }) -> storage.count++
188 })
189
190
191
192 mode('ignore', {
193 onEnter: ({ vim, storage }, count = null) ->
194 storage.count = count
195
196 onLeave: ({ vim, storage }) ->
197 vim._run('blur_active_element') unless storage.count?
198
199 onInput: (args, match) ->
200 { vim, storage } = args
201 switch storage.count
202 when null
203 if match.type == 'full'
204 match.command.run(args)
205 return true
206 when 1
207 vim.enterMode('normal')
208 else
209 storage.count--
210 return false
211
212 }, {
213 exit: ({ vim }) -> vim.enterMode('normal')
214 unquote: ({ vim }) -> vim.enterMode('normal', {returnTo: 'ignore'})
215 })
216
217
218
219 mode('find', {
220 onEnter: ->
221
222 onLeave: ({ vim }) ->
223 findBar = vim.window.gBrowser.getFindBar()
224 findStorage.lastSearchString = findBar._findField.value
225
226 onInput: (args, match) ->
227 args.findBar = args.vim.window.gBrowser.getFindBar()
228 if match.type == 'full'
229 match.command.run(args)
230 return true
231 return false
232
233 }, {
234 exit: ({ findBar }) -> findBar.close()
235 })
Imprint / Impressum