]> git.gir.st - VimFx.git/blob - extension/lib/modes.coffee
Refactor defaults
[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 utils = require('./utils')
23 { injectHints } = require('./hints')
24 { rotateOverlappingMarkers } = require('./marker')
25 { updateToolbarButton } = require('./button')
26 Command = require('./command')
27 { commands
28 , escapeCommand
29 , findStorage } = require('./commands')
30
31 { interfaces: Ci } = Components
32
33 XULDocument = Ci.nsIDOMXULDocument
34
35 exports['normal'] =
36 onEnter: (vim, storage) ->
37 storage.keys ?= []
38 storage.commands ?= {}
39
40 onLeave: (vim, storage) ->
41 storage.keys.length = 0
42
43 onInput: (vim, storage, keyStr, event) ->
44 target = event.originalTarget
45 document = target.ownerDocument
46
47 autoInsertMode = \
48 utils.isTextInputElement(target) or
49 utils.isContentEditable(target) or
50 (utils.isActivatable(target) and keyStr == '<enter>') or
51 (utils.isAdjustable(target) and keyStr in [
52 '<arrowup>', '<arrowdown>', '<arrowleft>', '<arrowright>'
53 '<space>', '<enter>'
54 ]) or
55 vim.rootWindow.TabView.isVisible() or
56 document.fullscreenElement or document.mozFullScreenElement
57
58 storage.keys.push(keyStr)
59
60 { match, exact, command, count } =
61 Command.searchForMatchingCommand(@commands, storage.keys)
62
63 if vim.state.blacklistedKeys and
64 storage.keys.join('') in vim.state.blacklistedKeys
65 match = false
66
67 if match
68
69 if autoInsertMode and command != escapeCommand
70 storage.keys.pop()
71 return false
72
73 if exact
74 command.func(vim, event, count)
75 storage.keys.length = 0
76
77 # Esc key is not suppressed, and passed to the browser in normal mode.
78 #
79 # - It allows for stopping the loading of the page.
80 # - It allows for closing many custom dialogs (and perhaps other things
81 # -- Esc is a very commonly used key).
82 # - It is not passed if Esc is used for `command_Esc` and we’re blurring
83 # an element. That allows for blurring an input in a custom dialog
84 # without closing the dialog too.
85 # - There are two reasons we might suppress it in other modes. If some
86 # custom dialog of a website is open, we should be able to cancel hint
87 # markers on it without closing it. Secondly, otherwise cancelling hint
88 # markers on Google causes its search bar to be focused.
89 # - It may only be suppressed in web pages, not in browser chrome. That
90 # allows for reseting the location bar when blurring it, and closing
91 # dialogs such as the “bookmark this page” dialog (<c-d>).
92 inBrowserChrome = (document instanceof XULDocument)
93 if keyStr == '<escape>' and (not autoInsertMode or inBrowserChrome)
94 return false
95
96 return true
97
98 else
99 storage.keys.length = 0 unless /^\d$/.test(keyStr)
100
101 return false
102
103 commands: commands
104
105 exports['insert'] =
106 onEnter: (vim, storage, count = null) ->
107 storage.count = count
108 updateToolbarButton(vim.rootWindow, {insertMode: true})
109 onLeave: (vim) ->
110 updateToolbarButton(vim.rootWindow, {insertMode: false})
111 utils.blurActiveElement(vim.window)
112 onInput: (vim, storage, keyStr) ->
113 switch storage.count
114 when null
115 if @commands['exit'].match(keyStr)
116 vim.enterMode('normal')
117 return true
118 when 1
119 vim.enterMode('normal')
120 else
121 storage.count--
122 return false
123 commands: [
124 'exit'
125 ]
126
127 exports['text-input'] =
128 onEnter: (vim, storage, inputs = []) ->
129 storage.inputs = inputs
130 onLeave: (vim, storage) ->
131 storage.inputs = null
132 onInput: (vim, storage, keyStr) ->
133 { inputs } = storage
134 index = inputs.indexOf(vim.window.document.activeElement)
135 switch
136 when index == -1
137 vim.enterMode('normal')
138 return false
139 when escapeCommand.match(keyStr)
140 utils.blurActiveElement(vim.window)
141 vim.enterMode('normal')
142 return true
143 # Override the built-in shortcuts <tab> and <s-tab> to switch between
144 # focusable inputs.
145 when keyStr == '<tab>'
146 index++
147 when keyStr == '<s-tab>'
148 index--
149 else
150 return false
151 inputs[index %% inputs.length].select()
152 return true
153
154 exports['find'] =
155 onEnter: ->
156
157 onLeave: (vim) ->
158 findBar = vim.rootWindow.gBrowser.getFindBar()
159 findStorage.lastSearchString = findBar._findField.value
160
161 onInput: (vim, storage, keyStr) ->
162 findBar = vim.rootWindow.gBrowser.getFindBar()
163 if @commands['exit'].match(keyStr)
164 findBar.close()
165 return true
166 return false
167
168 commands: [
169 'exit'
170 ]
171
172 exports['hints'] =
173 onEnter: (vim, storage, filter, callback) ->
174 [ markers, container ] = injectHints(vim.rootWindow, vim.window, filter)
175 if markers.length > 0
176 storage.markers = markers
177 storage.container = container
178 storage.callback = callback
179 storage.numEnteredChars = 0
180 else
181 vim.enterMode('normal')
182
183 onLeave: (vim, storage) ->
184 { container } = storage
185 vim.rootWindow.setTimeout((->
186 container?.remove()
187 ), @timeout)
188 for key of storage
189 storage[key] = null
190
191 onInput: (vim, storage, keyStr, event) ->
192 { markers, callback } = storage
193
194 switch
195 when @commands['exit'].match(keyStr)
196 # Remove the hints immediately.
197 storage.container?.remove()
198 vim.enterMode('normal')
199 return true
200
201 when @commands['rotate_markers_forward'].match(keyStr)
202 rotateOverlappingMarkers(markers, true)
203 when @commands['rotate_markers_backward'].match(keyStr)
204 rotateOverlappingMarkers(markers, false)
205
206 when @commands['delete_hint_char'].match(keyStr)
207 for marker in markers
208 switch marker.hintIndex - storage.numEnteredChars
209 when 0 then marker.deleteHintChar()
210 when -1 then marker.show()
211 storage.numEnteredChars-- unless storage.numEnteredChars == 0
212
213 else
214 if keyStr not in utils.getHintChars()
215 return true
216 matchedMarkers = []
217 for marker in markers when marker.hintIndex == storage.numEnteredChars
218 match = marker.matchHintChar(keyStr)
219 marker.hide() unless match
220 if marker.isMatched()
221 marker.markMatched(true)
222 matchedMarkers.push(marker)
223 if matchedMarkers.length > 0
224 again = callback(matchedMarkers[0])
225 if again
226 vim.rootWindow.setTimeout((->
227 marker.markMatched(false) for marker in matchedMarkers
228 ), @timeout)
229 marker.reset() for marker in markers
230 storage.numEnteredChars = 0
231 else
232 vim.enterMode('normal')
233 return true
234 storage.numEnteredChars++
235
236 return true
237
238 timeout: 200
239
240 commands: [
241 'exit'
242 'rotate_markers_forward'
243 'rotate_markers_backward'
244 'delete_hint_char'
245 ]
246
247 for modeName, mode of exports
248 commandNames = mode.commands
249 continue if not commandNames or commandNames == commands
250 mode.commands = {}
251 for commandName in commandNames
252 name = "mode_#{ modeName }_#{ commandName }"
253 mode.commands[commandName] = new Command(null, name, null)
Imprint / Impressum