]> git.gir.st - VimFx.git/blob - extension/lib/api.coffee
Use `vim.markPageInteraction()` instead of direct mutation
[VimFx.git] / extension / lib / api.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 defines VimFx’s config file API.
21
22 defaults = require('./defaults')
23 prefs = require('./prefs')
24 utils = require('./utils')
25 Vim = require('./vim')
26
27 counter = new utils.Counter({start: 10000, step: 100})
28
29 createConfigAPI = (vimfx, {allowDeprecated = true} = {}) -> {
30 get: (inputPref) ->
31 pref = alias(inputPref, allowDeprecated)
32 if pref != inputPref
33 try return prefs.get(inputPref)
34 return switch
35 when pref of defaults.parsed_options
36 vimfx.options[pref]
37 when pref of defaults.all_prefs or pref?.startsWith('custom.')
38 prefs.get(pref)
39 else
40 throw new Error("VimFx: Unknown option: #{pref}")
41
42 getDefault: (inputPref) ->
43 pref = alias(inputPref, allowDeprecated)
44 if pref != inputPref
45 try return prefs.default.get(inputPref)
46 return switch
47 when pref of defaults.parsed_options or pref?.startsWith('custom.')
48 throw new Error("VimFx: No default for option: #{pref}")
49 when pref of defaults.all_prefs
50 defaults.all_prefs[pref]
51 else
52 throw new Error("VimFx: Unknown option: #{pref}")
53
54 set: (inputPref, value) ->
55 pref = alias(inputPref, allowDeprecated)
56 switch
57 when pref of defaults.parsed_options
58 previousValue = vimfx.options[pref]
59 vimfx.options[pref] = value
60 onShutdown(vimfx, -> vimfx.options[pref] = previousValue)
61 when pref of defaults.all_prefs or pref?.startsWith('custom.')
62 previousValue = if prefs.has(pref) then prefs.get(pref) else null
63 prefs.set(pref, value)
64 onShutdown(vimfx, -> prefs.set(pref, previousValue))
65 else
66 throw new Error("VimFx: Unknown option: #{pref}")
67
68 addCommand: ({name, description, mode, category, order} = {}, fn) ->
69 mode ?= 'normal'
70 category ?= if mode == 'normal' then 'misc' else ''
71 order ?= counter.tick()
72
73 unless typeof name == 'string'
74 throw new Error(
75 "VimFx: A command name as a string is required. Got: #{name}"
76 )
77 unless /^[a-z_]+$/.test(name)
78 throw new Error(
79 "VimFx: Command names should only consist of a-z (lowercase) and
80 underscores. Got: #{name}"
81 )
82 unless typeof description == 'string' and description != ''
83 throw new Error(
84 "VimFx: Commands must have a non-empty description. Got: #{description}"
85 )
86 unless utils.has(vimfx.modes, mode)
87 modes = Object.keys(vimfx.modes).join(', ')
88 throw new Error(
89 "VimFx: Unknown mode. Available modes are: #{modes}. Got: #{mode}"
90 )
91 unless utils.has(vimfx.options.categories, category)
92 categories = Object.keys(vimfx.options.categories).join(', ')
93 throw new Error(
94 "VimFx: Unknown category. Available categories are: #{categories}.
95 Got: #{category}"
96 )
97 unless typeof order == 'number'
98 throw new Error("VimFx: Command order must be a number. Got: #{order}")
99 unless typeof fn == 'function'
100 throw new Error("VimFx: Commands need a function to run. Got: #{fn}")
101
102 pref = "#{defaults.BRANCH}custom.mode.#{mode}.#{name}"
103 prefs.root.default.set(pref, '')
104 vimfx.modes[mode].commands[name] = {
105 pref, category, order, run: fn, description
106 }
107 onShutdown(vimfx, -> delete vimfx.modes[mode].commands[name])
108
109 addOptionOverrides: (rules...) ->
110 validateRules(rules, (override) ->
111 unless Object::toString.call(override) == '[object Object]'
112 return 'an object'
113 return null
114 )
115
116 unless vimfx.optionOverrides
117 vimfx.optionOverrides = []
118 vimfx.options = new Proxy(vimfx.options, {
119 get: (options, pref) ->
120 location = utils.getCurrentLocation()
121 return options[pref] unless location
122 overrides = getOverrides(vimfx.optionOverrides ? [], location)
123 return overrides?[pref] ? options[pref]
124 })
125 onShutdown(vimfx, -> vimfx.optionOverrides = null)
126
127 vimfx.optionOverrides.push(rules...)
128
129 addKeyOverrides: (rules...) ->
130 validateRules(rules, (override) ->
131 unless Array.isArray(override) and
132 override.every((item) -> typeof item == 'string')
133 return 'an array of strings'
134 return null
135 )
136
137 unless vimfx.keyOverrides
138 vimfx.keyOverrides = []
139 vimfx.options.keyValidator = (keyStr, mode) ->
140 return true unless mode == 'normal'
141 location = utils.getCurrentLocation()
142 return true unless location
143 overrides = getOverrides(vimfx.keyOverrides ? [], location)
144 return keyStr not in (overrides ? [])
145 onShutdown(vimfx, -> vimfx.keyOverrides = null)
146
147 vimfx.keyOverrides.push(rules...)
148
149 send: (vim, message, data = null, callback = null) ->
150 unless vim instanceof Vim
151 throw new Error(
152 "VimFx: The first argument must be a vim object. Got: #{vim}"
153 )
154 unless typeof message == 'string'
155 throw new Error(
156 "VimFx: The second argument must be a message string. Got: #{message}"
157 )
158 if typeof data == 'function'
159 throw new Error(
160 "VimFx: The third argument must not be a function. Got: #{data}"
161 )
162
163 unless typeof callback == 'function' or callback == null
164 throw Error(
165 "VimFx: If provided, `callback` must be a function. Got: #{callback}"
166 )
167 vim._send(message, data, callback, {prefix: 'config:'})
168
169 on: (event, listener) ->
170 validateEventListener(event, listener)
171 vimfx.on(event, listener)
172 onShutdown(vimfx, -> vimfx.off(event, listener))
173
174 off: (event, listener) ->
175 validateEventListener(event, listener)
176 vimfx.off(event, listener)
177
178 modes: vimfx.modes
179 }
180
181 # Don’t crash the users’s entire config file on startup if they happen to try to
182 # set a renamed pref (only warn), but do throw an error if they reload the
183 # config file; then they could update while editing the file anyway.
184 alias = (pref, allowDeprecated) ->
185 if pref of renamedPrefs
186 newPref = renamedPrefs[pref]
187 message = "VimFx: `#{pref}` has been renamed to `#{newPref}`."
188 if allowDeprecated
189 console.warn(message)
190 return newPref
191 else
192 throw new Error(message)
193 else
194 return pref
195
196 renamedPrefs = {
197 'hint_chars': 'hints.chars'
198 'hints_sleep': 'hints.sleep'
199 'hints_timeout': 'hints.matched_timeout'
200 'hints_peek_through': 'hints.peek_through'
201 'hints_toggle_in_tab': 'hints.toggle_in_tab'
202 'hints_toggle_in_background': 'hints.toggle_in_background'
203 'mode.hints.delete_hint_char': 'mode.hints.delete_char'
204 }
205
206 getOverrides = (rules, args...) ->
207 for [matcher, override] in rules
208 return override if matcher(args...)
209 return null
210
211 validateRules = (rules, overrideValidator) ->
212 for rule in rules
213 unless Array.isArray(rule)
214 throw new Error(
215 "VimFx: An override rule must be an array. Got: #{rule}"
216 )
217 unless rule.length == 2
218 throw new Error(
219 "VimFx: An override rule array must be of length 2. Got: #{rule.length}"
220 )
221 [matcher, override] = rule
222 unless typeof matcher == 'function'
223 throw new Error(
224 "VimFx: The first item of an override rule array must be a function.
225 Got: #{matcher}"
226 )
227 overrideValidationMessage = overrideValidator(override)
228 if overrideValidationMessage
229 throw new Error(
230 "VimFx: The second item of an override rule array must be
231 #{overrideValidationMessage}. Got: #{override}"
232 )
233 return
234
235 validateEventListener = (event, listener) ->
236 unless typeof event == 'string'
237 throw new Error(
238 "VimFx: The first argument must be a string. Got: #{event}"
239 )
240 unless typeof listener == 'function'
241 throw new Error(
242 "VimFx: The second argument must be a function. Got: #{listener}"
243 )
244 return
245
246 onShutdown = (vimfx, handler) ->
247 fn = ->
248 handler()
249 vimfx.off('shutdown', fn)
250 vimfx.on('shutdown', fn)
251
252 module.exports = createConfigAPI
Imprint / Impressum