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