@@ -16,9 +16,19 @@ module.exports =
16
16
@loaded = false
17
17
@userSnippetsPath = null
18
18
@snippetIdCounter = 0
19
+ @snippetsByPackage = new Map
19
20
@parsedSnippetsById = new Map
20
21
@editorMarkerLayers = new WeakMap
22
+
21
23
@scopedPropertyStore = new ScopedPropertyStore
24
+ # The above ScopedPropertyStore will store the main registry of snippets.
25
+ # But we need a separate ScopedPropertyStore for the snippets that come
26
+ # from disabled packages. They're isolated so that they're not considered
27
+ # as candidates when the user expands a prefix, but we still need the data
28
+ # around so that the snippets provided by those packages can be shown in
29
+ # the settings view.
30
+ @disabledSnippetsScopedPropertyStore = new ScopedPropertyStore
31
+
22
32
@subscriptions = new CompositeDisposable
23
33
@subscriptions .add atom .workspace .addOpener (uri) =>
24
34
if uri is ' atom://.atom/snippets'
@@ -28,6 +38,9 @@ module.exports =
28
38
@watchUserSnippets (watchDisposable ) =>
29
39
@subscriptions .add (watchDisposable)
30
40
41
+ @subscriptions .add atom .config .onDidChange ' core.packagesWithSnippetsDisabled' , ({ newValue, oldValue }) =>
42
+ @ handleDisabledPackagesDidChange (newValue, oldValue)
43
+
31
44
snippets = this
32
45
33
46
@subscriptions .add atom .commands .add ' atom-text-editor' ,
@@ -120,19 +133,70 @@ module.exports =
120
133
else
121
134
callback (new Disposable -> )
122
135
136
+ # Called when a user's snippets file is changed, deleted, or moved so that we
137
+ # can immediately re-process the snippets it contains.
123
138
handleUserSnippetsDidChange : ->
124
139
userSnippetsPath = @ getUserSnippetsPath ()
125
140
atom .config .transact =>
126
141
@ clearSnippetsForPath (userSnippetsPath)
127
142
@ loadSnippetsFile userSnippetsPath, (result ) =>
128
143
@ add (userSnippetsPath, result)
129
144
145
+ # Called when the "Enable" checkbox is checked/unchecked in the Snippets
146
+ # section of a package's settings view.
147
+ handleDisabledPackagesDidChange : (newDisabledPackages , oldDisabledPackages ) ->
148
+ packagesToAdd = []
149
+ packagesToRemove = []
150
+ oldDisabledPackages ?= []
151
+ newDisabledPackages ?= []
152
+ for p in oldDisabledPackages
153
+ packagesToAdd .push (p) unless newDisabledPackages .includes (p)
154
+
155
+ for p in newDisabledPackages
156
+ packagesToRemove .push (p) unless oldDisabledPackages .includes (p)
157
+
158
+ atom .config .transact =>
159
+ @ removeSnippetsForPackage (p) for p in packagesToRemove
160
+ @ addSnippetsForPackage (p) for p in packagesToAdd
161
+
162
+ addSnippetsForPackage : (packageName ) ->
163
+ snippetSet = @snippetsByPackage .get (packageName)
164
+ for filePath, snippetsBySelector of snippetSet
165
+ @ add (filePath, snippetsBySelector)
166
+
167
+ removeSnippetsForPackage : (packageName ) ->
168
+ snippetSet = @snippetsByPackage .get (packageName)
169
+ for filePath, snippetsBySelector of snippetSet
170
+ @ clearSnippetsForPath (filePath)
171
+
130
172
loadPackageSnippets : (callback ) ->
131
- packages = atom .packages .getLoadedPackages ()
132
- snippetsDirPaths = (path .join (pack .path , ' snippets' ) for pack in packages).sort (a, b) ->
133
- if / \/ app\. asar\/ node_modules\/ / .test (a) then - 1 else 1
134
- async .map snippetsDirPaths, @loadSnippetsDirectory .bind (this ), (error , results ) ->
135
- callback (_ .extend ({}, results... ))
173
+ disabledPackageNames = atom .config .get (' core.packagesWithSnippetsDisabled' ) || []
174
+ packages = atom .packages .getLoadedPackages ().sort (pack, b) ->
175
+ if / \/ app\. asar\/ node_modules\/ / .test (pack .path ) then - 1 else 1
176
+
177
+ snippetsDirPaths = (path .join (pack .path , ' snippets' ) for pack in packages)
178
+
179
+ async .map snippetsDirPaths, @loadSnippetsDirectory .bind (this ), (error , results ) =>
180
+ zipped = ({result : result, pack : packages[key]} for key, result of results)
181
+ enabledPackages = []
182
+ for o in zipped
183
+ # Skip packages that contain no snippets.
184
+ continue if Object .keys (o .result ).length is 0
185
+ # Keep track of which snippets come from which packages so we can
186
+ # unload them selectively later. All packages get put into this map,
187
+ # even disabled packages, because we need to know which snippets to add
188
+ # if those packages are enabled again.
189
+ @snippetsByPackage .set (o .pack .name , o .result )
190
+ if disabledPackageNames .includes (o .pack .name )
191
+ # Since disabled packages' snippets won't get added to the main
192
+ # ScopedPropertyStore, we'll keep track of them in a separate
193
+ # ScopedPropertyStore so that they can still be represented in the
194
+ # settings view.
195
+ @ addSnippetsInDisabledPackage (o .result )
196
+ else
197
+ enabledPackages .push (o .result )
198
+
199
+ callback (_ .extend ({}, enabledPackages... ))
136
200
137
201
doneLoading : ->
138
202
@loaded = true
@@ -174,7 +238,7 @@ module.exports =
174
238
atom .notifications .addError (" Failed to load snippets from '#{ filePath} '" , {detail : error .message , dismissable : true })
175
239
callback (object)
176
240
177
- add : (filePath , snippetsBySelector ) ->
241
+ add : (filePath , snippetsBySelector , isDisabled = false ) ->
178
242
for selector, snippetsByName of snippetsBySelector
179
243
unparsedSnippetsByPrefix = {}
180
244
for name, attributes of snippetsByName
@@ -186,9 +250,13 @@ module.exports =
186
250
else if not body?
187
251
unparsedSnippetsByPrefix[prefix] = null
188
252
189
- @ storeUnparsedSnippets (unparsedSnippetsByPrefix, filePath, selector)
253
+ @ storeUnparsedSnippets (unparsedSnippetsByPrefix, filePath, selector, isDisabled )
190
254
return
191
255
256
+ addSnippetsInDisabledPackage : (bundle ) ->
257
+ for filePath, snippetsBySelector of bundle
258
+ @ add (filePath, snippetsBySelector, true )
259
+
192
260
getScopeChain : (object ) ->
193
261
scopesArray = object ? .getScopesArray ? ()
194
262
scopesArray ?= object
@@ -198,10 +266,16 @@ module.exports =
198
266
scope
199
267
.join (' ' )
200
268
201
- storeUnparsedSnippets : (value , path , selector ) ->
269
+ storeUnparsedSnippets : (value , path , selector , isDisabled = false ) ->
270
+ # The `isDisabled` flag determines which scoped property store we'll use.
271
+ # Active snippets get put into one and inactive snippets get put into
272
+ # another. Only the first one gets consulted when we look up a snippet
273
+ # prefix for expansion, but both stores have their contents exported when
274
+ # the settings view asks for all available snippets.
202
275
unparsedSnippets = {}
203
276
unparsedSnippets[selector] = {" snippets" : value}
204
- @scopedPropertyStore .addProperties (path, unparsedSnippets, priority : @ priorityForSource (path))
277
+ store = if isDisabled then @disabledSnippetsScopedPropertyStore else @scopedPropertyStore
278
+ store .addProperties (path, unparsedSnippets, priority : @ priorityForSource (path))
205
279
206
280
clearSnippetsForPath : (path ) ->
207
281
for scopeSelector of @scopedPropertyStore .propertiesForSource (path)
@@ -415,7 +489,21 @@ module.exports =
415
489
new SnippetExpansion (snippet, editor, cursor, this )
416
490
417
491
getUnparsedSnippets : ->
418
- _ .deepClone (@scopedPropertyStore .propertySets )
492
+ results = []
493
+ iterate = (sets ) ->
494
+ for item in sets
495
+ newItem = _ .deepClone (item)
496
+ # The atom-slick library has already parsed the `selector` property, so
497
+ # it's an AST here instead of a string. The object has a `toString`
498
+ # method that turns it back into a string. That custom behavior won't
499
+ # be preserved in the deep clone of the object, so we have to handle it
500
+ # separately.
501
+ newItem .selectorString = item .selector .toString ()
502
+ results .push (newItem)
503
+
504
+ iterate (@scopedPropertyStore .propertySets )
505
+ iterate (@disabledSnippetsScopedPropertyStore .propertySets )
506
+ results
419
507
420
508
provideSnippets : ->
421
509
bundledSnippetsLoaded : => @loaded
0 commit comments