]> git.gir.st - VimFx.git/blob - extension/lib/options.coffee
Improve links to the documentation index
[VimFx.git] / extension / lib / options.coffee
1 ###
2 # Copyright Simon Lydell 2015, 2016.
3 #
4 # This file is part of VimFx.
5 #
6 # VimFx is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # VimFx is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
18 ###
19
20 # This file constructs VimFx’s options UI in the Add-ons Manager.
21
22 defaults = require('./defaults')
23 prefs = require('./prefs')
24 prefsBulk = require('./prefs-bulk')
25 translate = require('./translate')
26 utils = require('./utils')
27
28 TYPE_MAP = {
29 string: 'string'
30 number: 'integer'
31 boolean: 'bool'
32 }
33
34 observe = (options) ->
35 observer = new Observer(options)
36 utils.observe('addon-options-displayed', observer)
37 utils.observe('addon-options-hidden', observer)
38 module.onShutdown(->
39 observer.destroy()
40 )
41
42 # Generalized observer.
43 class BaseObserver
44 constructor: (@options) ->
45 @document = null
46 @container = null
47 @listeners = []
48
49 useCapture: true
50
51 listen: (element, event, action) ->
52 element.addEventListener(event, action, @useCapture)
53 @listeners.push([element, event, action, @useCapture])
54
55 unlisten: ->
56 for [element, event, action, useCapture] in @listeners
57 element.removeEventListener(event, action, useCapture)
58 @listeners.length = 0
59
60 type: (value) -> TYPE_MAP[typeof value]
61
62 injectSettings: ->
63
64 appendSetting: (attributes) ->
65 setting = @document.createElement('setting')
66 utils.setAttributes(setting, attributes)
67 @container.appendChild(setting)
68 return setting
69
70 observe: (@document, topic, addonId) ->
71 return unless addonId == @options.id
72 switch topic
73 when 'addon-options-displayed'
74 @init()
75 when 'addon-options-hidden'
76 @destroy()
77
78 init: ->
79 @container = @document.getElementById('detail-rows')
80 @injectSettings()
81
82 destroy: ->
83 @unlisten()
84
85 # VimFx specific observer.
86 class Observer extends BaseObserver
87 constructor: (@vimfx) ->
88 super({id: @vimfx.id})
89
90 injectSettings: ->
91 @injectHeader()
92 @injectOptions()
93 @injectShortcuts()
94 @setupKeybindings()
95 @setupValidation()
96
97 if @vimfx.goToCommand
98 utils.nextTick(@document.ownerGlobal, =>
99 {pref} = @vimfx.goToCommand
100 setting = @container.querySelector("setting[pref='#{pref}']")
101 setting.scrollIntoView()
102 setting.input.select()
103 @vimfx.goToCommand = null
104 )
105
106 injectHeader: ->
107 setting = @appendSetting({
108 type: 'control'
109 title: translate('prefs.instructions.title')
110 desc: translate(
111 'prefs.instructions.desc',
112 @vimfx.options['options.key.quote'],
113 @vimfx.options['options.key.insert_default'],
114 @vimfx.options['options.key.reset_default'],
115 '<c-z>'
116 )
117 'first-row': 'true'
118 })
119
120 href = "#{@vimfx.info.homepageURL}/tree/master/documentation#contents"
121 docsLink = @document.createElement('label')
122 utils.setAttributes(docsLink, {
123 value: translate('prefs.documentation')
124 href
125 crop: 'end'
126 class: 'text-link'
127 })
128 docsLink.style.MozBoxFlex = '1'
129 setting.appendChild(docsLink)
130
131 for key, fn of BUTTONS
132 button = @document.createElement('button')
133 button.setAttribute('label', translate("prefs.#{key}.label"))
134 button.onclick = runWithVim.bind(null, @vimfx, fn)
135 setting.appendChild(button)
136
137 return
138
139 injectOptions: ->
140 for key, value of defaults.options
141 setting = @appendSetting({
142 pref: "#{defaults.BRANCH}#{key}"
143 type: @type(value)
144 title: translate("pref.#{key}.title")
145 desc: translate("pref.#{key}.desc")
146 })
147 return
148
149 injectShortcuts: ->
150 for mode in @vimfx.getGroupedCommands()
151 @appendSetting({
152 type: 'control'
153 title: mode.name
154 'first-row': 'true'
155 })
156
157 for category in mode.categories
158 if category.name
159 @appendSetting({
160 type: 'control'
161 title: category.name
162 'first-row': 'true'
163 })
164
165 for {command} in category.commands
166 @appendSetting({
167 pref: command.pref
168 type: 'string'
169 title: command.description
170 desc: @generateErrorMessage(command.pref)
171 class: 'is-shortcut'
172 })
173
174 return
175
176 generateErrorMessage: (pref) ->
177 commandErrors = @vimfx.errors[pref] ? []
178 return commandErrors.map(({id, context, subject}) ->
179 return translate("error.#{id}", context ? subject, subject)
180 ).join('\n')
181
182 setupKeybindings: ->
183 # Note that `setting = event.originalTarget` does _not_ return the correct
184 # element in these listeners!
185 quote = false
186 @listen(@container, 'keydown', (event) =>
187 setting = event.target
188 isString = (setting.type == 'string')
189
190 {input, pref} = setting
191 keyString = @vimfx.stringifyKeyEvent(event)
192
193 # Some shortcuts only make sense for string settings. We still match
194 # those shortcuts and suppress the default behavior for _all_ types of
195 # settings for consistency. For example, pressing <c-d> in a number input
196 # (which looks like a text input) would otherwise bookmark the page, and
197 # <c-q> would close the window!
198 switch
199 when not keyString
200 return
201 when quote
202 break unless isString
203 utils.insertText(input, keyString)
204 quote = false
205 when keyString == @vimfx.options['options.key.quote']
206 break unless isString
207 quote = true
208 # Override `<force>` commands (such as `<escape>` and `<tab>`).
209 return unless vim = @vimfx.getCurrentVim(utils.getCurrentWindow())
210 @vimfx.modes.normal.commands.quote.run({vim, count: 1})
211 when keyString == @vimfx.options['options.key.insert_default']
212 break unless isString
213 utils.insertText(input, prefs.root.default.get(pref))
214 when keyString == @vimfx.options['options.key.reset_default']
215 prefs.root.set(pref, null)
216 else
217 return
218
219 event.preventDefault()
220 setting.valueToPreference()
221 @refreshShortcutErrors()
222 )
223 @listen(@container, 'blur', -> quote = false)
224
225 setupValidation: ->
226 @listen(@container, 'input', (event) =>
227 setting = event.target
228 # Disable default behavior of updating the pref of the setting on each
229 # input. Do it on the 'change' event instead (see below), because all
230 # settings are validated and auto-adjusted as soon as the pref changes.
231 event.stopPropagation()
232 if setting.classList.contains('is-shortcut')
233 # However, for the shortcuts we _do_ want live validation, because they
234 # cannot be auto-adjusted. Instead an error message is shown.
235 setting.valueToPreference()
236 @refreshShortcutErrors()
237 )
238
239 @listen(@container, 'change', (event) ->
240 setting = event.target
241 unless setting.classList.contains('is-shortcut')
242 setting.valueToPreference()
243 )
244
245 refreshShortcutErrors: ->
246 for setting in @container.getElementsByClassName('is-shortcut')
247 setting.setAttribute('desc', @generateErrorMessage(setting.pref))
248 return
249
250 resetAllPrefs = (vim) ->
251 vim._modal('confirm', [translate('prefs.reset.enter')], (ok) ->
252 return unless ok
253 prefsBulk.resetAll()
254 vim.notify(translate('prefs.reset.success'))
255 )
256
257 exportAllPrefs = (vim) ->
258 exported = prefsBulk.exportAll()
259 if Object.keys(exported).length == 0
260 vim.notify(translate('prefs.export.none'))
261 else
262 utils.writeToClipboard(JSON.stringify(exported, null, 2))
263 vim.notify(translate('prefs.export.success'))
264
265 importExportedPrefs = (vim) ->
266 vim._modal('prompt', [translate('prefs.import.enter')], (input) ->
267 return if input == null or input.trim() == ''
268 result = prefsBulk.importExported(input.trim())
269 if result.errors.length == 0
270 vim.notify(translate('prefs.import.success'))
271 else
272 vim._modal('alert', [prefsBulk.createImportErrorReport(result)])
273 )
274
275 runWithVim = (vimfx, fn) ->
276 return unless vim = vimfx.getCurrentVim(utils.getCurrentWindow())
277 fn(vim)
278
279 BUTTONS = {
280 export: exportAllPrefs
281 import: importExportedPrefs
282 reset: resetAllPrefs
283 }
284
285 module.exports = {
286 observe
287 }
Imprint / Impressum