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