]> git.gir.st - VimFx.git/blob - extension/lib/options.coffee
Make `<c-q>` in the options able to insert `<escape>`
[VimFx.git] / extension / lib / options.coffee
1 ###
2 # Copyright Simon Lydell 2015.
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 defaults = require('./defaults')
21 translate = require('./l10n')
22 prefs = require('./prefs')
23 utils = require('./utils')
24
25
26 TYPE_MAP =
27 string: 'string'
28 number: 'integer'
29 boolean: 'bool'
30
31 observe = (options) ->
32 observer = new Observer(options)
33 utils.observe('addon-options-displayed', observer)
34 utils.observe('addon-options-hidden', observer)
35 module.onShutdown(->
36 observer.destroy()
37 )
38
39 # Generalized observer.
40 class BaseObserver
41 constructor: (@options) ->
42 @document = null
43 @container = null
44 @listeners = []
45
46 useCapture: true
47
48 listen: (element, event, action) ->
49 element.addEventListener(event, action, @useCapture)
50 @listeners.push([element, event, action, @useCapture])
51
52 unlisten: ->
53 for [element, event, action, useCapture] in @listeners
54 element.removeEventListener(event, action, useCapture)
55 @listeners.length = 0
56
57 type: (value) -> TYPE_MAP[typeof value]
58
59 injectSettings: ->
60
61 appendSetting: (attributes) ->
62 setting = @document.createElement('setting')
63 utils.setAttributes(setting, attributes)
64 @container.appendChild(setting)
65 return setting
66
67 observe: (@document, topic, addonId) ->
68 return unless addonId == @options.id
69 switch topic
70 when 'addon-options-displayed'
71 @init()
72 when 'addon-options-hidden'
73 @destroy()
74
75 init: ->
76 @container = @document.getElementById('detail-rows')
77 @injectSettings()
78
79 destroy: ->
80 @unlisten()
81
82 # VimFx specific observer.
83 class Observer extends BaseObserver
84 constructor: (@vimfx) ->
85 super({id: @vimfx.id})
86
87 injectSettings: ->
88 @injectInstructions()
89 @injectOptions()
90 @injectShortcuts()
91 @setupKeybindings()
92 @setupValidation()
93
94 injectInstructions: ->
95 setting = @appendSetting({
96 type: 'control'
97 title: translate('prefs.instructions.title')
98 desc: translate('prefs.instructions.desc',
99 @vimfx.options['options.key.quote'],
100 @vimfx.options['options.key.insert_default'],
101 @vimfx.options['options.key.reset_default'],
102 '<c-z>')
103 'first-row': 'true'
104 })
105 href = "#{ @vimfx.info.homepageURL }/tree/master/documentation"
106 docsLink = @document.createElement('label')
107 utils.setAttributes(docsLink, {
108 value: translate('prefs.documentation')
109 href
110 crop: 'end'
111 class: 'text-link'
112 })
113 setting.appendChild(docsLink)
114
115 injectOptions: ->
116 for key, value of defaults.options
117 setting = @appendSetting({
118 pref: "#{ defaults.BRANCH }#{ key }"
119 type: @type(value)
120 title: translate("pref.#{ key }.title")
121 desc: translate("pref.#{ key }.desc")
122 })
123 return
124
125 injectShortcuts: ->
126 for mode in @vimfx.getGroupedCommands()
127 @appendSetting({
128 type: 'control'
129 title: mode.name
130 'first-row': 'true'
131 })
132
133 for category in mode.categories
134 if category.name
135 @appendSetting({
136 type: 'control'
137 title: category.name
138 'first-row': 'true'
139 })
140
141 for { command } in category.commands
142 @appendSetting({
143 pref: command.pref
144 type: 'string'
145 title: command.description()
146 desc: @generateErrorMessage(command.pref)
147 class: 'is-shortcut'
148 })
149
150 return
151
152 generateErrorMessage: (pref) ->
153 commandErrors = @vimfx.errors[pref] ? []
154 return commandErrors.map(({ id, context, subject }) ->
155 return translate("error.#{ id }", context ? subject, subject)
156 ).join('\n')
157
158 setupKeybindings: ->
159 quote = false
160 @listen(@container, 'keydown', (event) =>
161 setting = event.target
162 isString = (setting.type == 'string')
163
164 { input, pref } = setting
165 keyString = @vimfx.stringifyKeyEvent(event)
166
167 # Some shortcuts only make sense for string settings. We still match
168 # those shortcuts and suppress the default behavior for _all_ types of
169 # settings for consistency. For example, pressing <c-d> in a number input
170 # (which looks like a text input) would otherwise bookmark the page, and
171 # <c-q> would close the window!
172 switch
173 when not keyString
174 return
175 when quote
176 break unless isString
177 utils.insertText(input, keyString)
178 quote = false
179 when keyString == @vimfx.options['options.key.quote']
180 break unless isString
181 quote = true
182 # Override `<force>` commands (such as `<escape>` and `<tab>`).
183 vim = @vimfx.currentVim
184 @vimfx.modes.normal.commands.quote.run({vim, count: 1})
185 when keyString == @vimfx.options['options.key.insert_default']
186 break unless isString
187 utils.insertText(input, prefs.root.default.get(pref))
188 when keyString == @vimfx.options['options.key.reset_default']
189 prefs.root.set(pref, null)
190 else
191 return
192
193 event.preventDefault()
194 setting.valueToPreference()
195 @refreshShortcutErrors()
196 )
197 @listen(@container, 'blur', -> quote = false)
198
199 setupValidation: ->
200 @listen(@container, 'input', (event) =>
201 setting = event.target
202 # Disable default behavior of updating the pref of the setting on each
203 # input. Do it on the 'change' event instead (see below), because all
204 # settings are validated and auto-adjusted as soon as the pref changes.
205 event.stopPropagation()
206 if setting.classList.contains('is-shortcut')
207 # However, for the shortcuts we _do_ want live validation, because they
208 # cannot be auto-adjusted. Instead an error message is shown.
209 setting.valueToPreference()
210 @refreshShortcutErrors()
211 )
212 @listen(@container, 'change', (event) ->
213 setting = event.target
214 unless setting.classList.contains('is-shortcut')
215 setting.valueToPreference()
216 )
217
218
219 refreshShortcutErrors: ->
220 for setting in @container.getElementsByClassName('is-shortcut')
221 setting.setAttribute('desc', @generateErrorMessage(setting.pref))
222 return
223
224 module.exports = {
225 observe
226 }
Imprint / Impressum