Merge branch 'master' into as-public-ci

This commit is contained in:
Antonio Scandurra
2015-04-16 08:31:21 +02:00
36 changed files with 660 additions and 161 deletions

View File

@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "0.157.0"
"atom-package-manager": "0.158.0"
}
}

View File

@@ -223,7 +223,7 @@ module.exports = (grunt) ->
ciTasks = ['output-disk-space', 'download-atom-shell', 'download-atom-shell-chromedriver', 'build']
ciTasks.push('dump-symbols') if process.platform isnt 'win32'
ciTasks.push('set-version', 'check-licenses', 'lint')
ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
ciTasks.push('mkdeb') if process.platform is 'linux'
ciTasks.push('create-windows-installer') if process.platform is 'win32'
ciTasks.push('test') if process.platform is 'darwin'
@@ -231,6 +231,6 @@ module.exports = (grunt) ->
ciTasks.push('publish-build') unless process.env.TRAVIS
grunt.registerTask('ci', ciTasks)
defaultTasks = ['download-atom-shell', 'download-atom-shell-chromedriver', 'build', 'set-version']
defaultTasks = ['download-atom-shell', 'download-atom-shell-chromedriver', 'build', 'set-version', 'generate-asar']
defaultTasks.push 'install' unless process.platform is 'linux'
grunt.registerTask('default', defaultTasks)

View File

@@ -6,13 +6,14 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"asar": "^0.4.4",
"async": "~0.2.9",
"donna": "1.0.10",
"formidable": "~1.0.14",
"fs-plus": "2.x",
"github-releases": "~0.2.0",
"grunt": "~0.4.1",
"grunt-atom-shell-installer": "^0.28.0",
"grunt-atom-shell-installer": "^0.29.0",
"grunt-cli": "~0.1.9",
"grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git#cfb99aa99811d52687969532bd5a98011ed95bfe",
"grunt-contrib-coffee": "~0.12.0",
@@ -31,7 +32,7 @@
"request": "~2.27.0",
"rimraf": "~2.2.2",
"runas": "^2",
"tello": "1.0.4",
"tello": "1.0.5",
"temp": "~0.8.1",
"underscore-plus": "1.x",
"unzip": "~0.1.9",

View File

@@ -22,7 +22,7 @@ module.exports = (grunt) ->
mkdir appDir
if process.platform isnt 'win32'
cp 'atom.sh', path.join(appDir, 'atom.sh')
cp 'atom.sh', path.resolve(appDir, '..', 'new-app', 'atom.sh')
cp 'package.json', path.join(appDir, 'package.json')
@@ -65,6 +65,7 @@ module.exports = (grunt) ->
path.join('jasmine-node', 'node_modules', 'gaze')
path.join('jasmine-node', 'spec')
path.join('node_modules', 'nan')
path.join('node_modules', 'native-mate')
path.join('build', 'binding.Makefile')
path.join('build', 'config.gypi')
path.join('build', 'gyp-mac-tool')
@@ -143,13 +144,13 @@ module.exports = (grunt) ->
for directory in packageDirectories
cp directory, path.join(appDir, directory), filter: filterPackage
cp 'spec', path.join(appDir, 'spec')
cp 'spec', path.join(appDir, 'spec'), filter: /fixtures|integration|.+-spec\.coffee/
cp 'src', path.join(appDir, 'src'), filter: /.+\.(cson|coffee)$/
cp 'static', path.join(appDir, 'static')
cp path.join('apm', 'node_modules', 'atom-package-manager'), path.join(appDir, 'apm'), filter: filterNodeModule
cp path.join('apm', 'node_modules', 'atom-package-manager'), path.resolve(appDir, '..', 'new-app', 'apm'), filter: filterNodeModule
if process.platform isnt 'win32'
fs.symlinkSync(path.join('..', '..', 'bin', 'apm'), path.join(appDir, 'apm', 'node_modules', '.bin', 'apm'))
fs.symlinkSync(path.join('..', '..', 'bin', 'apm'), path.resolve(appDir, '..', 'new-app', 'apm', 'node_modules', '.bin', 'apm'))
if process.platform is 'darwin'
grunt.file.recurse path.join('resources', 'mac'), (sourcePath, rootDirectory, subDirectory='', filename) ->

View File

@@ -0,0 +1,35 @@
asar = require 'asar'
fs = require 'fs'
path = require 'path'
module.exports = (grunt) ->
{cp, rm} = require('./task-helpers')(grunt)
grunt.registerTask 'generate-asar', 'Generate asar archive for the app', ->
done = @async()
unpack = [
'*.node'
'.ctags'
'ctags-darwin'
'ctags-linux'
'ctags-win32.exe'
]
unpack = "{#{unpack.join(',')}}"
appDir = grunt.config.get('atom.appDir')
unless fs.existsSync(appDir)
grunt.log.error 'The app has to be built before generating asar archive.'
return done(false)
asar.createPackageWithOptions appDir, path.resolve(appDir, '..', 'app.asar'), {unpack}, (err) ->
return done(err) if err?
rm appDir
fs.renameSync path.resolve(appDir, '..', 'new-app'), appDir
ctagsFolder = path.join("#{appDir}.asar.unpacked", 'node_modules', 'symbols-view', 'vendor')
for ctagsFile in fs.readdirSync(ctagsFolder)
fs.chmodSync(path.join(ctagsFolder, ctagsFile), "755")
done()

View File

@@ -17,7 +17,7 @@ module.exports = (grunt) ->
licenseText = getLicenseText(dependencyLicenses)
if mode is 'save'
targetPath = path.join(grunt.config.get('atom.appDir'), 'LICENSE.md')
targetPath = path.resolve(grunt.config.get('atom.appDir'), '..', 'LICENSE.md')
fs.writeFileSync(targetPath, licenseText)
else
console.log licenseText

View File

@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "0.192.0",
"version": "0.193.0",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@@ -113,7 +113,7 @@
"settings-view": "0.192.0",
"snippets": "0.88.0",
"spell-check": "0.55.0",
"status-bar": "0.68.0",
"status-bar": "0.69.0",
"styleguide": "0.44.0",
"symbols-view": "0.94.0",
"tabs": "0.67.0",
@@ -128,9 +128,9 @@
"language-coffee-script": "0.39.0",
"language-csharp": "0.5.0",
"language-css": "0.28.0",
"language-gfm": "0.68.0",
"language-gfm": "0.69.0",
"language-git": "0.10.0",
"language-go": "0.23.0",
"language-go": "0.25.0",
"language-html": "0.32.0",
"language-hyperlink": "0.12.2",
"language-java": "0.14.0",

View File

@@ -33,7 +33,7 @@ cp "$ICON_FILE" "$TARGET/usr/share/pixmaps"
# Copy generated LICENSE.md to /usr/share/doc/atom/copyright
mkdir -m $FILE_MODE -p "$TARGET/usr/share/doc/atom"
cp "$TARGET/usr/share/atom/resources/app/LICENSE.md" "$TARGET/usr/share/doc/atom/copyright"
cp "$TARGET/usr/share/atom/resources/LICENSE.md" "$TARGET/usr/share/doc/atom/copyright"
# Add lintian overrides
mkdir -m $FILE_MODE -p "$TARGET/usr/share/lintian/overrides"

View File

@@ -139,7 +139,7 @@ describe "the `atom` global", ->
windowState: null
spyOn(Atom, 'getLoadSettings').andCallFake -> loadSettings
spyOn(Atom, 'getStorageDirPath').andReturn(temp.mkdirSync("storage-dir-"))
spyOn(Atom.getStorageFolder(), 'getPath').andReturn(temp.mkdirSync("storage-dir-"))
atom.mode = "editor"
atom.state.stuff = "cool"

View File

@@ -0,0 +1 @@
<>

View File

@@ -0,0 +1,4 @@
{
"name": "theme-with-invalid-styles",
"theme": "ui"
}

View File

@@ -108,6 +108,15 @@ buildAtomClient = (args, env) ->
]), env: extend({}, process.env, env))
done()
.addCommand "dispatchCommand", (command, done) ->
@execute "atom.commands.dispatch(document.activeElement, '#{command}')"
.call(done)
.addCommand "simulateQuit", (done) ->
@execute -> atom.unloadEditorWindow()
.execute -> require("remote").require("app").emit("before-quit")
.call(done)
module.exports = (args, env, fn) ->
[chromedriver, chromedriverLogs, chromedriverExit] = []
@@ -133,6 +142,7 @@ module.exports = (args, env, fn) ->
waitsFor("webdriver to finish", (done) ->
finish = once ->
client
.simulateQuit()
.end()
.then(-> chromedriver.kill())
.then(chromedriverExit.then(
@@ -147,7 +157,7 @@ module.exports = (args, env, fn) ->
client = buildAtomClient(args, env)
client.on "error", (err) ->
jasmine.getEnv().currentSpec.fail(JSON.stringify(err))
jasmine.getEnv().currentSpec.fail(new Error(err.response?.body?.value?.message))
finish()
fn(client.init()).then(finish)

View File

@@ -6,22 +6,22 @@ return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
fs = require "fs"
path = require "path"
temp = require("temp").track()
AtomHome = temp.mkdirSync('atom-home')
fs.writeFileSync(path.join(AtomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config.cson')))
runAtom = require("./helpers/start-atom")
describe "Starting Atom", ->
[tempDirPath, otherTempDirPath] = []
[tempDirPath, otherTempDirPath, atomHome] = []
beforeEach ->
jasmine.useRealClock()
atomHome = temp.mkdirSync('atom-home')
fs.writeFileSync(path.join(atomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config.cson')))
tempDirPath = temp.mkdirSync("empty-dir")
otherTempDirPath = temp.mkdirSync("another-temp-dir")
describe "opening a new file", ->
it "opens the parent directory and creates an empty text editor", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: AtomHome}, (client) ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
@@ -36,13 +36,14 @@ describe "Starting Atom", ->
.keys("Hello!")
.execute -> atom.workspace.getActiveTextEditor().getText()
.then ({value}) -> expect(value).toBe "Hello!"
.dispatchCommand("editor:delete-line")
describe "when there is already a window open", ->
it "reuses that window when opening files, but not when opening directories", ->
tempFilePath = path.join(temp.mkdirSync("a-third-dir"), "a-file")
fs.writeFileSync(tempFilePath, "This file was already here.")
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: AtomHome}, (client) ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
@@ -50,7 +51,7 @@ describe "Starting Atom", ->
# Opening another file reuses the same window and does not change the
# project paths.
.startAnotherAtom([tempFilePath], ATOM_HOME: AtomHome)
.startAnotherAtom([tempFilePath], ATOM_HOME: atomHome)
.waitForPaneItemCount(2, 5000)
.waitForWindowCount(1, 1000)
.treeViewRootDirectories()
@@ -60,7 +61,7 @@ describe "Starting Atom", ->
# Opening another directory creates a second window.
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: AtomHome)
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(0, 1000)
@@ -69,15 +70,14 @@ describe "Starting Atom", ->
describe "reopening a directory that was previously opened", ->
it "remembers the state of the window", ->
runAtom [tempDirPath], {ATOM_HOME: AtomHome}, (client) ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(0, 3000)
.execute -> atom.workspace.open()
.waitForPaneItemCount(1, 3000)
.execute -> atom.unloadEditorWindow()
runAtom [tempDirPath], {ATOM_HOME: AtomHome}, (client) ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(1, 5000)
@@ -87,7 +87,7 @@ describe "Starting Atom", ->
nestedDir = path.join(otherTempDirPath, "nested-dir")
fs.mkdirSync(nestedDir)
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: AtomHome}, (client) ->
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace", 5000)
.treeViewRootDirectories()
@@ -95,42 +95,67 @@ describe "Starting Atom", ->
# Opening one of those directories again reuses the same window and
# does not change the project paths.
.startAnotherAtom([nestedDir], ATOM_HOME: AtomHome)
.startAnotherAtom([nestedDir], ATOM_HOME: atomHome)
.waitForExist("atom-workspace", 5000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath])
describe "when there is an existing window with no project path", ->
describe "opening a directory", ->
it "opens the directory in the existing window", ->
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
client
.waitForExist("atom-workspace")
it "reuses that window to open a directory", ->
runAtom [], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([])
.startAnotherAtom([tempDirPath], ATOM_HOME: atomHome)
.waitUntil(->
@treeViewRootDirectories()
.then ({value}) -> value[0] is tempDirPath
, 5000)
.then (result) -> expect(result).toBe(true)
.waitForWindowCount(1, 5000)
describe "launching with no path", ->
it "opens a new window with a single untitled buffer", ->
runAtom [], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForPaneItemCount(1, 5000)
# Opening with no file paths always creates a new window, even if
# existing windows have no project paths.
.waitForNewWindow(->
@startAnotherAtom([], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace")
.waitForPaneItemCount(1, 5000)
it "reopens any previously opened windows", ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace")
runAtom [], {ATOM_HOME: atomHome}, (client) ->
windowProjectPaths = []
client
.waitForWindowCount(2, 10000)
.then ({value: windowHandles}) ->
@window(windowHandles[0])
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([])
.then ({value: directories}) -> windowProjectPaths.push(directories)
.startAnotherAtom([tempDirPath], ATOM_HOME: AtomHome)
.waitUntil(->
@treeViewRootDirectories()
.then ({value}) -> value[0] is tempDirPath
, 5000)
.then (result) -> expect(result).toBe(true)
.waitForWindowCount(1, 5000)
.window(windowHandles[1])
.treeViewRootDirectories()
.then ({value: directories}) -> windowProjectPaths.push(directories)
describe "launching with no path", ->
it "always opens a new window with a single untitled buffer", ->
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForPaneItemCount(1, 5000)
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForPaneItemCount(1, 5000)
# Opening with no file paths always creates a new window, even if
# existing windows have no project paths.
.waitForNewWindow(->
@startAnotherAtom([], ATOM_HOME: AtomHome)
, 5000)
.call ->
expect(windowProjectPaths.sort()).toEqual [
[tempDirPath]
[otherTempDirPath]
].sort()

View File

@@ -28,6 +28,12 @@ describe "PackageManager", ->
expect(pack.metadata.name).toBe "package-with-invalid-styles"
expect(pack.stylesheets.length).toBe 0
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(-> pack.reloadStylesheets()).not.toThrow()
expect(addErrorHandler.callCount).toBe 2
expect(addErrorHandler.argsForCall[1][0].message).toContain("Failed to reload the package-with-invalid-styles package stylesheets")
it "returns null if the package has an invalid package.json", ->
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)

View File

@@ -0,0 +1,134 @@
PaneContainer = require '../src/pane-container'
PaneAxisElement = require '../src/pane-axis-element'
PaneAxis = require '../src/pane-axis'
describe "PaneContainerElement", ->
describe "when panes are added or removed", ->
[paneAxisElement, paneAxis] = []
beforeEach ->
paneAxis = new PaneAxis
paneAxisElement = new PaneAxisElement().initialize(paneAxis)
childTagNames = ->
child.nodeName.toLowerCase() for child in paneAxisElement.children
it "inserts or removes resize elements", ->
expect(childTagNames()).toEqual []
paneAxis.addChild(new PaneAxis)
expect(childTagNames()).toEqual [
'atom-pane-axis'
]
paneAxis.addChild(new PaneAxis)
expect(childTagNames()).toEqual [
'atom-pane-axis'
'atom-pane-resize-handle'
'atom-pane-axis'
]
paneAxis.addChild(new PaneAxis)
expect(childTagNames()).toEqual [
'atom-pane-axis'
'atom-pane-resize-handle'
'atom-pane-axis'
'atom-pane-resize-handle'
'atom-pane-axis'
]
paneAxis.removeChild(paneAxis.getChildren()[2])
expect(childTagNames()).toEqual [
'atom-pane-axis'
'atom-pane-resize-handle'
'atom-pane-axis'
]
describe "when the resize element is dragged ", ->
[container, containerElement] = []
beforeEach ->
container = new PaneContainer
containerElement = atom.views.getView(container)
document.querySelector('#jasmine-content').appendChild(containerElement)
dragElementToPosition = (element, clientX) ->
element.dispatchEvent(new MouseEvent('mousedown',
view: window
bubbles: true
button: 0
))
element.dispatchEvent(new MouseEvent 'mousemove',
view: window
bubbles: true
clientX: clientX
)
element.dispatchEvent(new MouseEvent 'mouseup',
iew: window
bubbles: true
button: 0
)
getElementWidth = (element) ->
element.getBoundingClientRect().width
expectPaneScale = (pairs...) ->
for [pane, expectedFlexScale] in pairs
expect(pane.getFlexScale()).toBeCloseTo(expectedFlexScale, 0.1)
getResizeElement = (i) ->
containerElement.querySelectorAll('atom-pane-resize-handle')[i]
getPaneElement = (i) ->
containerElement.querySelectorAll('atom-pane')[i]
it "adds and removes panes in the direction that the pane is being dragged", ->
leftPane = container.getActivePane()
expectPaneScale [leftPane, 1]
middlePane = leftPane.splitRight()
expectPaneScale [leftPane, 1], [middlePane, 1]
dragElementToPosition(
getResizeElement(0),
getElementWidth(getPaneElement(0)) / 2
)
expectPaneScale [leftPane, 0.5], [middlePane, 1.5]
rightPane = middlePane.splitRight()
expectPaneScale [leftPane, 0.5], [middlePane, 1.5], [rightPane, 1]
dragElementToPosition(
getResizeElement(1),
getElementWidth(getPaneElement(0)) + getElementWidth(getPaneElement(1)) / 2
)
expectPaneScale [leftPane, 0.5], [middlePane, 0.75], [rightPane, 1.75]
middlePane.close()
expectPaneScale [leftPane, 0.44], [rightPane, 1.55]
leftPane.close()
expectPaneScale [rightPane, 1]
it "splits or closes panes in orthogonal direction that the pane is being dragged", ->
leftPane = container.getActivePane()
expectPaneScale [leftPane, 1]
rightPane = leftPane.splitRight()
expectPaneScale [leftPane, 1], [rightPane, 1]
dragElementToPosition(
getResizeElement(0),
getElementWidth(getPaneElement(0)) / 2
)
expectPaneScale [leftPane, 0.5], [rightPane, 1.5]
# dynamically split pane, pane's flexScale will become to 1
lowerPane = leftPane.splitDown()
expectPaneScale [lowerPane, 1], [leftPane, 1], [leftPane.getParent(), 0.5]
# dynamically close pane, the pane's flexscale will recorver to origin value
lowerPane.close()
expectPaneScale [leftPane, 0.5], [rightPane, 1.5]

View File

@@ -696,7 +696,10 @@ describe "Pane", ->
pane = null
beforeEach ->
pane = new Pane(items: [new Item("A", "a"), new Item("B", "b"), new Item("C", "c")])
params =
items: [new Item("A", "a"), new Item("B", "b"), new Item("C", "c")]
flexScale: 2
pane = new Pane(params)
it "can serialize and deserialize the pane and all its items", ->
newPane = pane.testSerialization()

View File

@@ -118,6 +118,33 @@ describe "PaneView", ->
paneModel.activateItem(view2)
expect(pane.itemViews.find('#view-2').length).toBe 1
describe "when the new activeItem implements ::getPath", ->
beforeEach ->
paneModel.activateItem(editor1)
it "adds the file path as a data attribute to the pane", ->
expect(pane).toHaveAttr('data-active-item-path')
it "adds the file name as a data attribute to the pane", ->
expect(pane).toHaveAttr('data-active-item-name')
describe "when the activeItem is destroyed", ->
it "removes the data attributes", ->
pane.destroyItems()
expect(pane).not.toHaveAttr('data-active-item-path')
expect(pane).not.toHaveAttr('data-active-item-name')
describe "when the new activeItem does not implement ::getPath", ->
beforeEach ->
paneModel.activateItem(editor1)
paneModel.activateItem(document.createElement('div'))
it "does not add the file path as a data attribute to the pane", ->
expect(pane).not.toHaveAttr('data-active-item-path')
it "does not add the file name as data attribute to the pane", ->
expect(pane).not.toHaveAttr('data-active-item-name')
describe "when an item is destroyed", ->
it "triggers the 'pane:item-removed' event with the item and its former index", ->
itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
@@ -333,3 +360,30 @@ describe "PaneView", ->
pane3 = container3.getRoot()
container3.attachToDom()
expect(pane3).not.toMatchSelector(':has(:focus)')
describe "drag and drop", ->
buildDragEvent = (type, files) ->
dataTransfer =
files: files
data: {}
setData: (key, value) -> @data[key] = value
getData: (key) -> @data[key]
event = new CustomEvent("drop")
event.dataTransfer = dataTransfer
event
describe "when a file is dragged to window", ->
it "opens it", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [ {path: "/fake1"}, {path: "/fake2"} ])
pane[0].dispatchEvent(event)
expect(atom.open.callCount).toBe 1
expect(atom.open.argsForCall[0][0]).toEqual pathsToOpen: ['/fake1', '/fake2']
describe "when a non-file is dragged to window", ->
it "does nothing", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [])
pane[0].dispatchEvent(event)
expect(atom.open).not.toHaveBeenCalled()

View File

@@ -163,7 +163,6 @@ afterEach ->
jasmine.unspy(atom, 'saveSync')
ensureNoPathSubscriptions()
atom.grammars.clearObservers()
waits(0) # yield to ui thread to make screen update more frequently
ensureNoPathSubscriptions = ->

View File

@@ -390,6 +390,13 @@ describe "ThemeManager", ->
expect(note.getType()).toBe 'error'
expect(note.getMessage()).toContain 'Unable to watch path'
it "adds a notification when a theme's stylesheet is invalid", ->
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(-> atom.packages.activatePackage('theme-with-invalid-styles')).not.toThrow()
expect(addErrorHandler.callCount).toBe 2
expect(addErrorHandler.argsForCall[1][0].message).toContain("Failed to activate the theme-with-invalid-styles theme")
describe "when a non-existent theme is present in the config", ->
beforeEach ->
spyOn(console, 'warn')

View File

@@ -141,33 +141,6 @@ describe "Window", ->
expect(buffer.getSubscriptionCount()).toBe 0
describe "drag and drop", ->
buildDragEvent = (type, files) ->
dataTransfer =
files: files
data: {}
setData: (key, value) -> @data[key] = value
getData: (key) -> @data[key]
event = new CustomEvent("drop")
event.dataTransfer = dataTransfer
event
describe "when a file is dragged to window", ->
it "opens it", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [ {path: "/fake1"}, {path: "/fake2"} ])
document.dispatchEvent(event)
expect(atom.open.callCount).toBe 1
expect(atom.open.argsForCall[0][0]).toEqual pathsToOpen: ['/fake1', '/fake2']
describe "when a non-file is dragged to window", ->
it "does nothing", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [])
document.dispatchEvent(event)
expect(atom.open).not.toHaveBeenCalled()
describe "when a link is clicked", ->
it "opens the http/https links in an external application", ->
shell = require 'shell'

View File

@@ -14,6 +14,7 @@ Model = require './model'
{$} = require './space-pen-extensions'
WindowEventHandler = require './window-event-handler'
StylesElement = require './styles-element'
StorageFolder = require './storage-folder'
# Essential: Atom global for dealing with packages, themes, menus, and the window.
#
@@ -73,34 +74,24 @@ class Atom extends Model
# Loads and returns the serialized state corresponding to this window
# if it exists; otherwise returns undefined.
@loadState: (mode) ->
statePath = @getStatePath(@getLoadSettings().initialPaths, mode)
if stateKey = @getStateKey(@getLoadSettings().initialPaths, mode)
if state = @getStorageFolder().load(stateKey)
return state
if fs.existsSync(statePath)
if windowState = @getLoadSettings().windowState
try
stateString = fs.readFileSync(statePath, 'utf8')
JSON.parse(@getLoadSettings().windowState)
catch error
console.warn "Error reading window state: #{statePath}", error.stack, error
else
stateString = @getLoadSettings().windowState
try
JSON.parse(stateString) if stateString?
catch error
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
# Returns the path where the state for the current window will be
# located if it exists.
@getStatePath: (paths, mode) ->
switch mode
when 'spec'
filename = 'spec'
when 'editor'
if paths?.length > 0
sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex')
filename = "editor-#{sha1}"
if filename
path.join(@getStorageDirPath(), filename)
@getStateKey: (paths, mode) ->
if mode is 'spec'
'spec'
else if mode is 'editor' and paths?.length > 0
sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex')
"editor-#{sha1}"
else
null
@@ -110,11 +101,8 @@ class Atom extends Model
@getConfigDirPath: ->
@configDirPath ?= process.env.ATOM_HOME
# Get the path to Atom's storage directory.
#
# Returns the absolute path to ~/.atom/storage
@getStorageDirPath: ->
@storageDirPath ?= path.join(@getConfigDirPath(), 'storage')
@getStorageFolder: ->
@storageFolder ?= new StorageFolder(@getConfigDirPath())
# Returns the load settings hash associated with the current window.
@getLoadSettings: ->
@@ -227,7 +215,7 @@ class Atom extends Model
if openDevTools
@openDevTools()
@executeJavaScriptInDevTools('InspectorFrontendAPI.showConsole()')
@executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@emit 'uncaught-error', arguments... if includeDeprecatedAPIs
@emitter.emit 'did-throw-error', {message, url, line, column, originalError}
@@ -586,12 +574,12 @@ class Atom extends Model
# Call this method when establishing a real application window.
startEditorWindow: ->
{resourcePath, safeMode} = @getLoadSettings()
{safeMode} = @getLoadSettings()
CommandInstaller = require './command-installer'
CommandInstaller.installAtomCommand resourcePath, false, (error) ->
CommandInstaller.installAtomCommand false, (error) ->
console.warn error.message if error?
CommandInstaller.installApmCommand resourcePath, false, (error) ->
CommandInstaller.installApmCommand false, (error) ->
console.warn error.message if error?
dimensions = @restoreWindowDimensions()
@@ -791,11 +779,10 @@ class Atom extends Model
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
saveSync: ->
stateString = JSON.stringify(@state)
if statePath = @constructor.getStatePath(@project?.getPaths(), @mode)
fs.writeFileSync(statePath, stateString, 'utf8')
if storageKey = @constructor.getStateKey(@project?.getPaths(), @mode)
@constructor.getStorageFolder().store(storageKey, @state)
else
@getCurrentWindow().loadSettings.windowState = stateString
@getCurrentWindow().loadSettings.windowState = JSON.stringify(@state)
crashMainProcess: ->
remote.process.crash()

View File

@@ -3,6 +3,7 @@ ApplicationMenu = require './application-menu'
AtomProtocolHandler = require './atom-protocol-handler'
AutoUpdateManager = require './auto-update-manager'
BrowserWindow = require 'browser-window'
StorageFolder = require '../storage-folder'
Menu = require 'menu'
app = require 'app'
fs = require 'fs-plus'
@@ -43,7 +44,6 @@ class AtomApplication
createAtomApplication()
return
client = net.connect {path: options.socketPath}, ->
client.write JSON.stringify(options), ->
client.end()
@@ -78,10 +78,13 @@ class AtomApplication
@listenForArgumentsFromNewProcess()
@setupJavaScriptArguments()
@handleEvents()
@storageFolder = new StorageFolder(process.env.ATOM_HOME)
@openWithOptions(options)
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
@openWithOptions(options)
else
@loadState() or @openPath(options)
# Opens a new window based on the options provided.
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, apiPreviewMode, newWindow, specDirectory, logFile}) ->
if test
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
@@ -193,11 +196,14 @@ class AtomApplication
@openPathOnEvent('application:open-your-keymap', 'atom://.atom/keymap')
@openPathOnEvent('application:open-your-snippets', 'atom://.atom/snippets')
@openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
@openPathOnEvent('application:open-license', path.join(@resourcePath, 'LICENSE.md'))
@openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md'))
app.on 'window-all-closed', ->
app.quit() if process.platform in ['win32', 'linux']
app.on 'before-quit', =>
@saveState()
app.on 'will-quit', =>
@killAllProcesses()
@deleteSocketFile()
@@ -328,7 +334,7 @@ class AtomApplication
focusedWindow: ->
_.find @windows, (atomWindow) -> atomWindow.isFocused()
# Public: Opens multiple paths, in existing windows if possible.
# Public: Opens a single path, in an existing window if possible.
#
# options -
# :pathToOpen - The file path to open
@@ -341,7 +347,7 @@ class AtomApplication
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, window}) ->
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, window})
# Public: Opens a single path, in an existing window if possible.
# Public: Opens multiple paths, in existing windows if possible.
#
# options -
# :pathsToOpen - The array of file paths to open
@@ -411,6 +417,33 @@ class AtomApplication
console.log("Killing process #{pid} failed: #{error.code ? error.message}")
delete @pidsToOpenWindows[pid]
saveState: ->
states = []
for window in @windows
if loadSettings = window.getLoadSettings()
unless loadSettings.isSpec
states.push(_.pick(loadSettings,
'initialPaths'
'devMode'
'safeMode'
'apiPreviewMode'
))
@storageFolder.store('application.json', states)
loadState: ->
if (states = @storageFolder.load('application.json'))?.length > 0
for state in states
@openWithOptions({
pathsToOpen: state.initialPaths
urlsToOpen: []
devMode: state.devMode
safeMode: state.safeMode
apiPreviewMode: state.apiPreviewMode
})
true
else
false
# Open an atom:// url.
#
# The host of the URL being opened is assumed to be the package name

View File

@@ -87,8 +87,9 @@ class AtomWindow
hash: encodeURIComponent(JSON.stringify(loadSettings))
getLoadSettings: ->
hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
JSON.parse(decodeURIComponent(hash))
if @browserWindow.webContents.loaded
hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
JSON.parse(decodeURIComponent(hash))
hasProjectPath: -> @getLoadSettings().initialPaths?.length > 0
@@ -105,7 +106,7 @@ class AtomWindow
true
containsPath: (pathToCheck) ->
@getLoadSettings().initialPaths?.some (projectPath) ->
@getLoadSettings()?.initialPaths?.some (projectPath) ->
if not projectPath
false
else if not pathToCheck

View File

@@ -1,6 +1,4 @@
path = require 'path'
_ = require 'underscore-plus'
async = require 'async'
fs = require 'fs-plus'
runas = null # defer until used
@@ -36,12 +34,11 @@ module.exports =
message: "Failed to install shell commands"
detailedMessage: error.message
resourcePath = atom.getLoadSettings().resourcePath
@installAtomCommand resourcePath, true, (error) =>
@installAtomCommand true, (error) =>
if error?
showErrorDialog(error)
else
@installApmCommand resourcePath, true, (error) ->
@installApmCommand true, (error) ->
if error?
showErrorDialog(error)
else
@@ -49,12 +46,12 @@ module.exports =
message: "Commands installed."
detailedMessage: "The shell commands `atom` and `apm` are installed."
installAtomCommand: (resourcePath, askForPrivilege, callback) ->
commandPath = path.join(resourcePath, 'atom.sh')
installAtomCommand: (askForPrivilege, callback) ->
commandPath = path.join(process.resourcesPath, 'app', 'atom.sh')
@createSymlink commandPath, askForPrivilege, callback
installApmCommand: (resourcePath, askForPrivilege, callback) ->
commandPath = path.join(resourcePath, 'apm', 'node_modules', '.bin', 'apm')
installApmCommand: (askForPrivilege, callback) ->
commandPath = path.join(process.resourcesPath, 'app', 'apm', 'node_modules', '.bin', 'apm')
@createSymlink commandPath, askForPrivilege, callback
createSymlink: (commandPath, askForPrivilege, callback) ->

View File

@@ -111,7 +111,7 @@ class PackageManager
commandName = 'apm'
commandName += '.cmd' if process.platform is 'win32'
apmRoot = path.resolve(__dirname, '..', 'apm')
apmRoot = path.join(process.resourcesPath, 'app', 'apm')
@apmPath = path.join(apmRoot, 'bin', commandName)
unless fs.isFileSync(@apmPath)
@apmPath = path.join(apmRoot, 'node_modules', 'atom-package-manager', 'bin', commandName)

View File

@@ -371,7 +371,12 @@ class Package
reloadStylesheets: ->
oldSheets = _.clone(@stylesheets)
@loadStylesheets()
try
@loadStylesheets()
catch error
@handleError("Failed to reload the #{@name} package stylesheets", error)
@stylesheetDisposables?.dispose()
@stylesheetDisposables = new CompositeDisposable
@stylesheetsActivated = false

View File

@@ -1,5 +1,6 @@
{CompositeDisposable} = require 'event-kit'
{callAttachHooks} = require './space-pen-extensions'
PaneResizeHandleElement = require './pane-resize-handle-element'
class PaneAxisElement extends HTMLElement
createdCallback: ->
@@ -12,6 +13,7 @@ class PaneAxisElement extends HTMLElement
@subscriptions.add @model.onDidAddChild(@childAdded.bind(this))
@subscriptions.add @model.onDidRemoveChild(@childRemoved.bind(this))
@subscriptions.add @model.onDidReplaceChild(@childReplaced.bind(this))
@subscriptions.add @model.observeFlexScale(@flexScaleChanged.bind(this))
@childAdded({child, index}) for child, index in @model.getChildren()
@@ -22,13 +24,33 @@ class PaneAxisElement extends HTMLElement
@classList.add('vertical', 'pane-column')
this
isPaneResizeHandleElement: (element) ->
element?.nodeName.toLowerCase() is 'atom-pane-resize-handle'
childAdded: ({child, index}) ->
view = atom.views.getView(child)
@insertBefore(view, @children[index])
@insertBefore(view, @children[index * 2])
prevElement = view.previousSibling
# if previous element is not pane resize element, then insert new resize element
if prevElement? and not @isPaneResizeHandleElement(prevElement)
resizeHandle = document.createElement('atom-pane-resize-handle')
@insertBefore(resizeHandle, view)
nextElement = view.nextSibling
# if next element isnot resize element, then insert new resize element
if nextElement? and not @isPaneResizeHandleElement(nextElement)
resizeHandle = document.createElement('atom-pane-resize-handle')
@insertBefore(resizeHandle, nextElement)
callAttachHooks(view) # for backward compatibility with SpacePen views
childRemoved: ({child}) ->
view = atom.views.getView(child)
siblingView = view.previousSibling
# make sure next sibling view is pane resize view
if siblingView? and @isPaneResizeHandleElement(siblingView)
siblingView.remove()
view.remove()
childReplaced: ({index, oldChild, newChild}) ->
@@ -37,6 +59,8 @@ class PaneAxisElement extends HTMLElement
@childAdded({child: newChild, index})
focusedElement?.focus() if document.activeElement is document.body
flexScaleChanged: (flexScale) -> @style.flexGrow = flexScale
hasFocus: ->
this is document.activeElement or @contains(document.activeElement)

View File

@@ -12,13 +12,14 @@ class PaneAxis extends Model
container: null
orientation: null
constructor: ({@container, @orientation, children}) ->
constructor: ({@container, @orientation, children, flexScale}={}) ->
@emitter = new Emitter
@subscriptionsByChild = new WeakMap
@subscriptions = new CompositeDisposable
@children = []
if children?
@addChild(child) for child in children
@flexScale = flexScale ? 1
deserializeParams: (params) ->
{container} = params
@@ -28,6 +29,13 @@ class PaneAxis extends Model
serializeParams: ->
children: @children.map (child) -> child.serialize()
orientation: @orientation
flexScale: @flexScale
getFlexScale: -> @flexScale
setFlexScale: (@flexScale) ->
@emitter.emit 'did-change-flex-scale', @flexScale
@flexScale
getParent: -> @parent
@@ -59,6 +67,13 @@ class PaneAxis extends Model
onDidDestroy: (fn) ->
@emitter.on 'did-destroy', fn
onDidChangeFlexScale: (fn) ->
@emitter.on 'did-change-flex-scale', fn
observeFlexScale: (fn) ->
fn(@flexScale)
@onDidChangeFlexScale(fn)
addChild: (child, index=@children.length) ->
child.setParent(this)
child.setContainer(@container)
@@ -68,6 +83,16 @@ class PaneAxis extends Model
@children.splice(index, 0, child)
@emitter.emit 'did-add-child', {child, index}
adjustFlexScale: ->
# get current total flex scale of children
total = 0
total += child.getFlexScale() for child in @children
needTotal = @children.length
# set every child's flex scale by the ratio
for child in @children
child.setFlexScale(needTotal * child.getFlexScale() / total)
removeChild: (child, replacing=false) ->
index = @children.indexOf(child)
throw new Error("Removing non-existent child") if index is -1
@@ -75,6 +100,7 @@ class PaneAxis extends Model
@unsubscribeFromChild(child)
@children.splice(index, 1)
@adjustFlexScale()
@emitter.emit 'did-remove-child', {child, index}
@reparentLastChild() if not replacing and @children.length < 2
@@ -98,7 +124,9 @@ class PaneAxis extends Model
@addChild(newChild, index + 1)
reparentLastChild: ->
@parent.replaceChild(this, @children[0])
lastChild = @children[0]
lastChild.setFlexScale(@flexScale)
@parent.replaceChild(this, lastChild)
@destroy()
subscribeToChild: (child) ->

View File

@@ -1,3 +1,4 @@
path = require 'path'
{CompositeDisposable} = require 'event-kit'
Grim = require 'grim'
{$, callAttachHooks, callRemoveHooks} = require './space-pen-extensions'
@@ -38,8 +39,21 @@ class PaneElement extends HTMLElement
handleBlur = (event) =>
@model.blur() unless @contains(event.relatedTarget)
handleDragOver = (event) ->
event.preventDefault()
event.stopPropagation()
handleDrop = (event) =>
event.preventDefault()
event.stopPropagation()
@getModel().activate()
pathsToOpen = Array::map.call event.dataTransfer.files, (file) -> file.path
atom.open({pathsToOpen}) if pathsToOpen.length > 0
@addEventListener 'focus', handleFocus, true
@addEventListener 'blur', handleBlur, true
@addEventListener 'dragover', handleDragOver
@addEventListener 'drop', handleDrop
createSpacePenShim: ->
PaneView ?= require './pane-view'
@@ -51,6 +65,8 @@ class PaneElement extends HTMLElement
@subscriptions.add @model.observeActiveItem(@activeItemChanged.bind(this))
@subscriptions.add @model.onDidRemoveItem(@itemRemoved.bind(this))
@subscriptions.add @model.onDidDestroy(@paneDestroyed.bind(this))
@subscriptions.add @model.observeFlexScale(@flexScaleChanged.bind(this))
@__spacePenView.setModel(@model) if Grim.includeDeprecatedAPIs
this
@@ -66,11 +82,18 @@ class PaneElement extends HTMLElement
@classList.remove('active')
activeItemChanged: (item) ->
delete @dataset.activeItemName
delete @dataset.activeItemPath
return unless item?
hasFocus = @hasFocus()
itemView = atom.views.getView(item)
if itemPath = item.getPath?()
@dataset.activeItemName = path.basename(itemPath)
@dataset.activeItemPath = itemPath
unless @itemViews.contains(itemView)
@itemViews.appendChild(itemView)
callAttachHooks(itemView)
@@ -104,6 +127,9 @@ class PaneElement extends HTMLElement
paneDestroyed: ->
@subscriptions.dispose()
flexScaleChanged: (flexScale) ->
@style.flexGrow = flexScale
getActiveView: -> atom.views.getView(@model.getActiveItem())
hasFocus: ->

View File

@@ -0,0 +1,64 @@
class PaneResizeHandleElement extends HTMLElement
createdCallback: ->
@resizePane = @resizePane.bind(this)
@resizeStopped = @resizeStopped.bind(this)
@subscribeToDOMEvents()
subscribeToDOMEvents: ->
@addEventListener 'dblclick', @resizeToFitContent.bind(this)
@addEventListener 'mousedown', @resizeStarted.bind(this)
attachedCallback: ->
@isHorizontal = @parentElement.classList.contains("horizontal")
@classList.add if @isHorizontal then 'horizontal' else 'vertical'
resizeToFitContent: ->
# clear flex-grow css style of both pane
@previousSibling.model.setFlexScale(1)
@nextSibling.model.setFlexScale(1)
resizeStarted: (e) ->
e.stopPropagation()
document.addEventListener 'mousemove', @resizePane
document.addEventListener 'mouseup', @resizeStopped
resizeStopped: ->
document.removeEventListener 'mousemove', @resizePane
document.removeEventListener 'mouseup', @resizeStopped
calcRatio: (ratio1, ratio2, total) ->
allRatio = ratio1 + ratio2
[total * ratio1 / allRatio, total * ratio2 / allRatio]
setFlexGrow: (prevSize, nextSize) ->
@prevModel = @previousSibling.model
@nextModel = @nextSibling.model
totalScale = @prevModel.getFlexScale() + @nextModel.getFlexScale()
flexGrows = @calcRatio(prevSize, nextSize, totalScale)
@prevModel.setFlexScale flexGrows[0]
@nextModel.setFlexScale flexGrows[1]
fixInRange: (val, minValue, maxValue) ->
Math.min(Math.max(val, minValue), maxValue)
resizePane: ({clientX, clientY, which}) ->
return @resizeStopped() unless which is 1
if @isHorizontal
totalWidth = @previousSibling.clientWidth + @nextSibling.clientWidth
#get the left and right width after move the resize view
leftWidth = clientX - @previousSibling.getBoundingClientRect().left
leftWidth = @fixInRange(leftWidth, 0, totalWidth)
rightWidth = totalWidth - leftWidth
# set the flex grow by the ratio of left width and right width
# to change pane width
@setFlexGrow(leftWidth, rightWidth)
else
totalHeight = @previousSibling.clientHeight + @nextSibling.clientHeight
topHeight = clientY - @previousSibling.getBoundingClientRect().top
topHeight = @fixInRange(topHeight, 0, totalHeight)
bottomHeight = totalHeight - topHeight
@setFlexGrow(topHeight, bottomHeight)
module.exports = PaneResizeHandleElement =
document.registerElement 'atom-pane-resize-handle', prototype: PaneResizeHandleElement.prototype

View File

@@ -28,6 +28,7 @@ class Pane extends Model
@addItems(compact(params?.items ? []))
@setActiveItem(@items[0]) unless @getActiveItem()?
@setFlexScale(params?.flexScale ? 1)
# Called by the Serializable mixin during serialization.
serializeParams: ->
@@ -40,6 +41,7 @@ class Pane extends Model
items: compact(@items.map((item) -> item.serialize?()))
activeItemURI: activeItemURI
focused: @focused
flexScale: @flexScale
# Called by the Serializable mixin during deserialization.
deserializeParams: (params) ->
@@ -66,10 +68,36 @@ class Pane extends Model
@container = container
container.didAddPane({pane: this})
setFlexScale: (@flexScale) ->
@emitter.emit 'did-change-flex-scale', @flexScale
@flexScale
getFlexScale: -> @flexScale
###
Section: Event Subscription
###
# Public: Invoke the given callback when the pane resize
#
# the callback will be invoked when pane's flexScale property changes
#
# * `callback` {Function} to be called when the pane is resized
#
# Returns a {Disposable} on which '.dispose()' can be called to unsubscribe.
onDidChangeFlexScale: (callback) ->
@emitter.on 'did-change-flex-scale', callback
# Public: Invoke the given callback with all current and future items.
#
# * `callback` {Function} to be called with current and future items.
# * `item` An item that is present in {::getItems} at the time of
# subscription or that is added at some later time.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
observeFlexScale: (callback) ->
callback(@flexScale)
@onDidChangeFlexScale(callback)
# Public: Invoke the given callback when the pane is activated.
#
# The given callback will be invoked whenever {::activate} is called on the
@@ -591,7 +619,8 @@ class Pane extends Model
params.items.push(@copyActiveItem())
if @parent.orientation isnt orientation
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this], @flexScale}))
@setFlexScale(1)
newPane = new @constructor(params)
switch side

27
src/storage-folder.coffee Normal file
View File

@@ -0,0 +1,27 @@
path = require "path"
fs = require "fs-plus"
module.exports =
class StorageFolder
constructor: (containingPath) ->
@path = path.join(containingPath, "storage")
store: (name, object) ->
fs.writeFileSync(@pathForKey(name), JSON.stringify(object), 'utf8')
load: (name) ->
statePath = @pathForKey(name)
try
stateString = fs.readFileSync(statePath, 'utf8')
catch error
unless error.code is 'ENOENT'
console.warn "Error reading state file: #{statePath}", error.stack, error
return undefined
try
JSON.parse(stateString)
catch error
console.warn "Error parsing state file: #{statePath}", error.stack, error
pathForKey: (name) -> path.join(@getPath(), name)
getPath: -> @path

View File

@@ -83,7 +83,7 @@ class Task
taskPath = taskPath.replace(/\\/g, "\\\\")
env = _.extend({}, process.env, {taskPath, userAgent: navigator.userAgent})
@childProcess = fork '--eval', [bootstrap], {env, cwd: __dirname}
@childProcess = fork '--eval', [bootstrap], {env, silent: true}
@on "task:log", -> console.log(arguments...)
@on "task:warn", -> console.warn(arguments...)
@@ -100,6 +100,11 @@ class Task
@childProcess.removeAllListeners()
@childProcess.on 'message', ({event, args}) =>
@emit(event, args...) if @childProcess?
# Catch the errors that happened before task-bootstrap.
@childProcess.stdout.on 'data', (data) ->
console.log data.toString()
@childProcess.stderr.on 'data', (data) ->
console.error data.toString()
# Public: Starts the task.
#

View File

@@ -14,11 +14,7 @@ class ThemePackage extends Package
atom.config.removeAtKeyPath('core.themes', @name)
load: ->
@measure 'loadTime', =>
try
@metadata ?= Package.loadMetadata(@path)
catch error
console.warn "Failed to load theme named '#{@name}'", error.stack ? error
@loadTime = 0
this
activate: ->
@@ -26,7 +22,10 @@ class ThemePackage extends Package
@activationDeferred = Q.defer()
@measure 'activateTime', =>
@loadStylesheets()
@activateNow()
try
@loadStylesheets()
@activateNow()
catch error
@handleError("Failed to activate the #{@name} theme", error)
@activationDeferred.promise

View File

@@ -1,6 +1,5 @@
path = require 'path'
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
{Disposable} = require 'event-kit'
ipc = require 'ipc'
shell = require 'shell'
@@ -136,12 +135,11 @@ class WindowEventHandler
onDrop: (event) ->
event.preventDefault()
event.stopPropagation()
pathsToOpen = _.pluck(event.dataTransfer.files, 'path')
atom.open({pathsToOpen}) if pathsToOpen.length > 0
onDragOver: (event) ->
event.preventDefault()
event.stopPropagation()
event.dataTransfer.dropEffect = 'none'
openLink: ({target, currentTarget}) ->
location = target?.getAttribute('href') or currentTarget?.getAttribute('href')

View File

@@ -4,19 +4,42 @@
// settings-view, the archive-view, the image-view. Etc. Basically a non-
// editor resource with a tab.
atom-pane-container {
position: relative;
display: -webkit-flex;
-webkit-flex: 1;
atom-pane-axis.vertical {
atom-pane-axis {
display: -webkit-flex;
-webkit-flex: 1;
& > atom-pane-resize-handle {
position: absolute;
z-index: 3;
}
}
atom-pane-axis.vertical {
-webkit-flex-direction: column;
& > atom-pane-resize-handle {
width: 100%;
height: 8px;
margin-top: -4px;
cursor: ns-resize;
border-bottom: none;
}
}
atom-pane-axis.horizontal {
display: -webkit-flex;
-webkit-flex: 1;
-webkit-flex-direction: row;
& > atom-pane-resize-handle {
width: 8px;
height: 100%;
margin-left: -4px;
cursor: ew-resize;
border-right: none;
}
}
atom-pane {