@@ -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,74 @@ 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
+ # Copy these snippets to the "quarantined" ScopedPropertyStore so that they
170
+ # remain present in the list of unparsed snippets reported to the settings
171
+ # view.
172
+ @ addSnippetsInDisabledPackage (snippetSet)
173
+ for filePath, snippetsBySelector of snippetSet
174
+ @ clearSnippetsForPath (filePath)
175
+
130
176
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... ))
177
+ disabledPackageNames = atom .config .get (' core.packagesWithSnippetsDisabled' ) or []
178
+ packages = atom .packages .getLoadedPackages ().sort (pack, b) ->
179
+ if / \/ app\. asar\/ node_modules\/ / .test (pack .path ) then - 1 else 1
180
+
181
+ snippetsDirPaths = (path .join (pack .path , ' snippets' ) for pack in packages)
182
+
183
+ async .map snippetsDirPaths, @loadSnippetsDirectory .bind (this ), (error , results ) =>
184
+ zipped = ({result : result, pack : packages[key]} for key, result of results)
185
+ enabledPackages = []
186
+ for o in zipped
187
+ # Skip packages that contain no snippets.
188
+ continue if Object .keys (o .result ).length is 0
189
+ # Keep track of which snippets come from which packages so we can
190
+ # unload them selectively later. All packages get put into this map,
191
+ # even disabled packages, because we need to know which snippets to add
192
+ # if those packages are enabled again.
193
+ @snippetsByPackage .set (o .pack .name , o .result )
194
+ if disabledPackageNames .includes (o .pack .name )
195
+ # Since disabled packages' snippets won't get added to the main
196
+ # ScopedPropertyStore, we'll keep track of them in a separate
197
+ # ScopedPropertyStore so that they can still be represented in the
198
+ # settings view.
199
+ @ addSnippetsInDisabledPackage (o .result )
200
+ else
201
+ enabledPackages .push (o .result )
202
+
203
+ callback (_ .extend ({}, enabledPackages... ))
136
204
137
205
doneLoading : ->
138
206
@loaded = true
@@ -174,7 +242,7 @@ module.exports =
174
242
atom .notifications .addError (" Failed to load snippets from '#{ filePath} '" , {detail : error .message , dismissable : true })
175
243
callback (object)
176
244
177
- add : (filePath , snippetsBySelector ) ->
245
+ add : (filePath , snippetsBySelector , isDisabled = false ) ->
178
246
for selector, snippetsByName of snippetsBySelector
179
247
unparsedSnippetsByPrefix = {}
180
248
for name, attributes of snippetsByName
@@ -186,9 +254,13 @@ module.exports =
186
254
else if not body?
187
255
unparsedSnippetsByPrefix[prefix] = null
188
256
189
- @ storeUnparsedSnippets (unparsedSnippetsByPrefix, filePath, selector)
257
+ @ storeUnparsedSnippets (unparsedSnippetsByPrefix, filePath, selector, isDisabled )
190
258
return
191
259
260
+ addSnippetsInDisabledPackage : (bundle ) ->
261
+ for filePath, snippetsBySelector of bundle
262
+ @ add (filePath, snippetsBySelector, true )
263
+
192
264
getScopeChain : (object ) ->
193
265
scopesArray = object ? .getScopesArray ? ()
194
266
scopesArray ?= object
@@ -198,10 +270,16 @@ module.exports =
198
270
scope
199
271
.join (' ' )
200
272
201
- storeUnparsedSnippets : (value , path , selector ) ->
273
+ storeUnparsedSnippets : (value , path , selector , isDisabled = false ) ->
274
+ # The `isDisabled` flag determines which scoped property store we'll use.
275
+ # Active snippets get put into one and inactive snippets get put into
276
+ # another. Only the first one gets consulted when we look up a snippet
277
+ # prefix for expansion, but both stores have their contents exported when
278
+ # the settings view asks for all available snippets.
202
279
unparsedSnippets = {}
203
280
unparsedSnippets[selector] = {" snippets" : value}
204
- @scopedPropertyStore .addProperties (path, unparsedSnippets, priority : @ priorityForSource (path))
281
+ store = if isDisabled then @disabledSnippetsScopedPropertyStore else @scopedPropertyStore
282
+ store .addProperties (path, unparsedSnippets, priority : @ priorityForSource (path))
205
283
206
284
clearSnippetsForPath : (path ) ->
207
285
for scopeSelector of @scopedPropertyStore .propertiesForSource (path)
@@ -415,13 +493,28 @@ module.exports =
415
493
new SnippetExpansion (snippet, editor, cursor, this )
416
494
417
495
getUnparsedSnippets : ->
418
- _ .deepClone (@scopedPropertyStore .propertySets )
496
+ results = []
497
+ iterate = (sets ) ->
498
+ for item in sets
499
+ newItem = _ .deepClone (item)
500
+ # The atom-slick library has already parsed the `selector` property, so
501
+ # it's an AST here instead of a string. The object has a `toString`
502
+ # method that turns it back into a string. That custom behavior won't
503
+ # be preserved in the deep clone of the object, so we have to handle it
504
+ # separately.
505
+ newItem .selectorString = item .selector .toString ()
506
+ results .push (newItem)
507
+
508
+ iterate (@scopedPropertyStore .propertySets )
509
+ iterate (@disabledSnippetsScopedPropertyStore .propertySets )
510
+ results
419
511
420
512
provideSnippets : ->
421
513
bundledSnippetsLoaded : => @loaded
422
514
insertSnippet : @insert .bind (this )
423
515
snippetsForScopes : @parsedSnippetsForScopes .bind (this )
424
516
getUnparsedSnippets : @getUnparsedSnippets .bind (this )
517
+ getUserSnippetsPath : @getUserSnippetsPath .bind (this )
425
518
426
519
onUndoOrRedo : (editor , isUndo ) ->
427
520
activeExpansions = @ getExpansions (editor)
0 commit comments