Merge branch 'master' into windows-git-fixes

This commit is contained in:
joshaber
2016-03-01 15:43:47 -05:00
21 changed files with 280 additions and 58 deletions

View File

@@ -34,23 +34,10 @@ module.exports = (grunt) ->
grunt.file.setBase(path.resolve('..'))
# Options
[defaultChannel, releaseBranch] = getDefaultChannelAndReleaseBranch(packageJson.version)
installDir = grunt.option('install-dir')
buildDir = grunt.option('build-dir')
buildDir ?= 'out'
buildDir = path.resolve(buildDir)
channel = grunt.option('channel')
releasableBranches = ['stable', 'beta']
if process.env.APPVEYOR and not process.env.APPVEYOR_PULL_REQUEST_NUMBER
channel ?= process.env.APPVEYOR_REPO_BRANCH if process.env.APPVEYOR_REPO_BRANCH in releasableBranches
if process.env.TRAVIS and not process.env.TRAVIS_PULL_REQUEST
channel ?= process.env.TRAVIS_BRANCH if process.env.TRAVIS_BRANCH in releasableBranches
if process.env.JANKY_BRANCH
channel ?= process.env.JANKY_BRANCH if process.env.JANKY_BRANCH in releasableBranches
channel ?= 'dev'
buildDir = path.resolve(grunt.option('build-dir') ? 'out')
channel = grunt.option('channel') ? defaultChannel
metadata = packageJson
appName = packageJson.productName
@@ -189,7 +176,7 @@ module.exports = (grunt) ->
pkg: grunt.file.readJSON('package.json')
atom: {
appName, channel, metadata,
appName, channel, metadata, releaseBranch,
appFileName, apmFileName,
appDir, buildDir, contentsDir, installDir, shellAppDir, symbolsDir,
}
@@ -310,3 +297,20 @@ module.exports = (grunt) ->
unless process.platform is 'linux' or grunt.option('no-install')
defaultTasks.push 'install'
grunt.registerTask('default', defaultTasks)
getDefaultChannelAndReleaseBranch = (version) ->
if version.match(/dev/) or isBuildingPR()
channel = 'dev'
releaseBranch = null
else
if version.match(/beta/)
channel = 'beta'
else
channel = 'stable'
minorVersion = version.match(/^\d\.\d/)[0]
releaseBranch = "#{minorVersion}-releases"
[channel, releaseBranch]
isBuildingPR = ->
process.env.APPVEYOR_PULL_REQUEST_NUMBER? or process.env.TRAVIS_PULL_REQUEST?

View File

@@ -31,14 +31,9 @@ module.exports = (gruntObject) ->
cp path.join(docsOutputDir, 'api.json'), path.join(buildDir, 'atom-api.json')
grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
channel = grunt.config.get('atom.channel')
switch channel
when 'stable'
isPrerelease = false
when 'beta'
isPrerelease = true
else
return
releaseBranch = grunt.config.get('atom.releaseBranch')
isPrerelease = grunt.config.get('atom.channel') is 'beta'
return unless releaseBranch?
doneCallback = @async()
startTime = Date.now()
@@ -55,7 +50,7 @@ module.exports = (gruntObject) ->
zipAssets buildDir, assets, (error) ->
return done(error) if error?
getAtomDraftRelease isPrerelease, channel, (error, release) ->
getAtomDraftRelease isPrerelease, releaseBranch, (error, release) ->
return done(error) if error?
assetNames = (asset.assetName for asset in assets)
deleteExistingAssets release, assetNames, (error) ->

View File

@@ -5,9 +5,7 @@ module.exports = (grunt) ->
{spawn} = require('./task-helpers')(grunt)
getVersion = (callback) ->
releasableBranches = ['stable', 'beta']
channel = grunt.config.get('atom.channel')
shouldUseCommitHash = if channel in releasableBranches then false else true
shouldUseCommitHash = grunt.config.get('atom.channel') is 'dev'
inRepository = fs.existsSync(path.resolve(__dirname, '..', '..', '.git'))
{version} = require(path.join(grunt.config.get('atom.appDir'), 'package.json'))
if shouldUseCommitHash and inRepository

View File

@@ -77,7 +77,7 @@
"autocomplete-atom-api": "0.10.0",
"autocomplete-css": "0.11.0",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.29.0",
"autocomplete-plus": "2.29.1",
"autocomplete-snippets": "1.10.0",
"autoflow": "0.27.0",
"autosave": "0.23.1",
@@ -89,12 +89,12 @@
"dev-live-reload": "0.47.0",
"encoding-selector": "0.21.0",
"exception-reporting": "0.37.0",
"find-and-replace": "0.197.2",
"find-and-replace": "0.197.3",
"fuzzy-finder": "1.0.2",
"git-diff": "1.0.0",
"go-to-line": "0.30.0",
"grammar-selector": "0.48.1",
"image-view": "0.56.0",
"image-view": "0.57.0",
"incompatible-packages": "0.25.1",
"keybinding-resolver": "0.35.0",
"line-ending-selector": "0.3.1",
@@ -110,7 +110,7 @@
"status-bar": "1.1.0",
"styleguide": "0.45.2",
"symbols-view": "0.111.1",
"tabs": "0.90.2",
"tabs": "0.91.1",
"timecop": "0.33.1",
"tree-view": "0.201.5",
"update-package-dependencies": "0.10.0",
@@ -147,7 +147,7 @@
"language-text": "0.7.0",
"language-todo": "0.27.0",
"language-toml": "0.18.0",
"language-xml": "0.34.3",
"language-xml": "0.34.4",
"language-yaml": "0.25.1"
},
"private": true,

View File

@@ -0,0 +1,8 @@
{
"name": "package-with-a-git-prefixed-git-repo-url",
"repository": {
"type": "git",
"url": "git+https://github.com/example/repo.git"
},
"_id": "this is here to simulate the URL being already normalized by npm. we still need to stript git+ from the beginning and .git from the end."
}

View File

@@ -9,12 +9,23 @@ var quicksort = function () {
// Wowza
if (items.length <= 1) return items;
var pivot = items.shift(), current, left = [], right = [];
/*
This is a multiline comment block with
an empty line inside of it.
Awesome.
*/
while(items.length > 0) {
current = items.shift();
current < pivot ? left.push(current) : right.push(current);
}
// This is a collection of
// single line comments
// ...with an empty line
// among it, geez!
return sort(left).concat(pivot).concat(sort(right));
};
// this is a single-line comment
return sort(Array.apply(this, arguments));
};
};

View File

@@ -430,7 +430,7 @@ describe "LanguageMode", ->
languageMode.foldAll()
fold1 = editor.tokenizedLineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(1).fold
@@ -441,6 +441,14 @@ describe "LanguageMode", ->
fold4 = editor.tokenizedLineForScreenRow(3).fold
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [6, 8]
fold5 = editor.tokenizedLineForScreenRow(6).fold
expect([fold5.getStartRow(), fold5.getEndRow()]).toEqual [11, 16]
fold5.destroy()
fold6 = editor.tokenizedLineForScreenRow(13).fold
expect([fold6.getStartRow(), fold6.getEndRow()]).toEqual [21, 22]
fold6.destroy()
describe ".foldAllAtIndentLevel()", ->
it "folds every foldable range at a given indentLevel", ->
languageMode.foldAllAtIndentLevel(2)
@@ -450,19 +458,48 @@ describe "LanguageMode", ->
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(11).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 14]
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 16]
fold2.destroy()
fold3 = editor.tokenizedLineForScreenRow(17).fold
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [17, 20]
fold3.destroy()
fold4 = editor.tokenizedLineForScreenRow(21).fold
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [21, 22]
fold4.destroy()
fold5 = editor.tokenizedLineForScreenRow(24).fold
expect([fold5.getStartRow(), fold5.getEndRow()]).toEqual [24, 25]
fold5.destroy()
it "does not fold anything but the indentLevel", ->
languageMode.foldAllAtIndentLevel(0)
fold1 = editor.tokenizedLineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(5).fold
expect(fold2).toBeFalsy()
describe ".isFoldableAtBufferRow(bufferRow)", ->
it "returns true if the line starts a multi-line comment", ->
expect(languageMode.isFoldableAtBufferRow(1)).toBe true
expect(languageMode.isFoldableAtBufferRow(6)).toBe true
expect(languageMode.isFoldableAtBufferRow(8)).toBe false
expect(languageMode.isFoldableAtBufferRow(11)).toBe true
expect(languageMode.isFoldableAtBufferRow(15)).toBe false
expect(languageMode.isFoldableAtBufferRow(17)).toBe true
expect(languageMode.isFoldableAtBufferRow(21)).toBe true
expect(languageMode.isFoldableAtBufferRow(24)).toBe true
expect(languageMode.isFoldableAtBufferRow(28)).toBe false
it "does not return true for a line in the middle of a comment that's followed by an indented line", ->
expect(languageMode.isFoldableAtBufferRow(7)).toBe false
editor.buffer.insert([8, 0], ' ')
expect(languageMode.isFoldableAtBufferRow(7)).toBe false
describe "css", ->
beforeEach ->
waitsForPromise ->

View File

@@ -55,12 +55,17 @@ describe "PackageManager", ->
it "normalizes short repository urls in package.json", ->
{metadata} = atom.packages.loadPackage("package-with-short-url-package-json")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "https://github.com/example/repo.git"
expect(metadata.repository.url).toBe "https://github.com/example/repo"
{metadata} = atom.packages.loadPackage("package-with-invalid-url-package-json")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "foo"
it "trims git+ from the beginning and .git from the end of repository URLs, even if npm already normalized them ", ->
{metadata} = atom.packages.loadPackage("package-with-prefixed-and-suffixed-repo-url")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "https://github.com/example/repo"
it "returns null if the package is not found in any package directory", ->
spyOn(console, 'warn')
expect(atom.packages.loadPackage("this-package-cannot-be-found")).toBeNull()

View File

@@ -0,0 +1,38 @@
TextEditorRegistry = require '../src/text-editor-registry'
describe "TextEditorRegistry", ->
[registry, editor] = []
beforeEach ->
registry = new TextEditorRegistry
describe "when a TextEditor is added", ->
it "gets added to the list of registered editors", ->
editor = {}
registry.add(editor)
expect(registry.editors.size).toBe 1
expect(registry.editors.has(editor)).toBe(true)
it "returns a Disposable that can unregister the editor", ->
editor = {}
disposable = registry.add(editor)
expect(registry.editors.size).toBe 1
disposable.dispose()
expect(registry.editors.size).toBe 0
describe "when the registry is observed", ->
it "calls the callback for current and future editors until unsubscribed", ->
[editor1, editor2, editor3] = [{}, {}, {}]
registry.add(editor1)
subscription = registry.observe spy = jasmine.createSpy()
expect(spy.calls.length).toBe 1
registry.add(editor2)
expect(spy.calls.length).toBe 2
expect(spy.argsForCall[0][0]).toBe editor1
expect(spy.argsForCall[1][0]).toBe editor2
subscription.dispose()
registry.add(editor3)
expect(spy.calls.length).toBe 2

View File

@@ -2132,20 +2132,31 @@ describe "TextEditor", ->
editor.splitSelectionsIntoLines()
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
describe ".consolidateSelections()", ->
it "destroys all selections but the least recent, returning true if any selections were destroyed", ->
editor.setSelectedBufferRange([[3, 16], [3, 21]])
selection1 = editor.getLastSelection()
describe "::consolidateSelections()", ->
makeMultipleSelections = ->
selection.setBufferRange [[3, 16], [3, 21]]
selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]])
selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]])
selection4 = editor.addSelectionForBufferRange([[1, 6], [1, 10]])
expect(editor.getSelections()).toEqual [selection, selection2, selection3, selection4]
[selection, selection2, selection3, selection4]
it "destroys all selections but the oldest selection and autoscrolls to it, returning true if any selections were destroyed", ->
[selection1] = makeMultipleSelections()
autoscrollEvents = []
editor.onDidRequestAutoscroll (event) -> autoscrollEvents.push(event)
expect(editor.getSelections()).toEqual [selection1, selection2, selection3]
expect(editor.consolidateSelections()).toBeTruthy()
expect(editor.getSelections()).toEqual [selection1]
expect(selection1.isEmpty()).toBeFalsy()
expect(editor.consolidateSelections()).toBeFalsy()
expect(editor.getSelections()).toEqual [selection1]
expect(autoscrollEvents).toEqual([
{screenRange: selection1.getScreenRange(), options: {center: true, reversed: false}}
])
describe "when the cursor is moved while there is a selection", ->
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]

View File

@@ -604,6 +604,53 @@ describe "Workspace", ->
runs ->
expect(pane.getPendingItem()).toBeNull()
describe "when opening will switch from a pending tab to a permanent tab", ->
it "keeps the pending tab open", ->
editor1 = null
editor2 = null
waitsForPromise ->
atom.workspace.open('sample.txt').then (o) ->
editor1 = o
waitsForPromise ->
atom.workspace.open('sample2.txt', pending: true).then (o) ->
editor2 = o
runs ->
pane = atom.workspace.getActivePane()
pane.activateItem(editor1)
expect(pane.getItems().length).toBe 2
expect(pane.getItems()).toEqual [editor1, editor2]
describe "when replacing a pending item which is the last item in a second pane", ->
it "does not destory the pane even if core.destroyEmptyPanes is on", ->
atom.config.set('core.destroyEmptyPanes', true)
editor1 = null
editor2 = null
leftPane = atom.workspace.getActivePane()
rightPane = null
waitsForPromise ->
atom.workspace.open('sample.js', pending: true, split: 'right').then (o) ->
editor1 = o
rightPane = atom.workspace.getActivePane()
spyOn rightPane, "destroyed"
runs ->
expect(leftPane).not.toBe rightPane
expect(atom.workspace.getActivePane()).toBe rightPane
expect(atom.workspace.getActivePane().getItems().length).toBe 1
expect(rightPane.getPendingItem()).toBe editor1
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then (o) ->
editor2 = o
runs ->
expect(rightPane.getPendingItem()).toBe editor2
expect(rightPane.destroyed.callCount).toBe 0
describe "::reopenItem()", ->
it "opens the uri associated with the last closed pane that isn't currently open", ->
pane = workspace.getActivePane()

View File

@@ -40,6 +40,7 @@ Project = require './project'
TextEditor = require './text-editor'
TextBuffer = require 'text-buffer'
Gutter = require './gutter'
TextEditorRegistry = require './text-editor-registry'
WorkspaceElement = require './workspace-element'
PanelContainerElement = require './panel-container-element'
@@ -111,6 +112,9 @@ class AtomEnvironment extends Model
# Public: A {Workspace} instance
workspace: null
# Public: A {TextEditorRegistry} instance
textEditors: null
saveStateDebounceInterval: 1000
###
@@ -183,6 +187,8 @@ class AtomEnvironment extends Model
})
@themes.workspace = @workspace
@textEditors = new TextEditorRegistry
@config.load()
@themes.loadBaseStylesheets()

View File

@@ -319,6 +319,23 @@ ScopeDescriptor = require './scope-descriptor'
# * line breaks - `line breaks<br/>`
# * ~~strikethrough~~ - `~~strikethrough~~`
#
# #### order
#
# The settings view orders your settings alphabetically. You can override this
# ordering with the order key.
#
# ```coffee
# config:
# zSetting:
# type: 'integer'
# default: 4
# order: 1
# aSetting:
# type: 'integer'
# default: 4
# order: 2
# ```
#
# ## Best practices
#
# * Don't depend on (or write to) configuration keys outside of your keypath.

View File

@@ -147,13 +147,11 @@ class LanguageMode
if bufferRow > 0
for currentRow in [bufferRow-1..0] by -1
break if @buffer.isRowBlank(currentRow)
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
startRow = currentRow
if bufferRow < @buffer.getLastRow()
for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1
break if @buffer.isRowBlank(currentRow)
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
endRow = currentRow

View File

@@ -541,11 +541,12 @@ class PackageManager
unless typeof metadata.name is 'string' and metadata.name.length > 0
metadata.name = packageName
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
metadata.repository.url = metadata.repository.url.replace(/(^git\+)|(\.git$)/g, '')
metadata
normalizePackageMetadata: (metadata) ->
unless metadata?._id
normalizePackageData ?= require 'normalize-package-data'
normalizePackageData(metadata)
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
metadata.repository.url = metadata.repository.url.replace(/^git\+/, '')

View File

@@ -423,10 +423,6 @@ class Pane extends Model
return if item in @items
pendingItem = @getPendingItem()
@destroyItem(pendingItem) if pendingItem?
@setPendingItem(item) if pending
if typeof item.onDidDestroy is 'function'
itemSubscriptions = new CompositeDisposable
itemSubscriptions.add item.onDidDestroy => @removeItem(item, false)
@@ -437,6 +433,10 @@ class Pane extends Model
@subscriptionsPerItem.set item, itemSubscriptions
@items.splice(index, 0, item)
pendingItem = @getPendingItem()
@destroyItem(pendingItem) if pendingItem?
@setPendingItem(item) if pending
@emitter.emit 'did-add-item', {item, index, moved}
@setActiveItem(item) unless @getActiveItem()?
item

View File

@@ -810,11 +810,11 @@ class Selection extends Model
@wordwise = false
@linewise = false
autoscroll: ->
autoscroll: (options) ->
if @marker.hasTail()
@editor.scrollToScreenRange(@getScreenRange(), reversed: @isReversed())
@editor.scrollToScreenRange(@getScreenRange(), Object.assign({reversed: @isReversed()}, options))
else
@cursor.autoscroll()
@cursor.autoscroll(options)
clearAutoscroll: ->

View File

@@ -0,0 +1,40 @@
{Emitter, Disposable} = require 'event-kit'
# Experimental: This global registry tracks registered `TextEditors`.
#
# If you want to add functionality to a wider set of text editors than just
# those appearing within workspace panes, use `atom.textEditors.observe` to
# invoke a callback for all current and future registered text editors.
#
# If you want packages to be able to add functionality to your non-pane text
# editors (such as a search field in a custom user interface element), register
# them for observation via `atom.textEditors.add`. **Important:** When you're
# done using your editor, be sure to call `dispose` on the returned disposable
# to avoid leaking editors.
module.exports =
class TextEditorRegistry
constructor: ->
@editors = new Set
@emitter = new Emitter
# Register a `TextEditor`.
#
# * `editor` The editor to register.
#
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# added editor. To avoid any memory leaks this should be called when the
# editor is destroyed.
add: (editor) ->
@editors.add(editor)
@emitter.emit 'did-add-editor', editor
new Disposable => @editors.delete(editor)
# Invoke the given callback with all the current and future registered
# `TextEditors`.
#
# * `callback` {Function} to be called with current and future text editors.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
observe: (callback) ->
@editors.forEach(callback)
@emitter.on 'did-add-editor', callback

View File

@@ -82,7 +82,10 @@ class TextEditor extends Model
state.project = atomEnvironment.project
state.assert = atomEnvironment.assert.bind(atomEnvironment)
state.applicationDelegate = atomEnvironment.applicationDelegate
new this(state)
editor = new this(state)
disposable = atomEnvironment.textEditors.add(editor)
editor.onDidDestroy -> disposable.dispose()
editor
constructor: (params={}) ->
super
@@ -2466,6 +2469,7 @@ class TextEditor extends Model
selections = @getSelections()
if selections.length > 1
selection.destroy() for selection in selections[1...(selections.length)]
selections[0].autoscroll(center: true)
true
else
false

View File

@@ -498,7 +498,6 @@ class TokenizedLine
while iterator.next()
scopes = iterator.getScopes()
continue if scopes.length is 1
continue unless NonWhitespaceRegex.test(iterator.getText())
for scope in scopes
if CommentScopeRegex.test(scope)
@isCommentLine = true

View File

@@ -558,7 +558,10 @@ class Workspace extends Model
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
@grammarRegistry, @project, @assert, @applicationDelegate
}, params)
new TextEditor(params)
editor = new TextEditor(params)
disposable = atom.textEditors.add(editor)
editor.onDidDestroy -> disposable.dispose()
editor
# Public: Asynchronously reopens the last-closed item's URI if it hasn't already been
# reopened.