<commit_before>path = require 'path'
{Emitter, Disposable, CompositeDisposable, File} = require 'atom'
_ = require 'underscore-plus'
async = require 'async'
CSON = require 'season'
fs = require 'fs-plus'
ScopedPropertyStore = require 'scoped-property-store'

Snippet = require './snippet'
SnippetExpansion = require './snippet-expansion'
EditorStore = require './editor-store'
{getPackageRoot} = require './helpers'

module.exports =
  activate: ->
    @loaded = false
    @userSnippetsPath = null
    @snippetIdCounter = 0
    @snippetsByPackage = new Map
    @parsedSnippetsById = new Map
    @editorMarkerLayers = new WeakMap

    @scopedPropertyStore = new ScopedPropertyStore
    # The above ScopedPropertyStore will store the main registry of snippets.
    # But we need a separate ScopedPropertyStore for the snippets that come
    # from disabled packages. They're isolated so that they're not considered
    # as candidates when the user expands a prefix, but we still need the data
    # around so that the snippets provided by those packages can be shown in
    # the settings view.
    @disabledSnippetsScopedPropertyStore = new ScopedPropertyStore

    @subscriptions = new CompositeDisposable
    @subscriptions.add atom.workspace.addOpener (uri) =>
      if uri is 'atom://.atom/snippets'
        atom.workspace.openTextFile(@getUserSnippetsPath())

    @loadAll()
    @watchUserSnippets (watchDisposable) =>
      @subscriptions.add(watchDisposable)

    @subscriptions.add atom.config.onDidChange 'core.packagesWithSnippetsDisabled', ({newValue, oldValue}) =>
      @handleDisabledPackagesDidChange(newValue, oldValue)

    snippets = this

    @subscriptions.add atom.commands.add 'atom-text-editor',
      'snippets:expand': (event) ->
        editor = @getModel()
        if snippets.snippetToExpandUnderCursor(editor)
          snippets.clearExpansions(editor)
          snippets.expandSnippetsUnderCursors(editor)
        else
          event.abortKeyBinding()

      'snippets:next-tab-stop': (event) ->
        editor = @getModel()
        event.abortKeyBinding() unless snippets.goToNextTabStop(editor)

      'snippets:previous-tab-stop': (event) ->
        editor = @getModel()
        event.abortKeyBinding() unless snippets.goToPreviousTabStop(editor)

      'snippets:available': (event) ->
        editor = @getModel()
        SnippetsAvailable = require './snippets-available'
        snippets.availableSnippetsView ?= new SnippetsAvailable(snippets)
        snippets.availableSnippetsView.toggle(editor)

  deactivate: ->
    @emitter?.dispose()
    @emitter = null
    @editorSnippetExpansions = null
    atom.config.transact => @subscriptions.dispose()

  getUserSnippetsPath: ->
    return @userSnippetsPath if @userSnippetsPath?

    @userSnippetsPath = CSON.resolve(path.join(atom.getConfigDirPath(), 'snippets'))
    @userSnippetsPath ?= path.join(atom.getConfigDirPath(), 'snippets.cson')
    @userSnippetsPath

  loadAll: ->
    @loadBundledSnippets (bundledSnippets) =>
      @loadPackageSnippets (packageSnippets) =>
        @loadUserSnippets (userSnippets) =>
          atom.config.transact =>
            for snippetSet in [bundledSnippets, packageSnippets, userSnippets]
              for filepath, snippetsBySelector of snippetSet
                @add(filepath, snippetsBySelector)
            return
          @doneLoading()

  loadBundledSnippets: (callback) ->
    bundledSnippetsPath = CSON.resolve(path.join(getPackageRoot(), 'lib', 'snippets'))
    @loadSnippetsFile bundledSnippetsPath, (snippets) ->
      snippetsByPath = {}
      snippetsByPath[bundledSnippetsPath] = snippets
      callback(snippetsByPath)

  loadUserSnippets: (callback) ->
    userSnippetsPath = @getUserSnippetsPath()
    fs.stat userSnippetsPath, (error, stat) =>
      if stat?.isFile()
        @loadSnippetsFile userSnippetsPath, (snippets) ->
          result = {}
          result[userSnippetsPath] = snippets
          callback(result)
      else
        callback({})

  watchUserSnippets: (callback) ->
    userSnippetsPath = @getUserSnippetsPath()
    fs.stat userSnippetsPath, (error, stat) =>
      if stat?.isFile()
        userSnippetsFileDisposable = new CompositeDisposable()
        userSnippetsFile = new File(userSnippetsPath)
        try
          userSnippetsFileDisposable.add userSnippetsFile.onDidChange => @handleUserSnippetsDidChange()
          userSnippetsFileDisposable.add userSnippetsFile.onDidDelete => @handleUserSnippetsDidChange()
          userSnippetsFileDisposable.add userSnippetsFile.onDidRename => @handleUserSnippetsDidChange()
        catch e
          message = """
            Unable to watch path: `snippets.cson`. Make sure you have permissions
            to the `~/.atom` directory and `#{userSnippetsPath}`.

            On linux there are currently problems with watch sizes. See
            [this document][watches] for more info.
            [watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path
          """
          atom.notifications.addError(message, {dismissable: true})

        callback(userSnippetsFileDisposable)
      else
        callback(new Disposable -> )

  # Called when a user's snippets file is changed, deleted, or moved so that we
  # can immediately re-process the snippets it contains.
  handleUserSnippetsDidChange: ->
    userSnippetsPath = @getUserSnippetsPath()
    atom.config.transact =>
      @clearSnippetsForPath(userSnippetsPath)
      @loadSnippetsFile userSnippetsPath, (result) =>
        @add(userSnippetsPath, result)

  # Called when the "Enable" checkbox is checked/unchecked in the Snippets
  # section of a package's settings view.
  handleDisabledPackagesDidChange: (newDisabledPackages, oldDisabledPackages) ->
    packagesToAdd = []
    packagesToRemove = []
    oldDisabledPackages ?= []
    newDisabledPackages ?= []
    for p in oldDisabledPackages
      packagesToAdd.push(p) unless newDisabledPackages.includes(p)

    for p in newDisabledPackages
      packagesToRemove.push(p) unless oldDisabledPackages.includes(p)

    atom.config.transact =>
      @removeSnippetsForPackage(p) for p in packagesToRemove
      @addSnippetsForPackage(p) for p in packagesToAdd

  addSnippetsForPackage: (packageName) ->
    snippetSet = @snippetsByPackage.get(packageName)
    for filePath, snippetsBySelector of snippetSet
      @add(filePath, snippetsBySelector)

  removeSnippetsForPackage: (packageName) ->
    snippetSet = @snippetsByPackage.get(packageName)
    for filePath, snippetsBySelector of snippetSet
      @clearSnippetsForPath(filePath)

  loadPackageSnippets: (callback) ->
    disabledPackageNames = atom.config.get('core.packagesWithSnippetsDisabled') or []
    packages = atom.packages.getLoadedPackages().sort (pack, b) ->
      if /\/app\.asar\/node_modules\//.test(pack.path) then -1 else 1

    snippetsDirPaths = (path.join(pack.path, 'snippets') for pack in packages)
<commit_msg>Fix disappearing snippets in settings view.<commit_after>path = require 'path'
{Emitter, Disposable, CompositeDisposable, File} = require 'atom'
_ = require 'underscore-plus'
async = require 'async'
CSON = require 'season'
fs = require 'fs-plus'
ScopedPropertyStore = require 'scoped-property-store'

Snippet = require './snippet'
SnippetExpansion = require './snippet-expansion'
EditorStore = require './editor-store'
{getPackageRoot} = require './helpers'

module.exports =
  activate: ->
    @loaded = false
    @userSnippetsPath = null
    @snippetIdCounter = 0
    @snippetsByPackage = new Map
    @parsedSnippetsById = new Map
    @editorMarkerLayers = new WeakMap

    @scopedPropertyStore = new ScopedPropertyStore
    # The above ScopedPropertyStore will store the main registry of snippets.
    # But we need a separate ScopedPropertyStore for the snippets that come
    # from disabled packages. They're isolated so that they're not considered
    # as candidates when the user expands a prefix, but we still need the data
    # around so that the snippets provided by those packages can be shown in
    # the settings view.
    @disabledSnippetsScopedPropertyStore = new ScopedPropertyStore

    @subscriptions = new CompositeDisposable
    @subscriptions.add atom.workspace.addOpener (uri) =>
      if uri is 'atom://.atom/snippets'
        atom.workspace.openTextFile(@getUserSnippetsPath())

    @loadAll()
    @watchUserSnippets (watchDisposable) =>
      @subscriptions.add(watchDisposable)

    @subscriptions.add atom.config.onDidChange 'core.packagesWithSnippetsDisabled', ({newValue, oldValue}) =>
      @handleDisabledPackagesDidChange(newValue, oldValue)

    snippets = this

    @subscriptions.add atom.commands.add 'atom-text-editor',
      'snippets:expand': (event) ->
        editor = @getModel()
        if snippets.snippetToExpandUnderCursor(editor)
          snippets.clearExpansions(editor)
          snippets.expandSnippetsUnderCursors(editor)
        else
          event.abortKeyBinding()

      'snippets:next-tab-stop': (event) ->
        editor = @getModel()
        event.abortKeyBinding() unless snippets.goToNextTabStop(editor)

      'snippets:previous-tab-stop': (event) ->
        editor = @getModel()
        event.abortKeyBinding() unless snippets.goToPreviousTabStop(editor)

      'snippets:available': (event) ->
        editor = @getModel()
        SnippetsAvailable = require './snippets-available'
        snippets.availableSnippetsView ?= new SnippetsAvailable(snippets)
        snippets.availableSnippetsView.toggle(editor)

  deactivate: ->
    @emitter?.dispose()
    @emitter = null
    @editorSnippetExpansions = null
    atom.config.transact => @subscriptions.dispose()

  getUserSnippetsPath: ->
    return @userSnippetsPath if @userSnippetsPath?

    @userSnippetsPath = CSON.resolve(path.join(atom.getConfigDirPath(), 'snippets'))
    @userSnippetsPath ?= path.join(atom.getConfigDirPath(), 'snippets.cson')
    @userSnippetsPath

  loadAll: ->
    @loadBundledSnippets (bundledSnippets) =>
      @loadPackageSnippets (packageSnippets) =>
        @loadUserSnippets (userSnippets) =>
          atom.config.transact =>
            for snippetSet in [bundledSnippets, packageSnippets, userSnippets]
              for filepath, snippetsBySelector of snippetSet
                @add(filepath, snippetsBySelector)
            return
          @doneLoading()

  loadBundledSnippets: (callback) ->
    bundledSnippetsPath = CSON.resolve(path.join(getPackageRoot(), 'lib', 'snippets'))
    @loadSnippetsFile bundledSnippetsPath, (snippets) ->
      snippetsByPath = {}
      snippetsByPath[bundledSnippetsPath] = snippets
      callback(snippetsByPath)

  loadUserSnippets: (callback) ->
    userSnippetsPath = @getUserSnippetsPath()
    fs.stat userSnippetsPath, (error, stat) =>
      if stat?.isFile()
        @loadSnippetsFile userSnippetsPath, (snippets) ->
          result = {}
          result[userSnippetsPath] = snippets
          callback(result)
      else
        callback({})

  watchUserSnippets: (callback) ->
    userSnippetsPath = @getUserSnippetsPath()
    fs.stat userSnippetsPath, (error, stat) =>
      if stat?.isFile()
        userSnippetsFileDisposable = new CompositeDisposable()
        userSnippetsFile = new File(userSnippetsPath)
        try
          userSnippetsFileDisposable.add userSnippetsFile.onDidChange => @handleUserSnippetsDidChange()
          userSnippetsFileDisposable.add userSnippetsFile.onDidDelete => @handleUserSnippetsDidChange()
          userSnippetsFileDisposable.add userSnippetsFile.onDidRename => @handleUserSnippetsDidChange()
        catch e
          message = """
            Unable to watch path: `snippets.cson`. Make sure you have permissions
            to the `~/.atom` directory and `#{userSnippetsPath}`.

            On linux there are currently problems with watch sizes. See
            [this document][watches] for more info.
            [watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path
          """
          atom.notifications.addError(message, {dismissable: true})

        callback(userSnippetsFileDisposable)
      else
        callback(new Disposable -> )

  # Called when a user's snippets file is changed, deleted, or moved so that we
  # can immediately re-process the snippets it contains.
  handleUserSnippetsDidChange: ->
    userSnippetsPath = @getUserSnippetsPath()
    atom.config.transact =>
      @clearSnippetsForPath(userSnippetsPath)
      @loadSnippetsFile userSnippetsPath, (result) =>
        @add(userSnippetsPath, result)

  # Called when the "Enable" checkbox is checked/unchecked in the Snippets
  # section of a package's settings view.
  handleDisabledPackagesDidChange: (newDisabledPackages, oldDisabledPackages) ->
    packagesToAdd = []
    packagesToRemove = []
    oldDisabledPackages ?= []
    newDisabledPackages ?= []
    for p in oldDisabledPackages
      packagesToAdd.push(p) unless newDisabledPackages.includes(p)

    for p in newDisabledPackages
      packagesToRemove.push(p) unless oldDisabledPackages.includes(p)

    atom.config.transact =>
      @removeSnippetsForPackage(p) for p in packagesToRemove
      @addSnippetsForPackage(p) for p in packagesToAdd

  addSnippetsForPackage: (packageName) ->
    snippetSet = @snippetsByPackage.get(packageName)
    for filePath, snippetsBySelector of snippetSet
      @add(filePath, snippetsBySelector)

  removeSnippetsForPackage: (packageName) ->
    snippetSet = @snippetsByPackage.get(packageName)
    # Copy these snippets to the "quarantined" ScopedPropertyStore so that they
    # remain present in the list of unparsed snippets reported to the settings
    # view.
    @addSnippetsInDisabledPackage(snippetSet)
    for filePath, snippetsBySelector of snippetSet
      @clearSnippetsForPath(filePath)

  loadPackageSnippets: (callback) ->
    disabledPackageNames = atom.config.get('core.packagesWithSnippetsDisabled') or []
    packages = atom.packages.getLoadedPackages().sort (pack, b) ->
      if /\/app\.asar\/node_modules\//.test(pack.path) then -1 else 1

    snippetsDirPaths = (path.join(pack.path, 'snippets') for pack in packages)

{'fraction_non_alphanumeric': 0.09596847956096531, 'fraction_numerical': 0.0004221487370716949, 'mean_word_length': 3.4938349668036675, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 75, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '70096', 'n_tokens_mistral': 4376, 'n_tokens_neox': 4024, 'n_words': 921}