mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
Merge branch 'master' into as-public-ci
This commit is contained in:
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.157.0"
|
||||
"atom-package-manager": "0.158.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
35
build/tasks/generate-asar-task.coffee
Normal file
35
build/tasks/generate-asar-task.coffee
Normal 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()
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
1
spec/fixtures/packages/theme-with-invalid-styles/index.less
vendored
Normal file
1
spec/fixtures/packages/theme-with-invalid-styles/index.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<>
|
||||
4
spec/fixtures/packages/theme-with-invalid-styles/package.json
vendored
Normal file
4
spec/fixtures/packages/theme-with-invalid-styles/package.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "theme-with-invalid-styles",
|
||||
"theme": "ui"
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
134
spec/pane-container-element-spec.coffee
Normal file
134
spec/pane-container-element-spec.coffee
Normal 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]
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = ->
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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: ->
|
||||
|
||||
64
src/pane-resize-handle-element.coffee
Normal file
64
src/pane-resize-handle-element.coffee
Normal 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
|
||||
@@ -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
27
src/storage-folder.coffee
Normal 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
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user