Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit 5a98f45

Browse files
Add the ability to disable a package’s snippets.
Includes on-the-fly disabling and enabling without the need to reload the window. The UI to expose this will come later; see atom/settings-view#1076.
1 parent df459af commit 5a98f45

File tree

3 files changed

+145
-10
lines changed

3 files changed

+145
-10
lines changed

lib/snippets.coffee

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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,70 @@ 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+
for filePath, snippetsBySelector of snippetSet
170+
@clearSnippetsForPath(filePath)
171+
130172
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...))
136200

137201
doneLoading: ->
138202
@loaded = true
@@ -174,7 +238,7 @@ module.exports =
174238
atom.notifications.addError("Failed to load snippets from '#{filePath}'", {detail: error.message, dismissable: true})
175239
callback(object)
176240

177-
add: (filePath, snippetsBySelector) ->
241+
add: (filePath, snippetsBySelector, isDisabled = false) ->
178242
for selector, snippetsByName of snippetsBySelector
179243
unparsedSnippetsByPrefix = {}
180244
for name, attributes of snippetsByName
@@ -186,9 +250,13 @@ module.exports =
186250
else if not body?
187251
unparsedSnippetsByPrefix[prefix] = null
188252

189-
@storeUnparsedSnippets(unparsedSnippetsByPrefix, filePath, selector)
253+
@storeUnparsedSnippets(unparsedSnippetsByPrefix, filePath, selector, isDisabled)
190254
return
191255

256+
addSnippetsInDisabledPackage: (bundle) ->
257+
for filePath, snippetsBySelector of bundle
258+
@add(filePath, snippetsBySelector, true)
259+
192260
getScopeChain: (object) ->
193261
scopesArray = object?.getScopesArray?()
194262
scopesArray ?= object
@@ -198,10 +266,16 @@ module.exports =
198266
scope
199267
.join(' ')
200268

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.
202275
unparsedSnippets = {}
203276
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))
205279

206280
clearSnippetsForPath: (path) ->
207281
for scopeSelector of @scopedPropertyStore.propertiesForSource(path)
@@ -415,7 +489,21 @@ module.exports =
415489
new SnippetExpansion(snippet, editor, cursor, this)
416490

417491
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
419507

420508
provideSnippets: ->
421509
bundledSnippetsLoaded: => @loaded

spec/fixtures/package-with-snippets/snippets/test.cson

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
leftLabelHTML: "<span style=\"color:red\">Label</span>"
1818
rightLabelHTML: "<span style=\"color:white\">Label</span>"
1919

20+
".package-with-snippets-unique-scope":
21+
"Test Snippet":
22+
prefix: "test"
23+
body: "testing 123"
24+
2025
".source.js":
2126
"Overrides a core package's snippet":
2227
prefix: "log"

spec/snippet-loading-spec.coffee

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,45 @@ describe "Snippet Loading", ->
211211
runs ->
212212
expect(console.warn).toHaveBeenCalled()
213213
expect(atom.notifications.addError).toHaveBeenCalled() if atom.notifications?
214+
215+
describe "packages-with-snippets-disabled feature", ->
216+
it "disables no snippets if the config option is empty", ->
217+
originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
218+
atom.config.set('core.packagesWithSnippetsDisabled', [])
219+
220+
activateSnippetsPackage()
221+
runs ->
222+
snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
223+
expect(Object.keys(snippets).length).toBe 1
224+
atom.config.set('core.packagesWithSnippetsDisabled', originalConfig)
225+
226+
it "never loads a package's snippets when that package is disabled in config", ->
227+
originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
228+
atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets'])
229+
230+
activateSnippetsPackage()
231+
runs ->
232+
snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
233+
expect(Object.keys(snippets).length).toBe 0
234+
atom.config.set('core.packagesWithSnippetsDisabled', originalConfig)
235+
236+
it "unloads and/or reloads snippets from a package if the config option is changed after activation", ->
237+
originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
238+
atom.config.set('core.packagesWithSnippetsDisabled', [])
239+
240+
activateSnippetsPackage()
241+
runs ->
242+
snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
243+
expect(Object.keys(snippets).length).toBe 1
244+
245+
# Disable it.
246+
atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets'])
247+
snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
248+
expect(Object.keys(snippets).length).toBe 0
249+
250+
# Re-enable it.
251+
atom.config.set('core.packagesWithSnippetsDisabled', [])
252+
snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
253+
expect(Object.keys(snippets).length).toBe 1
254+
255+
atom.config.set('core.packagesWithSnippetsDisabled', originalConfig)

0 commit comments

Comments
 (0)