]> git.gir.st - VimFx.git/blob - extension/lib/help.coffee
Improve error reporting in test runner
[VimFx.git] / extension / lib / help.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2012, 2013.
3 # Copyright Simon Lydell 2013, 2014.
4 #
5 # This file is part of VimFx.
6 #
7 # VimFx is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # VimFx is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
19 ###
20
21 notation = require('vim-like-key-notation')
22 legacy = require('./legacy')
23 utils = require('./utils')
24 prefs = require('./prefs')
25 _ = require('./l10n')
26
27 { classes: Cc, interfaces: Ci, utils: Cu } = Components
28
29 XULDocument = Ci.nsIDOMXULDocument
30
31 CONTAINER_ID = 'VimFxHelpDialogContainer'
32
33 # coffeelint: disable=max_line_length
34
35 removeHelp = (document) ->
36 document.getElementById(CONTAINER_ID)?.remove()
37
38 injectHelp = (document, options) ->
39 { modes } = options
40 if document.documentElement
41 removeHelp(document)
42
43 type = if document instanceof XULDocument then 'box' else 'div'
44 container = utils.createElement(document, type, {id: CONTAINER_ID})
45
46 modeCommands = {}
47 for modeName of modes
48 cmds = modes[modeName].commands
49 modeCommands[modeName] =
50 if Array.isArray(cmds)
51 cmds
52 else
53 (cmds[commandName] for commandName of cmds)
54
55 container.appendChild(utils.parseHTML(document, helpDialogHtml(modeCommands, options)))
56 for element in container.getElementsByTagName('*')
57 element.classList.add('VimFxReset')
58
59 document.documentElement.appendChild(container)
60
61 for element in container.querySelectorAll('[data-commands]')
62 elementCommands = modeCommands[element.dataset.commands]
63 element.addEventListener('click',
64 removeHandler.bind(undefined, document, elementCommands), false)
65 element.addEventListener('click',
66 addHandler.bind(undefined, document, elementCommands), false)
67
68 if button = document.getElementById('VimFxClose')
69 clickHandler = (event) ->
70 event.stopPropagation()
71 event.preventDefault()
72 removeHelp(document)
73 button.addEventListener('click', clickHandler, false)
74
75 promptService = Cc['@mozilla.org/embedcomp/prompt-service;1']
76 .getService(Ci.nsIPromptService)
77
78 removeHandler = (document, commands, event) ->
79 return unless event.target.classList.contains('VimFxKeyLink')
80 event.preventDefault()
81 event.stopPropagation()
82 key = event.target.getAttribute('data-key')
83 name = event.target.getAttribute('data-command')
84 if cmd = commands.reduce(((m, v) -> if (v.name == name) then v else m), null)
85 title = _('help_remove_shortcut_title')
86 text = _('help_remove_shortcut_text')
87 if promptService.confirm(document.defaultView, title, text)
88 cmd.keys(cmd.keys().filter((a) -> utils.normalizedKey(a) != key))
89 event.target.remove()
90
91 addHandler = (document, commands, event) ->
92 return unless event.target.classList.contains('VimFxAddShortcutLink')
93 event.preventDefault()
94 event.stopPropagation()
95 name = event.target.getAttribute('data-command')
96 if cmd = commands.reduce(((m, v) -> if (v.name == name) then v else m), null)
97 errorText = ''
98 loop
99 title = _('help_add_shortcut_title')
100 text = errorText + _('help_add_shortcut_text')
101 value = {value: null}
102 check = {value: null}
103 if promptService.prompt(document.defaultView, title, text, value, null, check)
104 input = value.value.trim()
105 return if input.length == 0
106 key = notation.parseSequence(input)
107 try
108 if name.startsWith('mode_') and key.length > 1
109 throw {id: 'single_keystrokes_only'}
110 normalizedKey = utils.normalizedKey(key)
111 catch {id, context, subject}
112 if /^\s$/.test(subject) then id = 'invalid_whitespace'
113 errorText = _("error_#{ id }", context ? subject, subject) + '\n'
114 continue
115 conflicts = getConflicts(commands, normalizedKey)
116 if conflicts.length == 0 or overwriteCmd(document, conflicts, key)
117 cmd.keys(cmd.keys().concat([key]))
118 for div in document.getElementsByClassName('VimFxKeySequence')
119 if div.getAttribute('data-command') == cmd.name
120 div.insertAdjacentHTML('beforeend', hint(cmd, key))
121 break
122 return
123
124 getConflicts = (commands, value) ->
125 conflicts = []
126 for command in commands
127 conflictingKeys = []
128 for key in command.keys()
129 normalizedKey = utils.normalizedKey(key)
130 shortest = Math.min(value.length, normalizedKey.length)
131 if value[...shortest] == normalizedKey[...shortest]
132 conflictingKeys.push(key)
133 if conflictingKeys.length > 0
134 conflicts.push({command, conflictingKeys})
135 return conflicts
136
137 overwriteCmd = (document, conflicts, key) ->
138 title = _('help_add_shortcut_title')
139 conflictSummary = conflicts.map((conflict) ->
140 conflictingKeys = conflict.conflictingKeys
141 .map((key) -> key.join('')).join(' ')
142 return "#{ conflict.command.help() }: #{ conflictingKeys }"
143 ).join('\n')
144 text = """
145 #{ _('help_add_shortcut_text_overwrite', key.join('')) }
146
147 #{ conflictSummary }
148 """
149 if promptService.confirm(document.defaultView, title, text)
150 for { command, conflictingKeys } in conflicts
151 normalizedKeys = conflictingKeys.map((key) -> utils.normalizedKey(key))
152 command.keys(command.keys().filter((key) ->
153 return utils.normalizedKey(key) not in normalizedKeys
154 ))
155 for key in normalizedKeys
156 document.querySelector("a[data-key='#{ key }']").remove()
157 return true
158 else
159 return false
160
161 td = (text, klass = '') ->
162 """<td class="#{ klass }">#{ text }</td>"""
163
164 hint = (cmd, key) ->
165 normalizedKey = utils.escapeHTML(utils.normalizedKey(key))
166 displayKey = utils.escapeHTML(key.join(''))
167 """<a href="#" class="VimFxReset VimFxKeyLink" title="#{ _('help_remove_shortcut') }" \
168 data-command="#{ cmd.name }" data-key="#{ normalizedKey }">#{ displayKey }</a>"""
169
170 tr = (cmd) ->
171 hints = """
172 <div class="VimFxKeySequence" data-command="#{ cmd.name }">
173 #{ (hint(cmd, key) for key in cmd.keys()).join('\n') }
174 </div>
175 """
176 dot = '<span class="VimFxDot">&#8729;</span>'
177 a = """#{ cmd.help() }"""
178 add = """
179 <a href="#" data-command="#{ cmd.name }"
180 class="VimFxAddShortcutLink" title="#{ _('help_add_shortcut') }">&#8862;</a>
181 """
182
183 return """
184 <tr>
185 #{ td(hints) }
186 #{ td(add) }
187 #{ td(dot) }
188 #{ td(a) }
189 </tr>
190 """
191
192 table = (commands, modeName) ->
193 """
194 <table data-commands="#{modeName}">
195 #{ (tr(cmd) for cmd in commands).join('') }
196 </table>
197 """
198
199 section = (title, commands, modeName = 'normal') ->
200 """
201 <div class="VimFxSectionTitle">#{ title }</div>
202 #{ table(commands, modeName) }
203 """
204
205 helpDialogHtml = (modeCommands, options) ->
206 commands = modeCommands['normal']
207 return """
208 <div id="VimFxHelpDialog">
209 <div class="VimFxHeader">
210 <div class="VimFxTitle">
211 <span class="VimFxTitleVim">Vim</span><span class="VimFxTitleFx">Fx</span>
212 <span>#{ _('help_title') }</span>
213 </div>
214 <span class="VimFxVersion">#{ _('help_version') } #{ options.VERSION }</span>
215 <a class="VimFxClose" id="VimFxClose" href="#">&#10006;</a>
216 <div class="VimFxClearFix"></div>
217 <p>Did you know that you can add/remove shortucts in this dialog?</p>
218 <div class="VimFxClearFix"></div>
219 <p>Click the shortcut to remove it, and click &#8862; to add new shortcut!</p>
220 </div>
221
222 <div class="VimFxBody">
223 <div class="VimFxColumn">
224 #{ section(_('category.location'), commands.filter((a) -> a.group == 'location')) }
225 #{ section(_('category.scrolling'), commands.filter((a) -> a.group == 'scrolling')) }
226 #{ section(_('category.find'), commands.filter((a) -> a.group == 'find')) }
227 #{ section(_('category.misc'), commands.filter((a) -> a.group == 'misc')) }
228 </div>
229 <div class="VimFxColumn">
230 #{ section(_('category.tabs'), commands.filter((a) -> a.group == 'tabs')) }
231 #{ section(_('category.browsing'), commands.filter((a) -> a.group == 'browsing')) }
232 #{ section(_('mode.hints'), modeCommands['hints'], 'hints') }
233 #{ section(_('mode.insert'), modeCommands['insert'], 'insert') }
234 #{ section(_('mode.find'), modeCommands['find'], 'find') }
235 </div>
236 <div class="VimFxClearFix"></div>
237 </div>
238
239 <div class="VimFxFooter">
240 <p>
241 #{ _('help_found_bug') }
242 <a target="_blank" href="https://github.com/akhodakivskiy/VimFx/issues">
243 #{ _('help_report_bug') }
244 </a>
245 </p>
246 <p>
247 #{ _('help_enjoying') }
248 <a target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/vimfx/">
249 #{ _('help_feedback') }
250 </a>
251 </p>
252 </div>
253 </div>
254 """
255
256 exports.injectHelp = injectHelp
257 exports.removeHelp = removeHelp
Imprint / Impressum