This repository was archived by the owner on Dec 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 57
/
Copy pathgrammar-registry.coffee
252 lines (220 loc) · 7.98 KB
/
grammar-registry.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
_ = require 'underscore-plus'
CSON = require 'season'
{Emitter, Disposable} = require 'event-kit'
Grim = require 'grim'
Grammar = require './grammar'
NullGrammar = require './null-grammar'
# Extended: Registry containing one or more grammars.
module.exports =
class GrammarRegistry
constructor: (options={}) ->
@maxTokensPerLine = options.maxTokensPerLine ? Infinity
@maxLineLength = options.maxLineLength ? Infinity
@nullGrammar = new NullGrammar(this)
@clear()
clear: ->
@emitter = new Emitter
@grammars = []
@grammarsByScopeName = {}
@injectionGrammars = []
@grammarOverridesByPath = {}
@scopeIdCounter = -1
@idsByScope = {}
@scopesById = {}
@addGrammar(@nullGrammar)
###
Section: Event Subscription
###
# Public: Invoke the given callback when a grammar is added to the registry.
#
# * `callback` {Function} to call when a grammar is added.
# * `grammar` {Grammar} that was added.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidAddGrammar: (callback) ->
@emitter.on 'did-add-grammar', callback
# Public: Invoke the given callback when a grammar is updated due to a grammar
# it depends on being added or removed from the registry.
#
# * `callback` {Function} to call when a grammar is updated.
# * `grammar` {Grammar} that was updated.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidUpdateGrammar: (callback) ->
@emitter.on 'did-update-grammar', callback
# Public: Invoke the given callback when a grammar is removed from the registry.
#
# * `callback` {Function} to call when a grammar is removed.
# * `grammar` {Grammar} that was removed.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidRemoveGrammar: (callback) ->
@emitter.on 'did-remove-grammar', callback
###
Section: Managing Grammars
###
# Public: Get all the grammars in this registry.
#
# Returns a non-empty {Array} of {Grammar} instances.
getGrammars: ->
_.clone(@grammars)
# Public: Get a grammar with the given scope name.
#
# * `scopeName` A {String} such as `"source.js"`.
#
# Returns a {Grammar} or undefined.
grammarForScopeName: (scopeName) ->
@grammarsByScopeName[scopeName]
# Public: Add a grammar to this registry.
#
# A 'grammar-added' event is emitted after the grammar is added.
#
# * `grammar` The {Grammar} to add. This should be a value previously returned
# from {::readGrammar} or {::readGrammarSync}.
#
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# grammar.
addGrammar: (grammar) ->
@grammars.push(grammar)
@grammarsByScopeName[grammar.scopeName] = grammar
@injectionGrammars.push(grammar) if grammar.injectionSelector?
@grammarUpdated(grammar.scopeName)
@emit 'grammar-added', grammar if Grammar.includeDeprecatedAPIs
@emitter.emit 'did-add-grammar', grammar
new Disposable => @removeGrammar(grammar)
removeGrammar: (grammar) ->
_.remove(@grammars, grammar)
delete @grammarsByScopeName[grammar.scopeName]
_.remove(@injectionGrammars, grammar)
@grammarUpdated(grammar.scopeName)
@emitter.emit 'did-remove-grammar', grammar
undefined
# Public: Remove the grammar with the given scope name.
#
# * `scopeName` A {String} such as `"source.js"`.
#
# Returns the removed {Grammar} or undefined.
removeGrammarForScopeName: (scopeName) ->
grammar = @grammarForScopeName(scopeName)
@removeGrammar(grammar) if grammar?
grammar
# Public: Read a grammar synchronously but don't add it to the registry.
#
# * `grammarPath` A {String} absolute file path to a grammar file.
#
# Returns a {Grammar}.
readGrammarSync: (grammarPath) ->
grammar = CSON.readFileSync(grammarPath) ? {}
if typeof grammar.scopeName is 'string' and grammar.scopeName.length > 0
@createGrammar(grammarPath, grammar)
else
throw new Error("Grammar missing required scopeName property: #{grammarPath}")
# Public: Read a grammar asynchronously but don't add it to the registry.
#
# * `grammarPath` A {String} absolute file path to a grammar file.
# * `callback` A {Function} to call when read with the following arguments:
# * `error` An {Error}, may be null.
# * `grammar` A {Grammar} or null if an error occured.
#
# Returns undefined.
readGrammar: (grammarPath, callback) ->
CSON.readFile grammarPath, (error, grammar={}) =>
if error?
callback?(error)
else
if typeof grammar.scopeName is 'string' and grammar.scopeName.length > 0
callback?(null, @createGrammar(grammarPath, grammar))
else
callback?(new Error("Grammar missing required scopeName property: #{grammarPath}"))
undefined
# Public: Read a grammar synchronously and add it to this registry.
#
# * `grammarPath` A {String} absolute file path to a grammar file.
#
# Returns a {Grammar}.
loadGrammarSync: (grammarPath) ->
grammar = @readGrammarSync(grammarPath)
@addGrammar(grammar)
grammar
# Public: Read a grammar asynchronously and add it to the registry.
#
# * `grammarPath` A {String} absolute file path to a grammar file.
# * `callback` A {Function} to call when loaded with the following arguments:
# * `error` An {Error}, may be null.
# * `grammar` A {Grammar} or null if an error occured.
#
# Returns undefined.
loadGrammar: (grammarPath, callback) ->
@readGrammar grammarPath, (error, grammar) =>
if error?
callback?(error)
else
@addGrammar(grammar)
callback?(null, grammar)
undefined
startIdForScope: (scope) ->
unless id = @idsByScope[scope]
id = @scopeIdCounter
@scopeIdCounter -= 2
@idsByScope[scope] = id
@scopesById[id] = scope
id
endIdForScope: (scope) ->
@startIdForScope(scope) - 1
scopeForId: (id) ->
if (id % 2) is -1
@scopesById[id] # start id
else
@scopesById[id + 1] # end id
grammarUpdated: (scopeName) ->
for grammar in @grammars when grammar.scopeName isnt scopeName
if grammar.grammarUpdated(scopeName)
@emit 'grammar-updated', grammar if Grammar.includeDeprecatedAPIs
@emitter.emit 'did-update-grammar', grammar
return
createGrammar: (grammarPath, object) ->
object.maxTokensPerLine ?= @maxTokensPerLine
object.maxLineLength ?= @maxLineLength
if object.limitLineLength is false
object.maxLineLength = Infinity
grammar = new Grammar(this, object)
grammar.path = grammarPath
grammar
decodeTokens: (lineText, tags, scopeTags = [], fn) ->
offset = 0
scopeNames = scopeTags.map (tag) => @scopeForId(tag)
tokens = []
for tag, index in tags
# positive numbers indicate string content with length equaling the number
if tag >= 0
token = {
value: lineText.substring(offset, offset + tag)
scopes: scopeNames.slice()
}
token = fn(token, index) if fn?
tokens.push(token)
offset += tag
# odd negative numbers are begin scope tags
else if (tag % 2) is -1
scopeTags.push(tag)
scopeNames.push(@scopeForId(tag))
# even negative numbers are end scope tags
else
scopeTags.pop()
expectedScopeName = @scopeForId(tag + 1)
poppedScopeName = scopeNames.pop()
unless poppedScopeName is expectedScopeName
throw new Error("Expected popped scope to be #{expectedScopeName}, but it was #{poppedScopeName}")
tokens
if Grim.includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(GrammarRegistry)
GrammarRegistry::on = (eventName) ->
switch eventName
when 'grammar-added'
Grim.deprecate("Call GrammarRegistry::onDidAddGrammar instead")
when 'grammar-updated'
Grim.deprecate("Call GrammarRegistry::onDidUpdateGrammar instead")
else
Grim.deprecate("Call explicit event subscription methods instead")
EmitterMixin::on.apply(this, arguments)