mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-11-11 04:48:44 +03:00
Merge pull request #5475 from atom/mb-multi-folder-project
Allow project API to support multiple root directories
This commit is contained in:
commit
2dac7fd18e
@ -6,10 +6,9 @@
|
||||
# arguments, so this script accepts the following special switches:
|
||||
#
|
||||
# * `atom-path`: The path to the `Atom` binary.
|
||||
# * `atom-arg`: A positional argument to pass to Atom. This flag can be specified
|
||||
# multiple times.
|
||||
# * `atom-env`: A key=value environment variable to set for Atom. This flag can
|
||||
# be specified multiple times.
|
||||
# * `atom-args`: A space-separated list of positional arguments to pass to Atom.
|
||||
# * `atom-env`: A space-separated list of key=value pairs representing environment
|
||||
# variables to set for Atom.
|
||||
#
|
||||
# Any other switches will be passed through to `Atom`.
|
||||
|
||||
@ -23,12 +22,18 @@ for arg in "$@"; do
|
||||
atom_path="${arg#*=}"
|
||||
;;
|
||||
|
||||
--atom-arg=*)
|
||||
atom_args+=(${arg#*=})
|
||||
--atom-args=*)
|
||||
atom_arg_string="${arg#*=}"
|
||||
for atom_arg in $atom_arg_string; do
|
||||
atom_args+=($atom_arg)
|
||||
done
|
||||
;;
|
||||
|
||||
--atom-env=*)
|
||||
export ${arg#*=}
|
||||
atom_env_string="${arg#*=}"
|
||||
for atom_env_pair in $atom_env_string; do
|
||||
export $atom_env_pair
|
||||
done
|
||||
;;
|
||||
|
||||
*)
|
||||
|
@ -1,10 +1,10 @@
|
||||
path = require "path"
|
||||
temp = require("temp").track()
|
||||
remote = require "remote"
|
||||
{map, extend} = require "underscore-plus"
|
||||
async = require "async"
|
||||
{map, extend, once, difference} = require "underscore-plus"
|
||||
{spawn, spawnSync} = require "child_process"
|
||||
webdriverio = require "../../../build/node_modules/webdriverio"
|
||||
async = require "async"
|
||||
|
||||
AtomPath = remote.process.argv[0]
|
||||
AtomLauncherPath = path.join(__dirname, "..", "helpers", "atom-launcher.sh")
|
||||
@ -12,77 +12,120 @@ ChromedriverPath = path.resolve(__dirname, '..', '..', '..', 'atom-shell', 'chro
|
||||
SocketPath = path.join(temp.mkdirSync("socket-dir"), "atom.sock")
|
||||
ChromedriverPort = 9515
|
||||
|
||||
module.exports =
|
||||
driverTest: (fn) ->
|
||||
chromedriver = spawn(ChromedriverPath, [
|
||||
"--verbose",
|
||||
"--port=#{ChromedriverPort}",
|
||||
"--url-base=/wd/hub"
|
||||
])
|
||||
buildAtomClient = (args, env) ->
|
||||
client = webdriverio.remote(
|
||||
host: 'localhost'
|
||||
port: ChromedriverPort
|
||||
desiredCapabilities:
|
||||
browserName: "atom"
|
||||
chromeOptions:
|
||||
binary: AtomLauncherPath
|
||||
args: [
|
||||
"atom-path=#{AtomPath}"
|
||||
"atom-args=#{args.join(" ")}"
|
||||
"atom-env=#{map(env, (value, key) -> "#{key}=#{value}").join(" ")}"
|
||||
"dev"
|
||||
"safe"
|
||||
"user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}"
|
||||
"socket-path=#{SocketPath}"
|
||||
])
|
||||
|
||||
logs = []
|
||||
isRunning = false
|
||||
client.on "init", -> isRunning = true
|
||||
client.on "end", -> isRunning = false
|
||||
|
||||
client
|
||||
.addCommand "waitUntil", (conditionFn, timeout, cb) ->
|
||||
timedOut = succeeded = false
|
||||
pollingInterval = Math.min(timeout, 100)
|
||||
setTimeout((-> timedOut = true), timeout)
|
||||
async.until(
|
||||
(-> succeeded or timedOut),
|
||||
((next) =>
|
||||
setTimeout(=>
|
||||
conditionFn.call(this).then(
|
||||
((result) ->
|
||||
succeeded = result
|
||||
next()),
|
||||
((err) -> next(err))
|
||||
)
|
||||
, pollingInterval)),
|
||||
((err) -> cb(err, succeeded)))
|
||||
|
||||
.addCommand "waitForWindowCount", (count, timeout, cb) ->
|
||||
@waitUntil(
|
||||
(-> @windowHandles().then(({value}) -> value.length is count)),
|
||||
timeout)
|
||||
.then((result) -> expect(result).toBe(true))
|
||||
.windowHandles(cb)
|
||||
|
||||
.addCommand "waitForPaneItemCount", (count, timeout, cb) ->
|
||||
@waitUntil((->
|
||||
@execute(-> atom.workspace?.getActivePane()?.getItems().length)
|
||||
.then(({value}) -> value is count)), timeout)
|
||||
.then (result) ->
|
||||
expect(result).toBe(true)
|
||||
cb(null)
|
||||
|
||||
.addCommand("waitForNewWindow", (fn, timeout, done) ->
|
||||
@windowHandles()
|
||||
.then(({value}) ->
|
||||
return done() unless isRunning
|
||||
oldWindowHandles = value
|
||||
@call(-> fn.call(this))
|
||||
.waitForWindowCount(oldWindowHandles.length + 1, 5000)
|
||||
.then(({value}) ->
|
||||
[newWindowHandle] = difference(value, oldWindowHandles)
|
||||
@window(newWindowHandle, done))))
|
||||
|
||||
.addCommand "startAnotherAtom", (args, env, done) ->
|
||||
@call ->
|
||||
if isRunning
|
||||
spawnSync(AtomPath, args.concat([
|
||||
"--dev"
|
||||
"--safe"
|
||||
"--socket-path=#{SocketPath}"
|
||||
]), env: extend({}, process.env, env))
|
||||
done()
|
||||
|
||||
module.exports = (args, env, fn) ->
|
||||
chromedriver = spawn(ChromedriverPath, [
|
||||
"--verbose",
|
||||
"--port=#{ChromedriverPort}",
|
||||
"--url-base=/wd/hub"
|
||||
])
|
||||
|
||||
waits(50)
|
||||
|
||||
chromedriverLogs = []
|
||||
chromedriverExit = new Promise (resolve) ->
|
||||
errorCode = null
|
||||
chromedriver.on "exit", (code, signal) ->
|
||||
errorCode = code unless signal?
|
||||
chromedriver.stderr.on "data", (log) ->
|
||||
logs.push(log.toString())
|
||||
chromedriverLogs.push(log.toString())
|
||||
chromedriver.stderr.on "close", ->
|
||||
if errorCode?
|
||||
jasmine.getEnv().currentSpec.fail "Chromedriver exited. code: #{errorCode}. Logs: #{logs.join("\n")}"
|
||||
resolve(errorCode)
|
||||
|
||||
waitsFor "webdriver steps to complete", (done) ->
|
||||
fn()
|
||||
.catch((error) -> jasmine.getEnv().currentSpec.fail(err.message))
|
||||
waitsFor("webdriver to finish", (done) ->
|
||||
finish = once ->
|
||||
client
|
||||
.end()
|
||||
.call(done)
|
||||
, 30000
|
||||
.then(-> chromedriver.kill())
|
||||
.then(chromedriverExit.then(
|
||||
(errorCode) ->
|
||||
if errorCode?
|
||||
jasmine.getEnv().currentSpec.fail """
|
||||
Chromedriver exited with code #{errorCode}.
|
||||
Logs:\n#{chromedriverLogs.join("\n")}
|
||||
"""
|
||||
done()))
|
||||
|
||||
runs -> chromedriver.kill()
|
||||
client = buildAtomClient(args, env)
|
||||
|
||||
# Start Atom using chromedriver.
|
||||
startAtom: (args, env={}) ->
|
||||
webdriverio.remote(
|
||||
host: 'localhost'
|
||||
port: ChromedriverPort
|
||||
desiredCapabilities:
|
||||
browserName: "atom"
|
||||
chromeOptions:
|
||||
binary: AtomLauncherPath
|
||||
args: [
|
||||
"atom-path=#{AtomPath}"
|
||||
"dev"
|
||||
"safe"
|
||||
"user-data-dir=#{temp.mkdirSync('integration-spec-')}"
|
||||
"socket-path=#{SocketPath}"
|
||||
]
|
||||
.concat(map args, (arg) -> "atom-arg=#{arg}")
|
||||
.concat(map env, (value, key) -> "atom-env=#{key}=#{value}"))
|
||||
.init()
|
||||
.addCommand "waitForCondition", (conditionFn, timeout, cb) ->
|
||||
timedOut = succeeded = false
|
||||
pollingInterval = Math.min(timeout, 100)
|
||||
client.on "error", (err) ->
|
||||
jasmine.getEnv().currentSpec.fail(JSON.stringify(err))
|
||||
finish()
|
||||
|
||||
setTimeout((-> timedOut = true), timeout)
|
||||
|
||||
async.until(
|
||||
(-> succeeded or timedOut),
|
||||
((next) =>
|
||||
setTimeout(=>
|
||||
conditionFn.call(this).then(
|
||||
((result) ->
|
||||
succeeded = result
|
||||
next()),
|
||||
((err) -> next(err))
|
||||
)
|
||||
, pollingInterval)),
|
||||
((err) -> cb(err, succeeded))
|
||||
)
|
||||
|
||||
# Once one `Atom` window is open, subsequent invocations of `Atom` will exit
|
||||
# immediately.
|
||||
startAnotherAtom: (args, env={}) ->
|
||||
spawnSync(AtomPath, args.concat([
|
||||
"--dev"
|
||||
"--safe"
|
||||
"--socket-path=#{SocketPath}"
|
||||
]), env: extend({}, process.env, env))
|
||||
fn(client.init()).then(finish)
|
||||
, 30000)
|
||||
|
@ -7,34 +7,33 @@ 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')))
|
||||
|
||||
{startAtom, startAnotherAtom, driverTest} = require("./helpers/start-atom")
|
||||
runAtom = require("./helpers/start-atom")
|
||||
|
||||
describe "Starting Atom", ->
|
||||
beforeEach ->
|
||||
jasmine.useRealClock()
|
||||
|
||||
describe "opening paths via commmand-line arguments", ->
|
||||
[tempDirPath, tempFilePath] = []
|
||||
[tempDirPath, tempFilePath, otherTempDirPath] = []
|
||||
|
||||
beforeEach ->
|
||||
tempDirPath = temp.mkdirSync("empty-dir")
|
||||
otherTempDirPath = temp.mkdirSync("another-temp-dir")
|
||||
tempFilePath = path.join(tempDirPath, "an-existing-file")
|
||||
fs.writeFileSync(tempFilePath, "This was already here.")
|
||||
fs.writeFileSync(tempFilePath, "This file was already here.")
|
||||
|
||||
it "reuses existing windows when directories are reopened", ->
|
||||
driverTest ->
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: AtomHome}, (client) ->
|
||||
client
|
||||
|
||||
# Opening a new file creates one window with one empty text editor.
|
||||
startAtom([path.join(tempDirPath, "new-file")], ATOM_HOME: AtomHome)
|
||||
# Opening a new file creates one window with one empty text editor.
|
||||
.waitForExist("atom-text-editor", 5000)
|
||||
.then((exists) -> expect(exists).toBe true)
|
||||
.windowHandles()
|
||||
.then(({value}) -> expect(value.length).toBe 1)
|
||||
.execute(-> atom.workspace.getActivePane().getItems().length)
|
||||
.then(({value}) -> expect(value).toBe 1)
|
||||
.waitForWindowCount(1, 1000)
|
||||
.waitForPaneItemCount(1, 1000)
|
||||
.execute(-> atom.project.getPaths())
|
||||
.then(({value}) -> expect(value).toEqual([tempDirPath]))
|
||||
|
||||
# Typing in the editor changes its text.
|
||||
.execute(-> atom.workspace.getActiveTextEditor().getText())
|
||||
@ -46,25 +45,58 @@ describe "Starting Atom", ->
|
||||
|
||||
# Opening an existing file in the same directory reuses the window and
|
||||
# adds a new tab for the file.
|
||||
.call(-> startAnotherAtom([tempFilePath], ATOM_HOME: AtomHome))
|
||||
.waitForCondition(
|
||||
(-> @execute((-> atom.workspace.getActivePane().getItems().length)).then ({value}) -> value is 2),
|
||||
5000)
|
||||
.then((result) -> expect(result).toBe(true))
|
||||
.startAnotherAtom([tempFilePath], ATOM_HOME: AtomHome)
|
||||
.waitForExist("atom-workspace")
|
||||
.waitForPaneItemCount(2, 5000)
|
||||
.waitForWindowCount(1, 1000)
|
||||
.execute(-> atom.workspace.getActiveTextEditor().getText())
|
||||
.then(({value}) -> expect(value).toBe "This was already here.")
|
||||
.then(({value}) -> expect(value).toBe "This file was already here.")
|
||||
|
||||
# Opening a different directory creates a second window with no
|
||||
# tabs open.
|
||||
.call(-> startAnotherAtom([temp.mkdirSync("another-empty-dir")], ATOM_HOME: AtomHome))
|
||||
.waitForCondition(
|
||||
(-> @windowHandles().then(({value}) -> value.length is 2)),
|
||||
5000)
|
||||
.then((result) -> expect(result).toBe(true))
|
||||
.windowHandles()
|
||||
.then(({value}) ->
|
||||
@window(value[1])
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.then((exists) -> expect(exists).toBe true)
|
||||
.execute(-> atom.workspace.getActivePane().getItems().length)
|
||||
.then(({value}) -> expect(value).toBe 0))
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([otherTempDirPath], ATOM_HOME: AtomHome)
|
||||
, 5000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(0, 1000)
|
||||
|
||||
it "saves the state of closed windows", ->
|
||||
runAtom [tempDirPath], {ATOM_HOME: AtomHome}, (client) ->
|
||||
client
|
||||
|
||||
# In a second window, opening a new buffer creates a new tab.
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([otherTempDirPath], ATOM_HOME: AtomHome)
|
||||
, 5000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(0, 3000)
|
||||
.execute(-> atom.workspace.open())
|
||||
.waitForPaneItemCount(1, 3000)
|
||||
|
||||
# Closing that window and reopening that directory shows the
|
||||
# previously-created new buffer.
|
||||
.execute(-> atom.unloadEditorWindow())
|
||||
.close()
|
||||
.waitForWindowCount(1, 5000)
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([otherTempDirPath], ATOM_HOME: AtomHome)
|
||||
, 5000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
|
||||
it "allows multiple project directories to be passed as separate arguments", ->
|
||||
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: AtomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.then((exists) -> expect(exists).toBe true)
|
||||
.execute(-> atom.project.getPaths())
|
||||
.then(({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath]))
|
||||
|
||||
# Opening a file in one of the directories reuses the same window
|
||||
# and does not change the project paths.
|
||||
.startAnotherAtom([tempFilePath], ATOM_HOME: AtomHome)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
.execute(-> atom.project.getPaths())
|
||||
.then(({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath]))
|
||||
|
@ -7,6 +7,7 @@ path = require 'path'
|
||||
BufferedProcess = require '../src/buffered-process'
|
||||
{Directory} = require 'pathwatcher'
|
||||
GitRepository = require '../src/git-repository'
|
||||
temp = require "temp"
|
||||
|
||||
describe "Project", ->
|
||||
beforeEach ->
|
||||
@ -228,11 +229,33 @@ describe "Project", ->
|
||||
expect(atom.project.getDirectories()[0].path).toEqual path.dirname(require.resolve('./fixtures/dir/a'))
|
||||
|
||||
describe "when path is a directory", ->
|
||||
it "sets its path to the directory and updates the root directory", ->
|
||||
directory = fs.absolute(path.join(__dirname, 'fixtures', 'dir', 'a-dir'))
|
||||
atom.project.setPaths([directory])
|
||||
expect(atom.project.getPaths()[0]).toEqual directory
|
||||
expect(atom.project.getDirectories()[0].path).toEqual directory
|
||||
it "assigns the directories and repositories", ->
|
||||
directory1 = temp.mkdirSync("non-git-repo")
|
||||
directory2 = temp.mkdirSync("git-repo1")
|
||||
directory3 = temp.mkdirSync("git-repo2")
|
||||
|
||||
gitDirPath = fs.absolute(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
fs.copySync(gitDirPath, path.join(directory2, ".git"))
|
||||
fs.copySync(gitDirPath, path.join(directory3, ".git"))
|
||||
|
||||
atom.project.setPaths([directory1, directory2, directory3])
|
||||
|
||||
[repo1, repo2, repo3] = atom.project.getRepositories()
|
||||
expect(repo1).toBeNull()
|
||||
expect(repo2.getShortHead()).toBe "master"
|
||||
expect(repo2.getPath()).toBe fs.realpathSync(path.join(directory2, ".git"))
|
||||
expect(repo3.getShortHead()).toBe "master"
|
||||
expect(repo3.getPath()).toBe fs.realpathSync(path.join(directory3, ".git"))
|
||||
|
||||
it "calls callbacks registered with ::onDidChangePaths", ->
|
||||
onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy')
|
||||
atom.project.onDidChangePaths(onDidChangePathsSpy)
|
||||
|
||||
paths = [ temp.mkdirSync("dir1"), temp.mkdirSync("dir2") ]
|
||||
atom.project.setPaths(paths)
|
||||
|
||||
expect(onDidChangePathsSpy.callCount).toBe 1
|
||||
expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual(paths)
|
||||
|
||||
describe "when path is null", ->
|
||||
it "sets its path and root directory to null", ->
|
||||
@ -245,6 +268,53 @@ describe "Project", ->
|
||||
expect(atom.project.getPaths()[0]).toEqual path.dirname(require.resolve('./fixtures/dir/a'))
|
||||
expect(atom.project.getDirectories()[0].path).toEqual path.dirname(require.resolve('./fixtures/dir/a'))
|
||||
|
||||
describe ".addPath(path)", ->
|
||||
it "calls callbacks registered with ::onDidChangePaths", ->
|
||||
onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy')
|
||||
atom.project.onDidChangePaths(onDidChangePathsSpy)
|
||||
|
||||
[oldPath] = atom.project.getPaths()
|
||||
|
||||
newPath = temp.mkdirSync("dir")
|
||||
atom.project.addPath(newPath)
|
||||
|
||||
expect(onDidChangePathsSpy.callCount).toBe 1
|
||||
expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual([oldPath, newPath])
|
||||
|
||||
describe "when the project already has the path or one of its descendants", ->
|
||||
it "doesn't add it again", ->
|
||||
onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy')
|
||||
atom.project.onDidChangePaths(onDidChangePathsSpy)
|
||||
|
||||
[oldPath] = atom.project.getPaths()
|
||||
|
||||
atom.project.addPath(oldPath)
|
||||
atom.project.addPath(path.join(oldPath, "some-file.txt"))
|
||||
atom.project.addPath(path.join(oldPath, "a-dir"))
|
||||
atom.project.addPath(path.join(oldPath, "a-dir", "oh-git"))
|
||||
|
||||
expect(atom.project.getPaths()).toEqual([oldPath])
|
||||
expect(onDidChangePathsSpy).not.toHaveBeenCalled()
|
||||
|
||||
describe ".relativize(path)", ->
|
||||
it "returns the path, relative to whichever root directory it is inside of", ->
|
||||
rootPath = atom.project.getPaths()[0]
|
||||
childPath = path.join(rootPath, "some", "child", "directory")
|
||||
expect(atom.project.relativize(childPath)).toBe path.join("some", "child", "directory")
|
||||
|
||||
it "returns the given path if it is not in any of the root directories", ->
|
||||
randomPath = path.join("some", "random", "path")
|
||||
expect(atom.project.relativize(randomPath)).toBe randomPath
|
||||
|
||||
describe ".contains(path)", ->
|
||||
it "returns whether or not the given path is in one of the root directories", ->
|
||||
rootPath = atom.project.getPaths()[0]
|
||||
childPath = path.join(rootPath, "some", "child", "directory")
|
||||
expect(atom.project.contains(childPath)).toBe true
|
||||
|
||||
randomPath = path.join("some", "random", "path")
|
||||
expect(atom.project.contains(randomPath)).toBe false
|
||||
|
||||
describe ".eachBuffer(callback)", ->
|
||||
beforeEach ->
|
||||
atom.project.bufferForPathSync('a')
|
||||
|
@ -276,33 +276,31 @@ describe "Window", ->
|
||||
elements.trigger "core:focus-previous"
|
||||
expect(elements.find("[tabindex=1]:focus")).toExist()
|
||||
|
||||
describe "the window:open-path event", ->
|
||||
describe "the window:open-locations event", ->
|
||||
beforeEach ->
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.project.setPaths([])
|
||||
|
||||
describe "when the project does not have a path", ->
|
||||
beforeEach ->
|
||||
atom.project.setPaths([])
|
||||
describe "when the opened path exists", ->
|
||||
it "adds it to the project's paths", ->
|
||||
pathToOpen = __filename
|
||||
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
|
||||
expect(atom.project.getPaths()[0]).toBe __dirname
|
||||
|
||||
describe "when the opened path exists", ->
|
||||
it "sets the project path to the opened path", ->
|
||||
atom.getCurrentWindow().send 'message', 'open-path', pathToOpen: __filename
|
||||
expect(atom.project.getPaths()[0]).toBe __dirname
|
||||
|
||||
describe "when the opened path does not exist but its parent directory does", ->
|
||||
it "sets the project path to the opened path's parent directory", ->
|
||||
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
|
||||
atom.getCurrentWindow().send 'message', 'open-path', {pathToOpen}
|
||||
expect(atom.project.getPaths()[0]).toBe __dirname
|
||||
describe "when the opened path does not exist but its parent directory does", ->
|
||||
it "adds the parent directory to the project paths", ->
|
||||
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
|
||||
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
|
||||
expect(atom.project.getPaths()[0]).toBe __dirname
|
||||
|
||||
describe "when the opened path is a file", ->
|
||||
it "opens it in the workspace", ->
|
||||
atom.getCurrentWindow().send 'message', 'open-path', pathToOpen: __filename
|
||||
|
||||
pathToOpen = __filename
|
||||
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
|
||||
expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
|
||||
|
||||
describe "when the opened path is a directory", ->
|
||||
it "does not open it in the workspace", ->
|
||||
atom.getCurrentWindow().send 'message', 'open-path', pathToOpen: __dirname
|
||||
|
||||
pathToOpen = __dirname
|
||||
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
|
||||
expect(atom.workspace.open.callCount).toBe 0
|
||||
|
@ -95,9 +95,9 @@ class Atom extends Model
|
||||
when 'spec'
|
||||
filename = 'spec'
|
||||
when 'editor'
|
||||
{initialPath} = @getLoadSettings()
|
||||
if initialPath
|
||||
sha1 = crypto.createHash('sha1').update(initialPath).digest('hex')
|
||||
{initialPaths} = @getLoadSettings()
|
||||
if initialPaths
|
||||
sha1 = crypto.createHash('sha1').update(initialPaths.join("\n")).digest('hex')
|
||||
filename = "editor-#{sha1}"
|
||||
|
||||
if filename
|
||||
@ -407,6 +407,17 @@ class Atom extends Model
|
||||
open: (options) ->
|
||||
ipc.send('open', options)
|
||||
|
||||
# Extended: Show the native dialog to prompt the user to select a folder.
|
||||
#
|
||||
# * `callback` A {Function} to call once the user has selected a folder.
|
||||
# * `path` {String} the path to the folder the user selected.
|
||||
pickFolder: (callback) ->
|
||||
responseChannel = "atom-pick-folder-response"
|
||||
ipc.on responseChannel, (path) ->
|
||||
ipc.removeAllListeners(responseChannel)
|
||||
callback(path)
|
||||
ipc.send("pick-folder", responseChannel)
|
||||
|
||||
# Essential: Close the current window.
|
||||
close: ->
|
||||
@getCurrentWindow().close()
|
||||
@ -693,7 +704,7 @@ class Atom extends Model
|
||||
Project = require './project'
|
||||
|
||||
startTime = Date.now()
|
||||
@project ?= @deserializers.deserialize(@state.project) ? new Project(paths: [@getLoadSettings().initialPath])
|
||||
@project ?= @deserializers.deserialize(@state.project) ? new Project()
|
||||
@deserializeTimings.project = Date.now() - startTime
|
||||
|
||||
deserializeWorkspaceView: ->
|
||||
@ -736,7 +747,7 @@ class Atom extends Model
|
||||
# Notify the browser project of the window's current project path
|
||||
watchProjectPath: ->
|
||||
onProjectPathChanged = =>
|
||||
ipc.send('window-command', 'project-path-changed', @project.getPaths()[0])
|
||||
ipc.send('window-command', 'project-path-changed', @project.getPaths())
|
||||
@subscribe @project.onDidChangePaths(onProjectPathChanged)
|
||||
onProjectPathChanged()
|
||||
|
||||
|
@ -152,11 +152,11 @@ class AtomApplication
|
||||
@on 'application:quit', -> app.quit()
|
||||
@on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings()))
|
||||
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
|
||||
@on 'application:open', -> @promptForPath(_.extend(type: 'all', getLoadSettings()))
|
||||
@on 'application:open-file', -> @promptForPath(_.extend(type: 'file', getLoadSettings()))
|
||||
@on 'application:open-folder', -> @promptForPath(_.extend(type: 'folder', getLoadSettings()))
|
||||
@on 'application:open-dev', -> @promptForPath(devMode: true)
|
||||
@on 'application:open-safe', -> @promptForPath(safeMode: true)
|
||||
@on 'application:open', -> @promptForPathToOpen('all', getLoadSettings())
|
||||
@on 'application:open-file', -> @promptForPathToOpen('file', getLoadSettings())
|
||||
@on 'application:open-folder', -> @promptForPathToOpen('folder', getLoadSettings())
|
||||
@on 'application:open-dev', -> @promptForPathToOpen('all', devMode: true)
|
||||
@on 'application:open-safe', -> @promptForPathToOpen('all', safeMode: true)
|
||||
@on 'application:inspect', ({x,y, atomWindow}) ->
|
||||
atomWindow ?= @focusedWindow()
|
||||
atomWindow?.browserWindow.inspectElement(x, y)
|
||||
@ -227,7 +227,7 @@ class AtomApplication
|
||||
else
|
||||
new AtomWindow(options)
|
||||
else
|
||||
@promptForPath({window})
|
||||
@promptForPathToOpen('all', {window})
|
||||
|
||||
ipc.on 'update-application-menu', (event, template, keystrokesByCommand) =>
|
||||
win = BrowserWindow.fromWebContents(event.sender)
|
||||
@ -247,6 +247,10 @@ class AtomApplication
|
||||
win = BrowserWindow.fromWebContents(event.sender)
|
||||
win[method](args...)
|
||||
|
||||
ipc.on 'pick-folder', (event, responseChannel) =>
|
||||
@promptForPath "folder", (selectedPaths) ->
|
||||
event.sender.send(responseChannel, selectedPaths)
|
||||
|
||||
clipboard = null
|
||||
ipc.on 'write-text-to-selection-clipboard', (event, selectedText) ->
|
||||
clipboard ?= require 'clipboard'
|
||||
@ -307,9 +311,10 @@ class AtomApplication
|
||||
else
|
||||
@openPath({pathToOpen})
|
||||
|
||||
# Returns the {AtomWindow} for the given path.
|
||||
windowForPath: (pathToOpen) ->
|
||||
_.find @windows, (atomWindow) -> atomWindow.containsPath(pathToOpen)
|
||||
# Returns the {AtomWindow} for the given paths.
|
||||
windowForPaths: (pathsToOpen, devMode) ->
|
||||
_.find @windows, (atomWindow) ->
|
||||
atomWindow.devMode is devMode and atomWindow.containsPaths(pathsToOpen)
|
||||
|
||||
# Returns the {AtomWindow} for the given ipc event.
|
||||
windowForEvent: ({sender}) ->
|
||||
@ -323,49 +328,39 @@ class AtomApplication
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
#
|
||||
# options -
|
||||
# :pathsToOpen - The array of file paths to open
|
||||
# :pathToOpen - The file path to open
|
||||
# :pidToKillWhenClosed - The integer of the pid to kill
|
||||
# :newWindow - Boolean of whether this should be opened in a new window.
|
||||
# :devMode - Boolean to control the opened window's dev mode.
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, window}) ->
|
||||
for pathToOpen in pathsToOpen ? []
|
||||
@openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, window})
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, window}) ->
|
||||
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, window})
|
||||
|
||||
# Public: Opens a single path, in an existing window if possible.
|
||||
#
|
||||
# options -
|
||||
# :pathToOpen - The file path to open
|
||||
# :pathsToOpen - The array of file paths to open
|
||||
# :pidToKillWhenClosed - The integer of the pid to kill
|
||||
# :newWindow - Boolean of whether this should be opened in a new window.
|
||||
# :devMode - Boolean to control the opened window's dev mode.
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :windowDimensions - Object with height and width keys.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}={}) ->
|
||||
{pathToOpen, initialLine, initialColumn} = @locationForPathToOpen(pathToOpen)
|
||||
pathToOpen = fs.normalize(pathToOpen)
|
||||
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}={}) ->
|
||||
pathsToOpen = (fs.normalize(pathToOpen) for pathToOpen in pathsToOpen)
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen)
|
||||
|
||||
unless pidToKillWhenClosed or newWindow
|
||||
pathToOpenStat = fs.statSyncNoException(pathToOpen)
|
||||
existingWindow = @windowForPaths(pathsToOpen, devMode)
|
||||
|
||||
# Default to using the specified window or the last focused window
|
||||
currentWindow = window ? @lastFocusedWindow
|
||||
|
||||
if pathToOpenStat.isFile?()
|
||||
# Open the file in the current window
|
||||
existingWindow = currentWindow
|
||||
else if pathToOpenStat.isDirectory?()
|
||||
# Open the folder in the current window if it doesn't have a path
|
||||
existingWindow = currentWindow unless currentWindow?.hasProjectPath()
|
||||
|
||||
# Don't reuse windows in dev mode
|
||||
existingWindow ?= @windowForPath(pathToOpen) unless devMode
|
||||
# Default to using the specified window or the last focused window
|
||||
if pathsToOpen.every((pathToOpen) -> fs.statSyncNoException(pathToOpen).isFile?())
|
||||
existingWindow ?= window ? @lastFocusedWindow
|
||||
|
||||
if existingWindow?
|
||||
openedWindow = existingWindow
|
||||
openedWindow.openPath(pathToOpen, initialLine, initialColumn)
|
||||
openedWindow.openLocations(locationsToOpen)
|
||||
if openedWindow.isMinimized()
|
||||
openedWindow.restore()
|
||||
else
|
||||
@ -378,7 +373,7 @@ class AtomApplication
|
||||
|
||||
bootstrapScript ?= require.resolve('../window-bootstrap')
|
||||
resourcePath ?= @resourcePath
|
||||
openedWindow = new AtomWindow({pathToOpen, initialLine, initialColumn, bootstrapScript, resourcePath, devMode, safeMode, windowDimensions})
|
||||
openedWindow = new AtomWindow({locationsToOpen, bootstrapScript, resourcePath, devMode, safeMode, windowDimensions})
|
||||
|
||||
if pidToKillWhenClosed?
|
||||
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
|
||||
@ -497,8 +492,11 @@ class AtomApplication
|
||||
# :safeMode - A Boolean which controls whether any newly opened windows
|
||||
# should be in safe mode or not.
|
||||
# :window - An {AtomWindow} to use for opening a selected file path.
|
||||
promptForPath: ({type, devMode, safeMode, window}={}) ->
|
||||
type ?= 'all'
|
||||
promptForPathToOpen: (type, {devMode, safeMode, window}) ->
|
||||
@promptForPath type, (pathsToOpen) =>
|
||||
@openPaths({pathsToOpen, devMode, safeMode, window})
|
||||
|
||||
promptForPath: (type, callback) ->
|
||||
properties =
|
||||
switch type
|
||||
when 'file' then ['openFile']
|
||||
@ -523,5 +521,4 @@ class AtomApplication
|
||||
openOptions.defaultPath = projectPath
|
||||
|
||||
dialog = require 'dialog'
|
||||
dialog.showOpenDialog parentWindow, openOptions, (pathsToOpen) =>
|
||||
@openPaths({pathsToOpen, devMode, safeMode, window})
|
||||
dialog.showOpenDialog(parentWindow, openOptions, callback)
|
||||
|
@ -18,7 +18,8 @@ class AtomWindow
|
||||
isSpec: null
|
||||
|
||||
constructor: (settings={}) ->
|
||||
{@resourcePath, pathToOpen, initialLine, initialColumn, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings
|
||||
{@resourcePath, pathToOpen, @locationsToOpen, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings
|
||||
@locationsToOpen ?= [{pathToOpen}] if pathToOpen
|
||||
|
||||
# Normalize to make sure drive letter case is consistent on Windows
|
||||
@resourcePath = path.normalize(@resourcePath) if @resourcePath
|
||||
@ -51,21 +52,24 @@ class AtomWindow
|
||||
@constructor.includeShellLoadTime = false
|
||||
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
|
||||
|
||||
loadSettings.initialPath = pathToOpen
|
||||
if fs.statSyncNoException(pathToOpen).isFile?()
|
||||
loadSettings.initialPath = path.dirname(pathToOpen)
|
||||
loadSettings.initialPaths = for {pathToOpen} in (@locationsToOpen ? [])
|
||||
if fs.statSyncNoException(pathToOpen).isFile?()
|
||||
path.dirname(pathToOpen)
|
||||
else
|
||||
pathToOpen
|
||||
loadSettings.initialPaths.sort()
|
||||
|
||||
@browserWindow.loadSettings = loadSettings
|
||||
@browserWindow.once 'window:loaded', =>
|
||||
@emit 'window:loaded'
|
||||
@loaded = true
|
||||
|
||||
@browserWindow.on 'project-path-changed', (@projectPath) =>
|
||||
@browserWindow.on 'project-path-changed', (@projectPaths) =>
|
||||
|
||||
@browserWindow.loadUrl @getUrl(loadSettings)
|
||||
@browserWindow.focusOnWebView() if @isSpec
|
||||
|
||||
@openPath(pathToOpen, initialLine, initialColumn) unless @isSpecWindow()
|
||||
@openLocations(@locationsToOpen) unless @isSpecWindow()
|
||||
|
||||
getUrl: (loadSettingsObj) ->
|
||||
# Ignore the windowState when passing loadSettings via URL, since it could
|
||||
@ -79,10 +83,7 @@ class AtomWindow
|
||||
slashes: true
|
||||
query: {loadSettings: JSON.stringify(loadSettings)}
|
||||
|
||||
hasProjectPath: -> @projectPath?.length > 0
|
||||
|
||||
getInitialPath: ->
|
||||
@browserWindow.loadSettings.initialPath
|
||||
hasProjectPath: -> @projectPaths?.length > 0
|
||||
|
||||
setupContextMenu: ->
|
||||
ContextMenu = null
|
||||
@ -91,20 +92,25 @@ class AtomWindow
|
||||
ContextMenu ?= require './context-menu'
|
||||
new ContextMenu(menuTemplate, this)
|
||||
|
||||
containsPaths: (paths) ->
|
||||
for pathToCheck in paths
|
||||
return false unless @containsPath(pathToCheck)
|
||||
true
|
||||
|
||||
containsPath: (pathToCheck) ->
|
||||
initialPath = @getInitialPath()
|
||||
if not initialPath
|
||||
false
|
||||
else if not pathToCheck
|
||||
false
|
||||
else if pathToCheck is initialPath
|
||||
true
|
||||
else if fs.statSyncNoException(pathToCheck).isDirectory?()
|
||||
false
|
||||
else if pathToCheck.indexOf(path.join(initialPath, path.sep)) is 0
|
||||
true
|
||||
else
|
||||
false
|
||||
@projectPaths.some (projectPath) ->
|
||||
if not projectPath
|
||||
false
|
||||
else if not pathToCheck
|
||||
false
|
||||
else if pathToCheck is projectPath
|
||||
true
|
||||
else if fs.statSyncNoException(pathToCheck).isDirectory?()
|
||||
false
|
||||
else if pathToCheck.indexOf(path.join(projectPath, path.sep)) is 0
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
handleEvents: ->
|
||||
@browserWindow.on 'closed', =>
|
||||
@ -148,11 +154,14 @@ class AtomWindow
|
||||
@browserWindow.focusOnWebView() unless @isWindowClosing
|
||||
|
||||
openPath: (pathToOpen, initialLine, initialColumn) ->
|
||||
@openLocations([{pathToOpen, initialLine, initialColumn}])
|
||||
|
||||
openLocations: (locationsToOpen) ->
|
||||
if @loaded
|
||||
@focus()
|
||||
@sendMessage 'open-path', {pathToOpen, initialLine, initialColumn}
|
||||
@sendMessage 'open-locations', locationsToOpen
|
||||
else
|
||||
@browserWindow.once 'window:loaded', => @openPath(pathToOpen, initialLine, initialColumn)
|
||||
@browserWindow.once 'window:loaded', => @openLocations(locationsToOpen)
|
||||
|
||||
sendMessage: (message, detail) ->
|
||||
@browserWindow.webContents.send 'message', message, detail
|
||||
|
@ -38,6 +38,8 @@ class Project extends Model
|
||||
constructor: ({path, paths, @buffers}={}) ->
|
||||
@emitter = new Emitter
|
||||
@buffers ?= []
|
||||
@rootDirectories = []
|
||||
@repositories = []
|
||||
|
||||
# Mapping from the real path of a {Directory} to a {Promise} that resolves
|
||||
# to either a {Repository} or null. Ideally, the {Directory} would be used
|
||||
@ -61,12 +63,7 @@ class Project extends Model
|
||||
|
||||
destroyed: ->
|
||||
buffer.destroy() for buffer in @getBuffers()
|
||||
@destroyRepo()
|
||||
|
||||
destroyRepo: ->
|
||||
if @repo?
|
||||
@repo.destroy()
|
||||
@repo = null
|
||||
@setPaths([])
|
||||
|
||||
destroyUnretainedBuffers: ->
|
||||
buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained()
|
||||
@ -118,10 +115,10 @@ class Project extends Model
|
||||
# Promise.all(project.getDirectories().map(
|
||||
# project.repositoryForDirectory.bind(project)))
|
||||
# ```
|
||||
getRepositories: -> _.compact([@repo])
|
||||
getRepositories: -> @repositories
|
||||
getRepo: ->
|
||||
Grim.deprecate("Use ::getRepositories instead")
|
||||
@repo
|
||||
@getRepositories()[0]
|
||||
|
||||
# Public: Get the repository for a given directory asynchronously.
|
||||
#
|
||||
@ -154,43 +151,62 @@ class Project extends Model
|
||||
|
||||
# Public: Get an {Array} of {String}s containing the paths of the project's
|
||||
# directories.
|
||||
getPaths: -> _.compact([@rootDirectory?.path])
|
||||
getPaths: -> rootDirectory.path for rootDirectory in @rootDirectories
|
||||
getPath: ->
|
||||
Grim.deprecate("Use ::getPaths instead")
|
||||
@rootDirectory?.path
|
||||
@getPaths()[0]
|
||||
|
||||
# Public: Set the paths of the project's directories.
|
||||
#
|
||||
# * `projectPaths` {Array} of {String} paths.
|
||||
setPaths: (projectPaths) ->
|
||||
[projectPath] = projectPaths
|
||||
projectPath = path.normalize(projectPath) if projectPath
|
||||
@path = projectPath
|
||||
@rootDirectory?.off()
|
||||
rootDirectory.off() for rootDirectory in @rootDirectories
|
||||
repository?.destroy() for repository in @repositories
|
||||
@rootDirectories = []
|
||||
@repositories = []
|
||||
|
||||
@destroyRepo()
|
||||
if projectPath?
|
||||
directory = if fs.isDirectorySync(projectPath) then projectPath else path.dirname(projectPath)
|
||||
@rootDirectory = new Directory(directory)
|
||||
|
||||
# For now, use only the repositoryProviders with a sync API.
|
||||
for provider in @repositoryProviders
|
||||
break if @repo = provider.repositoryForDirectorySync?(@rootDirectory)
|
||||
else
|
||||
@rootDirectory = null
|
||||
@addPath(projectPath, emitEvent: false) for projectPath in projectPaths
|
||||
|
||||
@emit "path-changed"
|
||||
@emitter.emit 'did-change-paths', projectPaths
|
||||
|
||||
setPath: (path) ->
|
||||
Grim.deprecate("Use ::setPaths instead")
|
||||
@setPaths([path])
|
||||
|
||||
# Public: Add a path the project's list of root paths
|
||||
#
|
||||
# * `projectPath` {String} The path to the directory to add.
|
||||
addPath: (projectPath, options) ->
|
||||
projectPath = path.normalize(projectPath)
|
||||
|
||||
directoryPath = if fs.isDirectorySync(projectPath)
|
||||
projectPath
|
||||
else
|
||||
path.dirname(projectPath)
|
||||
|
||||
return if @getPaths().some (existingPath) ->
|
||||
(directoryPath is existingPath) or
|
||||
(directoryPath.indexOf(path.join(existingPath, path.sep)) is 0)
|
||||
|
||||
directory = new Directory(directoryPath)
|
||||
@rootDirectories.push(directory)
|
||||
|
||||
repo = null
|
||||
for provider in @repositoryProviders
|
||||
break if repo = provider.repositoryForDirectorySync?(directory)
|
||||
@repositories.push(repo ? null)
|
||||
|
||||
unless options?.emitEvent is false
|
||||
@emit "path-changed"
|
||||
@emitter.emit 'did-change-paths', @getPaths()
|
||||
|
||||
# Public: Get an {Array} of {Directory}s associated with this project.
|
||||
getDirectories: ->
|
||||
[@rootDirectory]
|
||||
@rootDirectories
|
||||
getRootDirectory: ->
|
||||
Grim.deprecate("Use ::getDirectories instead")
|
||||
@rootDirectory
|
||||
@getDirectories()[0]
|
||||
|
||||
resolve: (uri) ->
|
||||
Grim.deprecate("Use `Project::getDirectories()[0]?.resolve()` instead")
|
||||
@ -204,6 +220,8 @@ class Project extends Model
|
||||
else
|
||||
if fs.isAbsolute(uri)
|
||||
path.normalize(fs.absolute(uri))
|
||||
|
||||
# TODO: what should we do here when there are multiple directories?
|
||||
else if projectPath = @getPaths()[0]
|
||||
path.normalize(fs.absolute(path.join(projectPath, uri)))
|
||||
else
|
||||
@ -214,7 +232,10 @@ class Project extends Model
|
||||
# * `fullPath` {String} full path
|
||||
relativize: (fullPath) ->
|
||||
return fullPath if fullPath?.match(/[A-Za-z0-9+-.]+:\/\//) # leave path alone if it has a scheme
|
||||
@rootDirectory?.relativize(fullPath) ? fullPath
|
||||
for rootDirectory in @rootDirectories
|
||||
if (relativePath = rootDirectory.relativize(fullPath))?
|
||||
return relativePath
|
||||
fullPath
|
||||
|
||||
# Public: Determines whether the given path (real or symbolic) is inside the
|
||||
# project's directory.
|
||||
@ -244,7 +265,7 @@ class Project extends Model
|
||||
#
|
||||
# Returns whether the path is inside the project's root directory.
|
||||
contains: (pathToCheck) ->
|
||||
@rootDirectory?.contains(pathToCheck) ? false
|
||||
@rootDirectories.some (dir) -> dir.contains(pathToCheck)
|
||||
|
||||
###
|
||||
Section: Searching and Replacing
|
||||
|
@ -17,15 +17,13 @@ class WindowEventHandler
|
||||
|
||||
@subscribe ipc, 'message', (message, detail) ->
|
||||
switch message
|
||||
when 'open-path'
|
||||
{pathToOpen, initialLine, initialColumn} = detail
|
||||
when 'open-locations'
|
||||
for {pathToOpen, initialLine, initialColumn} in detail
|
||||
if pathToOpen and (fs.existsSync(pathToOpen) or fs.existsSync(path.dirname(pathToOpen)))
|
||||
atom.project?.addPath(pathToOpen)
|
||||
|
||||
unless atom.project?.getPaths().length
|
||||
if fs.existsSync(pathToOpen) or fs.existsSync(path.dirname(pathToOpen))
|
||||
atom.project?.setPaths([pathToOpen])
|
||||
|
||||
unless fs.isDirectorySync(pathToOpen)
|
||||
atom.workspace?.open(pathToOpen, {initialLine, initialColumn})
|
||||
unless fs.isDirectorySync(pathToOpen)
|
||||
atom.workspace?.open(pathToOpen, {initialLine, initialColumn})
|
||||
|
||||
when 'update-available'
|
||||
atom.updateAvailable(detail)
|
||||
|
Loading…
Reference in New Issue
Block a user