]> git.gir.st - VimFx.git/blob - extension/lib/modes.coffee
Fix #47: 'gi' to focus last focused or first text input
[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: ['<s-escape>']
125
126 exports['text-input'] =
127 onEnter: (vim, storage, inputs = []) ->
128 storage.inputs = inputs
129 onLeave: (vim, storage) ->
130 storage.inputs = null
131 onInput: (vim, storage, keyStr) ->
132 { inputs } = storage
133 index = inputs.indexOf(vim.window.document.activeElement)
134 switch
135 when index == -1
136 vim.enterMode('normal')
137 return false
138 when escapeCommand.match(keyStr)
139 utils.blurActiveElement(vim.window)
140 vim.enterMode('normal')
141 return true
142 # Override the built-in shortcuts <tab> and <s-tab> to switch between
143 # focusable inputs.
144 when keyStr == '<tab>'
145 index++
146 when keyStr == '<s-tab>'
147 index--
148 else
149 return false
150 inputs[index %% inputs.length].select()
151 return true
152
153 exports['find'] =
154 onEnter: ->
155
156 onLeave: (vim) ->
157 findBar = vim.rootWindow.gBrowser.getFindBar()
158 findStorage.lastSearchString = findBar._findField.value
159
160 onInput: (vim, storage, keyStr) ->
161 findBar = vim.rootWindow.gBrowser.getFindBar()
162 if @commands['exit'].match(keyStr)
163 findBar.close()
164 return true
165 return false
166
167 commands:
168 exit: ['<escape>', '<enter>']
169
170 exports['hints'] =
171 onEnter: (vim, storage, filter, callback) ->
172 [ markers, container ] = injectHints(vim.rootWindow, vim.window, filter)
173 if markers.length > 0
174 storage.markers = markers
175 storage.container = container
176 storage.callback = callback
177 storage.numEnteredChars = 0
178 else
179 vim.enterMode('normal')
180
181 onLeave: (vim, storage) ->
182 { container } = storage
183 vim.rootWindow.setTimeout((->
184 container?.remove()
185 ), @timeout)
186 for key of storage
187 storage[key] = null
188
189 onInput: (vim, storage, keyStr, event) ->
190 { markers, callback } = storage
191
192 switch
193 when @commands['exit'].match(keyStr)
194 # Remove the hints immediately.
195 storage.container?.remove()
196 vim.enterMode('normal')
197 return true
198
199 when @commands['rotate_markers_forward'].match(keyStr)
200 rotateOverlappingMarkers(markers, true)
201 when @commands['rotate_markers_backward'].match(keyStr)
202 rotateOverlappingMarkers(markers, false)
203
204 when @commands['delete_hint_char'].match(keyStr)
205 for marker in markers
206 switch marker.hintIndex - storage.numEnteredChars
207 when 0 then marker.deleteHintChar()
208 when -1 then marker.show()
209 storage.numEnteredChars-- unless storage.numEnteredChars == 0
210
211 else
212 if keyStr not in utils.getHintChars()
213 return true
214 matchedMarkers = []
215 for marker in markers when marker.hintIndex == storage.numEnteredChars
216 match = marker.matchHintChar(keyStr)
217 marker.hide() unless match
218 if marker.isMatched()
219 marker.markMatched(true)
220 matchedMarkers.push(marker)
221 if matchedMarkers.length > 0
222 again = callback(matchedMarkers[0])
223 if again
224 vim.rootWindow.setTimeout((->
225 marker.markMatched(false) for marker in matchedMarkers
226 ), @timeout)
227 marker.reset() for marker in markers
228 storage.numEnteredChars = 0
229 else
230 vim.enterMode('normal')
231 return true
232 storage.numEnteredChars++
233
234 return true
235
236 timeout: 200
237
238 commands:
239 exit: ['<escape>']
240 rotate_markers_forward: ['<space>']
241 rotate_markers_backward: ['<s-space>']
242 delete_hint_char: ['<backspace>']
243
244 for modeName, mode of exports when not Array.isArray(mode.commands ? [])
245 for commandName of mode.commands
246 name = "mode_#{ modeName }_#{ commandName }"
247 keys = mode.commands[commandName].map((key) -> [key])
248 mode.commands[commandName] = new Command(null, name, null, keys)
Imprint / Impressum