]> git.gir.st - VimFx.git/blob - extension/lib/help.coffee
Allow shortcuts in other modes to be customized
[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, modes) ->
39 if document.documentElement
40 removeHelp(document)
41
42 type = if document instanceof XULDocument then 'box' else 'div'
43 container = utils.createElement(document, type, {id: CONTAINER_ID})
44
45 modeCommands = {}
46 for modeName of modes
47 cmds = modes[modeName].commands
48 modeCommands[modeName] =
49 if Array.isArray(cmds)
50 cmds
51 else
52 (cmds[commandName] for commandName of cmds)
53
54 container.appendChild(utils.parseHTML(document, helpDialogHtml(modeCommands)))
55 for element in container.getElementsByTagName('*')
56 element.classList.add('VimFxReset')
57
58 document.documentElement.appendChild(container)
59
60 for element in container.querySelectorAll('[data-commands]')
61 elementCommands = modeCommands[element.dataset.commands]
62 element.addEventListener('click',
63 removeHandler.bind(undefined, document, elementCommands), false)
64 element.addEventListener('click',
65 addHandler.bind(undefined, document, elementCommands), false)
66
67 if button = document.getElementById('VimFxClose')
68 clickHandler = (event) ->
69 event.stopPropagation()
70 event.preventDefault()
71 removeHelp(document)
72 button.addEventListener('click', clickHandler, false)
73
74 promptService = Cc['@mozilla.org/embedcomp/prompt-service;1']
75 .getService(Ci.nsIPromptService)
76
77 removeHandler = (document, commands, event) ->
78 return unless event.target.classList.contains('VimFxKeyLink')
79 event.preventDefault()
80 event.stopPropagation()
81 key = event.target.getAttribute('data-key')
82 name = event.target.getAttribute('data-command')
83 if cmd = commands.reduce(((m, v) -> if (v.name == name) then v else m), null)
84 title = _('help_remove_shortcut_title')
85 text = _('help_remove_shortcut_text')
86 if promptService.confirm(document.defaultView, title, text)
87 cmd.keys(cmd.keys().filter((a) -> utils.normalizedKey(a) != key))
88 event.target.remove()
89
90 addHandler = (document, commands, event) ->
91 return unless event.target.classList.contains('VimFxAddShortcutLink')
92 event.preventDefault()
93 event.stopPropagation()
94 name = event.target.getAttribute('data-command')
95 if cmd = commands.reduce(((m, v) -> if (v.name == name) then v else m), null)
96 errorText = ''
97 loop
98 title = _('help_add_shortcut_title')
99 text = errorText + _('help_add_shortcut_text')
100 value = {value: null}
101 check = {value: null}
102 if promptService.prompt(document.defaultView, title, text, value, null, check)
103 input = value.value.trim()
104 return if input.length == 0
105 key = notation.parseSequence(input)
106 try
107 if name.startsWith('mode_') and key.length > 1
108 throw {id: 'single_keystrokes_only'}
109 normalizedKey = utils.normalizedKey(key)
110 catch {id, context, subject}
111 if /^\s$/.test(subject) then id = 'invalid_whitespace'
112 errorText = _("error_#{ id }", context ? subject, subject) + '\n'
113 continue
114 conflicts = getConflicts(commands, normalizedKey)
115 if conflicts.length == 0 or overwriteCmd(document, conflicts, key)
116 cmd.keys(cmd.keys().concat([key]))
117 for div in document.getElementsByClassName('VimFxKeySequence')
118 if div.getAttribute('data-command') == cmd.name
119 div.insertAdjacentHTML('beforeend', hint(cmd, key))
120 break
121 return
122
123 getConflicts = (commands, value) ->
124 conflicts = []
125 for command in commands
126 conflictingKeys = []
127 for key in command.keys()
128 normalizedKey = utils.normalizedKey(key)
129 shortest = Math.min(value.length, normalizedKey.length)
130 if value[...shortest] == normalizedKey[...shortest]
131 conflictingKeys.push(key)
132 if conflictingKeys.length > 0
133 conflicts.push({command, conflictingKeys})
134 return conflicts
135
136 overwriteCmd = (document, conflicts, key) ->
137 title = _('help_add_shortcut_title')
138 conflictSummary = conflicts.map((conflict) ->
139 conflictingKeys = conflict.conflictingKeys
140 .map((key) -> key.join('')).join(' ')
141 return "#{ conflict.command.help() }: #{ conflictingKeys }"
142 ).join('\n')
143 text = """
144 #{ _('help_add_shortcut_text_overwrite', key.join('')) }
145
146 #{ conflictSummary }
147 """
148 if promptService.confirm(document.defaultView, title, text)
149 for { command, conflictingKeys } in conflicts
150 normalizedKeys = conflictingKeys.map((key) -> utils.normalizedKey(key))
151 command.keys(command.keys().filter((key) ->
152 return utils.normalizedKey(key) not in normalizedKeys
153 ))
154 for key in normalizedKeys
155 document.querySelector("a[data-key='#{ key }']").remove()
156 return true
157 else
158 return false
159
160 td = (text, klass = '') ->
161 """<td class="#{ klass }">#{ text }</td>"""
162
163 hint = (cmd, key) ->
164 normalizedKey = utils.escapeHTML(utils.normalizedKey(key))
165 displayKey = utils.escapeHTML(key.join(''))
166 """<a href="#" class="VimFxReset VimFxKeyLink" title="#{ _('help_remove_shortcut') }" \
167 data-command="#{ cmd.name }" data-key="#{ normalizedKey }">#{ displayKey }</a>"""
168
169 tr = (cmd) ->
170 hints = """
171 <div class="VimFxKeySequence" data-command="#{ cmd.name }">
172 #{ (hint(cmd, key) for key in cmd.keys()).join('\n') }
173 </div>
174 """
175 dot = '<span class="VimFxDot">&#8729;</span>'
176 a = """#{ cmd.help() }"""
177 add = """
178 <a href="#" data-command="#{ cmd.name }"
179 class="VimFxAddShortcutLink" title="#{ _('help_add_shortcut') }">&#8862;</a>
180 """
181
182 return """
183 <tr>
184 #{ td(hints) }
185 #{ td(add) }
186 #{ td(dot) }
187 #{ td(a) }
188 </tr>
189 """
190
191 table = (commands, modeName) ->
192 """
193 <table #{ "data-commands='#{modeName}'" }>
194 #{ (tr(cmd) for cmd in commands).join('') }
195 </table>
196 """
197
198 section = (title, commands, modeName = 'normal') ->
199 """
200 <div class="VimFxSectionTitle">#{ title }</div>
201 #{ table(commands, modeName) }
202 """
203
204 helpDialogHtml = (modeCommands) ->
205 commands = modeCommands['normal']
206 return """
207 <div id="VimFxHelpDialog">
208 <div class="VimFxHeader">
209 <div class="VimFxTitle">
210 <span class="VimFxTitleVim">Vim</span><span class="VimFxTitleFx">Fx</span>
211 <span>#{ _('help_title') }</span>
212 </div>
213 <span class="VimFxVersion">#{ _('help_version') } #{ utils.getVersion() }</span>
214 <a class="VimFxClose" id="VimFxClose" href="#">&#10006;</a>
215 <div class="VimFxClearFix"></div>
216 <p>Did you know that you can add/remove shortucts in this dialog?</p>
217 <div class="VimFxClearFix"></div>
218 <p>Click the shortcut to remove it, and click &#8862; to add new shortcut!</p>
219 </div>
220
221 <div class="VimFxBody">
222 <div class="VimFxColumn">
223 #{ section(_('help_section_urls'), commands.filter((a) -> a.group == 'urls')) }
224 #{ section(_('help_section_nav'), commands.filter((a) -> a.group == 'nav')) }
225 #{ section(_('help_section_misc'), commands.filter((a) -> a.group == 'misc')) }
226 </div>
227 <div class="VimFxColumn">
228 #{ section(_('help_section_tabs'), commands.filter((a) -> a.group == 'tabs')) }
229 #{ section(_('help_section_browse'), commands.filter((a) -> a.group == 'browse')) }
230 #{ section(_('help_section_mode_hints'), modeCommands['hints'], 'hints') }
231 #{ section(_('help_section_mode_insert'), modeCommands['insert'], 'insert') }
232 #{ section(_('help_section_mode_find'), modeCommands['find'], 'find') }
233 </div>
234 <div class="VimFxClearFix"></div>
235 </div>
236
237 <div class="VimFxFooter">
238 <p>
239 #{ _('help_found_bug') }
240 <a target="_blank" href="https://github.com/akhodakivskiy/VimFx/issues">
241 #{ _('help_report_bug') }
242 </a>
243 </p>
244 <p>
245 #{ _('help_enjoying') }
246 <a target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/vimfx/">
247 #{ _('help_feedback') }
248 </a>
249 </p>
250 </div>
251 </div>
252 """
253
254 exports.injectHelp = injectHelp
255 exports.removeHelp = removeHelp
Imprint / Impressum