Merge branch 'master' into as-user-keybindings-priority

# Conflicts:
#	spec/package-manager-spec.coffee
This commit is contained in:
Antonio Scandurra 2015-12-04 16:18:56 +01:00
commit 1c5c973823
44 changed files with 579 additions and 161 deletions

24
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,24 @@
# Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery
- Personal attacks
- Trolling or insulting/derogatory comments
- Public or private harassment
- Publishing other's private information, such as physical or electronic addresses, without explicit permission
- Other unethical or unprofessional conduct
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/

View File

@ -30,7 +30,7 @@ These are just guidelines, not rules, use your best judgment and feel free to pr
### Code of Conduct
This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0).
This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code.
Please report unacceptable behavior to [atom@github.com](mailto:atom@github.com).

View File

@ -11,7 +11,7 @@ Visit [atom.io](https://atom.io) to learn more or visit the [Atom forum](https:/
Follow [@AtomEditor](https://twitter.com/atomeditor) on Twitter for important
announcements.
This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0).
This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior to atom@github.com.
## Documentation

View File

@ -8,6 +8,7 @@
"dependencies": {
"asar": "^0.8.0",
"async": "~0.2.9",
"aws-sdk": "^2.2.18",
"donna": "^1.0.13",
"formidable": "~1.0.14",
"fs-plus": "2.x",

View File

@ -20,9 +20,6 @@ module.exports = (grunt) ->
copyFolder = path.resolve 'script', 'copy-folder.cmd'
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: true) isnt 0
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
createShortcut = path.resolve 'script', 'create-shortcut.cmd'
runas('cmd', ['/c', createShortcut, path.join(installDir, 'atom.exe'), appName])
else if process.platform is 'darwin'
rm installDir
mkdir path.dirname(installDir)

View File

@ -6,6 +6,7 @@ async = require 'async'
fs = require 'fs-plus'
GitHub = require 'github-releases'
request = require 'request'
AWS = require 'aws-sdk'
grunt = null
@ -210,7 +211,7 @@ deleteExistingAssets = (release, assetNames, callback) ->
async.parallel(tasks, callback)
uploadAssets = (release, buildDir, assets, callback) ->
upload = (release, assetName, assetPath, callback) ->
uploadToReleases = (release, assetName, assetPath, callback) ->
options =
uri: release.upload_url.replace(/\{.*$/, "?name=#{assetName}")
method: 'POST'
@ -221,15 +222,43 @@ uploadAssets = (release, buildDir, assets, callback) ->
assetRequest = request options, (error, response, body='') ->
if error? or response.statusCode >= 400
logError("Upload release asset #{assetName} failed", error, body)
logError("Upload release asset #{assetName} to Releases failed", error, body)
callback(error ? new Error(response.statusCode))
else
callback(null, release)
fs.createReadStream(assetPath).pipe(assetRequest)
uploadToS3 = (release, assetName, assetPath, callback) ->
s3Key = process.env.BUILD_ATOM_RELEASES_S3_KEY
s3Secret = process.env.BUILD_ATOM_RELEASES_S3_SECRET
s3Bucket = process.env.BUILD_ATOM_RELEASES_S3_BUCKET
unless s3Key and s3Secret and s3Bucket
callback(new Error('BUILD_ATOM_RELEASES_S3_KEY, BUILD_ATOM_RELEASES_S3_SECRET, and BUILD_ATOM_RELEASES_S3_BUCKET environment variables must be set.'))
return
s3Info =
accessKeyId: s3Key
secretAccessKey: s3Secret
s3 = new AWS.S3 s3Info
key = "releases/#{release.tag_name}/#{assetName}"
uploadParams =
Bucket: s3Bucket
ACL: 'public-read'
Key: key
Body: fs.createReadStream(assetPath)
s3.upload uploadParams, (error, data) ->
if error?
logError("Upload release asset #{assetName} to S3 failed", error)
callback(error)
else
callback(null, release)
tasks = []
for {assetName} in assets
assetPath = path.join(buildDir, assetName)
tasks.push(upload.bind(this, release, assetName, assetPath))
tasks.push(uploadToReleases.bind(this, release, assetName, assetPath))
tasks.push(uploadToS3.bind(this, release, assetName, assetPath))
async.parallel(tasks, callback)

View File

@ -75,8 +75,8 @@
"autocomplete-atom-api": "0.9.2",
"autocomplete-css": "0.11.0",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.23.1",
"autocomplete-snippets": "1.8.0",
"autocomplete-plus": "2.24.0",
"autocomplete-snippets": "1.9.0",
"autoflow": "0.26.0",
"autosave": "0.23.0",
"background-tips": "0.26.0",
@ -87,7 +87,7 @@
"dev-live-reload": "0.47.0",
"encoding-selector": "0.21.0",
"exception-reporting": "0.37.0",
"find-and-replace": "0.191.0",
"find-and-replace": "0.192.0",
"fuzzy-finder": "0.93.0",
"git-diff": "0.57.0",
"go-to-line": "0.30.0",
@ -97,13 +97,13 @@
"keybinding-resolver": "0.33.0",
"line-ending-selector": "0.3.0",
"link": "0.31.0",
"markdown-preview": "0.156.2",
"markdown-preview": "0.157.0",
"metrics": "0.53.1",
"notifications": "0.62.1",
"open-on-github": "0.40.0",
"package-generator": "0.41.0",
"release-notes": "0.53.0",
"settings-view": "0.232.0",
"settings-view": "0.232.1",
"snippets": "1.0.1",
"spell-check": "0.63.0",
"status-bar": "0.80.0",
@ -116,38 +116,38 @@
"welcome": "0.33.0",
"whitespace": "0.32.1",
"wrap-guide": "0.38.1",
"language-c": "0.50.0",
"language-clojure": "0.18.0",
"language-coffee-script": "0.45.0",
"language-c": "0.51.0",
"language-clojure": "0.19.0",
"language-coffee-script": "0.46.0",
"language-csharp": "0.11.0",
"language-css": "0.35.1",
"language-gfm": "0.81.0",
"language-git": "0.10.0",
"language-css": "0.36.0",
"language-gfm": "0.82.0",
"language-git": "0.11.0",
"language-go": "0.40.0",
"language-html": "0.42.0",
"language-hyperlink": "0.15.0",
"language-html": "0.43.1",
"language-hyperlink": "0.16.0",
"language-java": "0.17.0",
"language-javascript": "0.102.1",
"language-json": "0.17.1",
"language-javascript": "0.104.0",
"language-json": "0.17.2",
"language-less": "0.29.0",
"language-make": "0.20.0",
"language-make": "0.21.0",
"language-mustache": "0.13.0",
"language-objective-c": "0.15.0",
"language-perl": "0.31.0",
"language-objective-c": "0.15.1",
"language-perl": "0.32.0",
"language-php": "0.34.0",
"language-property-list": "0.8.0",
"language-python": "0.42.1",
"language-ruby": "0.64.1",
"language-ruby": "0.65.0",
"language-ruby-on-rails": "0.24.0",
"language-sass": "0.44.1",
"language-shellscript": "0.20.0",
"language-sass": "0.45.0",
"language-shellscript": "0.21.0",
"language-source": "0.9.0",
"language-sql": "0.19.0",
"language-sql": "0.20.0",
"language-text": "0.7.0",
"language-todo": "0.27.0",
"language-toml": "0.17.0",
"language-toml": "0.18.0",
"language-xml": "0.34.1",
"language-yaml": "0.24.0"
"language-yaml": "0.25.0"
},
"private": true,
"scripts": {

View File

@ -40,6 +40,10 @@ function setEnvironmentVariables() {
process.env.CC = 'clang';
process.env.CXX = 'clang++';
process.env.npm_config_clang = '1';
} else if (process.platform === 'win32') {
process.env.BUILD_ATOM_RELEASES_S3_KEY = process.env.BUILD_ATOM_WIN_RELEASES_S3_KEY
process.env.BUILD_ATOM_RELEASES_S3_SECRET = process.env.BUILD_ATOM_WIN_RELEASES_S3_SECRET
process.env.BUILD_ATOM_RELEASES_S3_BUCKET = process.env.BUILD_ATOM_WIN_RELEASES_S3_BUCKET
}
}

View File

@ -3,6 +3,9 @@
set -e
export ATOM_ACCESS_TOKEN=$BUILD_ATOM_LINUX_ACCESS_TOKEN
export BUILD_ATOM_RELEASES_S3_KEY=$BUILD_ATOM_LINUX_RELEASES_S3_KEY
export BUILD_ATOM_RELEASES_S3_SECRET=$BUILD_ATOM_LINUX_RELEASES_S3_SECRET
export BUILD_ATOM_RELEASES_S3_BUCKET=$BUILD_ATOM_LINUX_RELEASES_S3_BUCKET
if [ -d /usr/local/share/nodenv ]; then
export NODENV_ROOT=/usr/local/share/nodenv

View File

@ -8,5 +8,8 @@ docker run \
--env JANKY_SHA1="$JANKY_SHA1" \
--env JANKY_BRANCH="$JANKY_BRANCH" \
--env ATOM_ACCESS_TOKEN="$BUILD_ATOM_RPM_ACCESS_TOKEN" \
--env BUILD_ATOM_RELEASES_S3_KEY="$BUILD_ATOM_RPM_RELEASES_S3_KEY" \
--env BUILD_ATOM_RELEASES_S3_SECRET="$BUILD_ATOM_RPM_RELEASES_S3_SECRET" \
--env BUILD_ATOM_RELEASES_S3_BUCKET="$BUILD_ATOM_RPM_RELEASES_S3_BUCKET" \
atom-rpm /atom/script/rpmbuild
docker rmi atom-rpm

View File

@ -1,23 +0,0 @@
@echo off
set USAGE=Usage: %0 source name-on-desktop
if [%1] == [] (
echo %USAGE%
exit 1
)
if [%2] == [] (
echo %USAGE%
exit 2
)
set SCRIPT="%TEMP%\%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%.vbs"
echo Set oWS = WScript.CreateObject("WScript.Shell") >> %SCRIPT%
echo sLinkFile = "%USERPROFILE%\Desktop\%2.lnk" >> %SCRIPT%
echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %SCRIPT%
echo oLink.TargetPath = %1 >> %SCRIPT%
echo oLink.Save >> %SCRIPT%
cscript /nologo %SCRIPT%
del %SCRIPT%

View File

@ -81,6 +81,27 @@ describe 'CompileCache', ->
waits(1)
runs ->
error = new Error("Oops again")
console.log error.stack
expect(error.stack).toContain('compile-cache-spec.coffee')
expect(Array.isArray(error.getRawStack())).toBe true
it 'does not infinitely loop when the original prepareStackTrace value is reassigned', ->
originalPrepareStackTrace = Error.prepareStackTrace
Error.prepareStackTrace = -> 'a-stack-trace'
Error.prepareStackTrace = originalPrepareStackTrace
error = new Error('Oops')
expect(error.stack).toContain('compile-cache-spec.coffee')
expect(Array.isArray(error.getRawStack())).toBe true
it 'does not infinitely loop when the assigned prepareStackTrace calls the original prepareStackTrace', ->
originalPrepareStackTrace = Error.prepareStackTrace
Error.prepareStackTrace = (error, stack) ->
error.foo = 'bar'
originalPrepareStackTrace(error, stack)
error = new Error('Oops')
expect(error.stack).toContain('compile-cache-spec.coffee')
expect(error.foo).toBe('bar')
expect(Array.isArray(error.getRawStack())).toBe true

View File

@ -0,0 +1,6 @@
module.exports = function (state) {
return {
wasDeserializedBy: 'Deserializer1',
state: state
}
}

View File

@ -0,0 +1,6 @@
module.exports = function (state) {
return {
wasDeserializedBy: 'Deserializer2',
state: state
}
}

View File

@ -0,0 +1,3 @@
module.exports = {
activate: function() {}
}

View File

@ -0,0 +1,9 @@
{
"name": "package-with-deserializers",
"version": "1.0.0",
"main": "./index",
"deserializers": {
"Deserializer1": "./deserializer-1.js",
"Deserializer2": "./deserializer-2.js"
}
}

View File

@ -0,0 +1,5 @@
atom.deserializers.add('MyDeserializer', function (state) {
return {state: state, a: 'b'}
})
exports.activate = function () {}

View File

@ -0,0 +1,5 @@
{
"name": "package-with-eval-time-api-calls",
"version": "1.2.3",
"main": "./index"
}

View File

@ -0,0 +1,13 @@
{
"name": "package-with-json-config-schema",
"configSchema": {
"a": {
"type": "number",
"default": 5
},
"b": {
"type": "string",
"default": "five"
}
}
}

View File

@ -1 +1,2 @@
'main': 'main-module.coffee'
'version': '2.3.4'

View File

@ -0,0 +1,3 @@
module.exports = function (state) {
return {state: state}
}

View File

@ -0,0 +1,3 @@
module.exports = {
activate: function() {}
}

View File

@ -0,0 +1,12 @@
{
"name": "package-with-view-providers",
"main": "./index",
"version": "1.0.0",
"deserializers": {
"DeserializerFromPackageWithViewProviders": "./deserializer"
},
"viewProviders": [
"./view-provider-1",
"./view-provider-2"
]
}

View File

@ -0,0 +1,9 @@
'use strict'
module.exports = function (model) {
if (model.worksWithViewProvider1) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-1'
return element
}
}

View File

@ -0,0 +1,9 @@
'use strict'
module.exports = function (model) {
if (model.worksWithViewProvider2) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-2'
return element
}
}

View File

@ -4,6 +4,7 @@ temp = require 'temp'
fs = require 'fs-plus'
{Disposable} = require 'atom'
{buildKeydownEvent} = require '../src/keymap-extensions'
{mockLocalStorage} = require './spec-helper'
describe "PackageManager", ->
workspaceElement = null
@ -82,6 +83,111 @@ describe "PackageManager", ->
expect(loadedPackage.name).toBe "package-with-main"
it "registers any deserializers specified in the package's package.json", ->
pack = atom.packages.loadPackage("package-with-deserializers")
state1 = {deserializer: 'Deserializer1', a: 'b'}
expect(atom.deserializers.deserialize(state1)).toEqual {
wasDeserializedBy: 'Deserializer1'
state: state1
}
state2 = {deserializer: 'Deserializer2', c: 'd'}
expect(atom.deserializers.deserialize(state2)).toEqual {
wasDeserializedBy: 'Deserializer2'
state: state2
}
expect(pack.mainModule).toBeNull()
describe "when there are view providers specified in the package's package.json", ->
model1 = {worksWithViewProvider1: true}
model2 = {worksWithViewProvider2: true}
afterEach ->
atom.packages.deactivatePackage('package-with-view-providers')
atom.packages.unloadPackage('package-with-view-providers')
it "does not load the view providers immediately", ->
pack = atom.packages.loadPackage("package-with-view-providers")
expect(pack.mainModule).toBeNull()
expect(-> atom.views.getView(model1)).toThrow()
expect(-> atom.views.getView(model2)).toThrow()
it "registers the view providers when the package is activated", ->
pack = atom.packages.loadPackage("package-with-view-providers")
waitsForPromise ->
atom.packages.activatePackage("package-with-view-providers").then ->
element1 = atom.views.getView(model1)
expect(element1 instanceof HTMLDivElement).toBe true
expect(element1.dataset.createdBy).toBe 'view-provider-1'
element2 = atom.views.getView(model2)
expect(element2 instanceof HTMLDivElement).toBe true
expect(element2.dataset.createdBy).toBe 'view-provider-2'
it "registers the view providers when any of the package's deserializers are used", ->
pack = atom.packages.loadPackage("package-with-view-providers")
spyOn(atom.views, 'addViewProvider').andCallThrough()
atom.deserializers.deserialize({
deserializer: 'DeserializerFromPackageWithViewProviders',
a: 'b'
})
expect(atom.views.addViewProvider.callCount).toBe 2
atom.deserializers.deserialize({
deserializer: 'DeserializerFromPackageWithViewProviders',
a: 'b'
})
expect(atom.views.addViewProvider.callCount).toBe 2
element1 = atom.views.getView(model1)
expect(element1 instanceof HTMLDivElement).toBe true
expect(element1.dataset.createdBy).toBe 'view-provider-1'
element2 = atom.views.getView(model2)
expect(element2 instanceof HTMLDivElement).toBe true
expect(element2.dataset.createdBy).toBe 'view-provider-2'
it "registers the config schema in the package's metadata, if present", ->
pack = atom.packages.loadPackage("package-with-json-config-schema")
expect(atom.config.getSchema('package-with-json-config-schema')).toEqual {
type: 'object'
properties: {
a: {type: 'number', default: 5}
b: {type: 'string', default: 'five'}
}
}
expect(pack.mainModule).toBeNull()
describe "when a package does not have deserializers, view providers or a config schema in its package.json", ->
beforeEach ->
atom.packages.unloadPackage('package-with-main')
mockLocalStorage()
it "defers loading the package's main module if the package previously used no Atom APIs when its main module was required", ->
pack1 = atom.packages.loadPackage('package-with-main')
expect(pack1.mainModule).toBeDefined()
atom.packages.unloadPackage('package-with-main')
pack2 = atom.packages.loadPackage('package-with-main')
expect(pack2.mainModule).toBeNull()
it "does not defer loading the package's main module if the package previously used Atom APIs when its main module was required", ->
pack1 = atom.packages.loadPackage('package-with-eval-time-api-calls')
expect(pack1.mainModule).toBeDefined()
atom.packages.unloadPackage('package-with-eval-time-api-calls')
pack2 = atom.packages.loadPackage('package-with-eval-time-api-calls')
expect(pack2.mainModule).not.toBeNull()
describe "::unloadPackage(name)", ->
describe "when the package is active", ->
it "throws an error", ->

View File

@ -1,6 +1,7 @@
path = require 'path'
Package = require '../src/package'
ThemePackage = require '../src/theme-package'
{mockLocalStorage} = require './spec-helper'
describe "Package", ->
build = (constructor, path) ->
@ -10,6 +11,7 @@ describe "Package", ->
keymapManager: atom.keymaps, commandRegistry: atom.command,
grammarRegistry: atom.grammars, themeManager: atom.themes,
menuManager: atom.menu, contextMenuManager: atom.contextMenu,
deserializerManager: atom.deserializers, viewRegistry: atom.views,
devMode: false
)
@ -19,10 +21,7 @@ describe "Package", ->
describe "when the package contains incompatible native modules", ->
beforeEach ->
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined
mockLocalStorage()
it "does not activate it", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
@ -54,10 +53,7 @@ describe "Package", ->
describe "::rebuild()", ->
beforeEach ->
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined
mockLocalStorage()
it "returns a promise resolving to the results of `apm rebuild`", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index')

View File

@ -265,3 +265,9 @@ window.advanceClock = (delta=1) ->
true
callback() for callback in callbacks
exports.mockLocalStorage = ->
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item.toString(); undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined

View File

@ -484,7 +484,7 @@ describe('TextEditorComponent', function () {
it('displays newlines as their own token outside of the other tokens\' scopeDescriptor', async function () {
editor.setText('let\n')
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(0).innerHTML).toBe('<span class="source js"><span class="storage modifier js">let</span></span><span class="invisible-character">' + invisibles.eol + '</span>')
expect(component.lineNodeForScreenRow(0).innerHTML).toBe('<span class="source js"><span class="storage type var js">let</span></span><span class="invisible-character">' + invisibles.eol + '</span>')
})
it('displays trailing carriage returns using a visible, non-empty value', async function () {

View File

@ -611,7 +611,7 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().hiddenInput.width).toBe 15
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'r', 20)
presenter.characterWidthsChanged()
expect(presenter.getState().hiddenInput.width).toBe 20
@ -1449,12 +1449,12 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(explicitHeight: 20)
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'v', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'v', 20)
presenter.characterWidthsChanged()
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 10, height: 10}
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'r', 20)
presenter.characterWidthsChanged()
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}

View File

@ -762,11 +762,24 @@ describe "TextEditor", ->
editor.moveToBeginningOfWord()
expect(editor.getCursorBufferPosition()).toEqual [10, 0]
it "treats lines with only whitespace as a word (CRLF line ending)", ->
editor.buffer.setText(buffer.getText().replace(/\n/g, "\r\n"))
editor.setCursorBufferPosition([11, 0])
editor.moveToBeginningOfWord()
expect(editor.getCursorBufferPosition()).toEqual [10, 0]
it "works when the current line is blank", ->
editor.setCursorBufferPosition([10, 0])
editor.moveToBeginningOfWord()
expect(editor.getCursorBufferPosition()).toEqual [9, 2]
it "works when the current line is blank (CRLF line ending)", ->
editor.buffer.setText(buffer.getText().replace(/\n/g, "\r\n"))
editor.setCursorBufferPosition([10, 0])
editor.moveToBeginningOfWord()
expect(editor.getCursorBufferPosition()).toEqual [9, 2]
editor.buffer.setText(buffer.getText().replace(/\r\n/g, "\n"))
describe ".moveToPreviousWordBoundary()", ->
it "moves the cursor to the previous word boundary", ->
editor.setCursorBufferPosition [0, 8]
@ -821,11 +834,23 @@ describe "TextEditor", ->
editor.moveToEndOfWord()
expect(editor.getCursorBufferPosition()).toEqual [10, 0]
it "treats lines with only whitespace as a word (CRLF line ending)", ->
editor.buffer.setText(buffer.getText().replace(/\n/g, "\r\n"))
editor.setCursorBufferPosition([9, 4])
editor.moveToEndOfWord()
expect(editor.getCursorBufferPosition()).toEqual [10, 0]
it "works when the current line is blank", ->
editor.setCursorBufferPosition([10, 0])
editor.moveToEndOfWord()
expect(editor.getCursorBufferPosition()).toEqual [11, 8]
it "works when the current line is blank (CRLF line ending)", ->
editor.buffer.setText(buffer.getText().replace(/\n/g, "\r\n"))
editor.setCursorBufferPosition([10, 0])
editor.moveToEndOfWord()
expect(editor.getCursorBufferPosition()).toEqual [11, 8]
describe ".moveToBeginningOfNextWord()", ->
it "moves the cursor before the first character of the next word", ->
editor.setCursorBufferPosition [0, 6]
@ -1055,8 +1080,36 @@ describe "TextEditor", ->
editor.moveToBeginningOfNextParagraph()
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
it "moves the cursor before the first line of the next paragraph (CRLF line endings)", ->
editor.setText(editor.getText().replace(/\n/g, '\r\n'))
editor.setCursorBufferPosition [0, 6]
editor.foldBufferRow(4)
editor.moveToBeginningOfNextParagraph()
expect(editor.getCursorBufferPosition()).toEqual [10, 0]
editor.setText("")
editor.setCursorBufferPosition [0, 0]
editor.moveToBeginningOfNextParagraph()
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
describe ".moveToBeginningOfPreviousParagraph()", ->
it "moves the cursor before the first line of the pevious paragraph", ->
it "moves the cursor before the first line of the previous paragraph", ->
editor.setCursorBufferPosition [10, 0]
editor.foldBufferRow(4)
editor.moveToBeginningOfPreviousParagraph()
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
editor.setText("")
editor.setCursorBufferPosition [0, 0]
editor.moveToBeginningOfPreviousParagraph()
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
it "moves the cursor before the first line of the previous paragraph (CRLF line endings)", ->
editor.setText(editor.getText().replace(/\n/g, '\r\n'))
editor.setCursorBufferPosition [10, 0]
editor.foldBufferRow(4)
@ -5337,7 +5390,7 @@ describe "TextEditor", ->
tokens = atom.grammars.decodeTokens(line, tags)
expect(tokens[0].value).toBe "var"
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
expect(tokens[0].scopes).toEqual ["source.js", "storage.type.var.js"]
expect(tokens[6].value).toBe "http://github.com"
expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]

View File

@ -198,7 +198,7 @@ describe "TokenizedBuffer", ->
buffer.setTextInRange([[1, 0], [3, 0]], "foo()")
# previous line 0 remains
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.modifier.js'])
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.type.var.js'])
# previous line 3 should be combined with input to form line 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
@ -242,7 +242,7 @@ describe "TokenizedBuffer", ->
buffer.setTextInRange([[1, 0], [2, 0]], "foo()\nbar()\nbaz()\nquux()")
# previous line 0 remains
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.modifier.js'])
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.type.var.js'])
# 3 new lines inserted
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
@ -582,7 +582,7 @@ describe "TokenizedBuffer", ->
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([1, 0]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1, 1]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1, 2]).scopes).toEqual ["source.js", "storage.modifier.js"]
expect(tokenizedBuffer.tokenForPosition([1, 2]).scopes).toEqual ["source.js", "storage.type.var.js"]
describe ".bufferRangeForScopeAtPosition(selector, position)", ->
beforeEach ->
@ -599,8 +599,8 @@ describe "TokenizedBuffer", ->
describe "when the selector matches a single token at the position", ->
it "returns the range covered by the token", ->
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.storage.modifier.js', [0, 1])).toEqual [[0, 0], [0, 3]]
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.storage.modifier.js', [0, 3])).toEqual [[0, 0], [0, 3]]
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.storage.type.var.js', [0, 1])).toEqual [[0, 0], [0, 3]]
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.storage.type.var.js', [0, 3])).toEqual [[0, 0], [0, 3]]
describe "when the selector matches a run of multiple tokens at the position", ->
it "returns the range covered by all contigous tokens (within a single line)", ->

View File

@ -47,6 +47,21 @@ describe "ViewRegistry", ->
expect(view2 instanceof TestView).toBe true
expect(view2.model).toBe subclassModel
describe "when a view provider is registered generically, and works with the object", ->
it "constructs a view element and assigns the model on it", ->
model = {a: 'b'}
registry.addViewProvider (model) ->
if model.a is 'b'
element = document.createElement('div')
element.className = 'test-element'
element
view = registry.getView({a: 'b'})
expect(view.className).toBe 'test-element'
expect(-> registry.getView({a: 'c'})).toThrow()
describe "when no view provider is registered for the object's constructor", ->
it "throws an exception", ->
expect(-> registry.getView(new Object)).toThrow()

View File

@ -76,7 +76,7 @@ describe "Workspace", ->
expect(editor4.getCursorScreenPosition()).toEqual [2, 4]
expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath()
expect(document.title).toBe "#{path.basename(editor3.getLongTitle())} - #{atom.project.getPaths()[0]} - Atom"
expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{atom.project.getPaths()[0]}///
describe "where there are no open panes or editors", ->
it "constructs the view with no open editors", ->
@ -661,7 +661,7 @@ describe "Workspace", ->
describe "::isTextEditor(obj)", ->
it "returns true when the passed object is an instance of `TextEditor`", ->
expect(workspace.isTextEditor(atom.workspace.buildTextEditor())).toBe(true)
expect(workspace.isTextEditor({getText: ->})).toBe(false)
expect(workspace.isTextEditor({getText: -> null})).toBe(false)
expect(workspace.isTextEditor(null)).toBe(false)
expect(workspace.isTextEditor(undefined)).toBe(false)
@ -732,7 +732,7 @@ describe "Workspace", ->
describe "when the project has no path", ->
it "sets the title to 'untitled'", ->
atom.project.setPaths([])
expect(document.title).toBe 'untitled - Atom'
expect(document.title).toMatch ///^untitled///
describe "when the project has a path", ->
beforeEach ->
@ -742,25 +742,25 @@ describe "Workspace", ->
describe "when there is an active pane item", ->
it "sets the title to the pane item's title plus the project path", ->
item = atom.workspace.getActivePaneItem()
expect(document.title).toBe "#{item.getTitle()} - #{atom.project.getPaths()[0]} - Atom"
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
describe "when the title of the active pane item changes", ->
it "updates the window title based on the item's new title", ->
editor = atom.workspace.getActivePaneItem()
editor.buffer.setPath(path.join(temp.dir, 'hi'))
expect(document.title).toBe "#{editor.getTitle()} - #{atom.project.getPaths()[0]} - Atom"
expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
describe "when the active pane's item changes", ->
it "updates the title to the new item's title plus the project path", ->
atom.workspace.getActivePane().activateNextItem()
item = atom.workspace.getActivePaneItem()
expect(document.title).toBe "#{item.getTitle()} - #{atom.project.getPaths()[0]} - Atom"
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
describe "when the last pane item is removed", ->
it "updates the title to contain the project's path", ->
atom.workspace.getActivePane().destroy()
expect(atom.workspace.getActivePaneItem()).toBeUndefined()
expect(document.title).toBe "#{atom.project.getPaths()[0]} - Atom"
expect(document.title).toMatch ///^#{atom.project.getPaths()[0]}///
describe "when an inactive pane's item changes", ->
it "does not update the title", ->
@ -784,7 +784,7 @@ describe "Workspace", ->
})
workspace2.deserialize(atom.workspace.serialize(), atom.deserializers)
item = workspace2.getActivePaneItem()
expect(document.title).toBe "#{item.getLongTitle()} - #{atom.project.getPaths()[0]} - Atom"
expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
workspace2.destroy()
describe "document edited status", ->

View File

@ -151,7 +151,7 @@ class AtomEnvironment extends Model
@packages = new PackageManager({
devMode, configDirPath, resourcePath, safeMode, @config, styleManager: @styles,
commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications,
grammarRegistry: @grammars
grammarRegistry: @grammars, deserializerManager: @deserializers, viewRegistry: @views
})
@themes = new ThemeManager({

View File

@ -82,6 +82,7 @@ class AtomApplication
@listenForArgumentsFromNewProcess()
@setupJavaScriptArguments()
@handleEvents()
@setupDockMenu()
@storageFolder = new StorageFolder(process.env.ATOM_HOME)
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
@ -280,6 +281,13 @@ class AtomApplication
ipc.on 'write-to-stderr', (event, output) ->
process.stderr.write(output)
setupDockMenu: ->
if process.platform is 'darwin'
dockMenu = Menu.buildFromTemplate [
{label: 'New Window', click: => @emit('application:new-window')}
]
app.dock.setMenu dockMenu
# Public: Executes the given command.
#
# If it isn't handled globally, delegate to the currently focused window.

View File

@ -163,8 +163,12 @@ var prepareStackTraceWithSourceMapping = Error.prepareStackTrace
let prepareStackTrace = prepareStackTraceWithSourceMapping
function prepareStackTraceWithRawStackAssignment (error, frames) {
error.rawStack = frames
return prepareStackTrace(error, frames)
if (error.rawStack) { // avoid infinite recursion
return prepareStackTraceWithSourceMapping(error, frames)
} else {
error.rawStack = frames
return prepareStackTrace(error, frames)
}
}
Object.defineProperty(Error, 'prepareStackTrace', {

View File

@ -381,8 +381,8 @@ class Config
# ```
#
# * `keyPath` {String} name of the key to observe
# * `options` {Object}
# * `scopeDescriptor` (optional) {ScopeDescriptor} describing a path from
# * `options` (optional) {Object}
# * `scope` (optional) {ScopeDescriptor} describing a path from
# the root of the syntax tree to a token. Get one by calling
# {editor.getLastCursor().getScopeDescriptor()}. See {::get} for examples.
# See [the scopes docs](https://atom.io/docs/latest/behind-atom-scoped-settings-scopes-and-scope-descriptors)
@ -412,8 +412,8 @@ class Config
#
# * `keyPath` (optional) {String} name of the key to observe. Must be
# specified if `scopeDescriptor` is specified.
# * `optional` (optional) {Object}
# * `scopeDescriptor` (optional) {ScopeDescriptor} describing a path from
# * `options` (optional) {Object}
# * `scope` (optional) {ScopeDescriptor} describing a path from
# the root of the syntax tree to a token. Get one by calling
# {editor.getLastCursor().getScopeDescriptor()}. See {::get} for examples.
# See [the scopes docs](https://atom.io/docs/latest/behind-atom-scoped-settings-scopes-and-scope-descriptors)

View File

@ -3,6 +3,8 @@
_ = require 'underscore-plus'
Model = require './model'
EmptyLineRegExp = /(\r\n[\t ]*\r\n)|(\n[\t ]*\n)/g
# Extended: The `Cursor` class represents the little blinking line identifying
# where text can be inserted.
#
@ -467,10 +469,13 @@ class Cursor extends Model
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)
@editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, matchText, stop}) ->
# Ignore 'empty line' matches between '\r' and '\n'
return if matchText is '' and range.start.column isnt 0
if range.start.isLessThan(currentBufferPosition)
if range.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
beginningOfWordPosition = range.start
stop()
if beginningOfWordPosition?
@ -496,13 +501,12 @@ class Cursor extends Model
scanRange = [currentBufferPosition, @editor.getEofBufferPosition()]
endOfWordPosition = null
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
if allowNext
if range.end.isGreaterThan(currentBufferPosition)
endOfWordPosition = range.end
stop()
else
if range.start.isLessThanOrEqual(currentBufferPosition)
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, matchText, stop}) ->
# Ignore 'empty line' matches between '\r' and '\n'
return if matchText is '' and range.start.column isnt 0
if range.end.isGreaterThan(currentBufferPosition)
if allowNext or range.start.isLessThanOrEqual(currentBufferPosition)
endOfWordPosition = range.end
stop()
@ -603,14 +607,14 @@ class Cursor extends Model
# non-word characters in the regex. (default: true)
#
# Returns a {RegExp}.
wordRegExp: ({includeNonWordCharacters}={}) ->
includeNonWordCharacters ?= true
nonWordCharacters = @config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
segments = ["^[\t ]*$"]
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
if includeNonWordCharacters
segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+")
new RegExp(segments.join("|"), "g")
wordRegExp: (options) ->
scope = @getScopeDescriptor()
nonWordCharacters = _.escapeRegExp(@config.get('editor.nonWordCharacters', {scope}))
source = "^[\t ]*$|[^\\s#{nonWordCharacters}]+"
if options?.includeNonWordCharacters ? true
source += "|" + "[#{nonWordCharacters}]+"
new RegExp(source, "g")
# Public: Get the RegExp used by the cursor to determine what a "subword" is.
#
@ -666,10 +670,9 @@ class Cursor extends Model
{row, column} = eof
position = new Point(row, column - 1)
@editor.scanInBufferRange /^\n*$/g, scanRange, ({range, stop}) ->
unless range.start.isEqual(start)
position = range.start
stop()
@editor.scanInBufferRange EmptyLineRegExp, scanRange, ({range, stop}) ->
position = range.start.traverse(Point(1, 0))
stop() unless position.isEqual(start)
position
getBeginningOfPreviousParagraphBufferPosition: ->
@ -679,8 +682,7 @@ class Cursor extends Model
scanRange = [[row-1, column], [0, 0]]
position = new Point(0, 0)
zero = new Point(0, 0)
@editor.backwardsScanInBufferRange /^\n*$/g, scanRange, ({range, stop}) ->
unless range.start.isEqual(zero)
position = range.start
stop()
@editor.backwardsScanInBufferRange EmptyLineRegExp, scanRange, ({range, stop}) ->
position = range.start.traverse(Point(1, 0))
stop() unless position.isEqual(start)
position

View File

@ -39,6 +39,9 @@ class DeserializerManager
delete @deserializers[deserializer.name] for deserializer in deserializers
return
getDeserializerCount: ->
Object.keys(@deserializers).length
# Public: Deserialize the state and params.
#
# * `state` The state {Object} to deserialize.

View File

@ -31,7 +31,8 @@ class PackageManager
constructor: (params) ->
{
configDirPath, @devMode, safeMode, @resourcePath, @config, @styleManager,
@notificationManager, @keymapManager, @commandRegistry, @grammarRegistry
@notificationManager, @keymapManager, @commandRegistry, @grammarRegistry,
@deserializerManager, @viewRegistry
} = params
@emitter = new Emitter
@ -375,7 +376,8 @@ class PackageManager
options = {
path: packagePath, metadata, packageManager: this, @config, @styleManager,
@commandRegistry, @keymapManager, @devMode, @notificationManager,
@grammarRegistry, @themeManager, @menuManager, @contextMenuManager
@grammarRegistry, @themeManager, @menuManager, @contextMenuManager,
@deserializerManager, @viewRegistry
}
if metadata.theme
pack = new ThemePackage(options)

View File

@ -33,7 +33,7 @@ class Package
{
@path, @metadata, @packageManager, @config, @styleManager, @commandRegistry,
@keymapManager, @devMode, @notificationManager, @grammarRegistry, @themeManager,
@menuManager, @contextMenuManager
@menuManager, @contextMenuManager, @deserializerManager, @viewRegistry
} = params
@emitter = new Emitter
@ -84,12 +84,24 @@ class Package
@loadKeymaps()
@loadMenus()
@loadStylesheets()
@loadDeserializers()
@configSchemaRegisteredOnLoad = @registerConfigSchemaFromMetadata()
@settingsPromise = @loadSettings()
@requireMainModule() unless @mainModule? or @activationShouldBeDeferred()
if @shouldRequireMainModuleOnLoad() and not @mainModule?
@requireMainModule()
catch error
@handleError("Failed to load the #{@name} package", error)
this
shouldRequireMainModuleOnLoad: ->
not (
@metadata.deserializers? or
@metadata.viewProviders? or
@metadata.configSchema? or
@activationShouldBeDeferred() or
localStorage.getItem(@getCanDeferMainModuleRequireStorageKey()) is 'true'
)
reset: ->
@stylesheets = []
@keymaps = []
@ -117,9 +129,12 @@ class Package
activateNow: ->
try
@activateConfig()
@requireMainModule() unless @mainModule?
@configSchemaRegisteredOnActivate = @registerConfigSchemaFromMainModule()
@registerViewProviders()
@activateStylesheets()
if @mainModule? and not @mainActivated
@mainModule.activateConfig?()
@mainModule.activate?(@packageManager.getPackageState(@name) ? {})
@mainActivated = true
@activateServices()
@ -128,15 +143,22 @@ class Package
@resolveActivationPromise?()
activateConfig: ->
return if @configActivated
registerConfigSchemaFromMetadata: ->
if configSchema = @metadata.configSchema
@config.setSchema @name, {type: 'object', properties: configSchema}
true
else
false
@requireMainModule() unless @mainModule?
if @mainModule?
registerConfigSchemaFromMainModule: ->
if @mainModule? and not @configSchemaRegisteredOnLoad
if @mainModule.config? and typeof @mainModule.config is 'object'
@config.setSchema @name, {type: 'object', properties: @mainModule.config}
@mainModule.activateConfig?()
@configActivated = true
return true
false
# TODO: Remove. Settings view calls this method currently.
activateConfig: -> @registerConfigSchemaFromMainModule()
activateStylesheets: ->
return if @stylesheetsActivated
@ -253,6 +275,26 @@ class Package
@stylesheets = @getStylesheetPaths().map (stylesheetPath) =>
[stylesheetPath, @themeManager.loadStylesheet(stylesheetPath, true)]
loadDeserializers: ->
if @metadata.deserializers?
for name, implementationPath of @metadata.deserializers
do =>
deserializePath = path.join(@path, implementationPath)
deserializeFunction = null
atom.deserializers.add
name: name,
deserialize: =>
@registerViewProviders()
deserializeFunction ?= require(deserializePath)
deserializeFunction.apply(this, arguments)
return
registerViewProviders: ->
if @metadata.viewProviders? and not @registeredViewProviders
for implementationPath in @metadata.viewProviders
@viewRegistry.addViewProvider(require(path.join(@path, implementationPath)))
@registeredViewProviders = true
getStylesheetsPath: ->
path.join(@path, 'styles')
@ -343,21 +385,18 @@ class Package
@activationPromise = null
@resolveActivationPromise = null
@activationCommandSubscriptions?.dispose()
@configSchemaRegisteredOnActivate = false
@deactivateResources()
@deactivateConfig()
@deactivateKeymaps()
if @mainActivated
try
@mainModule?.deactivate?()
@mainModule?.deactivateConfig?()
@mainActivated = false
catch e
console.error "Error deactivating package '#{@name}'", e.stack
@emitter.emit 'did-deactivate'
deactivateConfig: ->
@mainModule?.deactivateConfig?()
@configActivated = false
deactivateResources: ->
grammar.deactivate() for grammar in @grammars
settings.deactivate() for settings in @settings
@ -392,7 +431,13 @@ class Package
mainModulePath = @getMainModulePath()
if fs.isFileSync(mainModulePath)
@mainModuleRequired = true
previousViewProviderCount = @viewRegistry.getViewProviderCount()
previousDeserializerCount = @deserializerManager.getDeserializerCount()
@mainModule = require(mainModulePath)
if (@viewRegistry.getViewProviderCount() is previousViewProviderCount and
@deserializerManager.getDeserializerCount() is previousDeserializerCount)
localStorage.setItem(@getCanDeferMainModuleRequireStorageKey(), 'true')
getMainModulePath: ->
return @mainModulePath if @resolvedMainModulePath
@ -586,6 +631,9 @@ class Package
electronVersion = process.versions['electron'] ? process.versions['atom-shell']
"installed-packages:#{@name}:#{@metadata.version}:electron-#{electronVersion}:incompatible-native-modules"
getCanDeferMainModuleRequireStorageKey: ->
"installed-packages:#{@name}:#{@metadata.version}:can-defer-main-module-require"
# Get the incompatible native modules that this package depends on.
# This recurses through all dependencies and requires all modules that
# contain a `.node` file.

View File

@ -3,6 +3,8 @@ Grim = require 'grim'
{Disposable} = require 'event-kit'
_ = require 'underscore-plus'
AnyConstructor = Symbol('any-constructor')
# Essential: `ViewRegistry` handles the association between model and view
# types in Atom. We call this association a View Provider. As in, for a given
# model, this class can provide a view via {::getView}, as long as the
@ -76,16 +78,27 @@ class ViewRegistry
# textEditorElement
# ```
#
# * `modelConstructor` Constructor {Function} for your model.
# * `modelConstructor` (optional) Constructor {Function} for your model. If
# a constructor is given, the `createView` function will only be used
# for model objects inheriting from that constructor. Otherwise, it will
# will be called for any object.
# * `createView` Factory {Function} that is passed an instance of your model
# and must return a subclass of `HTMLElement` or `undefined`.
# and must return a subclass of `HTMLElement` or `undefined`. If it returns
# `undefined`, then the registry will continue to search for other view
# providers.
#
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# added provider.
addViewProvider: (modelConstructor, createView) ->
if arguments.length is 1
Grim.deprecate("atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.")
provider = modelConstructor
switch typeof modelConstructor
when 'function'
provider = {createView: modelConstructor, modelConstructor: AnyConstructor}
when 'object'
Grim.deprecate("atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.")
provider = modelConstructor
else
throw new TypeError("Arguments to addViewProvider must be functions")
else
provider = {modelConstructor, createView}
@ -93,6 +106,9 @@ class ViewRegistry
new Disposable =>
@providers = @providers.filter (p) -> p isnt provider
getViewProviderCount: ->
@providers.length
# Essential: Get the view associated with an object in the workspace.
#
# If you're just *using* the workspace, you shouldn't need to access the view
@ -153,25 +169,34 @@ class ViewRegistry
createView: (object) ->
if object instanceof HTMLElement
object
else if object?.element instanceof HTMLElement
object.element
else if object?.jquery
object[0]
else if provider = @findProvider(object)
element = provider.createView?(object, @atomEnvironment)
unless element?
element = new provider.viewConstructor
element.initialize?(object) ? element.setModel?(object)
element
else if viewConstructor = object?.getViewClass?()
view = new viewConstructor(object)
view[0]
else
throw new Error("Can't create a view for #{object.constructor.name} instance. Please register a view provider.")
return object
findProvider: (object) ->
find @providers, ({modelConstructor}) -> object instanceof modelConstructor
if object?.element instanceof HTMLElement
return object.element
if object?.jquery
return object[0]
for provider in @providers
if provider.modelConstructor is AnyConstructor
if element = provider.createView(object, @atomEnvironment)
return element
continue
if object instanceof provider.modelConstructor
if element = provider.createView?(object, @atomEnvironment)
return element
if viewConstructor = provider.viewConstructor
element = new viewConstructor
element.initialize?(object) ? element.setModel?(object)
return element
if viewConstructor = object?.getViewClass?()
view = new viewConstructor(object)
return view[0]
throw new Error("Can't create a view for #{object.constructor.name} instance. Please register a view provider.")
updateDocument: (fn) ->
@documentWriters.push(fn)

View File

@ -161,15 +161,22 @@ class Workspace extends Model
itemTitle ?= "untitled"
projectPath ?= projectPaths[0]
titleParts = []
if item? and projectPath?
document.title = "#{itemTitle} - #{projectPath} - #{appName}"
@applicationDelegate.setRepresentedFilename(itemPath ? projectPath)
titleParts.push itemTitle, projectPath
representedPath = itemPath ? projectPath
else if projectPath?
document.title = "#{projectPath} - #{appName}"
@applicationDelegate.setRepresentedFilename(projectPath)
titleParts.push projectPath
representedPath = projectPath
else
document.title = "#{itemTitle} - #{appName}"
@applicationDelegate.setRepresentedFilename("")
titleParts.push itemTitle
representedPath = ""
unless process.platform is 'darwin'
titleParts.push appName
document.title = titleParts.join(" \u2014 ")
@applicationDelegate.setRepresentedFilename(representedPath)
# On OS X, fades the application window's proxy icon when the current file
# has been modified.