Remove logging

Remove logging

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -81,8 +81,6 @@
     return callback() unless segments.indexOf(binFolder) is -1

     segments.push(binFolder)
-    console.log 'updating'
-    console.log segments.join(';')
     args = ['add', environmentKeyPath, '/v', 'Path', '/d', segments.join(';'), '/f']
     spawnReg(args, callback)


Revert "Click the Allow Reacting option error"

--- packages/rocketchat-channel-settings/client/views/channelSettings.coffee
+++ packages/rocketchat-channel-settings/client/views/channelSettings.coffee
@@ -198,7 +198,7 @@
 			canView: (room) => room.t isnt 'd' and room.ro
 			canEdit: (room) => RocketChat.authz.hasAllPermission('set-react-when-readonly', room._id)
 			save: (value, room) ->
-				Meteor.call 'saveRoomSettings', room._id, 'reactWhenReadOnly', value, (err, result) =>
+				Meteor.call 'saveRoomSettings', room._id, 'reactWhenReadOnly', value, (err, result) ->
 					return handleError err if err
 					toastr.success TAPi18n.__ 'React_when_read_only_changed_successfully'


Remove unused adapter mock.

Remove unused adapter mock.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/JoshuaToenyes/stout-core.git/'


Add DisplayBufferMarker::getInvalidationStrategy

--- src/display-buffer-marker.coffee
+++ src/display-buffer-marker.coffee
@@ -129,6 +129,14 @@
   isReversed: ->
     @bufferMarker.isReversed()

+  # Extended: Get the invalidation strategy for this marker.
+  #
+  # Valid values include: `never`, `surround`, `overlap`, `inside`, and `touch`.
+  #
+  # Returns a {String}.
+  getInvalidationStrategy: ->
+    @bufferMarker.getInvalidationStrategy()
+
   # Extended: Returns an {Object} containing any custom properties associated with
   # the marker.
   getAttributes: ->


am going to back this out, it's not working and opaque to debug. instead, need to revamp directory layout....

error: pathspec 'src/metastitch.iced' did not match any file(s) known to git

--- src/metastitch.iced
+++ src/metastitch.iced
@@ -0,0 +1,20 @@
+@._stitchings = {}
+
+metastitch_module = (mod) ->
+  @._stitchings[mod] = @.require
+  @.require = null
+
+metastitch_finish = () ->
+  active_require = @.require
+  @.require = (name) ->
+    console.log "new require #{name}"
+    p = name.split '/'
+    console.log "part1 = #{p[0]}"
+    console.log "stored = #{_stitchings[p[0]]}"
+    if p.length and p[0]? and (rqfn = _stitchings[p[0]])?
+      rest = p[1...].join '/'
+      rqfn rest
+    else
+      active_require name
+
+


Added BackboneViewWidget

Added BackboneViewWidget

Used for embedding side-effectful backbone views inside the (otherwise side-effect free) render functions for virtual-dom

--- admin/static/coffee/util.coffee
+++ admin/static/coffee/util.coffee
@@ -412,6 +412,41 @@
                 value: if value then 'true' else 'false'

+class BackboneViewWidget
+    # An adapter class to allow a Backbone View to act as a
+    # virtual-dom Widget. This allows the view to update itself and
+    # not have to worry about being destroyed and reinserted, causing
+    # loss of listeners etc.
+    type: "Widget"  # required by virtual-dom to detect this as a widget.
+    constructor: (thunk) ->
+        # The constructor doesn't create the view or cause a render
+        # because many many widgets will be created, and they need to
+        # be very lightweight.
+        @thunk = thunk
+        @view = null
+    init: () ->
+        # init is called when virtual-dom first notices a widget. This
+        # will initialize the view with the given options, render the
+        # view, and return its DOM element for virtual-dom to place in
+        # the document
+        @view = @thunk()
+        @view.render()
+        @view.el  # init needs to return a raw dom element
+    update: (previous, domNode) ->
+        # Every time the vdom is updated, a new widget will be
+        # created, which will be passed the old widget (previous). We
+        # need to copy the state from the old widget over and return
+        # null to indicate the DOM element should be re-used and not
+        # replaced.
+        @view = previous.view
+        return null
+    destroy: (domNode) ->
+        # This is called when virtual-dom detects the widget should be
+        # removed from the tree. It just calls `.remove()` on the
+        # underlying view so listeners etc can be cleaned up.
+        @view?.remove()
+
+
 exports.capitalize = capitalize
 exports.humanize_table_status = humanize_table_status
 exports.form_data_as_object = form_data_as_object
@@ -426,3 +461,4 @@
 exports.pluralize_noun = pluralize_noun
 exports.pluralize_verb = pluralize_verb
 exports.humanize_uuid = humanize_uuid
+exports.BackboneViewWidget = BackboneViewWidget


Use model full points, if available

Use model full points, if available

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/UM-USElab/gradecraft-development.git/'


Stub DomUtils.consumeKeyup to fix handler stack tests

--- tests/unit_tests/handler_stack_test.coffee
+++ tests/unit_tests/handler_stack_test.coffee
@@ -4,6 +4,7 @@
 context "handlerStack",
   setup ->
     stub global, "DomUtils", {}
+    stub DomUtils, "consumeKeyup", ->
     stub DomUtils, "suppressEvent", ->
     stub DomUtils, "suppressPropagation", ->
     @handlerStack = new HandlerStack


chore (TestIndex) Remove breaking code

chore (TestIndex) Remove breaking code

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/AppGyver/supersonic.git/'


Make DateTimePicker a class so we can reuse it later

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/garyfoster/bootstrap_datetimepicker.git/'


solve compatibility of Jquery and turbolinks

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/hiveer/daily_plan.git/'


fixed don't count cancel for post action

--- src/coffeescripts/Action.coffee
+++ src/coffeescripts/Action.coffee
@@ -145,7 +145,8 @@
     task = new Task detail, options, (err, data) =>
       callback?(err, data)
       if not err
-        @postAction()
+        # Cancel has no data
+        @postAction() if data
         @close()
       else
         LoggrUtil.log "Error: Task creation failed."


Give the user settings a priority

Give the user settings a priority

Base index 1000

--- src/config.coffee
+++ src/config.coffee
@@ -314,6 +314,7 @@
     @settings = {}
     @scopedSettingsStore = new ScopedPropertyStore
     @usersScopedSettings = new CompositeDisposable
+    @usersScopedSettingPriority = {indexBase: 1000}
     @configFileHasErrors = false
     @configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson'])
     @configFilePath ?= path.join(@configDirPath, 'config.cson')
@@ -537,7 +538,7 @@
       settings = @scopedSettingsStore.propertiesForSourceAndSelector('user-config', scopeSelector)
       @scopedSettingsStore.removePropertiesForSourceAndSelector('user-config', scopeSelector)
       _.setValueForKeyPath(settings, keyPath, undefined)
-      @addScopedSettings('user-config', scopeSelector, settings)
+      @addScopedSettings('user-config', scopeSelector, settings, @usersScopedSettingPriority)
       @save() unless @configFileHasErrors
       @getDefault(scopeSelector, keyPath)
     else
@@ -881,13 +882,13 @@
   resetUserScopedSettings: (newScopedSettings) ->
     @usersScopedSettings?.dispose()
     @usersScopedSettings = new CompositeDisposable
-    @usersScopedSettings.add @scopedSettingsStore.addProperties('user-config', newScopedSettings)
+    @usersScopedSettings.add @scopedSettingsStore.addProperties('user-config', newScopedSettings, @usersScopedSettingPriority)
     @emitter.emit 'did-change'

-  addScopedSettings: (source, selector, value) ->
+  addScopedSettings: (source, selector, value, options) ->
     settingsBySelector = {}
     settingsBySelector[selector] = value
-    disposable = @scopedSettingsStore.addProperties(source, settingsBySelector)
+    disposable = @scopedSettingsStore.addProperties(source, settingsBySelector, options)
     @emitter.emit 'did-change'
     new Disposable =>
       disposable.dispose()
@@ -901,7 +902,7 @@

     settingsBySelector = {}
     settingsBySelector[selector] = value
-    @usersScopedSettings.add @scopedSettingsStore.addProperties('user-config', settingsBySelector)
+    @usersScopedSettings.add @scopedSettingsStore.addProperties('user-config', settingsBySelector, @usersScopedSettingPriority)
     @emitter.emit 'did-change'

   getRawScopedValue: (scopeDescriptor, keyPath) ->


Remove package's package.json from cache

This file is inlined in the main package.json

--- build/tasks/compile-packages-slug-task.coffee
+++ build/tasks/compile-packages-slug-task.coffee
@@ -1,6 +1,7 @@
 path = require 'path'
 CSON = require 'season'
 fs = require 'fs-plus'
+_ = require 'underscore-plus'

 module.exports = (grunt) ->
   {spawn, rm} = require('./task-helpers')(grunt)
@@ -18,7 +19,10 @@
       metadata = grunt.file.readJSON(metadataPath)
       continue unless metadata?.engines?.atom?

+      moduleCache = metadata._atomModuleCache ? {}
+
       rm metadataPath
+      _.remove(moduleCache.extensions?['.json'] ? [], 'package.json')

       for property in ['_from', '_id', 'dist', 'readme', 'readmeFilename']
         delete metadata[property]
@@ -37,6 +41,10 @@

       packages[metadata.name] = pack

+      for extension, paths of moduleCache.extensions
+        if paths.length is 0
+          delete metadata._atomModuleCache?.extensions[extension]
+
     metadata = grunt.file.readJSON(path.join(appDir, 'package.json'))
     metadata._atomPackages = packages


I always forget to add the new file

I always forget to add the new file

error: pathspec 'coffeescript/love.js/filesystem.coffee' did not match any file(s) known to git

--- coffeescript/love.js/filesystem.coffee
+++ coffeescript/love.js/filesystem.coffee
@@ -0,0 +1,58 @@
+class FileSystem
+  constructor: () ->
+
+  append: () =>
+
+  createDirectory: () =>
+
+  exists: (filename) =>
+    localStorage.getItem(filename) != null
+
+  getAppdataDirectory: () =>
+
+  getDirectoryItems: () =>
+
+  getIdentity: () =>
+
+  getLastModified: () =>
+
+  getSaveDirectory: () =>
+
+  getSize: () =>
+
+  getUserDirectory: () =>
+
+  getWorkingDirectory: () =>
+
+  init: () =>
+
+  isDirectory: () =>
+
+  isFile: () =>
+
+  isFused: () =>
+
+  lines: () =>
+
+  load: () =>
+
+  mount: () =>
+
+  newFile: () =>
+
+  newFileData: () =>
+
+  read: (filename) =>
+    localStorage.getItem(filename)
+
+  remove: (filename) =>
+    localStorage.removeItem(filename)
+
+  setIdentity: () =>
+
+  setSource: () =>
+
+  unmount: () =>
+
+  write: (filename, data) =>
+    localStorage.setItem(filename, data)


Add tests on conversations move

Add tests on conversations move

error: pathspec 'client/tests/casper/full/convactions.coffee' did not match any file(s) known to git

--- client/tests/casper/full/convactions.coffee
+++ client/tests/casper/full/convactions.coffee
@@ -0,0 +1,62 @@
+if global?
+    require = patchRequire global.require
+else
+    require = patchRequire this.require
+    require.globals.casper = casper
+init  = require(fs.workingDirectory + "/client/tests/casper/common").init
+utils = require "utils.js"
+x     = require('casper.js').selectXPath
+
+casper.test.begin 'Test Actions on conversations', (test) ->
+    init casper
+
+    casper.start casper.cozy.startUrl, ->
+        casper.evaluate ->
+            window.cozyMails.setSetting 'messageDisplayHTML', true
+            window.cozyMails.setSetting 'messageDisplayImages', false
+            window.cozyMails.setSetting 'displayConversation', true
+            window.cozyMails.setSetting 'displayPreview', true
+            window.cozyMails.setSetting 'messageConfirmDelete', true
+            window.cozyMails.setSetting 'composeInHTML', true
+
+    casper.then ->
+        if casper.getEngine() isnt 'slimer'
+            test.comment "Skipping, as PhantomJS doesn't support PATCH verb"
+            test.skip 1
+        else
+            test.comment "Move Message"
+
+            casper.cozy.selectMessage "DoveCot", "Test Folder", "Re: troll", (subject, messageID) ->
+                nbMessages = casper.getElementsInfo(".message-list li.message").length
+                nbInConv = casper.fetchText ".message-list li.message.active .badge.conversation-length"
+                casper.mouse.move ".message-list li.message.active .fa-user"
+                casper.click ".message-list li.message.active input[type=checkbox]"
+                casper.waitUntilVisible '.message-list-actions .menu-move button', ->
+                    casper.click '.message-list-actions .menu-move button'
+                    casper.waitUntilVisible '.message-list-actions .menu-move a[data-value="0b3a2b31-7acb-dbab-d57f-3050ae2c78c5"]', ->
+                        casper.click '.message-list-actions .menu-move a[data-value="0b3a2b31-7acb-dbab-d57f-3050ae2c78c5"]'
+                        casper.waitWhileVisible ".message-list li[data-message-id='#{messageID}']", ->
+                            test.assertEquals casper.getElementsInfo(".message-list li.message").length, nbMessages - 1,
+                                "Message no more in folder"
+                            casper.evaluate ->
+                                window.cozyMails.messageClose()
+                            casper.cozy.selectMessage "DoveCot", "Flagged Email", "Re: troll", (subject, messageID) ->
+                                test.pass "Message Moved"
+                                test.assertEquals casper.fetchText(".message-list li.message[data-message-id='#{messageID}'] .badge.conversation-length"),
+                                    nbInConv, "All messages in conv moved"
+                                casper.click ".conversation .message.active .messageToolbox button.move"
+                                boxSelector = ".conversation .message.active .messageToolbox [data-value='f5cbd722-c3f9-4f6e-73d0-c75ddf65a2f1']"
+                                casper.waitUntilVisible boxSelector, ->
+                                    casper.click boxSelector
+                                    casper.waitWhileSelector ".message-list li.message[data-message-id='#{messageID}']", ->
+                                        test.pass "Message no more in folder"
+                                        casper.cozy.selectMessage "DoveCot", "Test Folder", subject, messageID, ->
+                                            test.pass "Message moved back to original folder"
+                                            test.assertEquals casper.getElementsInfo(".message-list li.message").length, nbMessages,
+                                                "message is back"
+                                            test.assertEquals casper.fetchText(".message-list li.message.active .badge.conversation-length"),
+                                                nbInConv, "All messages in conv moved"
+
+
+    casper.run ->
+        test.done()


Add proper command comment for youtube-info.coffee

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/chauffer/hubot-scripts.git/'


Reorganize cursor into sections

Reorganize cursor into sections

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -54,11 +54,14 @@
       @emitter.dispose()
     @needsAutoscroll = true

+  destroy: ->
+    @marker.destroy()
+
   ###
   Section: Event Subscription
   ###

-  # Essential: Calls your `callback` when the cursor has been moved.
+  # Public: Calls your `callback` when the cursor has been moved.
   #
   # * `callback` {Function}
   #   * `event` {Object}
@@ -72,7 +75,7 @@
   onDidChangePosition: (callback) ->
     @emitter.on 'did-change-position', callback

-  # Extended: Calls your `callback` when the cursor is destroyed
+  # Public: Calls your `callback` when the cursor is destroyed
   #
   # * `callback` {Function}
   #
@@ -80,7 +83,7 @@
   onDidDestroy: (callback) ->
     @emitter.on 'did-destroy', callback

-  # Extended: Calls your `callback` when the cursor's visibility has changed
+  # Public: Calls your `callback` when the cursor's visibility has changed
   #
   # * `callback` {Function}
   #   * `visibility` {Boolean}
@@ -102,22 +105,8 @@
     super

   ###
-  Section: Methods
-  ###
-
-  destroy: ->
-    @marker.destroy()
-
-  changePosition: (options, fn) ->
-    @clearSelection()
-    @needsAutoscroll = options.autoscroll ? @isLastCursor()
-    fn()
-    if @needsAutoscroll
-      @emit 'autoscrolled' # Support legacy editor
-      @autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view
-
-  getPixelRect: ->
-    @editor.pixelRectForScreenRange(@getScreenRange())
+  Section: Managing Cursor Position
+  ###

   # Public: Moves a cursor to a given screen position.
   #
@@ -133,10 +122,6 @@
   getScreenPosition: ->
     @marker.getHeadScreenPosition()

-  getScreenRange: ->
-    {row, column} = @getScreenPosition()
-    new Range(new Point(row, column), new Point(row, column + 1))
-
   # Public: Moves a cursor to a given buffer position.
   #
   # * `bufferPosition` {Array} of two numbers: the buffer row, and the buffer column.
@@ -151,8 +136,436 @@
   getBufferPosition: ->
     @marker.getHeadBufferPosition()

-  autoscroll: (options) ->
-    @editor.scrollToScreenRange(@getScreenRange(), options)
+  # Public: Returns the cursor's current screen row.
+  getScreenRow: ->
+    @getScreenPosition().row
+
+  # Public: Returns the cursor's current screen column.
+  getScreenColumn: ->
+    @getScreenPosition().column
+
+  # Public: Retrieves the cursor's current buffer row.
+  getBufferRow: ->
+    @getBufferPosition().row
+
+  # Public: Returns the cursor's current buffer column.
+  getBufferColumn: ->
+    @getBufferPosition().column
+
+  # Public: Returns the cursor's current buffer row of text excluding its line
+  # ending.
+  getCurrentBufferLine: ->
+    @editor.lineTextForBufferRow(@getBufferRow())
+
+  # Public: Returns whether the cursor is at the start of a line.
+  isAtBeginningOfLine: ->
+    @getBufferPosition().column == 0
+
+  # Public: Returns whether the cursor is on the line return character.
+  isAtEndOfLine: ->
+    @getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
+
+  ###
+  Section: Position-dependent cursor properties
+  ###
+
+  # Public: Identifies if the cursor is surrounded by whitespace.
+  #
+  # "Surrounded" here means that the character directly before and after the
+  # cursor are both whitespace.
+  #
+  # Returns a {Boolean}.
+  isSurroundedByWhitespace: ->
+    {row, column} = @getBufferPosition()
+    range = [[row, column - 1], [row, column + 1]]
+    /^\s+$/.test @editor.getTextInBufferRange(range)
+
+  # Public: Returns whether the cursor is currently between a word and non-word
+  # character. The non-word characters are defined by the
+  # `editor.nonWordCharacters` config value.
+  #
+  # This method returns false if the character before or after the cursor is
+  # whitespace.
+  #
+  # Returns a Boolean.
+  isBetweenWordAndNonWord: ->
+    return false if @isAtBeginningOfLine() or @isAtEndOfLine()
+
+    {row, column} = @getBufferPosition()
+    range = [[row, column - 1], [row, column + 1]]
+    [before, after] = @editor.getTextInBufferRange(range)
+    return false if /\s/.test(before) or /\s/.test(after)
+
+    nonWordCharacters = atom.config.get('editor.nonWordCharacters').split('')
+    _.contains(nonWordCharacters, before) isnt _.contains(nonWordCharacters, after)
+
+  # Public: Returns whether this cursor is between a word's start and end.
+  isInsideWord: ->
+    {row, column} = @getBufferPosition()
+    range = [[row, column], [row, Infinity]]
+    @editor.getTextInBufferRange(range).search(@wordRegExp()) == 0
+
+  # Public: Returns the indentation level of the current line.
+  getIndentLevel: ->
+    if @editor.getSoftTabs()
+      @getBufferColumn() / @editor.getTabLength()
+    else
+      @getBufferColumn()
+
+  # Public: Retrieves the grammar's token scopes for the line.
+  #
+  # Returns an {Array} of {String}s.
+  getScopes: ->
+    @editor.scopesForBufferPosition(@getBufferPosition())
+
+  # Public: Returns true if this cursor has no non-whitespace characters before
+  # its current position.
+  hasPrecedingCharactersOnLine: ->
+    bufferPosition = @getBufferPosition()
+    line = @editor.lineTextForBufferRow(bufferPosition.row)
+    firstCharacterColumn = line.search(/\S/)
+
+    if firstCharacterColumn is -1
+      false
+    else
+      bufferPosition.column > firstCharacterColumn
+
+  # Public: Identifies if this cursor is the last in the {Editor}.
+  #
+  # "Last" is defined as the most recently added cursor.
+  #
+  # Returns a {Boolean}.
+  isLastCursor: ->
+    this == @editor.getLastCursor()
+
+  ###
+  Section: Moving the Cursor
+  ###
+
+  # Public: Moves the cursor up one screen row.
+  #
+  # * `rowCount` (optional) {Number} number of rows to move (default: 1)
+  # * `options` (optional) {Object} with the following keys:
+  #   * `moveToEndOfSelection` if true, move to the left of the selection if a
+  #     selection exists.
+  moveUp: (rowCount=1, {moveToEndOfSelection}={}) ->
+    range = @marker.getScreenRange()
+    if moveToEndOfSelection and not range.isEmpty()
+      { row, column } = range.start
+    else
+      { row, column } = @getScreenPosition()
+
+    column = @goalColumn if @goalColumn?
+    @setScreenPosition({row: row - rowCount, column: column})
+    @goalColumn = column
+
+  # Public: Moves the cursor down one screen row.
+  #
+  # * `rowCount` (optional) {Number} number of rows to move (default: 1)
+  # * `options` (optional) {Object} with the following keys:
+  #   * `moveToEndOfSelection` if true, move to the left of the selection if a
+  #     selection exists.
+  moveDown: (rowCount=1, {moveToEndOfSelection}={}) ->
+    range = @marker.getScreenRange()
+    if moveToEndOfSelection and not range.isEmpty()
+      { row, column } = range.end
+    else
+      { row, column } = @getScreenPosition()
+
+    column = @goalColumn if @goalColumn?
+    @setScreenPosition({row: row + rowCount, column: column})
+    @goalColumn = column
+
+  # Public: Moves the cursor left one screen column.
+  #
+  # * `columnCount` (optional) {Number} number of columns to move (default: 1)
+  # * `options` (optional) {Object} with the following keys:
+  #   * `moveToEndOfSelection` if true, move to the left of the selection if a
+  #     selection exists.
+  moveLeft: (columnCount=1, {moveToEndOfSelection}={}) ->
+    range = @marker.getScreenRange()
+    if moveToEndOfSelection and not range.isEmpty()
+      @setScreenPosition(range.start)
+    else
+      {row, column} = @getScreenPosition()
+
+      while columnCount > column and row > 0
+        columnCount -= column
+        column = @editor.lineTextForScreenRow(--row).length
+        columnCount-- # subtract 1 for the row move
+
+      column = column - columnCount
+      @setScreenPosition({row, column})
+
+  # Public: Moves the cursor right one screen column.
+  #
+  # * `columnCount` (optional) {Number} number of columns to move (default: 1)
+  # * `options` (optional) {Object} with the following keys:
+  #   * `moveToEndOfSelection` if true, move to the right of the selection if a
+  #     selection exists.
+  moveRight: (columnCount=1, {moveToEndOfSelection}={}) ->
+    range = @marker.getScreenRange()
+    if moveToEndOfSelection and not range.isEmpty()
+      @setScreenPosition(range.end)
+    else
+      { row, column } = @getScreenPosition()
+      maxLines = @editor.getScreenLineCount()
+      rowLength = @editor.lineTextForScreenRow(row).length
+      columnsRemainingInLine = rowLength - column
+
+      while columnCount > columnsRemainingInLine and row < maxLines - 1
+        columnCount -= columnsRemainingInLine
+        columnCount-- # subtract 1 for the row move
+
+        column = 0
+        rowLength = @editor.lineTextForScreenRow(++row).length
+        columnsRemainingInLine = rowLength
+
+      column = column + columnCount
+      @setScreenPosition({row, column}, skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
+
+  # Public: Moves the cursor to the top of the buffer.
+  moveToTop: ->
+    @setBufferPosition([0,0])
+
+  # Public: Moves the cursor to the bottom of the buffer.
+  moveToBottom: ->
+    @setBufferPosition(@editor.getEofBufferPosition())
+
+  # Public: Moves the cursor to the beginning of the line.
+  moveToBeginningOfScreenLine: ->
+    @setScreenPosition([@getScreenRow(), 0])
+
+  # Public: Moves the cursor to the beginning of the buffer line.
+  moveToBeginningOfLine: ->
+    @setBufferPosition([@getBufferRow(), 0])
+
+  # Public: Moves the cursor to the beginning of the first character in the
+  # line.
+  moveToFirstCharacterOfLine: ->
+    screenRow = @getScreenRow()
+    lineBufferRange = @editor.bufferRangeForScreenRange([[screenRow, 0], [screenRow, Infinity]])
+
+    firstCharacterColumn = null
+    @editor.scanInBufferRange /\S/, lineBufferRange, ({range, stop}) ->
+      firstCharacterColumn = range.start.column
+      stop()
+
+    if firstCharacterColumn? and firstCharacterColumn isnt @getBufferColumn()
+      targetBufferColumn = firstCharacterColumn
+    else
+      targetBufferColumn = lineBufferRange.start.column
+
+    @setBufferPosition([lineBufferRange.start.row, targetBufferColumn])
+
+  # Public: Moves the cursor to the end of the line.
+  moveToEndOfScreenLine: ->
+    @setScreenPosition([@getScreenRow(), Infinity])
+
+  # Public: Moves the cursor to the end of the buffer line.
+  moveToEndOfLine: ->
+    @setBufferPosition([@getBufferRow(), Infinity])
+
+  # Public: Moves the cursor to the beginning of the word.
+  moveToBeginningOfWord: ->
+    @setBufferPosition(@getBeginningOfCurrentWordBufferPosition())
+
+  # Public: Moves the cursor to the end of the word.
+  moveToEndOfWord: ->
+    if position = @getEndOfCurrentWordBufferPosition()
+      @setBufferPosition(position)
+
+  # Public: Moves the cursor to the beginning of the next word.
+  moveToBeginningOfNextWord: ->
+    if position = @getBeginningOfNextWordBufferPosition()
+      @setBufferPosition(position)
+
+  # Public: Moves the cursor to the previous word boundary.
+  moveToPreviousWordBoundary: ->
+    if position = @getPreviousWordBoundaryBufferPosition()
+      @setBufferPosition(position)
+
+  # Public: Moves the cursor to the next word boundary.
+  moveToNextWordBoundary: ->
+    if position = @getMoveNextWordBoundaryBufferPosition()
+      @setBufferPosition(position)
+
+  # Public: Moves the cursor to the beginning of the buffer line, skipping all
+  # whitespace.
+  skipLeadingWhitespace: ->
+    position = @getBufferPosition()
+    scanRange = @getCurrentLineBufferRange()
+    endOfLeadingWhitespace = null
+    @editor.scanInBufferRange /^[ \t]*/, scanRange, ({range}) ->
+      endOfLeadingWhitespace = range.end
+
+    @setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
+
+  # Public: Moves the cursor to the beginning of the next paragraph
+  moveToBeginningOfNextParagraph: ->
+    if position = @getBeginningOfNextParagraphBufferPosition()
+      @setBufferPosition(position)
+
+  # Public: Moves the cursor to the beginning of the previous paragraph
+  moveToBeginningOfPreviousParagraph: ->
+    if position = @getBeginningOfPreviousParagraphBufferPosition()
+      @setBufferPosition(position)
+
+  ###
+  Section: Retrieving Positions and Ranges of local boundaries
+  ###
+
+  # Public: Retrieves the buffer position of where the current word starts.
+  #
+  # * `options` (optional) An {Object} with the following keys:
+  #   * `wordRegex` A {RegExp} indicating what constitutes a "word"
+  #     (default: {::wordRegExp}).
+  #   * `includeNonWordCharacters` A {Boolean} indicating whether to include
+  #     non-word characters in the default word regex.
+  #     Has no effect if wordRegex is set.
+  #   * `allowPrevious` A {Boolean} indicating whether the beginning of the
+  #     previous word can be returned.
+  #
+  # Returns a {Range}.
+  getBeginningOfCurrentWordBufferPosition: (options = {}) ->
+    allowPrevious = options.allowPrevious ? true
+    currentBufferPosition = @getBufferPosition()
+    previousNonBlankRow = @editor.buffer.previousNonBlankRow(currentBufferPosition.row) ? 0
+    scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
+
+    beginningOfWordPosition = null
+    @editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
+      if range.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
+        beginningOfWordPosition = range.start
+      if not beginningOfWordPosition?.isEqual(currentBufferPosition)
+        stop()
+
+    if beginningOfWordPosition?
+      beginningOfWordPosition
+    else if allowPrevious
+      new Point(0, 0)
+    else
+      currentBufferPosition
+
+  # Public: Retrieves buffer position of previous word boundary. It might be on
+  # the current word, or the previous word.
+  getPreviousWordBoundaryBufferPosition: (options = {}) ->
+    currentBufferPosition = @getBufferPosition()
+    previousNonBlankRow = @editor.buffer.previousNonBlankRow(currentBufferPosition.row)
+    scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
+
+    beginningOfWordPosition = null
+    @editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
+      if range.start.row < currentBufferPosition.row and currentBufferPosition.column > 0
+        # force it to stop at the beginning of each line
+        beginningOfWordPosition = new Point(currentBufferPosition.row, 0)
+      else if range.end.isLessThan(currentBufferPosition)
+        beginningOfWordPosition = range.end
+      else
+        beginningOfWordPosition = range.start
+
+      if not beginningOfWordPosition?.isEqual(currentBufferPosition)
+        stop()
+
+    beginningOfWordPosition or currentBufferPosition
+
+  # Public: Retrieves buffer position of the next word boundary. It might be on
+  # the current word, or the previous word.
+  getMoveNextWordBoundaryBufferPosition: (options = {}) ->
+    currentBufferPosition = @getBufferPosition()
+    scanRange = [currentBufferPosition, @editor.getEofBufferPosition()]
+
+    endOfWordPosition = null
+    @editor.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
+      if range.start.row > currentBufferPosition.row
+        # force it to stop at the beginning of each line
+        endOfWordPosition = new Point(range.start.row, 0)
+      else if range.start.isGreaterThan(currentBufferPosition)
+        endOfWordPosition = range.start
+      else
+        endOfWordPosition = range.end
+
+      if not endOfWordPosition?.isEqual(currentBufferPosition)
+        stop()
+
+    endOfWordPosition or currentBufferPosition
+
+  # Public: Retrieves the buffer position of where the current word ends.
+  #
+  # * `options` (optional) {Object} with the following keys:
+  #   * `wordRegex` A {RegExp} indicating what constitutes a "word"
+  #      (default: {::wordRegExp})
+  #   * `includeNonWordCharacters` A Boolean indicating whether to include
+  #     non-word characters in the default word regex. Has no effect if
+  #     wordRegex is set.
+  #
+  # Returns a {Range}.
+  getEndOfCurrentWordBufferPosition: (options = {}) ->
+    allowNext = options.allowNext ? true
+    currentBufferPosition = @getBufferPosition()
+    scanRange = [currentBufferPosition, @editor.getEofBufferPosition()]
+
+    endOfWordPosition = null
+    @editor.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
+      if range.start.isLessThanOrEqual(currentBufferPosition) or allowNext
+        endOfWordPosition = range.end
+      if not endOfWordPosition?.isEqual(currentBufferPosition)
+        stop()
+
+    endOfWordPosition ? currentBufferPosition
+
+  # Public: Retrieves the buffer position of where the next word starts.
+  #
+  # * `options` (optional) {Object}
+  #   * `wordRegex` A {RegExp} indicating what constitutes a "word"
+  #     (default: {::wordRegExp}).
+  #
+  # Returns a {Range}
+  getBeginningOfNextWordBufferPosition: (options = {}) ->
+    currentBufferPosition = @getBufferPosition()
+    start = if @isInsideWord() then @getEndOfCurrentWordBufferPosition() else currentBufferPosition
+    scanRange = [start, @editor.getEofBufferPosition()]
+
+    beginningOfNextWordPosition = null
+    @editor.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
+      beginningOfNextWordPosition = range.start
+      stop()
+
+    beginningOfNextWordPosition or currentBufferPosition
+
+  # Public: Returns the buffer Range occupied by the word located under the cursor.
+  #
+  # * `options` (optional) {Object}
+  #   * `wordRegex` A {RegExp} indicating what constitutes a "word"
+  #     (default: {::wordRegExp}).
+  getCurrentWordBufferRange: (options={}) ->
+    startOptions = _.extend(_.clone(options), allowPrevious: false)
+    endOptions = _.extend(_.clone(options), allowNext: false)
+    new Range(@getBeginningOfCurrentWordBufferPosition(startOptions), @getEndOfCurrentWordBufferPosition(endOptions))
+
+  # Public: Returns the buffer Range for the current line.
+  #
+  # * `options` (optional) {Object}
+  #   * `includeNewline` A {Boolean} which controls whether the Range should
+  #     include the newline.
+  getCurrentLineBufferRange: (options) ->
+    @editor.bufferRangeForBufferRow(@getBufferRow(), options)
+
+  # Public: Retrieves the range for the current paragraph.
+  #
+  # A paragraph is defined as a block of text surrounded by empty lines.
+  #
+  # Returns a {Range}.
+  getCurrentParagraphBufferRange: ->
+    @editor.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow())
+
+  # Public: Returns the characters preceding the cursor in the current word.
+  getCurrentWordPrefix: ->
+    @editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
+
+  ###
+  Section: Visibility
+  ###

   # Public: If the marker range is empty, the cursor is marked as being visible.
   updateVisibility: ->
@@ -168,6 +581,30 @@

   # Public: Returns the visibility of the cursor.
   isVisible: -> @visible
+
+  ###
+  Section: Comparing to another cursor
+  ###
+
+  # Public: Compare this cursor's buffer position to another cursor's buffer position.
+  #
+  # See {Point::compare} for more details.
+  #
+  # * `otherCursor`{Cursor} to compare against
+  compare: (otherCursor) ->
+    @getBufferPosition().compare(otherCursor.getBufferPosition())
+
+  ###
+  Section: Utilities
+  ###
+
+  # Public: Prevents this cursor from causing scrolling.
+  clearAutoscroll: ->
+    @needsAutoscroll = null
+
+  # Public: Deselects the current selection.
+  clearSelection: ->
+    @selection?.clear()

   # Public: Get the RegExp used by the cursor to determine what a "word" is.
   #
@@ -185,383 +622,27 @@
       segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+")
     new RegExp(segments.join("|"), "g")

-  # Public: Identifies if this cursor is the last in the {Editor}.
-  #
-  # "Last" is defined as the most recently added cursor.
-  #
-  # Returns a {Boolean}.
-  isLastCursor: ->
-    this == @editor.getLastCursor()
-
-  # Public: Identifies if the cursor is surrounded by whitespace.
-  #
-  # "Surrounded" here means that the character directly before and after the
-  # cursor are both whitespace.
-  #
-  # Returns a {Boolean}.
-  isSurroundedByWhitespace: ->
-    {row, column} = @getBufferPosition()
-    range = [[row, column - 1], [row, column + 1]]
-    /^\s+$/.test @editor.getTextInBufferRange(range)
-
-  # Public: Returns whether the cursor is currently between a word and non-word
-  # character. The non-word characters are defined by the
-  # `editor.nonWordCharacters` config value.
-  #
-  # This method returns false if the character before or after the cursor is
-  # whitespace.
-  #
-  # Returns a Boolean.
-  isBetweenWordAndNonWord: ->
-    return false if @isAtBeginningOfLine() or @isAtEndOfLine()
-
-    {row, column} = @getBufferPosition()
-    range = [[row, column - 1], [row, column + 1]]
-    [before, after] = @editor.getTextInBufferRange(range)
-    return false if /\s/.test(before) or /\s/.test(after)
-
-    nonWordCharacters = atom.config.get('editor.nonWordCharacters').split('')
-    _.contains(nonWordCharacters, before) isnt _.contains(nonWordCharacters, after)
-
-  # Public: Returns whether this cursor is between a word's start and end.
-  isInsideWord: ->
-    {row, column} = @getBufferPosition()
-    range = [[row, column], [row, Infinity]]
-    @editor.getTextInBufferRange(range).search(@wordRegExp()) == 0
-
-  # Public: Prevents this cursor from causing scrolling.
-  clearAutoscroll: ->
-    @needsAutoscroll = null
-
-  # Public: Deselects the current selection.
-  clearSelection: ->
-    @selection?.clear()
-
-  # Public: Returns the cursor's current screen row.
-  getScreenRow: ->
-    @getScreenPosition().row
-
-  # Public: Returns the cursor's current screen column.
-  getScreenColumn: ->
-    @getScreenPosition().column
-
-  # Public: Retrieves the cursor's current buffer row.
-  getBufferRow: ->
-    @getBufferPosition().row
-
-  # Public: Returns the cursor's current buffer column.
-  getBufferColumn: ->
-    @getBufferPosition().column
-
-  # Public: Returns the cursor's current buffer row of text excluding its line
-  # ending.
-  getCurrentBufferLine: ->
-    @editor.lineTextForBufferRow(@getBufferRow())
-
-  # Public: Moves the cursor up one screen row.
-  #
-  # * `rowCount` (optional) {Number} number of rows to move (default: 1)
-  # * `options` (optional) {Object} with the following keys:
-  #   * `moveToEndOfSelection` if true, move to the left of the selection if a
-  #     selection exists.
-  moveUp: (rowCount=1, {moveToEndOfSelection}={}) ->
-    range = @marker.getScreenRange()
-    if moveToEndOfSelection and not range.isEmpty()
-      { row, column } = range.start
-    else
-      { row, column } = @getScreenPosition()
-
-    column = @goalColumn if @goalColumn?
-    @setScreenPosition({row: row - rowCount, column: column})
-    @goalColumn = column
-
-  # Public: Moves the cursor down one screen row.
-  #
-  # * `rowCount` (optional) {Number} number of rows to move (default: 1)
-  # * `options` (optional) {Object} with the following keys:
-  #   * `moveToEndOfSelection` if true, move to the left of the selection if a
-  #     selection exists.
-  moveDown: (rowCount=1, {moveToEndOfSelection}={}) ->
-    range = @marker.getScreenRange()
-    if moveToEndOfSelection and not range.isEmpty()
-      { row, column } = range.end
-    else
-      { row, column } = @getScreenPosition()
-
-    column = @goalColumn if @goalColumn?
-    @setScreenPosition({row: row + rowCount, column: column})
-    @goalColumn = column
-
-  # Public: Moves the cursor left one screen column.
-  #
-  # * `columnCount` (optional) {Number} number of columns to move (default: 1)
-  # * `options` (optional) {Object} with the following keys:
-  #   * `moveToEndOfSelection` if true, move to the left of the selection if a
-  #     selection exists.
-  moveLeft: (columnCount=1, {moveToEndOfSelection}={}) ->
-    range = @marker.getScreenRange()
-    if moveToEndOfSelection and not range.isEmpty()
-      @setScreenPosition(range.start)
-    else
-      {row, column} = @getScreenPosition()
-
-      while columnCount > column and row > 0
-        columnCount -= column
-        column = @editor.lineTextForScreenRow(--row).length
-        columnCount-- # subtract 1 for the row move
-
-      column = column - columnCount
-      @setScreenPosition({row, column})
-
-  # Public: Moves the cursor right one screen column.
-  #
-  # * `columnCount` (optional) {Number} number of columns to move (default: 1)
-  # * `options` (optional) {Object} with the following keys:
-  #   * `moveToEndOfSelection` if true, move to the right of the selection if a
-  #     selection exists.
-  moveRight: (columnCount=1, {moveToEndOfSelection}={}) ->
-    range = @marker.getScreenRange()
-    if moveToEndOfSelection and not range.isEmpty()
-      @setScreenPosition(range.end)
-    else
-      { row, column } = @getScreenPosition()
-      maxLines = @editor.getScreenLineCount()
-      rowLength = @editor.lineTextForScreenRow(row).length
-      columnsRemainingInLine = rowLength - column
-
-      while columnCount > columnsRemainingInLine and row < maxLines - 1
-        columnCount -= columnsRemainingInLine
-        columnCount-- # subtract 1 for the row move
-
-        column = 0
-        rowLength = @editor.lineTextForScreenRow(++row).length
-        columnsRemainingInLine = rowLength
-
-      column = column + columnCount
-      @setScreenPosition({row, column}, skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
-
-  # Public: Moves the cursor to the top of the buffer.
-  moveToTop: ->
-    @setBufferPosition([0,0])
-
-  # Public: Moves the cursor to the bottom of the buffer.
-  moveToBottom: ->
-    @setBufferPosition(@editor.getEofBufferPosition())
-
-  # Public: Moves the cursor to the beginning of the line.
-  moveToBeginningOfScreenLine: ->
-    @setScreenPosition([@getScreenRow(), 0])
-
-  # Public: Moves the cursor to the beginning of the buffer line.
-  moveToBeginningOfLine: ->
-    @setBufferPosition([@getBufferRow(), 0])
-
-  # Public: Moves the cursor to the beginning of the first character in the
-  # line.
-  moveToFirstCharacterOfLine: ->
-    screenRow = @getScreenRow()
-    lineBufferRange = @editor.bufferRangeForScreenRange([[screenRow, 0], [screenRow, Infinity]])
-
-    firstCharacterColumn = null
-    @editor.scanInBufferRange /\S/, lineBufferRange, ({range, stop}) ->
-      firstCharacterColumn = range.start.column
-      stop()
-
-    if firstCharacterColumn? and firstCharacterColumn isnt @getBufferColumn()
-      targetBufferColumn = firstCharacterColumn
-    else
-      targetBufferColumn = lineBufferRange.start.column
-
-    @setBufferPosition([lineBufferRange.start.row, targetBufferColumn])
-
-  # Public: Moves the cursor to the end of the line.
-  moveToEndOfScreenLine: ->
-    @setScreenPosition([@getScreenRow(), Infinity])
-
-  # Public: Moves the cursor to the end of the buffer line.
-  moveToEndOfLine: ->
-    @setBufferPosition([@getBufferRow(), Infinity])
-
-  # Public: Moves the cursor to the beginning of the word.
-  moveToBeginningOfWord: ->
-    @setBufferPosition(@getBeginningOfCurrentWordBufferPosition())
-
-  # Public: Moves the cursor to the end of the word.
-  moveToEndOfWord: ->
-    if position = @getEndOfCurrentWordBufferPosition()
-      @setBufferPosition(position)
-
-  # Public: Moves the cursor to the beginning of the next word.
-  moveToBeginningOfNextWord: ->
-    if position = @getBeginningOfNextWordBufferPosition()
-      @setBufferPosition(position)
-
-  # Public: Moves the cursor to the previous word boundary.
-  moveToPreviousWordBoundary: ->
-    if position = @getPreviousWordBoundaryBufferPosition()
-      @setBufferPosition(position)
-
-  # Public: Moves the cursor to the next word boundary.
-  moveToNextWordBoundary: ->
-    if position = @getMoveNextWordBoundaryBufferPosition()
-      @setBufferPosition(position)
-
-  # Public: Moves the cursor to the beginning of the buffer line, skipping all
-  # whitespace.
-  skipLeadingWhitespace: ->
-    position = @getBufferPosition()
-    scanRange = @getCurrentLineBufferRange()
-    endOfLeadingWhitespace = null
-    @editor.scanInBufferRange /^[ \t]*/, scanRange, ({range}) ->
-      endOfLeadingWhitespace = range.end
-
-    @setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
-
-  # Public: Retrieves the buffer position of where the current word starts.
-  #
-  # * `options` (optional) An {Object} with the following keys:
-  #   * `wordRegex` A {RegExp} indicating what constitutes a "word"
-  #     (default: {::wordRegExp}).
-  #   * `includeNonWordCharacters` A {Boolean} indicating whether to include
-  #     non-word characters in the default word regex.
-  #     Has no effect if wordRegex is set.
-  #   * `allowPrevious` A {Boolean} indicating whether the beginning of the
-  #     previous word can be returned.
-  #
-  # Returns a {Range}.
-  getBeginningOfCurrentWordBufferPosition: (options = {}) ->
-    allowPrevious = options.allowPrevious ? true
-    currentBufferPosition = @getBufferPosition()
-    previousNonBlankRow = @editor.buffer.previousNonBlankRow(currentBufferPosition.row) ? 0
-    scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
-
-    beginningOfWordPosition = null
-    @editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
-      if range.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
-        beginningOfWordPosition = range.start
-      if not beginningOfWordPosition?.isEqual(currentBufferPosition)
-        stop()
-
-    if beginningOfWordPosition?
-      beginningOfWordPosition
-    else if allowPrevious
-      new Point(0, 0)
-    else
-      currentBufferPosition
-
-  # Public: Retrieves buffer position of previous word boundary. It might be on
-  # the current word, or the previous word.
-  getPreviousWordBoundaryBufferPosition: (options = {}) ->
-    currentBufferPosition = @getBufferPosition()
-    previousNonBlankRow = @editor.buffer.previousNonBlankRow(currentBufferPosition.row)
-    scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
-
-    beginningOfWordPosition = null
-    @editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
-      if range.start.row < currentBufferPosition.row and currentBufferPosition.column > 0
-        # force it to stop at the beginning of each line
-        beginningOfWordPosition = new Point(currentBufferPosition.row, 0)
-      else if range.end.isLessThan(currentBufferPosition)
-        beginningOfWordPosition = range.end
-      else
-        beginningOfWordPosition = range.start
-
-      if not beginningOfWordPosition?.isEqual(currentBufferPosition)
-        stop()
-
-    beginningOfWordPosition or currentBufferPosition
-
-  # Public: Retrieves buffer position of the next word boundary. It might be on
-  # the current word, or the previous word.
-  getMoveNextWordBoundaryBufferPosition: (options = {}) ->
-    currentBufferPosition = @getBufferPosition()
-    scanRange = [currentBufferPosition, @editor.getEofBufferPosition()]
-
-    endOfWordPosition = null
-    @editor.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
-      if range.start.row > currentBufferPosition.row
-        # force it to stop at the beginning of each line
-        endOfWordPosition = new Point(range.start.row, 0)
-      else if range.start.isGreaterThan(currentBufferPosition)
-        endOfWordPosition = range.start
-      else
-        endOfWordPosition = range.end
-
-      if not endOfWordPosition?.isEqual(currentBufferPosition)
-        stop()
-
-    endOfWordPosition or currentBufferPosition
-
-  # Public: Retrieves the buffer position of where the current word ends.
-  #
-  # * `options` (optional) {Object} with the following keys:
-  #   * `wordRegex` A {RegExp} indicating what constitutes a "word"
-  #      (default: {::wordRegExp})
-  #   * `includeNonWordCharacters` A Boolean indicating whether to include
-  #     non-word characters in the default word regex. Has no effect if
-  #     wordRegex is set.
-  #
-  # Returns a {Range}.
-  getEndOfCurrentWordBufferPosition: (options = {}) ->
-    allowNext = options.allowNext ? true
-    currentBufferPosition = @getBufferPosition()
-    scanRange = [currentBufferPosition, @editor.getEofBufferPosition()]
-
-    endOfWordPosition = null
-    @editor.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
-      if range.start.isLessThanOrEqual(currentBufferPosition) or allowNext
-        endOfWordPosition = range.end
-      if not endOfWordPosition?.isEqual(currentBufferPosition)
-        stop()
-
-    endOfWordPosition ? currentBufferPosition
-
-  # Public: Retrieves the buffer position of where the next word starts.
-  #
-  # * `options` (optional) {Object}
-  #   * `wordRegex` A {RegExp} indicating what constitutes a "word"
-  #     (default: {::wordRegExp}).
-  #
-  # Returns a {Range}
-  getBeginningOfNextWordBufferPosition: (options = {}) ->
-    currentBufferPosition = @getBufferPosition()
-    start = if @isInsideWord() then @getEndOfCurrentWordBufferPosition() else currentBufferPosition
-    scanRange = [start, @editor.getEofBufferPosition()]
-
-    beginningOfNextWordPosition = null
-    @editor.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
-      beginningOfNextWordPosition = range.start
-      stop()
-
-    beginningOfNextWordPosition or currentBufferPosition
-
-  # Public: Returns the buffer Range occupied by the word located under the cursor.
-  #
-  # * `options` (optional) {Object}
-  #   * `wordRegex` A {RegExp} indicating what constitutes a "word"
-  #     (default: {::wordRegExp}).
-  getCurrentWordBufferRange: (options={}) ->
-    startOptions = _.extend(_.clone(options), allowPrevious: false)
-    endOptions = _.extend(_.clone(options), allowNext: false)
-    new Range(@getBeginningOfCurrentWordBufferPosition(startOptions), @getEndOfCurrentWordBufferPosition(endOptions))
-
-  # Public: Returns the buffer Range for the current line.
-  #
-  # * `options` (optional) {Object}
-  #   * `includeNewline` A {Boolean} which controls whether the Range should
-  #     include the newline.
-  getCurrentLineBufferRange: (options) ->
-    @editor.bufferRangeForBufferRow(@getBufferRow(), options)
-
-  # Public: Moves the cursor to the beginning of the next paragraph
-  moveToBeginningOfNextParagraph: ->
-    if position = @getBeginningOfNextParagraphBufferPosition()
-      @setBufferPosition(position)
-
-  # Public: Moves the cursor to the beginning of the previous paragraph
-  moveToBeginningOfPreviousParagraph: ->
-    if position = @getBeginningOfPreviousParagraphBufferPosition()
-      @setBufferPosition(position)
+  ###
+  Section: Private
+  ###
+
+  changePosition: (options, fn) ->
+    @clearSelection()
+    @needsAutoscroll = options.autoscroll ? @isLastCursor()
+    fn()
+    if @needsAutoscroll
+      @emit 'autoscrolled' # Support legacy editor
+      @autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view
+
+  getPixelRect: ->
+    @editor.pixelRectForScreenRange(@getScreenRange())
+
+  getScreenRange: ->
+    {row, column} = @getScreenPosition()
+    new Range(new Point(row, column), new Point(row, column + 1))
+
+  autoscroll: (options) ->
+    @editor.scrollToScreenRange(@getScreenRange(), options)

   getBeginningOfNextParagraphBufferPosition: (editor) ->
     start = @getBufferPosition()
@@ -589,56 +670,3 @@
         position = range.start
         stop()
     @editor.screenPositionForBufferPosition(position)
-
-  # Public: Retrieves the range for the current paragraph.
-  #
-  # A paragraph is defined as a block of text surrounded by empty lines.
-  #
-  # Returns a {Range}.
-  getCurrentParagraphBufferRange: ->
-    @editor.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow())
-
-  # Public: Returns the characters preceding the cursor in the current word.
-  getCurrentWordPrefix: ->
-    @editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
-
-  # Public: Returns whether the cursor is at the start of a line.
-  isAtBeginningOfLine: ->
-    @getBufferPosition().column == 0
-
-  # Public: Returns the indentation level of the current line.
-  getIndentLevel: ->
-    if @editor.getSoftTabs()
-      @getBufferColumn() / @editor.getTabLength()
-    else
-      @getBufferColumn()
-
-  # Public: Returns whether the cursor is on the line return character.
-  isAtEndOfLine: ->
-    @getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
-
-  # Public: Retrieves the grammar's token scopes for the line.
-  #
-  # Returns an {Array} of {String}s.
-  getScopes: ->
-    @editor.scopesForBufferPosition(@getBufferPosition())
-
-  # Public: Returns true if this cursor has no non-whitespace characters before
-  # its current position.
-  hasPrecedingCharactersOnLine: ->
-    bufferPosition = @getBufferPosition()
-    line = @editor.lineTextForBufferRow(bufferPosition.row)
-    firstCharacterColumn = line.search(/\S/)
-
-    if firstCharacterColumn is -1
-      false
-    else
-      bufferPosition.column > firstCharacterColumn
-
-  # Public: Compare this cursor's buffer position to another cursor's buffer position.
-  #
-  # See {Point::compare} for more details.
-  #
-  # * `otherCursor`{Cursor} to compare against
-  compare: (otherCursor) ->
-    @getBufferPosition().compare(otherCursor.getBufferPosition())


Added more functionality to websocket server

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/MicrosmartSA/ubiquity-ui.git/'


fix reference to undeclared variable

fix reference to undeclared variable


Updated..

Updated..

I check it all on Microsoft World, the archive had a lot of wrong translations... i corrected a lot.
Pd: Someone use google translate to edit the archive, and google translate sucks...(at least long sentences)

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -1,4 +1,4 @@
-module.exports = nativeDescription: "español (América Latina)", englishDescription: "Spanish (Latin America)", translation:
+module.exports = nativeDescription: "Español (América Latina)", englishDescription: "Spanish (Latin America)", translation:
   home:
     slogan: "Aprende a programar jugando"
     no_ie: "¡Lo sentimos! CodeCombat no funciona en Internet Explorer 8 o versiones anteriores." # Warning that only shows up in IE8 and older
@@ -69,7 +69,7 @@
     change_hero: "Cambiar héroe" # Go back from choose inventory to choose hero
     choose_inventory: "Equipar objetos"
     buy_gems: "Comprar gemas"
-#    campaign_desert: "Desert Campaign"
+    campaign_desert: "Camapaña del Desierto"
     campaign_forest: "Campaña del Bosque"
     campaign_dungeon: "Campaña Calabozo"
     subscription_required: "Requiere Suscripción"
@@ -109,7 +109,7 @@
     finishing: "Finalizando"
     sign_in_with_facebook: "Registrarse con Facebook"
     sign_in_with_gplus: "Registrarse con G+"
-    signup_switch: "¿Queres crear una cuenta?"
+    signup_switch: "¿Quieres crear una cuenta?"

   signup:
     email_announcements: "Recibe noticias por email"
@@ -203,8 +203,8 @@
     home: "Inicio" # Not used any more, will be removed soon.
     level: "Nivel" # Like "Level: Dungeons of Kithgard"
     skip: "Omitir"
-    game_menu: "Menu del Juego"
-    guide: "Guia"
+    game_menu: "Menú del Juego"
+    guide: "Guía"
     restart: "Reiniciar"
     goals: "Objetivos"
     goal: "Objetivo"
@@ -213,7 +213,7 @@
     incomplete: "Incompleto"
     timed_out: "Se te acabo el tiempo"
     failing: "Fallando"
-    action_timeline: "Cronologia de Accion"
+    action_timeline: "Cronología de Acción"
     click_to_select: "Has click en una unidad para seleccionarla."
     control_bar_multiplayer: "Multijugador"
     control_bar_join_game: "Ingresar al juego"
@@ -224,7 +224,7 @@
     victory: "Victoria"
     victory_title_prefix: "¡"
     victory_title_suffix: " Completo!"
-    victory_sign_up: "Registrate para recibir actualizaciones"
+    victory_sign_up: "Regístrate para recibir actualizaciones"
     victory_sign_up_poke: "¿Quieres recibir las ultimas noticias por correo? ¡Crea una cuenta gratuita y te mantendremos informado!"
     victory_rate_the_level: "Valora el nivel: " # Only in old-style levels.
     victory_return_to_ladder: "Volver a la escalera"
@@ -288,13 +288,13 @@
     tip_patience: "Paciencia debes tener, joven Padawan. - Yoda"
     tip_documented_bug: "Un error documentad no es un error; es una característica."
     tip_impossible: "Siempre parece imposible hasta que se hace. - Nelson Mandela"
-    tip_talk_is_cheap: "Hablar es barato. Muestrame el código. - Linus Torvalds"
-    tip_first_language: "La cosa más desastroza que puedes aprender es tu primer lenguaje de programación. - Alan Kay"
+    tip_talk_is_cheap: "Hablar es barato. Muéstrame el código. - Linus Torvalds"
+    tip_first_language: "La cosa más desastrosa que puedes aprender es tu primer lenguaje de programación. - Alan Kay"
     tip_hardware_problem: "P: ¿Cuántos programadores son necesarios para cambiar una bombilla eléctrica?  R: Ninguno, es un problema de hardware."
-    tip_hofstadters_law: "Ley de Hofstadter: Siempre toma más tiempo del que esperas, inclso cuando tienes en cuenta la ley de Hofstadter."
+    tip_hofstadters_law: "Ley de Hofstadter: Siempre toma más tiempo del que esperas, incluso cuando tienes en cuenta la ley de Hofstadter."
     tip_premature_optimization: "La optimización prematura es la raíz de la maldad. - Donald Knuth"
     tip_brute_force: "Cuando tengas duda, usa la fuerza bruta. - Ken Thompson"
-    tip_extrapolation: "Solo hay dos tipos de personas: Esas que pueden extraplolar dese información incompleta..."
+    tip_extrapolation: "Solo hay dos tipos de personas: Esas que pueden extrapolar desde información incompleta..."

   game_menu:
     inventory_tab: "Inventario"
@@ -330,7 +330,7 @@
     purchasing: "Comprando..."
     declined: "Su tarjeta fue rechazada"
     retrying: "Error del servidor, recargando."
-    prompt_title: "Gemas insuficientess"
+    prompt_title: "Gemas insuficientes"
     prompt_body: "¿Quieres obtener más?"
     prompt_button: "Entrar al mercado"

@@ -340,13 +340,13 @@
     levels: "Adquirí más practica con un nivel bonus!"
     heroes: "Héroes más poderosos!"
     gems: "Bonus de 3500 todos los meses!"
-    items: "Más de 250 items de bonus!"
+    items: "Más de 250 ítems de bonus!"
     parents: "Para padres"
     parents_title: "Su hijo aprenderá a programar."
     parents_blurb1: "Con CodeCombat, su hijo aprenderá a escribiendo código real. Empezaran aprendiendo comandos simples avanzando a tópicos más complejos."
     parents_blurb2: "Por $9.99 USD/mes, recibirán nuevos desafíos todas las semanas y soporte personal por email de programadores profesionales."
     parents_blurb3: "Sin Riesgo: Garantía de 100% de devolución, fácil 1-click y desuscribirse."
-    subscribe_button: "Suscribete Ahora"
+    subscribe_button: "Suscríbete  Ahora"
     stripe_description: "Suscripción Mensual"
     subscription_required_to_play: "Necesitas una suscripción para jugar este nivel."

@@ -373,7 +373,7 @@
     regeneration: "Regeneración"
     range: "Rango" # As in "attack or visual range"
     blocks: "Bloqueo" # As in "this shield blocks this much damage"
-#    backstab: "Backstab" # As in "this dagger does this much backstab damage"
+    backstab: "Apuñala" # As in "this dagger does this much backstab damage"
     skills: "Habilidades"
     available_for_purchase: "Disponible para Comprar" # Shows up when you have unlocked, but not purchased, a hero in the hero store
     level_to_unlock: "Nivel para desbloquear:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
@@ -383,18 +383,18 @@
 #    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
     read_only: "Sólo Lectura"
     action_name: "nombre"
-#    action_cooldown: "Takes"
-#    action_specific_cooldown: "Cooldown"
+    action_cooldown: "Toma"
+    action_specific_cooldown: "Enfriamiento"
     action_damage: "Daño"
     action_range: "Rango"
-    action_radius: "Radip"
+    action_radius: "Radio"
     action_duration: "Duración"
     example: "Ejemplo"
     ex: "ej" # Abbreviation of "example"
-#    current_value: "Current Value"
-#    default_value: "Default value"
+    current_value: "Valor actual"
+    default_value: "Valor por defecto"
     parameters: "Parámetros"
-#    returns: "Returns"
+    returns: "Devoluciones"
     granted_by: "Concedido por"

   save_load:
@@ -406,7 +406,7 @@
     volume_label: "Volumen"
     music_label: "Música"
     music_description: "Música encendida/apagada."
-    autorun_label: "Autoejecutar"
+    autorun_label: "Auto ejecutar"
     autorun_description: "Controlar ejecución automática de código."
     editor_config: "Config. de Editor"
     editor_config_title: "Configuración del Editor"
@@ -421,8 +421,8 @@
     editor_config_livecompletion_description: "Despliega sugerencias de autocompletado mientras escribes."
     editor_config_invisibles_label: "Mostrar Invisibles"
     editor_config_invisibles_description: "Visualiza invisibles tales como espacios o tabulaciones."
-    editor_config_indentguides_label: "Mostrar guías de indentación"
-    editor_config_indentguides_description: "Visualiza líneas verticales para ver mejor la indentación."
+    editor_config_indentguides_label: "Mostrar guías de indentación"
+    editor_config_indentguides_description: "Visualiza líneas verticales para ver mejor la indentación."
     editor_config_behaviors_label: "Comportamientos Inteligentes"
     editor_config_behaviors_description: "Autocompleta corchetes, llaves y comillas."

@@ -433,8 +433,8 @@
     why_paragraph_2_italic: "bien un premio"
     why_paragraph_2_center: "pero algo divertido"
     why_paragraph_2_italic_caps: "¡NO MAMÁ, TENGO QUE FINALIZAR EL NIVEL!"
-    why_paragraph_2_suffix: "Por tal motivo CodeCombat es un juego multiusuario, no un curso con gamificación. No finalizaremos hasta que terminos--pero en esta ocasión, es una buena cosa."
-    why_paragraph_3: "si te vas a volver adicto a un juego, hazlo a este y conviértete en un de los magos de la era tecnológica."
+    why_paragraph_2_suffix: "Por tal motivo CodeCombat es un juego multiusuario, no un curso con gamificación. No finalizaremos hasta que terminemos--pero en esta ocasión, es una buena cosa."
+    why_paragraph_3: "si te vas a volver adicto a un juego, hazlo a este y conviértete en uno de los magos de la era tecnológica."
     press_title: "Blogeros/Prensa"
     press_paragraph_1_prefix: "¿Quieres escribirnos? Descarga y usa con confianza todos los recursos incluídos en nuestro"
     press_paragraph_1_link: "paquete de prensa"
@@ -470,7 +470,7 @@
     forum_suffix: "en su lugar."
     send: "Enviar Comentario"
     contact_candidate: "Contacta un Candidato" # Deprecated
-    recruitment_reminder: "Usa este formulario para llegar a los candidadtos que estés interesado en entrevistar. Recuerda que CodeCombat cobra 18% del primer año de salario. Este honorario se debe a la contratación del empleado y reembolsable por 90 days si el empleado no permanece contratado. Tiempo partcial, remoto, y empleados por contrato son gratiso, como así también internos." # Deprecated
+    recruitment_reminder: "Usa este formulario para llegar a los candidatos que estés interesado en entrevistar. Recuerda que CodeCombat cobra 18% del primer año de salario. Este honorario se debe a la contratación del empleado y reembolsable por 90 días si el empleado no permanece contratado. Tiempo parcial, remoto, y empleados por contrato son gratis, como así también internos." # Deprecated

   account_settings:
     title: "Configuración de la Cuenta"
@@ -539,9 +539,9 @@
 #    thang_editor_suffix: "to modify the CodeCombat source artwork. Allow units to throw projectiles, alter the direction of an animation, change a unit's hit points, or upload your own vector sprites."
 #    article_editor_prefix: "See a mistake in some of our docs? Want to make some instructions for your own creations? Check out the"
 #    article_editor_suffix: "and help CodeCombat players get the most out of their playtime."
-#    find_us: "Find us on these sites"
-#    social_blog: "Read the CodeCombat blog on Sett"
-#    social_discource: "Join the discussion on our Discourse forum"
+    find_us: "Encuentranos en etsos sitios"
+    social_blog: "Lee el blog de CodeCombat en Sett"
+    social_discource: "Unite a la discusión en nuestro foro"
     social_facebook: "Me Gusta CodeCombat en Facebook"
     social_twitter: "Sigue a CodeCombat en Twitter"
     social_gplus: "Únete a CodeCombat con Google+"
@@ -573,56 +573,56 @@
     revert_models: "Revertir Modelos"
     pick_a_terrain: "Elije un Terreno"
     small: "Pequeño"
-#    grassy: "Grassy"
+    grassy: "Herboso"
 #    fork_title: "Fork New Version"
 #    fork_creating: "Creating Fork..."
-#    generate_terrain: "Generate Terrain"
-#    more: "More"
-#    wiki: "Wiki"
-#    live_chat: "Live Chat"
+    generate_terrain: "Generar terreno"
+    more: "Más"
+    wiki: "Wiki"
+    live_chat: "Chat en vivo"
 #    level_some_options: "Some Options?"
 #    level_tab_thangs: "Thangs"
 #    level_tab_scripts: "Scripts"
-#    level_tab_settings: "Settings"
-#    level_tab_components: "Components"
-#    level_tab_systems: "Systems"
-#    level_tab_docs: "Documentation"
+    level_tab_settings: "Opciones"
+    level_tab_components: "Componentes"
+    level_tab_systems: "Sistemas"
+    level_tab_docs: "Documentación"
 #    level_tab_thangs_title: "Current Thangs"
-#    level_tab_thangs_all: "All"
+    level_tab_thangs_all: "Todo"
 #    level_tab_thangs_conditions: "Starting Conditions"
 #    level_tab_thangs_add: "Add Thangs"
-#    delete: "Delete"
-#    duplicate: "Duplicate"
-#    rotate: "Rotate"
-#    level_settings_title: "Settings"
-#    level_component_tab_title: "Current Components"
-#    level_component_btn_new: "Create New Component"
-#    level_systems_tab_title: "Current Systems"
-#    level_systems_btn_new: "Create New System"
-#    level_systems_btn_add: "Add System"
+    delete: "Borrar"
+    duplicate: "Duplicar"
+    rotate: "Rotar"
+    level_settings_title: "Opciones"
+    level_component_tab_title: "Componentes Actuales"
+   level_component_btn_new: "Crear Nuevo Componente"
+    level_systems_tab_title: "Sistemas Actuales Systems"
+    level_systems_btn_new: "Crear Nuevo Sistema New System"
+    level_systems_btn_add: "Agregar Sistema"
 #    level_components_title: "Back to All Thangs"
-#    level_components_type: "Type"
-#    level_component_edit_title: "Edit Component"
+    level_components_type: "Tipo"
+    level_component_edit_title: "Editar Componente"
 #    level_component_config_schema: "Config Schema"
-#    level_component_settings: "Settings"
-#    level_system_edit_title: "Edit System"
-#    create_system_title: "Create New System"
-#    new_component_title: "Create New Component"
-#    new_component_field_system: "System"
-#    new_article_title: "Create a New Article"
+    level_component_settings: "Opciones"
+    level_system_edit_title: "Editar Sistema"
+    create_system_title: "Crear Nuevo Sistema"
+    new_component_title: "Crear Nuevo Componente"
+    new_component_field_system: "Sistema"
+    new_article_title: "Crear un Nuevo Artículo"
 #    new_thang_title: "Create a New Thang Type"
-#    new_level_title: "Create a New Level"
-#    new_article_title_login: "Log In to Create a New Article"
+    new_level_title: "Crear un Nuevo Nivel"
+   new_article_title_login: "Ingresa para Crear un Nuevo Artículo"
 #    new_thang_title_login: "Log In to Create a New Thang Type"
-#    new_level_title_login: "Log In to Create a New Level"
-#    new_achievement_title: "Create a New Achievement"
-#    new_achievement_title_login: "Log In to Create a New Achievement"
-#    article_search_title: "Search Articles Here"
+    new_level_title_login: "Ingresa para Crear un Nuevo Nivel"
+   new_achievement_title: "Crear un Nuevo Logro"
+    new_achievement_title_login: "Ingresa para Crear un Nuevo Logro"
+    article_search_title: "Buscar Artículos aquí"
 #    thang_search_title: "Search Thang Types Here"
-#    level_search_title: "Search Levels Here"
-#    achievement_search_title: "Search Achievements"
-#    read_only_warning2: "Note: you can't save any edits here, because you're not logged in."
-#    no_achievements: "No achievements have been added for this level yet."
+    level_search_title: "Buscar Niveles aquí"
+    achievement_search_title: "Buscar logros"
+    read_only_warning2: "Nota: no puedes guardar ediciones aquí, porque no estas logeado."
+    no_achievements: "No hay logros agregados en este nivel por ahora."
 #    achievement_query_misc: "Key achievement off of miscellanea"
 #    achievement_query_goals: "Key achievement off of level goals"
 #    level_completion: "Level Completion"
@@ -808,26 +808,26 @@
     left_xp_postfix: ""

 #  account:
-#    recently_played: "Recently Played"
-#    no_recent_games: "No games played during the past two weeks."
+    recently_played: "Recientemente jugado"
+#    no_recent_games: "No juegos jugados duramente las últimas dos semanas."
 #    payments: "Payments"
 #    purchased: "Purchased"
-#    subscription: "Subscription"
-#    service_apple: "Apple"
-#    service_web: "Web"
+    subscription: "Suscripción"
+    service_apple: "Apple"
+    service_web: "Web"
 #    paid_on: "Paid On"
-#    service: "Service"
-#    price: "Price"
-#    gems: "Gems"
-#    active: "Active"
-#    subscribed: "Subscribed"
-#    unsubscribed: "Unsubscribed"
-#    active_until: "Active Until"
-#    cost: "Cost"
-#    next_payment: "Next Payment"
-#    card: "Card"
-#    status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
-#    status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"
+    service: "Servicio"
+    price: "Precio"
+    gems: "Gemas"
+    active: "Activo"
+    subscribed: "Suscripto"
+    unsubscribed: "Insuscripto"
+   active_until: "Activo Hasta"
+    cost: "Costo"
+    next_payment: "Próximo Pago"
+    card: "Tarjeta"
+    status_unsubscribed_active: "No estas suscripto y no se te cobrará, pero tu cuenta estar activa por ahora."
+    status_unsubscribed: "Obtén acceso a nuevos niveles, heroés, items y gemas extras con la suscripción a CodeCombat!"

   loading_error:
     could_not_load: "Error cargando del servidor"
@@ -843,7 +843,7 @@
     unknown: "Error desconocido."

   resources:
-#    sessions: "Sessions"
+    sessions: "Sesiones"
     your_sessions: "Tus sesiones"
     level: "Nivel"
     social_network_apis: "APIs de Redes Sociales"
@@ -858,77 +858,77 @@
     patches: "Parches"
 #    patched_model: "Source Document"
     model: "Modelo"
-#    system: "System"
-#    systems: "Systems"
-#    component: "Component"
-#    components: "Components"
+    system: "Sistema"
+    systems: "Sistemas"
+    component: "Componente"
+    components: "Componentes"
 #    thang: "Thang"
 #    thangs: "Thangs"
-#    level_session: "Your Session"
-#    opponent_session: "Opponent Session"
-#    article: "Article"
-#    user_names: "User Names"
+    level_session: "Tu Sesión"
+    opponent_session: "Sesión del Oponente"
+    article: "Artícule"
+   user_names: "Nombres de usuario"
 #    thang_names: "Thang Names"
     files: "Archivos"
 #    top_simulators: "Top Simulators"
 #    source_document: "Source Document"
-#    document: "Document"
+    document: "Documento"
 #    sprite_sheet: "Sprite Sheet"
     employers: "Empleadores"
     candidates: "Candidatos"
 #    candidate_sessions: "Candidate Sessions"
 #    user_remark: "User Remark"
 #    user_remarks: "User Remarks"
-#    versions: "Versions"
-#    items: "Items"
+    versions: "Versiones"
+    items: "Items"
     heroes: "Héroes"
-#    achievement: "Achievement"
+#   achievement: "Logros"
 #    clas: "CLAs"
 #    play_counts: "Play Counts"
-#    feedback: "Feedback"
-#    payment_info: "Payment Info"
-
-#  delta:
-#    added: "Added"
-#    modified: "Modified"
-#    deleted: "Deleted"
+    feedback: "Feedback"
+    payment_info: "Información de pago"
+
+  delta:
+    added: "Agregado"
+    modified: "Modificado"
+    deleted: "Borrado"
 #    moved_index: "Moved Index"
 #    text_diff: "Text Diff"
 #    merge_conflict_with: "MERGE CONFLICT WITH"
-#    no_changes: "No Changes"
-
-#  guide:
-#    temp: "Temp"
+#    no_changes: "Sin cambios"
+
+  guide:
+   temp: "Temp"

   multiplayer:
     multiplayer_title: "Configuración de Multijugador" # We'll be changing this around significantly soon. Until then, it's not important to translate.
-#    multiplayer_toggle: "Enable multiplayer"
-#    multiplayer_toggle_description: "Allow others to join your game."
+    multiplayer_toggle: "Activar Multijugador"
+    multiplayer_toggle_description: "Permitir a otros ingresar a tu juego"
     multiplayer_link_description: "Da este enlace a cualquiera para que se te una."
     multiplayer_hint_label: "Consejo:"
     multiplayer_hint: " Cliquea el enlace para seleccionar todo, luego presiona ⌘-C o Ctrl-C para copiar el enlace."
     multiplayer_coming_soon: "¡Más características de multijugador por venir!"
 #    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."

-#  legal:
-#    page_title: "Legal"
-#    opensource_intro: "CodeCombat is completely open source."
-#    opensource_description_prefix: "Check out "
-#    github_url: "our GitHub"
-#    opensource_description_center: "and help out if you like! CodeCombat is built on dozens of open source projects, and we love them. See "
-#    archmage_wiki_url: "our Archmage wiki"
-#    opensource_description_suffix: "for a list of the software that makes this game possible."
+  legal:
+    page_title: "Legal"
+    opensource_intro: "CodeCombat is completamente open source."
+    opensource_description_prefix: "Echa un vistazo "
+    github_url: "nuestro GitHub"
+    opensource_description_center: "y ayudanos si quieres! CodeCombat esta construido por docenas de proyectos open source, y los amamos. Mira "
+    archmage_wiki_url: "nuestra wiki de Archimago wiki"
+    opensource_description_suffix: "Para la lista de softwares que hacen al juego posible."
 #    practices_title: "Respectful Best Practices"
 #    practices_description: "These are our promises to you, the player, in slightly less legalese."
-#    privacy_title: "Privacy"
-#    privacy_description: "We will not sell any of your personal information."
-#    security_title: "Security"
+    privacy_title: "Privacidad"
+    privacy_description: "No vederemos nada sobre tu información personalWe will not sell any of your personal information."
+    security_title: "Seguridad"
 #    security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
-#    email_title: "Email"
-#    email_description_prefix: "We will not inundate you with spam. Through"
-#    email_settings_url: "your email settings"
-#    email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time."
-#    cost_title: "Cost"
+    email_title: "Mail"
+    email_description_prefix: "No te vamos a inundar de Spam. Mediante"
+   email_settings_url: "tus opciones de mail"
+    email_description_suffix: "o mediante links en los mails que mandamos, tu puedas cambiar tus preferencias y fácilmente desuscribirte en cualquier momento."
+    cost_title: "Costo"
 #    cost_description: "CodeCombat is free to play for all of its core levels, with a $9.99 USD/mo subscription for access to extra level branches and 3500 bonus gems per month. You can cancel with a click, and we offer a 100% money-back guarantee."
 #    copyrights_title: "Copyrights and Licenses"
 #    contributor_title: "Contributor License Agreement"
@@ -943,8 +943,8 @@
 #    art_description_prefix: "All common content is available under the"
 #    cc_license_url: "Creative Commons Attribution 4.0 International License"
 #    art_description_suffix: "Common content is anything made generally available by CodeCombat for the purpose of creating Levels. This includes:"
-#    art_music: "Music"
-#    art_sound: "Sound"
+    art_music: "Musica"
+    art_sound: "Sonido"
 #    art_artwork: "Artwork"
 #    art_sprites: "Sprites"
 #    art_other: "Any and all other non-code creative works that are made available when creating Levels."
@@ -953,7 +953,7 @@
 #    use_list_1: "If used in a movie or another game, include codecombat.com in the credits."
 #    use_list_2: "If used on a website, include a link near the usage, for example underneath an image, or in a general attributions page where you might also mention other Creative Commons works and open source software being used on the site. Something that's already clearly referencing CodeCombat, such as a blog post mentioning CodeCombat, does not need some separate attribution."
 #    art_paragraph_2: "If the content being used is created not by CodeCombat but instead by a user of codecombat.com, attribute them instead, and follow attribution directions provided in that resource's description if there are any."
-#    rights_title: "Rights Reserved"
+    rights_title: "Derechos Reservados"
 #    rights_desc: "All rights are reserved for Levels themselves. This includes"
 #    rights_scripts: "Scripts"
 #    rights_unit: "Unit configuration"
@@ -965,24 +965,24 @@
 #    nutshell_description: "Any resources we provide in the Level Editor are free to use as you like for creating Levels. But we reserve the right to restrict distribution of the Levels themselves (that are created on codecombat.com) so that they may be charged for in the future, if that's what ends up happening."
 #    canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."

-#  ladder_prizes:
-#    title: "Tournament Prizes" # This section was for an old tournament and doesn't need new translations now.
-#    blurb_1: "These prizes will be awarded according to"
-#    blurb_2: "the tournament rules"
-#    blurb_3: "to the top human and ogre players."
-#    blurb_4: "Two teams means double the prizes!"
-#    blurb_5: "(There will be two first place winners, two second-place winners, etc.)"
-#    rank: "Rank"
-#    prizes: "Prizes"
-#    total_value: "Total Value"
-#    in_cash: "in cash"
-#    custom_wizard: "Custom CodeCombat Wizard"
+  ladder_prizes:
+    title: "Premios de Torneos" # This section was for an old tournament and doesn't need new translations now.
+    blurb_1: "Estos premios seran dados de acuerdo a "
+    blurb_2: "las reglas del torneo"
+    blurb_3: "a los mejores jugadores humanos y ogros."
+    blurb_4: "Dos equipos significan el doble de premios!"
+    blurb_5: "(Habrán dos ganadores en el primer puesto, dos en el segundo puesto, etc.)"
+    rank: "Ranking"
+    prizes: "Premios"
+    total_value: "Valor Total"
+    in_cash: "en dinero"
+#    custom_wizard: "CodeCombat Mago Modificado"
 #    custom_avatar: "Custom CodeCombat avatar"
 #    heap: "for six months of \"Startup\" access"
-#    credits: "credits"
-#    one_month_coupon: "coupon: choose either Rails or HTML"
-#    one_month_discount: "discount, 30% off: choose either Rails or HTML"
-#    license: "license"
+   credits: "creditos"
+    one_month_coupon: "Cupón: elegí entre Rails o HTML."
+    one_month_discount: "descuento del 30%: elegí entre Rails o HTML"
+    license: "licencia"
 #    oreilly: "ebook of your choice"

   account_profile:
@@ -1091,8 +1091,8 @@
     player_code: "Código de Jugador"

   employers:
-#    deprecation_warning_title: "Sorry, CodeCombat is not recruiting right now."
-#    deprecation_warning: "We are focusing on beginner levels instead of finding expert developers for the time being."
+    deprecation_warning_title: "Lo sentimos, CodeCombat no esta reclutando en estos momentos."
+    deprecation_warning: "Estamos enfocados en los niveles de principiante en lugar de buscar desarrolladores expertos por el momento."
     hire_developers_not_credentials: "Contrata desarrolladores, no credenciales." # We are not actively recruiting right now, so there's no need to add new translations for the rest of this section.
     get_started: "Comenzar"
     already_screened: "Ya hemos realizado un monitoreo técnico de todos los candidatos"
@@ -1123,7 +1123,7 @@
     what: "Que es CodeCombat?"
     what_blurb: "CodeCombat es un juego multijugador de programación para navegadores. Los jugadores escriben un código para medirse en batalla contra otros desarrolladores. Nuestros jugadores cuentan con experiencia en los principales lenguajes técnicos."
     cost: "¿Cuánto cobramos?"
-    cost_blurb: "Cobramos un 15% del primer salario anual y ofrecemos una garantía de devolución del 100% del dinero por 90 días. No cobramos por candidatos que actualmente se encuentren siendo entrevistados de forma activa por tu compañia."
+    cost_blurb: "Cobramos un 15% del primer salario anual y ofrecemos una garantía de devolución del 100% del dinero por 90 días. No cobramos por candidatos que actualmente se encuentren siendo entrevistados de forma activa por tu compañía."
     candidate_name: "Nombre"
     candidate_location: "Ubicación"
     candidate_looking_for: "Buscando"
@@ -1137,21 +1137,21 @@
     inactive_developers: "Desarrolladores Inactivos"

   admin:
-#    av_espionage: "Espionage" # Really not important to translate /admin controls.
-#    av_espionage_placeholder: "Email or username"
-#    av_usersearch: "User Search"
-#    av_usersearch_placeholder: "Email, username, name, whatever"
-#    av_usersearch_search: "Search"
+    av_espionage: "Espionaje" # Really not important to translate /admin controls.
+    av_espionage_placeholder: "Mail o usuario"
+    av_usersearch: "Buscar Usuario"
+    av_usersearch_placeholder: "Mail, usuario, nombre, lo que sea"
+    av_usersearch_search: "Buscar"
     av_title: "Vistas de Admin"
     av_entities_sub_title: "Entidades"
     av_entities_users_url: "Usuarios"
     av_entities_active_instances_url: "Instancias Activas"
-#    av_entities_employer_list_url: "Employer List"
-#    av_entities_candidates_list_url: "Candidate List"
-#    av_entities_user_code_problems_list_url: "User Code Problems List"
+    av_entities_employer_list_url: "Lista de Empleadores"
+    av_entities_candidates_list_url: "Lista de Candidatos"
+    av_entities_user_code_problems_list_url: "Lista de Usuarios con problemas de código"
     av_other_sub_title: "Otro"
     av_other_debug_base_url: "Base (para depurar base.jade)"
     u_title: "Lista de Usuarios"
-#    ucp_title: "User Code Problems"
+    ucp_title: "Usuario con problemas de código"
     lg_title: "Últimos Juegos"
     clas: "CLAs"


:non-potable_water: Call off on active item when it is removed

--- src/pane.coffee
+++ src/pane.coffee
@@ -281,6 +281,7 @@
   # Public: Just remove the item at the given index.
   removeItemAtIndex: (index, options={}) ->
     item = @items[index]
+    @activeItem.off? 'title-changed', @activeItemTitleChanged if item is @activeItem
     @showNextItem() if item is @activeItem and @items.length > 1
     _.remove(@items, item)
     @state.get('items').remove(index) if options.updateState ? true


v1.3.3

--- src/simrou.coffee
+++ src/simrou.coffee
@@ -1,5 +1,5 @@
 ###*
-* @preserve Simrou v1.3.2 - Released under the MIT License.
+* @preserve Simrou v1.3.3 - Released under the MIT License.
 * Copyright (c) 2012 büro für ideen, www.buero-fuer-ideen.de
 ###

@@ -38,7 +38,7 @@
     # Allows to bulk register routes.
     addRoutes: (routes) ->
         if $.isFunction(routes)
-            list = routes(@)
+            list = routes.call(@)
         else if $.isArray(routes)
             list = []
             for route in routes


Simple Journeyman-BM strategy

Simple Journeyman-BM strategy

error: pathspec 'strategies/BigJourneyman.coffee' did not match any file(s) known to git

--- strategies/BigJourneyman.coffee
+++ strategies/BigJourneyman.coffee
@@ -0,0 +1,32 @@
+{
+  name: 'BigJourneyman'
+  requires: ['Journeyman']
+  gainPriority: (state, my) -> [
+    "Colony" if my.countInDeck("Platinum") > 0
+    "Province" if state.countInSupply("Colony") <= 6 \
+               or state.countInSupply("Province") <= 6
+
+    "Duchy" if 0 < state.gainsToEndGame() <= 5
+    "Estate" if 0 < state.gainsToEndGame() <= 2
+    "Platinum"
+    "Gold"
+    "Journeyman" if my.countInDeck("Journeyman") < 2 \
+                 and my.numCardsInDeck() >= 18
+    "Journeyman" if my.countInDeck("Journeyman") < 1
+    "Silver"
+    "Copper" if state.gainsToEndGame() <= 3
+  ]
+
+  wantsToJM: (state, my) ->
+    #my.draw.length >= 3
+    true
+
+  skipPriority: (state, my) -> [
+    "Copper" if my.getTotalMoney() / my.numCardsInDeck() > 1
+    "Province" if my.countInDeck("Province") > my.countInDeck("Duchy") \
+               and my.countInDeck("Province") > my.countInDeck("Estate")
+    "Duchy" if my.countInDeck("Duchy") > my.countInDeck("Estate")
+    "Estate"
+  ]
+}
+


Fixed issue of level editor getting super low res when resizing the window when not looking at the ThangsTabView.

--- app/lib/surface/Surface.coffee
+++ app/lib/surface/Surface.coffee
@@ -538,6 +538,7 @@
       newHeight = newWidth / aspectRatio
     return unless newWidth > 0 and newHeight > 0
     return if newWidth is oldWidth and newHeight is oldHeight and not @options.spectateGame
+    #return if newWidth < 200 or newHeight < 200
     #scaleFactor = if application.isIPadApp then 2 else 1  # Retina
     scaleFactor = 1
     @normalCanvas.add(@webGLCanvas).attr width: newWidth * scaleFactor, height: newHeight * scaleFactor


unparse

unparse

error: pathspec 'resource/Better' did not match any file(s) known to git
error: pathspec 'BibTeX.coffee' did not match any file(s) known to git


:fire: Delete unused code

:fire: Delete unused code

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -332,7 +332,6 @@
       endRow = Math.min(@model.getScreenLineCount(), startRow + @tileSize)

       tile = @state.content.tiles[startRow] ?= {}
-      tile.startRow = startRow
       tile.top = startRow * @lineHeight - @scrollTop
       tile.left = -@scrollLeft
       tile.height = @tileSize * @lineHeight
@@ -1295,7 +1294,6 @@
         flashCount: 0
         flashDuration: null
         flashClass: null
-        tileRow: tileStartRow
       }

       if flash?


Fix randomized spec failures

Fix randomized spec failures

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -605,18 +605,13 @@
     @lineNumberDecorationsByScreenRow = {}
     @highlightDecorationsById = {}

-    return if @model.isMini()
-
     visibleHighlights = {}
     startRow = @computeStartRow()
     endRow = @computeEndRow()
     return unless 0 <= startRow <= endRow <= Infinity

     for markerId, decorations of @model.decorationsForScreenRowRange(startRow, endRow - 1)
-      marker = @model.getMarker(markerId)
-      continue unless marker.isValid()
-
-      range = marker.getScreenRange()
+      range = @model.getMarker(markerId).getScreenRange()
       for decoration in decorations
         if decoration.isType('line') or decoration.isType('line-number')
           @updateLineDecorationCaches(decoration, range)
@@ -660,7 +655,7 @@
     marker = decoration.getMarker()
     range = marker.getScreenRange()

-    if decoration.isDestroyed() or not marker.isValid() or not range.intersectsRowRange(startRow, endRow - 1)
+    if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(startRow, endRow - 1)
       delete @state.content.highlights[decoration.id]
       return


add atom config

add atom config

error: pathspec 'atom/config.cson' did not match any file(s) known to git

--- atom/config.cson
+++ atom/config.cson
@@ -0,0 +1,48 @@
+"*":
+  editor:
+    invisibles: {}
+    fontFamily: "Source Code Pro Semibold"
+    fontSize: 15
+    showIndentGuide: true
+    tabLength: 4
+  core:
+    themes: [
+      "seti-ui"
+      "seti-syntax"
+    ]
+    audioBeep: false
+    autoHideMenuBar: true
+    disabledPackages: [
+      "metrics"
+      "script"
+      "merge-conflicts"
+    ]
+    ignoredNames: [
+      ".eggs"
+      "*.pyc"
+      ".coverage"
+      "*.egg-info"
+      ".git"
+      ".tox"
+    ]
+  "exception-reporting":
+    userId: "**redacted**"
+  welcome:
+    showOnStartup: false
+  linter:
+    lintOnEditorFocus: false
+    showHighlighting: true
+    lintOnChangeInterval: 1000
+    lintDebug: false
+  "vim-mode":
+    useClipboardAsDefaultRegister: false
+  "linter-jshint": {}
+  emmet: {}
+  "linter-jscs": {}
+  "jscs-fixer": {}
+  whitespace:
+    ignoreWhitespaceOnCurrentLine: false
+  "linter-pep8":
+    maxLineLength: 160
+  "tree-view":
+    hideIgnoredNames: true


atom: add `platformio-ide-terminal` settings

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/blivesta/dotfiles.git/'


test(sref): initial tests

test(sref): initial tests

error: pathspec 'test/spec/directives/sref.coffee' did not match any file(s) known to git

--- test/spec/directives/sref.coffee
+++ test/spec/directives/sref.coffee
@@ -0,0 +1,37 @@
+describe 'srefDirective', ->
+
+  element = null
+  scope   = null
+  state   = null
+
+  beforeEach module 'bp'
+
+  beforeEach module ($stateProvider, $urlRouterProvider) ->
+    $urlRouterProvider.otherwise '/first'
+    $stateProvider
+      .state 'first',
+        url: '/first'
+      .state 'second',
+        url: '/second'
+    null
+
+  beforeEach inject ($rootScope, $compile, $state) ->
+    scope = $rootScope.$new()
+    state = $state
+    element = $compile('

') scope
+    scope.$apply()
+
+  describe 'events', ->
+    it 'should bind tap', ->
+      events = $._data(element.get(0)).events
+      expect(events.tap?).toBe true
+
+    it 'should change state on tap', inject ($timeout) ->
+      element.trigger 'tap'
+      $timeout.flush()
+      expect(state.$current.name).toBe 'second'
+
+    it 'should unbind tap after destroy', ->
+      events = $._data(element.get(0)).events
+      scope.$destroy()
+      expect(events.tap?).toBe false


add cursor unit tests

add cursor unit tests

error: pathspec 'tests/scripts/unit/cursor.coffee' did not match any file(s) known to git

--- tests/scripts/unit/cursor.coffee
+++ tests/scripts/unit/cursor.coffee
@@ -0,0 +1,75 @@
+describe('Cursor', ->
+  describe('basic operations', ->
+    initial = '
+
BoldItalic

+

+
Large

+    '
+    $('#test-container').html(Scribe.Utils.cleanHtml(initial, true))
+    editor = new Scribe.Editor('test-container')
+    cursorManager = new Scribe.MultiCursor(editor)
+
+    it('should set cursor', ->
+      cursorManager.setCursor('id', 2, 'Test', 'red')
+      cursor = cursorManager.container.querySelector('.cursor')
+      expect(cursor.classList.contains('top')).to.be.true
+      inner = cursor.querySelector('.cursor-inner')
+      expect(inner.style.backgroundColor).to.equal('red')
+      name = cursor.querySelector('.cursor-name')
+      expect(name.textContent).to.equal('Test')
+    )
+
+    it('should set at middle of leaf', ->
+      cursorManager.setCursor('id', 2, 'Test', 'red')
+      cursor = cursorManager.container.querySelector('.cursor')
+      expect(cursor.style.left).to.equal('17px')
+      expect(cursor.style.top).to.equal('0px')
+    )
+
+    it('should set at leaf boundary', ->
+      cursorManager.setCursor('id', 4, 'Test', 'red')
+      cursor = cursorManager.container.querySelector('.cursor')
+      expect(cursor.style.left).to.equal('29px')
+      expect(cursor.style.top).to.equal('0px')
+    )
+
+    it('should set at newline', ->
+      cursorManager.setCursor('id', 11, 'Test', 'red')
+      cursor = cursorManager.container.querySelector('.cursor')
+      expect(cursor.style.left).to.equal('0px')
+      expect(cursor.style.top).to.equal('15px')
+    )
+
+    it('should set at beginning', ->
+      cursorManager.setCursor('id', 0, 'Test', 'red')
+      cursor = cursorManager.container.querySelector('.cursor')
+      expect(cursor.style.left).to.equal('0px')
+      expect(cursor.style.top).to.equal('0px')
+    )
+
+    it('should set at end', ->
+      cursorManager.setCursor('id', 17, 'Test', 'red')
+      cursor = cursorManager.container.querySelector('.cursor')
+      expect(cursor.style.left).to.equal('46px')
+      expect(cursor.style.top).to.equal('35px')
+    )
+
+    it('should set at beginning of large font', ->
+      cursorManager.setCursor('id', 12, 'Test', 'red')
+      cursor = cursorManager.container.querySelector('.cursor')
+      expect(cursor.style.left).to.equal('0px')
+      expect(cursor.style.top).to.equal('30px')
+      inner = cursor.querySelector('.cursor-inner')
+      expect(inner.style.height).to.equal('21px')
+    )
+
+    it('should set in middle of large font', ->
+      cursorManager.setCursor('id', 14, 'Test', 'red')
+      cursor = cursorManager.container.querySelector('.cursor')
+      expect(cursor.style.left).to.equal('20px')
+      expect(cursor.style.top).to.equal('30px')
+      inner = cursor.querySelector('.cursor-inner')
+      expect(inner.style.height).to.equal('21px')
+    )
+  )
+)


improved tests for #1416, fixed accidental scope leak in test for #1420

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/maowug/coffeescript.git/'


chore (Test) Add descriptor blocks

chore (Test) Add descriptor blocks

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/AppGyver/ag-module-router.git/'


Fix thangs not rendering on level editor sometimes

When placing a thangtype like Grass, sometimes it would never get rendered
onto the Land spritesheet. This was because the Floating layer would get the
process started (since thangs being placed go there) and the Land layer would
not trigger a re-render when it loaded. Now it does.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/VilkkuV/codecombat.git/'


Lie less in the comments.

Lie less in the comments.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/rfw/basilisk.git/'


Subs dashboard perf

Subs dashboard perf

Update sub sponsors API to use payments collection before users
collection to whittle down potential sponsors.

--- server/users/user_handler.coffee
+++ server/users/user_handler.coffee
@@ -13,6 +13,7 @@
 Clan = require '../clans/Clan'
 LevelSession = require '../levels/sessions/LevelSession'
 LevelSessionHandler = require '../levels/sessions/level_session_handler'
+Payment = require '../payments/Payment'
 SubscriptionHandler = require '../payments/subscription_handler'
 DiscountHandler = require '../payments/discount_handler'
 EarnedAchievement = require '../achievements/EarnedAchievement'
@@ -395,13 +396,13 @@

   getSubSponsors: (req, res) ->
     return @sendForbiddenError(res) unless req.user?.isAdmin()
-    # TODO: fix perf on this query
-    sponsoredSubsAddDate = new Date('2015-03-12')
-    query = {$and: [{dateCreated: {$gte: sponsoredSubsAddDate}}, {"stripe.sponsorSubscriptionID": {$exists: true}}]}
-    User.find query, (err, sponsors) =>
+    Payment.find {$where: 'this.purchaser.valueOf() != this.recipient.valueOf()'}, (err, payments) =>
       return @sendDatabaseError(res, err) if err
-      cleandocs = (@formatEntity(req, doc) for doc in sponsors)
-      @sendSuccess(res, cleandocs)
+      sponsorIDs = (payment.get('purchaser') for payment in payments)
+      User.find {$and: [{_id: {$in: sponsorIDs}}, {"stripe.sponsorSubscriptionID": {$exists: true}}]}, (err, users) =>
+        return @sendDatabaseError(res, err) if err
+        sponsors = (@formatEntity(req, doc) for doc in users when doc.get('stripe').recipients?.length > 0)
+        @sendSuccess(res, sponsors)

   sendOneTimeEmail: (req, res) ->
     # TODO: Should this API be somewhere else?


adding tests for environment

adding tests for environment

error: pathspec 'test/environment.coffee' did not match any file(s) known to git

--- test/environment.coffee
+++ test/environment.coffee
@@ -0,0 +1,9 @@
+describe 'environment', ->
+  describe 'check system properties', ->
+    it 'system path should be set', ->
+      path = process.env.PATH
+      delete process.env.PATH
+      locate 'ls', null, (err, cmd) ->
+        assert.equal 'system path not set', err.message, 'invalid error message'
+        assert.isUndefined cmd
+      process.env.PATH = path