@@ -16,9 +16,19 @@ module.exports =
1616 @loaded = false
1717 @userSnippetsPath = null
1818 @snippetIdCounter = 0
19+ @snippetsByPackage = new Map
1920 @parsedSnippetsById = new Map
2021 @editorMarkerLayers = new WeakMap
22+
2123 @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+
2232 @subscriptions = new CompositeDisposable
2333 @subscriptions .add atom .workspace .addOpener (uri) =>
2434 if uri is ' atom://.atom/snippets'
@@ -28,6 +38,9 @@ module.exports =
2838 @watchUserSnippets (watchDisposable ) =>
2939 @subscriptions .add (watchDisposable)
3040
41+ @subscriptions .add atom .config .onDidChange ' core.packagesWithSnippetsDisabled' , ({newValue, oldValue}) =>
42+ @ handleDisabledPackagesDidChange (newValue, oldValue)
43+
3144 snippets = this
3245
3346 @subscriptions .add atom .commands .add ' atom-text-editor' ,
@@ -120,19 +133,74 @@ module.exports =
120133 else
121134 callback (new Disposable -> )
122135
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.
123138 handleUserSnippetsDidChange : ->
124139 userSnippetsPath = @ getUserSnippetsPath ()
125140 atom .config .transact =>
126141 @ clearSnippetsForPath (userSnippetsPath)
127142 @ loadSnippetsFile userSnippetsPath, (result ) =>
128143 @ add (userSnippetsPath, result)
129144
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+
130176 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... ))
136204
137205 doneLoading : ->
138206 @loaded = true
@@ -174,7 +242,7 @@ module.exports =
174242 atom .notifications .addError (" Failed to load snippets from '#{ filePath} '" , {detail : error .message , dismissable : true })
175243 callback (object)
176244
177- add : (filePath , snippetsBySelector ) ->
245+ add : (filePath , snippetsBySelector , isDisabled = false ) ->
178246 for selector, snippetsByName of snippetsBySelector
179247 unparsedSnippetsByPrefix = {}
180248 for name, attributes of snippetsByName
@@ -186,9 +254,13 @@ module.exports =
186254 else if not body?
187255 unparsedSnippetsByPrefix[prefix] = null
188256
189- @ storeUnparsedSnippets (unparsedSnippetsByPrefix, filePath, selector)
257+ @ storeUnparsedSnippets (unparsedSnippetsByPrefix, filePath, selector, isDisabled )
190258 return
191259
260+ addSnippetsInDisabledPackage : (bundle ) ->
261+ for filePath, snippetsBySelector of bundle
262+ @ add (filePath, snippetsBySelector, true )
263+
192264 getScopeChain : (object ) ->
193265 scopesArray = object ? .getScopesArray ? ()
194266 scopesArray ?= object
@@ -198,10 +270,16 @@ module.exports =
198270 scope
199271 .join (' ' )
200272
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.
202279 unparsedSnippets = {}
203280 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))
205283
206284 clearSnippetsForPath : (path ) ->
207285 for scopeSelector of @scopedPropertyStore .propertiesForSource (path)
@@ -415,13 +493,28 @@ module.exports =
415493 new SnippetExpansion (snippet, editor, cursor, this )
416494
417495 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
419511
420512 provideSnippets : ->
421513 bundledSnippetsLoaded : => @loaded
422514 insertSnippet : @insert .bind (this )
423515 snippetsForScopes : @parsedSnippetsForScopes .bind (this )
424516 getUnparsedSnippets : @getUnparsedSnippets .bind (this )
517+ getUserSnippetsPath : @getUserSnippetsPath .bind (this )
425518
426519 onUndoOrRedo : (editor , isUndo ) ->
427520 activeExpansions = @ getExpansions (editor)
0 commit comments