Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 87 additions & 6 deletions lib/tab-bar-view.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class TabBarView extends View
'tabs:split-down': => @splitTab('splitDown')
'tabs:split-left': => @splitTab('splitLeft')
'tabs:split-right': => @splitTab('splitRight')
'tabs:open-in-new-window': => @onOpenInNewWindow()

@on 'dragstart', '.sortable', @onDragStart
@on 'dragend', '.sortable', @onDragEnd
Expand Down Expand Up @@ -101,9 +102,12 @@ class TabBarView extends View
false

RendererIpc.on('tab:dropped', @onDropOnOtherWindow)
RendererIpc.on('tab:new-window-opened', @onNewWindowOpened)

unsubscribe: ->
RendererIpc.removeListener('tab:dropped', @onDropOnOtherWindow)
RendererIpc.removeListener('tab:new-window-opened', @onNewWindowOpened)

@subscriptions.dispose()

handleTreeViewEvents: ->
Expand Down Expand Up @@ -251,12 +255,7 @@ class TabBarView extends View
item = @pane.getItems()[element.index()]
return unless item?

if typeof item.getURI is 'function'
itemURI = item.getURI() ? ''
else if typeof item.getPath is 'function'
itemURI = item.getPath() ? ''
else if typeof item.getUri is 'function'
itemURI = item.getUri() ? ''
itemURI = @getItemURI item

if itemURI?
event.originalEvent.dataTransfer.setData 'text/plain', itemURI
Expand All @@ -269,6 +268,82 @@ class TabBarView extends View
event.originalEvent.dataTransfer.setData 'has-unsaved-changes', 'true'
event.originalEvent.dataTransfer.setData 'modified-text', item.getText()

getItemURI: (item) ->
return unless item?
if typeof item.getURI is 'function'
itemURI = item.getURI() ? ''
else if typeof item.getPath is 'function'
itemURI = item.getPath() ? ''
else if typeof item.getUri is 'function'
itemURI = item.getUri() ? ''

onNewWindowOpened: (title, openURI, hasUnsavedChanges, modifiedText, scrollTop, fromWindowId) =>
#remove any panes created by opening the window
for item in @pane.getItems()
@pane.destroyItem(item)

# open the content and reset state based on previous state
atom.workspace.open(openURI).then (item) ->
item.setText?(modifiedText) if hasUnsavedChanges
item.setScrollTop?(scrollTop)

atom.focus()

browserWindow = @browserWindowForId(fromWindowId)
browserWindow?.webContents.send('tab:item-moved-to-window')

onOpenInNewWindow: (active) =>
tabs = @getTabs()
active ?= @children('.right-clicked')[0]
@openTabInNewWindow(active, window.screenX + 20, window.screenY + 20)

openTabInNewWindow: (tab, windowX=0, windowY=0) =>
item = @pane.getItems()[$(tab).index()]
itemURI = @getItemURI(item)
return unless itemURI?

# open and then find the new window
atom.commands.dispatch(@element, 'application:new-window')
BrowserWindow ?= require('remote').require('browser-window')
windows = BrowserWindow.getAllWindows()
newWindow = windows[windows.length - 1]

# move the tab to the new window
newWindow.webContents.once 'did-finish-load', =>
@moveAndSizeNewWindow(newWindow, windowX, windowY)
itemScrollTop = item.getScrollTop?() ? 0
hasUnsavedChanges = item.isModified?() ? false
itemText = if hasUnsavedChanges then item.getText() else ""

#tell the new window to open this item and pass the current item state
newWindow.send('tab:new-window-opened',
item.getTitle(), itemURI, hasUnsavedChanges,
itemText, itemScrollTop, @getWindowId())

#listen for open success, so old tab can be removed
RendererIpc.on('tab:item-moved-to-window', => @onTabMovedToWindow(item))

onTabMovedToWindow: (item) ->
# clear changes so moved item can be closed without a warning
item.getBuffer?().reload()
@pane.destroyItem(item)
RendererIpc.removeListener('tab:item-moved-to-window', @onTabMovedToWindow)

moveAndSizeNewWindow: (newWindow, windowX=0, windowY=0) ->
WINDOW_MIN_WIDTH_HEIGHT = 300
windowWidth = Math.min(window.innerWidth, window.screen.availWidth - windowX)
windowHeight = Math.min(window.innerHeight, window.screen.availHeight - windowY)
if windowWidth < WINDOW_MIN_WIDTH_HEIGHT
windowWidth = WINDOW_MIN_WIDTH_HEIGHT
windowX = window.screen.availWidth - WINDOW_MIN_WIDTH_HEIGHT

if windowHeight < WINDOW_MIN_WIDTH_HEIGHT
windowHeight = WINDOW_MIN_WIDTH_HEIGHT
windowY = window.screen.availHeight - WINDOW_MIN_WIDTH_HEIGHT

newWindow.setPosition(windowX, windowY)
newWindow.setSize(windowWidth, windowHeight)

uriHasProtocol: (uri) ->
try
require('url').parse(uri).protocol?
Expand All @@ -279,6 +354,12 @@ class TabBarView extends View
@removePlaceholder()

onDragEnd: (event) =>
{dataTransfer, screenX, screenY} = event.originalEvent

#if the drop target doesn't handle the drop then this is a new window
if dataTransfer.dropEffect is "none"
@openTabInNewWindow(event.target, screenX, screenY)

@clearDropTarget()

onDragOver: (event) =>
Expand Down
4 changes: 4 additions & 0 deletions menus/tabs.cson
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

{type: 'separator'}

{label: 'Open In New Window', command: 'tabs:open-in-new-window'}

{type: 'separator'}

{label: 'Split Up', command: 'tabs:split-up'}
{label: 'Split Down', command: 'tabs:split-down'}
{label: 'Split Left', command: 'tabs:split-left'}
Expand Down
45 changes: 45 additions & 0 deletions spec/tabs-spec.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
BrowserWindow = null
{$, View} = require 'atom-space-pen-views'
_ = require 'underscore-plus'
path = require 'path'
Expand Down Expand Up @@ -429,6 +430,23 @@ describe "TabBarView", ->
expect(pane.getItems().length).toBe 1
expect(pane.getItems()[0]).toBe item1

describe "when tabs:open-in-new-window is fired", ->
it "calls the open new window command with the selected tab", ->
spyOn(tabBar, "onOpenInNewWindow").andCallThrough()
spyOn(tabBar, "openTabInNewWindow").andCallThrough()
spyOn(atom.workspace, "open").andCallThrough()

triggerMouseDownEvent(tabBar.tabForItem(editor1), which: 3)
atom.commands.dispatch(tabBar.element, 'tabs:open-in-new-window')

waitsFor ->
atom.workspace.open()

runs ->
expect(tabBar.onOpenInNewWindow).toHaveBeenCalled()
expect(tabBar.openTabInNewWindow).toHaveBeenCalled()
expect(atom.workspace.open).toHaveBeenCalled()

describe "when tabs:split-up is fired", ->
it "splits the selected tab up", ->
triggerMouseDownEvent(tabBar.tabForItem(item2), which: 3)
Expand Down Expand Up @@ -521,6 +539,15 @@ describe "TabBarView", ->
expect(pane.getItems()[0]).toBe item1

describe "dragging and dropping tabs", ->
describe "when getting dragged tab's URI", ->
it "getItemURI returns the tab location information", ->

itemWithNoURI = tabBar.getItemURI(item1)
expect(itemWithNoURI).not.toBeDefined()

itemWithURI = tabBar.getItemURI(editor1)
expect(itemWithURI).toBe editor1.getURI()

describe "when a tab is dragged within the same pane", ->
describe "when it is dropped on tab that's later in the list", ->
it "moves the tab and its item, shows the tab's item, and focuses the pane", ->
Expand Down Expand Up @@ -673,6 +700,24 @@ describe "TabBarView", ->
if process.platform is 'darwin'
expect(dragStartEvent.originalEvent.dataTransfer.getData("text/uri-list")).toEqual "file://#{editor1.getPath()}"

it "should open a new window if the target doesn't handle the file information", ->
[dragStartEvent, dropEvent] = buildDragEvents(tabBar.tabAtIndex(1), tabBar.tabAtIndex(0))
spyOn(tabBar, "openTabInNewWindow").andCallThrough()
spyOn(atom.workspace, "open").andCallThrough()

tabBar.onDragStart(dragStartEvent)
dropEvent.originalEvent.dataTransfer.dropEffect = "none"
dropEvent.originalEvent.screenX = 10
dropEvent.originalEvent.screenY = 20
tabBar.onDragEnd(dropEvent)

waitsFor ->
atom.workspace.open()

runs ->
expect(tabBar.openTabInNewWindow).toHaveBeenCalledWith(dropEvent.target, 10, 20)
expect(atom.workspace.open).toHaveBeenCalled()

describe "when a tab is dragged to another Atom window", ->
it "closes the tab in the first window and opens the tab in the second window", ->
[dragStartEvent, dropEvent] = buildDragEvents(tabBar.tabAtIndex(1), tabBar.tabAtIndex(0))
Expand Down