<commit_before>Shortcuts      = require 'shortcuts'
defaults       = require './config'
events         = require 'events'
_              = require 'underscore'
kd             = require 'kd'
globals        = require 'globals'
ShortcutsModal = require './views/modal'
cloneArray     = require 'app/util/cloneArray'
AppController  = require 'app/appcontroller'

STORAGE_NAME    = 'shortcuts'
STORAGE_VERSION = '4'
THROTTLE_WAIT   = 500

module.exports =

class ShortcutsController extends events.EventEmitter

  klass = this

  # Manages keyboard shortcuts.
  #
  # Wraps over a _shortcuts_ instance by default (if not passed explicitly
  # within opts.shortcuts), and makes sure we are listening and dispatching
  # correct keyboard events depending on an application's config.
  #
  # This also exposes convenience proxy methods to the underlying _keyconfig_
  # instance, and persists the state of a keyconfig#Collection to the app storage.
  #
  constructor: (opts={}) ->

    super()

    @_store = null

    if opts.shortcuts instanceof Shortcuts
      @shortcuts = opts.shortcuts
    else
      @shortcuts = new Shortcuts _.keys(defaults).reduce (acc, key) ->
          acc[key] = defaults[key].data
          return acc
        , {}

    @_buffer = @shortcuts.config.reduce (acc, collection) ->
      acc[collection.name] = []
      return acc
    , {}


  # Party starts here.
  #
  # This adds the necessary event listeners, and fetches changes.
  # Make sure to call this after _appStorageController_ & _appManager_
  # singletons are set.
  #
  addEventListeners: ->

    { appManager, appStorageController } = kd.singletons

    @_store = appStorageController.storage
      name    : klass.getPlatformStorageKey()
      version : STORAGE_VERSION
      fetch   : no

    @_store.once 'ready', _.bind @_handleStoreReady, this

    # Add valid keyboard event listeners and invalidate obsolete upon
    # front application is set and ready.
    appManager.on 'FrontAppChange', _.bind @_handleFrontAppChange, this

    @_store.fetchStorage null, yes


  # Persists changes to app storage.
  #
  # This is a throttled method and will only be called per every
  # _THROTTLE_WAIT_ milliseconds.
  #
  _save: _.throttle ->

    throw 'could not persist changes'  unless @_store.isReady

    for key, models of @_buffer

      set = []

      while (model = @_buffer[key].pop())?
        value =
        _
          .chain(model.toJSON())
          # since every platform has independent storages, we are only
          # saving this platform's bindings
          .extend binding: klass._getPlatformBinding model
          .pick 'binding', 'options', 'name' # and nothing more
          .tap (obj) -> # to validate
            if _.isObject obj.options
              # the only whitelisted options prop is _enabled_
              obj.options = _.pick obj.options, 'enabled'
              delete obj.options  \
                if (_.isEmpty obj.options) or (not _.isBoolean obj.options.enabled)
            if (not _.isArray obj.binding) or _.isEmpty obj.binding
              delete obj.binding
          .value()

        set.push value  unless _.isEmpty value

      unless _.isEmpty set
        @_store.setValue key, set,
          _
            .chain (collectionName, modelNames) ->
              _.each modelNames, (modelName) =>
                collection = @shortcuts.get collectionName
                @emit 'change', collection, collection.find name: modelName
            .partial key, _.pluck set, 'name'
            .bind @
            .value()

  , THROTTLE_WAIT,<commit_msg>shortcuts: disable shortcuts silently which has no bindings defined :palm_tree:<commit_after>Shortcuts      = require 'shortcuts'
defaults       = require './config'
events         = require 'events'
_              = require 'underscore'
kd             = require 'kd'
globals        = require 'globals'
ShortcutsModal = require './views/modal'
cloneArray     = require 'app/util/cloneArray'
AppController  = require 'app/appcontroller'

STORAGE_NAME    = 'shortcuts'
STORAGE_VERSION = '4.1'
THROTTLE_WAIT   = 500

module.exports =

class ShortcutsController extends events.EventEmitter

  klass = this

  # Manages keyboard shortcuts.
  #
  # Wraps over a _shortcuts_ instance by default (if not passed explicitly
  # within opts.shortcuts), and makes sure we are listening and dispatching
  # correct keyboard events depending on an application's config.
  #
  # This also exposes convenience proxy methods to the underlying _keyconfig_
  # instance, and persists the state of a keyconfig#Collection to the app storage.
  #
  constructor: (opts={}) ->

    super()

    @_store = null

    if opts.shortcuts instanceof Shortcuts
      @shortcuts = opts.shortcuts
    else
      @shortcuts = new Shortcuts _.keys(defaults).reduce (acc, key) ->
          acc[key] = defaults[key].data
          return acc
        , {}

    @_buffer = @shortcuts.config.reduce (acc, collection) ->
      acc[collection.name] = []
      return acc
    , {}


  # Party starts here.
  #
  # This adds the necessary event listeners, and fetches changes.
  # Make sure to call this after _appStorageController_ & _appManager_
  # singletons are set.
  #
  addEventListeners: ->

    { appManager, appStorageController } = kd.singletons

    @_store = appStorageController.storage
      name    : klass.getPlatformStorageKey()
      version : STORAGE_VERSION
      fetch   : no

    @_store.once 'ready', _.bind @_handleStoreReady, this

    # Add valid keyboard event listeners and invalidate obsolete upon
    # front application is set and ready.
    appManager.on 'FrontAppChange', _.bind @_handleFrontAppChange, this

    @_store.fetchStorage null, yes


  # Persists changes to app storage.
  #
  # This is a throttled method and will only be called per every
  # _THROTTLE_WAIT_ milliseconds.
  #
  _save: _.throttle ->

    throw 'could not persist changes'  unless @_store.isReady

    for key, models of @_buffer

      set = []

      while (model = @_buffer[key].pop())?
        value =
        _
          .chain(model.toJSON())
          # since every platform has independent storages, we are only
          # saving this platform's bindings
          .extend binding: klass._getPlatformBinding model
          .pick 'binding', 'options', 'name' # and nothing more
          .tap (obj) -> # to validate
            if _.isObject obj.options
              # the only whitelisted options prop is _enabled_
              obj.options = _.pick obj.options, 'enabled'
              delete obj.options  \
                if (_.isEmpty obj.options) or (not _.isBoolean obj.options.enabled)
            if (not _.isArray obj.binding) or _.isEmpty obj.binding
              delete obj.binding
              # and disable it silently
              obj.options or= {}
              _.extend obj.options, enabled: no
          .value()

        # this should pass anyhow (since there will be always _name_ prop)
        # but still it doesn't hurt to be little paranoid here.
        set.push value  unless _.isEmpty value

      unless _.isEmpty set
        @_store.setValue key, set,
          _
            .chain (collectionName, modelNames) ->
              _.each modelNames, (modelName) =>
                collection = @shortcuts.get collectionName
                @emit 'change', collection, collection.find name: modelName
            .partial key, _.pluck set, 'name'
            .bind @
            .value()

  , THROTTLE_WAIT,
{'fraction_non_alphanumeric': 0.08379962696509459, 'fraction_numerical': 0.001199040767386091, 'mean_word_length': 2.3275709219858154, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 0, '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': '4136085', 'n_tokens_mistral': 2126, 'n_tokens_neox': 2019, 'n_words': 677}