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