diff --git a/package.json b/package.json index 9ec8e83bf..faff2a9d2 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "image-view": "0.21.0", "keybinding-resolver": "0.10.0", "link": "0.17.0", - "markdown-preview": "0.28.0", + "markdown-preview": "0.29.0", "metrics": "0.26.0", "package-generator": "0.26.0", "release-notes": "0.20.0", diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 1ac76e204..e33413f97 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -72,7 +72,7 @@ describe "Project", -> expect(atom.project.getEditors()[1]).toBe editor2 describe ".openSync(path)", -> - [fooOpener, barOpener, absolutePath, newBufferHandler, newEditorHandler] = [] + [absolutePath, newBufferHandler, newEditorHandler] = [] beforeEach -> absolutePath = require.resolve('./fixtures/dir/a') newBufferHandler = jasmine.createSpy('newBufferHandler') @@ -80,129 +80,92 @@ describe "Project", -> newEditorHandler = jasmine.createSpy('newEditorHandler') atom.project.on 'editor-created', newEditorHandler - fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/) - barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//) - atom.project.registerOpener(fooOpener) - atom.project.registerOpener(barOpener) + describe "when given an absolute path that hasn't been opened previously", -> + it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", -> + editor = atom.project.openSync(absolutePath) + expect(editor.buffer.getPath()).toBe absolutePath + expect(newBufferHandler).toHaveBeenCalledWith editor.buffer + expect(newEditorHandler).toHaveBeenCalledWith editor - afterEach -> - atom.project.unregisterOpener(fooOpener) - atom.project.unregisterOpener(barOpener) + describe "when given a relative path that hasn't been opened previously", -> + it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", -> + editor = atom.project.openSync('a') + expect(editor.buffer.getPath()).toBe absolutePath + expect(newBufferHandler).toHaveBeenCalledWith editor.buffer + expect(newEditorHandler).toHaveBeenCalledWith editor - describe "when passed a path that doesn't match a custom opener", -> - describe "when given an absolute path that hasn't been opened previously", -> - it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", -> - editor = atom.project.openSync(absolutePath) + describe "when passed the path to a buffer that has already been opened", -> + it "returns a new edit session containing previously opened buffer and emits a 'editor-created' event", -> + editor = atom.project.openSync(absolutePath) + newBufferHandler.reset() + expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer + expect(atom.project.openSync('a').buffer).toBe editor.buffer + expect(newBufferHandler).not.toHaveBeenCalled() + expect(newEditorHandler).toHaveBeenCalledWith editor + + describe "when not passed a path", -> + it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", -> + editor = atom.project.openSync() + expect(editor.buffer.getPath()).toBeUndefined() + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer) + expect(newEditorHandler).toHaveBeenCalledWith editor + + describe ".open(path)", -> + [absolutePath, newBufferHandler, newEditorHandler] = [] + + beforeEach -> + absolutePath = require.resolve('./fixtures/dir/a') + newBufferHandler = jasmine.createSpy('newBufferHandler') + atom.project.on 'buffer-created', newBufferHandler + newEditorHandler = jasmine.createSpy('newEditorHandler') + atom.project.on 'editor-created', newEditorHandler + + describe "when given an absolute path that isn't currently open", -> + it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", -> + editor = null + waitsForPromise -> + atom.project.open(absolutePath).then (o) -> editor = o + + runs -> expect(editor.buffer.getPath()).toBe absolutePath expect(newBufferHandler).toHaveBeenCalledWith editor.buffer expect(newEditorHandler).toHaveBeenCalledWith editor - describe "when given a relative path that hasn't been opened previously", -> - it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", -> - editor = atom.project.openSync('a') + describe "when given a relative path that isn't currently opened", -> + it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", -> + editor = null + waitsForPromise -> + atom.project.open(absolutePath).then (o) -> editor = o + + runs -> expect(editor.buffer.getPath()).toBe absolutePath expect(newBufferHandler).toHaveBeenCalledWith editor.buffer expect(newEditorHandler).toHaveBeenCalledWith editor - describe "when passed the path to a buffer that has already been opened", -> - it "returns a new edit session containing previously opened buffer and emits a 'editor-created' event", -> - editor = atom.project.openSync(absolutePath) + describe "when passed the path to a buffer that is currently opened", -> + it "returns a new edit session containing currently opened buffer and emits a 'editor-created' event", -> + editor = null + waitsForPromise -> + atom.project.open(absolutePath).then (o) -> editor = o + + runs -> newBufferHandler.reset() expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer expect(atom.project.openSync('a').buffer).toBe editor.buffer expect(newBufferHandler).not.toHaveBeenCalled() expect(newEditorHandler).toHaveBeenCalledWith editor - describe "when not passed a path", -> - it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", -> - editor = atom.project.openSync() + describe "when not passed a path", -> + it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", -> + editor = null + waitsForPromise -> + atom.project.open().then (o) -> editor = o + + runs -> expect(editor.buffer.getPath()).toBeUndefined() expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer) expect(newEditorHandler).toHaveBeenCalledWith editor - describe "when passed a path that matches a custom opener", -> - it "returns the resource returned by the custom opener", -> - pathToOpen = atom.project.resolve('a.foo') - expect(atom.project.openSync(pathToOpen, hey: "there")).toEqual { foo: pathToOpen, options: {hey: "there"} } - expect(atom.project.openSync("bar://baz")).toEqual { bar: "bar://baz" } - - describe ".open(path)", -> - [fooOpener, barOpener, absolutePath, newBufferHandler, newEditorHandler] = [] - - beforeEach -> - absolutePath = require.resolve('./fixtures/dir/a') - newBufferHandler = jasmine.createSpy('newBufferHandler') - atom.project.on 'buffer-created', newBufferHandler - newEditorHandler = jasmine.createSpy('newEditorHandler') - atom.project.on 'editor-created', newEditorHandler - - fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/) - barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//) - atom.project.registerOpener(fooOpener) - atom.project.registerOpener(barOpener) - - afterEach -> - atom.project.unregisterOpener(fooOpener) - atom.project.unregisterOpener(barOpener) - - describe "when passed a path that doesn't match a custom opener", -> - describe "when given an absolute path that isn't currently open", -> - it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", -> - editor = null - waitsForPromise -> - atom.project.open(absolutePath).then (o) -> editor = o - - runs -> - expect(editor.buffer.getPath()).toBe absolutePath - expect(newBufferHandler).toHaveBeenCalledWith editor.buffer - expect(newEditorHandler).toHaveBeenCalledWith editor - - describe "when given a relative path that isn't currently opened", -> - it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", -> - editor = null - waitsForPromise -> - atom.project.open(absolutePath).then (o) -> editor = o - - runs -> - expect(editor.buffer.getPath()).toBe absolutePath - expect(newBufferHandler).toHaveBeenCalledWith editor.buffer - expect(newEditorHandler).toHaveBeenCalledWith editor - - describe "when passed the path to a buffer that is currently opened", -> - it "returns a new edit session containing currently opened buffer and emits a 'editor-created' event", -> - editor = null - waitsForPromise -> - atom.project.open(absolutePath).then (o) -> editor = o - - runs -> - newBufferHandler.reset() - expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer - expect(atom.project.openSync('a').buffer).toBe editor.buffer - expect(newBufferHandler).not.toHaveBeenCalled() - expect(newEditorHandler).toHaveBeenCalledWith editor - - describe "when not passed a path", -> - it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", -> - editor = null - waitsForPromise -> - atom.project.open().then (o) -> editor = o - - runs -> - expect(editor.buffer.getPath()).toBeUndefined() - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer) - expect(newEditorHandler).toHaveBeenCalledWith editor - - describe "when passed a path that matches a custom opener", -> - it "returns the resource returned by the custom opener", -> - waitsForPromise -> - pathToOpen = atom.project.resolve('a.foo') - atom.project.open(pathToOpen, hey: "there").then (item) -> - expect(item).toEqual { foo: pathToOpen, options: {hey: "there"} } - - waitsForPromise -> - atom.project.open("bar://baz").then (item) -> - expect(item).toEqual { bar: "bar://baz" } - it "returns number of read bytes as progress indicator", -> filePath = atom.project.resolve 'a' totalBytes = 0 diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index fd99c3d74..312e3ea4e 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -126,6 +126,22 @@ describe "Workspace", -> expect(pane1.items).toEqual [] expect(pane2.items).toEqual [editor] + describe "when passed a path that matches a custom opener", -> + it "returns the resource returned by the custom opener", -> + fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/) + barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//) + workspace.registerOpener(fooOpener) + workspace.registerOpener(barOpener) + + waitsForPromise -> + pathToOpen = atom.project.resolve('a.foo') + workspace.open(pathToOpen, hey: "there").then (item) -> + expect(item).toEqual { foo: pathToOpen, options: {hey: "there"} } + + waitsForPromise -> + workspace.open("bar://baz").then (item) -> + expect(item).toEqual { bar: "bar://baz" } + describe "::openSync(uri, options)", -> [activePane, initialItemCount] = [] diff --git a/src/project.coffee b/src/project.coffee index f69af88f5..d93ef4e4c 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -30,11 +30,12 @@ class Project extends Model constructor: ({path, @buffers}={}) -> @buffers ?= [] + @openers = [] + for buffer in @buffers do (buffer) => buffer.once 'destroyed', => @removeBuffer(buffer) - @openers = [] @editors = [] @setPath(path) @@ -46,23 +47,6 @@ class Project extends Model params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState) params - # Public: Register an opener for project files. - # - # An {Editor} will be used if no openers return a value. - # - # ## Example - # ```coffeescript - # atom.project.registerOpener (filePath) -> - # if path.extname(filePath) is '.toml' - # return new TomlEditor(filePath) - # ``` - # - # opener - A {Function} to be called when a path is being opened. - registerOpener: (opener) -> @openers.push(opener) - - # Public: Remove a previously registered opener. - unregisterOpener: (opener) -> _.remove(@openers, opener) - destroyed: -> editor.destroy() for editor in @getEditors() buffer.destroy() for buffer in @getBuffers() @@ -129,7 +113,7 @@ class Project extends Model contains: (pathToCheck) -> @rootDirectory?.contains(pathToCheck) ? false - # Public: Given a path to a file, this constructs and associates a new + # Given a path to a file, this constructs and associates a new # {Editor}, showing the file. # # filePath - The {String} path of the file to associate with. @@ -137,34 +121,21 @@ class Project extends Model # # Returns a promise that resolves to an {Editor}. open: (filePath, options={}) -> - filePath = @resolve(filePath) ? '' - resource = opener(filePath, options) for opener in @openers when !resource + filePath = @resolve(filePath) + @bufferForPath(filePath).then (buffer) => + @buildEditorForBuffer(buffer, options) - if resource - Q(resource) - else - @bufferForPath(filePath).then (buffer) => - @buildEditorForBuffer(buffer, options) - - # Only be used in specs + # Deprecated openSync: (filePath, options={}) -> - filePath = @resolve(filePath) ? '' - resource = opener(filePath, options) for opener in @openers when !resource + filePath = @resolve(filePath) + @buildEditorForBuffer(@bufferForPathSync(filePath), options) - resource or @buildEditorForBuffer(@bufferForPathSync(filePath), options) - - # Public: Retrieves all {Editor}s for all open files. - # - # Returns an {Array} of {Editor}s. - getEditors: -> - new Array(@editors...) - - # Public: Add the given {Editor}. + # Add the given {Editor}. addEditor: (editor) -> @editors.push editor @emit 'editor-created', editor - # Public: Return and removes the given {Editor}. + # Return and removes the given {Editor}. removeEditor: (editor) -> _.remove(@editors, editor) @@ -330,10 +301,6 @@ class Project extends Model @addEditor(editor) editor - eachEditor: (callback) -> - callback(editor) for editor in @getEditors() - @on 'editor-created', (editor) -> callback(editor) - eachBuffer: (args...) -> subscriber = args.shift() if args.length > 1 callback = args.shift() @@ -343,3 +310,20 @@ class Project extends Model subscriber.subscribe this, 'buffer-created', (buffer) -> callback(buffer) else @on 'buffer-created', (buffer) -> callback(buffer) + + # Deprecated: delegate + registerOpener: (opener) -> + @openers.push(opener) + + # Deprecated: delegate + unregisterOpener: (opener) -> + _.remove(@openers, opener) + + # Deprecated: delegate + eachEditor: (callback) -> + callback(editor) for editor in @getEditors() + @on 'editor-created', (editor) -> callback(editor) + + # Deprecated: delegate + getEditors: -> + new Array(@editors...) diff --git a/src/workspace.coffee b/src/workspace.coffee index 4409ed95d..8b8138b67 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -27,7 +27,7 @@ class Workspace extends Model constructor: -> super @subscribe @paneContainer, 'item-destroyed', @onPaneItemDestroyed - atom.project.registerOpener (filePath) => + @registerOpener (filePath) => switch filePath when 'atom://.atom/stylesheet' @open(atom.themes.getUserStylesheetPath()) @@ -48,9 +48,20 @@ class Workspace extends Model paneContainer: @paneContainer.serialize() fullScreen: atom.isFullScreen() + # Public: Calls callback for every existing {Editor} and for all new {Editors} + # that are created. + # + # callback - A {Function} with an {Editor} as its only argument + eachEditor: (callback) -> + atom.project.eachEditor(callback) + + # Public: Returns an {Array} of all open {Editor}s. + getEditors: -> + atom.project.getEditors() + # Public: Asynchronously opens a given a filepath in Atom. # - # filePath - A {String} file path. + # uri - A {String} uri. # options - An options {Object} (default: {}). # :initialLine - A {Number} indicating which line number to open to. # :split - A {String} ('left' or 'right') that opens the filePath in a new @@ -58,16 +69,13 @@ class Workspace extends Model # :changeFocus - A {Boolean} that allows the filePath to be opened without # changing focus. # :searchAllPanes - A {Boolean} that will open existing editors from any pane - # if the filePath is already open (default: false) + # if the uri is already open (default: false) # # Returns a promise that resolves to the {Editor} for the file URI. - open: (filePath, options={}) -> - changeFocus = options.changeFocus ? true - filePath = atom.project.resolve(filePath) - initialLine = options.initialLine + open: (uri, options={}) -> searchAllPanes = options.searchAllPanes split = options.split - uri = atom.project.relativize(filePath) + uri = atom.project.relativize(uri) ? '' pane = switch split when 'left' @@ -80,42 +88,73 @@ class Workspace extends Model else @activePane - Q(pane.itemForUri(uri) ? atom.project.open(filePath, options)) - .then (editor) => - if not pane - pane = new Pane(items: [editor]) - @paneContainer.root = pane - - @itemOpened(editor) - pane.activateItem(editor) - pane.activate() if changeFocus - @emit "uri-opened" - editor - .catch (error) -> - console.error(error.stack ? error) + @openUriInPane(uri, pane, options) # Only used in specs openSync: (uri, options={}) -> {initialLine} = options # TODO: Remove deprecated changeFocus option activatePane = options.activatePane ? options.changeFocus ? true - uri = atom.project.relativize(uri) + uri = atom.project.relativize(uri) ? '' - if uri? - editor = @activePane.itemForUri(uri) ? atom.project.openSync(uri, {initialLine}) - else - editor = atom.project.openSync() + item = @activePane.itemForUri(uri) + if uri + item ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item + item ?= atom.project.openSync(uri, {initialLine}) - @activePane.activateItem(editor) - @itemOpened(editor) + @activePane.activateItem(item) + @itemOpened(item) @activePane.activate() if activatePane - editor + item + + openUriInPane: (uri, pane, options={}) -> + changeFocus = options.changeFocus ? true + + item = pane.itemForUri(uri) + if uri + item ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item + item ?= atom.project.open(uri, options) + + Q(item) + .then (item) => + if not pane + pane = new Pane(items: [item]) + @paneContainer.root = pane + @itemOpened(item) + pane.activateItem(item) + pane.activate() if changeFocus + @emit "uri-opened" + item + .catch (error) -> + console.error(error.stack ? error) # Public: Reopens the last-closed item uri if it hasn't already been reopened. reopenItemSync: -> if uri = @destroyedItemUris.pop() @openSync(uri) + # Public: Register an opener for a uri. + # + # An {Editor} will be used if no openers return a value. + # + # ## Example + # ```coffeescript + # atom.project.registerOpener (uri) -> + # if path.extname(uri) is '.toml' + # return new TomlEditor(uri) + # ``` + # + # opener - A {Function} to be called when a path is being opened. + registerOpener: (opener) -> + atom.project.registerOpener(opener) + + # Public: Remove a registered opener. + unregisterOpener: (opener) -> + atom.project.unregisterOpener(opener) + + getOpeners: -> + atom.project.openers + # Public: save the active item. saveActivePaneItem: -> @activePane?.saveActiveItem()