mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-10-05 15:07:49 +03:00
Revert "Merge pull request #356 from pulsar-edit/machine-decafe-tabs"
This reverts commit1da0dab087
, reversing changes made toecb27d5797
.
This commit is contained in:
parent
a6253bea80
commit
9365b1ea91
@ -1,7 +1,7 @@
|
||||
const {CompositeDisposable, Disposable} = require('atom')
|
||||
const getIconServices = require('./get-icon-services')
|
||||
const layout = require('./layout')
|
||||
const TabBarView = require('./tab-bar-view.js')
|
||||
const TabBarView = require('./tab-bar-view')
|
||||
const MRUListView = require('./mru-list-view')
|
||||
const _ = require('underscore-plus')
|
||||
|
||||
|
558
packages/tabs/lib/tab-bar-view.coffee
Normal file
558
packages/tabs/lib/tab-bar-view.coffee
Normal file
@ -0,0 +1,558 @@
|
||||
BrowserWindow = null # Defer require until actually used
|
||||
{ipcRenderer} = require 'electron'
|
||||
|
||||
{CompositeDisposable} = require 'atom'
|
||||
TabView = require './tab-view'
|
||||
|
||||
module.exports =
|
||||
class TabBarView
|
||||
constructor: (@pane, @location) ->
|
||||
@element = document.createElement('ul')
|
||||
@element.classList.add("list-inline")
|
||||
@element.classList.add("tab-bar")
|
||||
@element.classList.add("inset-panel")
|
||||
@element.setAttribute('is', 'atom-tabs')
|
||||
@element.setAttribute("tabindex", -1)
|
||||
@element.setAttribute("location", @location)
|
||||
|
||||
@tabs = []
|
||||
@tabsByElement = new WeakMap
|
||||
@subscriptions = new CompositeDisposable
|
||||
|
||||
@paneElement = @pane.getElement()
|
||||
|
||||
@subscriptions.add atom.commands.add @paneElement,
|
||||
'tabs:keep-pending-tab': => @terminatePendingStates()
|
||||
'tabs:close-tab': => @closeTab(@getActiveTab())
|
||||
'tabs:close-other-tabs': => @closeOtherTabs(@getActiveTab())
|
||||
'tabs:close-tabs-to-right': => @closeTabsToRight(@getActiveTab())
|
||||
'tabs:close-tabs-to-left': => @closeTabsToLeft(@getActiveTab())
|
||||
'tabs:close-saved-tabs': => @closeSavedTabs()
|
||||
'tabs:close-all-tabs': (event) =>
|
||||
event.stopPropagation()
|
||||
@closeAllTabs()
|
||||
'tabs:open-in-new-window': => @openInNewWindow()
|
||||
|
||||
addElementCommands = (commands) =>
|
||||
commandsWithPropagationStopped = {}
|
||||
Object.keys(commands).forEach (name) ->
|
||||
commandsWithPropagationStopped[name] = (event) ->
|
||||
event.stopPropagation()
|
||||
commands[name]()
|
||||
|
||||
@subscriptions.add(atom.commands.add(@element, commandsWithPropagationStopped))
|
||||
|
||||
addElementCommands
|
||||
'tabs:close-tab': => @closeTab()
|
||||
'tabs:close-other-tabs': => @closeOtherTabs()
|
||||
'tabs:close-tabs-to-right': => @closeTabsToRight()
|
||||
'tabs:close-tabs-to-left': => @closeTabsToLeft()
|
||||
'tabs:close-saved-tabs': => @closeSavedTabs()
|
||||
'tabs:close-all-tabs': => @closeAllTabs()
|
||||
'tabs:split-up': => @splitTab('splitUp')
|
||||
'tabs:split-down': => @splitTab('splitDown')
|
||||
'tabs:split-left': => @splitTab('splitLeft')
|
||||
'tabs:split-right': => @splitTab('splitRight')
|
||||
|
||||
@element.addEventListener "mouseenter", @onMouseEnter.bind(this)
|
||||
@element.addEventListener "mouseleave", @onMouseLeave.bind(this)
|
||||
@element.addEventListener "mousewheel", @onMouseWheel.bind(this)
|
||||
@element.addEventListener "dragstart", @onDragStart.bind(this)
|
||||
@element.addEventListener "dragend", @onDragEnd.bind(this)
|
||||
@element.addEventListener "dragleave", @onDragLeave.bind(this)
|
||||
@element.addEventListener "dragover", @onDragOver.bind(this)
|
||||
@element.addEventListener "drop", @onDrop.bind(this)
|
||||
|
||||
# Toggle the tab bar when a tab is dragged over the pane with alwaysShowTabBar = false
|
||||
@paneElement.addEventListener 'dragenter', @onPaneDragEnter.bind(this)
|
||||
@paneElement.addEventListener 'dragleave', @onPaneDragLeave.bind(this)
|
||||
|
||||
@paneContainer = @pane.getContainer()
|
||||
@addTabForItem(item) for item in @pane.getItems()
|
||||
|
||||
@subscriptions.add @pane.onDidDestroy =>
|
||||
@destroy()
|
||||
|
||||
@subscriptions.add @pane.onDidAddItem ({item, index}) =>
|
||||
@addTabForItem(item, index)
|
||||
|
||||
@subscriptions.add @pane.onDidMoveItem ({item, newIndex}) =>
|
||||
@moveItemTabToIndex(item, newIndex)
|
||||
|
||||
@subscriptions.add @pane.onDidRemoveItem ({item}) =>
|
||||
@removeTabForItem(item)
|
||||
|
||||
@subscriptions.add @pane.onDidChangeActiveItem (item) =>
|
||||
@updateActiveTab()
|
||||
|
||||
@subscriptions.add atom.config.observe 'tabs.tabScrolling', (value) => @updateTabScrolling(value)
|
||||
@subscriptions.add atom.config.observe 'tabs.tabScrollingThreshold', (value) => @updateTabScrollingThreshold(value)
|
||||
@subscriptions.add atom.config.observe 'tabs.alwaysShowTabBar', => @updateTabBarVisibility()
|
||||
|
||||
@updateActiveTab()
|
||||
|
||||
@element.addEventListener "mousedown", @onMouseDown.bind(this)
|
||||
@element.addEventListener "click", @onClick.bind(this)
|
||||
@element.addEventListener "auxclick", @onClick.bind(this)
|
||||
@element.addEventListener "dblclick", @onDoubleClick.bind(this)
|
||||
|
||||
@onDropOnOtherWindow = @onDropOnOtherWindow.bind(this)
|
||||
ipcRenderer.on('tab:dropped', @onDropOnOtherWindow)
|
||||
|
||||
destroy: ->
|
||||
ipcRenderer.removeListener('tab:dropped', @onDropOnOtherWindow)
|
||||
@subscriptions.dispose()
|
||||
@element.remove()
|
||||
|
||||
terminatePendingStates: ->
|
||||
tab.terminatePendingState?() for tab in @getTabs()
|
||||
return
|
||||
|
||||
addTabForItem: (item, index) ->
|
||||
tabView = new TabView({
|
||||
item,
|
||||
@pane,
|
||||
@tabs,
|
||||
didClickCloseIcon: =>
|
||||
@closeTab(tabView)
|
||||
return
|
||||
@location
|
||||
})
|
||||
tabView.terminatePendingState() if @isItemMovingBetweenPanes
|
||||
@tabsByElement.set(tabView.element, tabView)
|
||||
@insertTabAtIndex(tabView, index)
|
||||
if atom.config.get('tabs.addNewTabsAtEnd')
|
||||
@pane.moveItem(item, @pane.getItems().length - 1) unless @isItemMovingBetweenPanes
|
||||
|
||||
moveItemTabToIndex: (item, index) ->
|
||||
tabIndex = @tabs.findIndex((t) -> t.item is item)
|
||||
if tabIndex isnt -1
|
||||
tab = @tabs[tabIndex]
|
||||
tab.element.remove()
|
||||
@tabs.splice(tabIndex, 1)
|
||||
@insertTabAtIndex(tab, index)
|
||||
|
||||
insertTabAtIndex: (tab, index) ->
|
||||
followingTab = @tabs[index] if index?
|
||||
if followingTab
|
||||
@element.insertBefore(tab.element, followingTab.element)
|
||||
@tabs.splice(index, 0, tab)
|
||||
else
|
||||
@element.appendChild(tab.element)
|
||||
@tabs.push(tab)
|
||||
|
||||
tab.updateTitle()
|
||||
@updateTabBarVisibility()
|
||||
|
||||
removeTabForItem: (item) ->
|
||||
tabIndex = @tabs.findIndex((t) -> t.item is item)
|
||||
if tabIndex isnt -1
|
||||
tab = @tabs[tabIndex]
|
||||
@tabs.splice(tabIndex, 1)
|
||||
@tabsByElement.delete(tab)
|
||||
tab.destroy()
|
||||
tab.updateTitle() for tab in @getTabs()
|
||||
@updateTabBarVisibility()
|
||||
|
||||
updateTabBarVisibility: ->
|
||||
# Show tab bar if the setting is true or there is more than one tab
|
||||
if atom.config.get('tabs.alwaysShowTabBar') or @pane.getItems().length > 1
|
||||
@element.classList.remove('hidden')
|
||||
else
|
||||
@element.classList.add('hidden')
|
||||
|
||||
getTabs: ->
|
||||
@tabs.slice()
|
||||
|
||||
tabAtIndex: (index) ->
|
||||
@tabs[index]
|
||||
|
||||
tabForItem: (item) ->
|
||||
@tabs.find((t) -> t.item is item)
|
||||
|
||||
setActiveTab: (tabView) ->
|
||||
if tabView? and tabView isnt @activeTab
|
||||
@activeTab?.element.classList.remove('active')
|
||||
@activeTab = tabView
|
||||
@activeTab.element.classList.add('active')
|
||||
@activeTab.element.scrollIntoView(false)
|
||||
|
||||
getActiveTab: ->
|
||||
@tabForItem(@pane.getActiveItem())
|
||||
|
||||
updateActiveTab: ->
|
||||
@setActiveTab(@tabForItem(@pane.getActiveItem()))
|
||||
|
||||
closeTab: (tab) ->
|
||||
tab ?= @rightClickedTab
|
||||
@pane.destroyItem(tab.item) if tab?
|
||||
|
||||
openInNewWindow: (tab) ->
|
||||
tab ?= @rightClickedTab
|
||||
item = tab?.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()
|
||||
return unless itemURI?
|
||||
@closeTab(tab)
|
||||
tab.element.style.maxWidth = '' for tab in @getTabs()
|
||||
pathsToOpen = [atom.project.getPaths(), itemURI].reduce ((a, b) -> a.concat(b)), []
|
||||
atom.open({pathsToOpen: pathsToOpen, newWindow: true, devMode: atom.devMode, safeMode: atom.safeMode})
|
||||
|
||||
splitTab: (fn) ->
|
||||
if item = @rightClickedTab?.item
|
||||
if copiedItem = item.copy?()
|
||||
@pane[fn](items: [copiedItem])
|
||||
|
||||
closeOtherTabs: (active) ->
|
||||
tabs = @getTabs()
|
||||
active ?= @rightClickedTab
|
||||
return unless active?
|
||||
@closeTab tab for tab in tabs when tab isnt active
|
||||
|
||||
closeTabsToRight: (active) ->
|
||||
tabs = @getTabs()
|
||||
active ?= @rightClickedTab
|
||||
index = tabs.indexOf(active)
|
||||
return if index is -1
|
||||
@closeTab tab for tab, i in tabs when i > index
|
||||
|
||||
closeTabsToLeft: (active) ->
|
||||
tabs = @getTabs()
|
||||
active ?= @rightClickedTab
|
||||
index = tabs.indexOf(active)
|
||||
return if index is -1
|
||||
@closeTab tab for tab, i in tabs when i < index
|
||||
|
||||
closeSavedTabs: ->
|
||||
for tab in @getTabs()
|
||||
@closeTab(tab) unless tab.item.isModified?()
|
||||
|
||||
closeAllTabs: ->
|
||||
@closeTab(tab) for tab in @getTabs()
|
||||
|
||||
getWindowId: ->
|
||||
@windowId ?= atom.getCurrentWindow().id
|
||||
|
||||
onDragStart: (event) ->
|
||||
@draggedTab = @tabForElement(event.target)
|
||||
return unless @draggedTab
|
||||
@lastDropTargetIndex = null
|
||||
|
||||
event.dataTransfer.setData 'atom-tab-event', 'true'
|
||||
|
||||
@draggedTab.element.classList.add('is-dragging')
|
||||
@draggedTab.destroyTooltip()
|
||||
|
||||
tabIndex = @tabs.indexOf(@draggedTab)
|
||||
event.dataTransfer.setData 'sortable-index', tabIndex
|
||||
|
||||
paneIndex = @paneContainer.getPanes().indexOf(@pane)
|
||||
event.dataTransfer.setData 'from-pane-index', paneIndex
|
||||
event.dataTransfer.setData 'from-pane-id', @pane.id
|
||||
event.dataTransfer.setData 'from-window-id', @getWindowId()
|
||||
|
||||
item = @pane.getItems()[@tabs.indexOf(@draggedTab)]
|
||||
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() ? ''
|
||||
|
||||
if typeof item.getAllowedLocations is 'function'
|
||||
for location in item.getAllowedLocations()
|
||||
event.dataTransfer.setData("allowed-location-#{location}", 'true')
|
||||
else
|
||||
event.dataTransfer.setData 'allow-all-locations', 'true'
|
||||
|
||||
if itemURI?
|
||||
event.dataTransfer.setData 'text/plain', itemURI
|
||||
|
||||
if process.platform is 'darwin' # see #69
|
||||
itemURI = "file://#{itemURI}" unless @uriHasProtocol(itemURI)
|
||||
event.dataTransfer.setData 'text/uri-list', itemURI
|
||||
|
||||
if item.isModified?() and item.getText?
|
||||
event.dataTransfer.setData 'has-unsaved-changes', 'true'
|
||||
event.dataTransfer.setData 'modified-text', item.getText()
|
||||
|
||||
uriHasProtocol: (uri) ->
|
||||
try
|
||||
require('url').parse(uri).protocol?
|
||||
catch error
|
||||
false
|
||||
|
||||
onDragLeave: (event) ->
|
||||
# Do not do anything unless the drag goes outside the tab bar
|
||||
unless event.currentTarget.contains(event.relatedTarget)
|
||||
@removePlaceholder()
|
||||
@lastDropTargetIndex = null
|
||||
tab.element.style.maxWidth = '' for tab in @getTabs()
|
||||
|
||||
onDragEnd: (event) ->
|
||||
return unless @tabForElement(event.target)
|
||||
|
||||
@clearDropTarget()
|
||||
|
||||
onDragOver: (event) ->
|
||||
return unless @isAtomTabEvent(event)
|
||||
return unless @itemIsAllowed(event, @location)
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
newDropTargetIndex = @getDropTargetIndex(event)
|
||||
return unless newDropTargetIndex?
|
||||
return if @lastDropTargetIndex is newDropTargetIndex
|
||||
@lastDropTargetIndex = newDropTargetIndex
|
||||
|
||||
@removeDropTargetClasses()
|
||||
|
||||
tabs = @getTabs()
|
||||
placeholder = @getPlaceholder()
|
||||
return unless placeholder?
|
||||
|
||||
if newDropTargetIndex < tabs.length
|
||||
tab = tabs[newDropTargetIndex]
|
||||
tab.element.classList.add 'is-drop-target'
|
||||
tab.element.parentElement.insertBefore(placeholder, tab.element)
|
||||
else
|
||||
if tab = tabs[newDropTargetIndex - 1]
|
||||
tab.element.classList.add 'drop-target-is-after'
|
||||
if sibling = tab.element.nextSibling
|
||||
tab.element.parentElement.insertBefore(placeholder, sibling)
|
||||
else
|
||||
tab.element.parentElement.appendChild(placeholder)
|
||||
|
||||
onDropOnOtherWindow: (event, fromPaneId, fromItemIndex) ->
|
||||
if @pane.id is fromPaneId
|
||||
if itemToRemove = @pane.getItems()[fromItemIndex]
|
||||
@pane.destroyItem(itemToRemove)
|
||||
|
||||
@clearDropTarget()
|
||||
|
||||
clearDropTarget: ->
|
||||
@draggedTab?.element.classList.remove('is-dragging')
|
||||
@draggedTab?.updateTooltip()
|
||||
@draggedTab = null
|
||||
@removeDropTargetClasses()
|
||||
@removePlaceholder()
|
||||
|
||||
onDrop: (event) ->
|
||||
return unless @isAtomTabEvent(event)
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
fromWindowId = parseInt(event.dataTransfer.getData('from-window-id'))
|
||||
fromPaneId = parseInt(event.dataTransfer.getData('from-pane-id'))
|
||||
fromIndex = parseInt(event.dataTransfer.getData('sortable-index'))
|
||||
fromPaneIndex = parseInt(event.dataTransfer.getData('from-pane-index'))
|
||||
|
||||
hasUnsavedChanges = event.dataTransfer.getData('has-unsaved-changes') is 'true'
|
||||
modifiedText = event.dataTransfer.getData('modified-text')
|
||||
|
||||
toIndex = @getDropTargetIndex(event)
|
||||
toPane = @pane
|
||||
|
||||
@clearDropTarget()
|
||||
|
||||
return unless @itemIsAllowed(event, @location)
|
||||
|
||||
if fromWindowId is @getWindowId()
|
||||
fromPane = @paneContainer.getPanes()[fromPaneIndex]
|
||||
if fromPane?.id isnt fromPaneId
|
||||
# If dragging from a different pane container, we have to be more
|
||||
# exhaustive in our search.
|
||||
fromPane = Array.from document.querySelectorAll('atom-pane')
|
||||
.map (paneEl) -> paneEl.model
|
||||
.find (pane) -> pane.id is fromPaneId
|
||||
item = fromPane.getItems()[fromIndex]
|
||||
@moveItemBetweenPanes(fromPane, fromIndex, toPane, toIndex, item) if item?
|
||||
else
|
||||
droppedURI = event.dataTransfer.getData('text/plain')
|
||||
atom.workspace.open(droppedURI).then (item) =>
|
||||
# Move the item from the pane it was opened on to the target pane
|
||||
# where it was dropped onto
|
||||
activePane = atom.workspace.getActivePane()
|
||||
activeItemIndex = activePane.getItems().indexOf(item)
|
||||
@moveItemBetweenPanes(activePane, activeItemIndex, toPane, toIndex, item)
|
||||
item.setText?(modifiedText) if hasUnsavedChanges
|
||||
|
||||
if not isNaN(fromWindowId)
|
||||
# Let the window where the drag started know that the tab was dropped
|
||||
browserWindow = @browserWindowForId(fromWindowId)
|
||||
browserWindow?.webContents.send('tab:dropped', fromPaneId, fromIndex)
|
||||
|
||||
atom.focus()
|
||||
|
||||
# Show the tab bar when a tab is being dragged in this pane when alwaysShowTabBar = false
|
||||
onPaneDragEnter: (event) ->
|
||||
return unless @isAtomTabEvent(event)
|
||||
return unless @itemIsAllowed(event, @location)
|
||||
return if @pane.getItems().length > 1 or atom.config.get('tabs.alwaysShowTabBar')
|
||||
if @paneElement.contains(event.relatedTarget)
|
||||
@element.classList.remove('hidden')
|
||||
|
||||
# Hide the tab bar when the dragged tab leaves this pane when alwaysShowTabBar = false
|
||||
onPaneDragLeave: (event) ->
|
||||
return unless @isAtomTabEvent(event)
|
||||
return unless @itemIsAllowed(event, @location)
|
||||
return if @pane.getItems().length > 1 or atom.config.get('tabs.alwaysShowTabBar')
|
||||
unless @paneElement.contains(event.relatedTarget)
|
||||
@element.classList.add('hidden')
|
||||
|
||||
onMouseWheel: (event) ->
|
||||
return if event.shiftKey or not @tabScrolling
|
||||
|
||||
@wheelDelta ?= 0
|
||||
@wheelDelta += event.wheelDeltaY
|
||||
|
||||
if @wheelDelta <= -@tabScrollingThreshold
|
||||
@wheelDelta = 0
|
||||
@pane.activateNextItem()
|
||||
else if @wheelDelta >= @tabScrollingThreshold
|
||||
@wheelDelta = 0
|
||||
@pane.activatePreviousItem()
|
||||
|
||||
onMouseDown: (event) ->
|
||||
@pane.activate() unless @pane.isDestroyed()
|
||||
|
||||
tab = @tabForElement(event.target)
|
||||
return unless tab
|
||||
|
||||
if event.button is 2 or (event.button is 0 and event.ctrlKey is true)
|
||||
@rightClickedTab = tab
|
||||
event.preventDefault()
|
||||
else if event.button is 1
|
||||
# This prevents Chromium from activating "scroll mode" when
|
||||
# middle-clicking while some tabs are off-screen.
|
||||
event.preventDefault()
|
||||
|
||||
onClick: (event) ->
|
||||
tab = @tabForElement(event.target)
|
||||
return unless tab
|
||||
|
||||
event.preventDefault()
|
||||
if event.button is 2 or (event.button is 0 and event.ctrlKey is true)
|
||||
# Bail out early when receiving this event, because we have already
|
||||
# handled it in the mousedown handler.
|
||||
return
|
||||
else if event.button is 0 and not event.target.classList.contains('close-icon')
|
||||
@pane.activateItem(tab.item)
|
||||
else if event.button is 1
|
||||
@pane.destroyItem(tab.item)
|
||||
|
||||
onDoubleClick: (event) ->
|
||||
if tab = @tabForElement(event.target)
|
||||
tab.item.terminatePendingState?()
|
||||
else if event.target is @element
|
||||
atom.commands.dispatch(@element, 'application:new-file')
|
||||
event.preventDefault()
|
||||
|
||||
updateTabScrollingThreshold: (value) ->
|
||||
@tabScrollingThreshold = value
|
||||
|
||||
updateTabScrolling: (value) ->
|
||||
if value is 'platform'
|
||||
@tabScrolling = (process.platform is 'linux')
|
||||
else
|
||||
@tabScrolling = value
|
||||
|
||||
browserWindowForId: (id) ->
|
||||
BrowserWindow ?= require('electron').remote.BrowserWindow
|
||||
|
||||
BrowserWindow.fromId id
|
||||
|
||||
moveItemBetweenPanes: (fromPane, fromIndex, toPane, toIndex, item) ->
|
||||
try
|
||||
if toPane is fromPane
|
||||
toIndex-- if fromIndex < toIndex
|
||||
toPane.moveItem(item, toIndex)
|
||||
else
|
||||
@isItemMovingBetweenPanes = true
|
||||
fromPane.moveItemToPane(item, toPane, toIndex--)
|
||||
toPane.activateItem(item)
|
||||
toPane.activate()
|
||||
finally
|
||||
@isItemMovingBetweenPanes = false
|
||||
|
||||
removeDropTargetClasses: ->
|
||||
workspaceElement = atom.workspace.getElement()
|
||||
for dropTarget in workspaceElement.querySelectorAll('.tab-bar .is-drop-target')
|
||||
dropTarget.classList.remove('is-drop-target')
|
||||
|
||||
for dropTarget in workspaceElement.querySelectorAll('.tab-bar .drop-target-is-after')
|
||||
dropTarget.classList.remove('drop-target-is-after')
|
||||
|
||||
getDropTargetIndex: (event) ->
|
||||
target = event.target
|
||||
|
||||
return if @isPlaceholder(target)
|
||||
|
||||
tabs = @getTabs()
|
||||
tab = @tabForElement(target)
|
||||
tab ?= tabs[tabs.length - 1]
|
||||
|
||||
return 0 unless tab?
|
||||
|
||||
{left, width} = tab.element.getBoundingClientRect()
|
||||
elementCenter = left + width / 2
|
||||
elementIndex = tabs.indexOf(tab)
|
||||
|
||||
if event.pageX < elementCenter
|
||||
elementIndex
|
||||
else
|
||||
elementIndex + 1
|
||||
|
||||
getPlaceholder: ->
|
||||
return @placeholderEl if @placeholderEl?
|
||||
|
||||
@placeholderEl = document.createElement("li")
|
||||
@placeholderEl.classList.add("placeholder")
|
||||
@placeholderEl
|
||||
|
||||
removePlaceholder: ->
|
||||
@placeholderEl?.remove()
|
||||
@placeholderEl = null
|
||||
|
||||
isPlaceholder: (element) ->
|
||||
element.classList.contains('placeholder')
|
||||
|
||||
onMouseEnter: ->
|
||||
for tab in @getTabs()
|
||||
{width} = tab.element.getBoundingClientRect()
|
||||
tab.element.style.maxWidth = width.toFixed(2) + 'px'
|
||||
return
|
||||
|
||||
onMouseLeave: ->
|
||||
tab.element.style.maxWidth = '' for tab in @getTabs()
|
||||
return
|
||||
|
||||
tabForElement: (element) ->
|
||||
currentElement = element
|
||||
while currentElement?
|
||||
if tab = @tabsByElement.get(currentElement)
|
||||
return tab
|
||||
else
|
||||
currentElement = currentElement.parentElement
|
||||
|
||||
isAtomTabEvent: (event) ->
|
||||
for item in event.dataTransfer.items
|
||||
if item.type is 'atom-tab-event'
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
itemIsAllowed: (event, location) ->
|
||||
for item in event.dataTransfer.items
|
||||
if item.type is 'allow-all-locations' or item.type is "allowed-location-#{location}"
|
||||
return true
|
||||
|
||||
return false
|
@ -1,766 +0,0 @@
|
||||
let BrowserWindow = null; // Defer require until actually used
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
const {CompositeDisposable} = require('atom');
|
||||
const TabView = require('./tab-view.js');
|
||||
|
||||
class TabBarView {
|
||||
constructor(pane, location) {
|
||||
this.pane = pane;
|
||||
this.location = location;
|
||||
this.element = document.createElement("ul");
|
||||
this.element.classList.add("list-inline");
|
||||
this.element.classList.add("tab-bar");
|
||||
this.element.classList.add("inset-panel");
|
||||
this.element.setAttribute("is", "atom-tabs");
|
||||
this.element.setAttribute("tabindex", -1);
|
||||
this.element.setAttribute("location", this.location);
|
||||
|
||||
this.tabs = [];
|
||||
this.tabsByElement = new WeakMap;
|
||||
this.subscriptions = new CompositeDisposable;
|
||||
|
||||
this.paneElement = this.pane.getElement();
|
||||
|
||||
this.subscriptions.add(atom.commands.add(this.paneElement, {
|
||||
"tabs:keep-pending-tab": () => this.terminatePendingStates(),
|
||||
"tabs:close-tab": () => this.closeTab(this.getActiveTab()),
|
||||
"tabs:close-other-tabs": () => this.closeOtherTabs(this.getActiveTab()),
|
||||
"tabs:close-tabs-to-right": () => this.closeTabsToRight(this.getActiveTab()),
|
||||
"tabs:close-tabs-to-left": () => this.closeTabsToLeft(this.getActiveTab()),
|
||||
"tabs:close-saved-tabs": () => this.closeSavedTabs(),
|
||||
"tabs:close-all-tabs": event => {
|
||||
event.stopPropagation();
|
||||
return this.closeAllTabs();
|
||||
},
|
||||
"tabs:open-in-new-window": () => this.openInNewWindow()
|
||||
}));
|
||||
|
||||
const addElementCommands = commands => {
|
||||
const commandsWithPropagationStopped = {};
|
||||
Object.keys(commands).forEach(name => commandsWithPropagationStopped[name] = function(event) {
|
||||
event.stopPropagation();
|
||||
return commands[name]();
|
||||
});
|
||||
|
||||
return this.subscriptions.add(atom.commands.add(this.element, commandsWithPropagationStopped));
|
||||
};
|
||||
|
||||
addElementCommands({
|
||||
"tabs:close-tab": () => this.closeTab(),
|
||||
"tabs:close-other-tabs": () => this.closeOtherTabs(),
|
||||
"tabs:close-tabs-to-right": () => this.closeTabsToRight(),
|
||||
"tabs:close-tabs-to-left": () => this.closeTabsToLeft(),
|
||||
"tabs:close-saved-tabs": () => this.closeSavedTabs(),
|
||||
"tabs:close-all-tabs": () => this.closeAllTabs(),
|
||||
"tabs:split-up": () => this.splitTab("splitUp"),
|
||||
"tabs:split-down": () => this.splitTab("splitDown"),
|
||||
"tabs:split-left": () => this.splitTab("splitLeft"),
|
||||
"tabs:split-right": () => this.splitTab("splitRight")
|
||||
});
|
||||
|
||||
this.element.addEventListener("mouseenter", this.onMouseEnter.bind(this));
|
||||
this.element.addEventListener("mouseleave", this.onMouseLeave.bind(this));
|
||||
this.element.addEventListener("mousewheel", this.onMouseWheel.bind(this));
|
||||
this.element.addEventListener("dragstart", this.onDragStart.bind(this));
|
||||
this.element.addEventListener("dragend", this.onDragEnd.bind(this));
|
||||
this.element.addEventListener("dragleave", this.onDragLeave.bind(this));
|
||||
this.element.addEventListener("dragover", this.onDragOver.bind(this));
|
||||
this.element.addEventListener("drop", this.onDrop.bind(this));
|
||||
|
||||
// Toggle the tab bar when a tab is dragged over the pane with alwaysShowTabBar = false
|
||||
this.paneElement.addEventListener('dragenter', this.onPaneDragEnter.bind(this));
|
||||
this.paneElement.addEventListener('dragleave', this.onPaneDragLeave.bind(this));
|
||||
|
||||
this.paneContainer = this.pane.getContainer();
|
||||
for (let item of Array.from(this.pane.getItems())) { this.addTabForItem(item); }
|
||||
|
||||
this.subscriptions.add(this.pane.onDidDestroy(() => {
|
||||
return this.destroy();
|
||||
}));
|
||||
|
||||
this.subscriptions.add(this.pane.onDidAddItem(({item, index}) => {
|
||||
return this.addTabForItem(item, index);
|
||||
}));
|
||||
|
||||
this.subscriptions.add(this.pane.onDidMoveItem(({item, newIndex}) => {
|
||||
return this.moveItemTabToIndex(item, newIndex);
|
||||
}));
|
||||
|
||||
this.subscriptions.add(this.pane.onDidRemoveItem(({item}) => {
|
||||
return this.removeTabForItem(item);
|
||||
}));
|
||||
|
||||
this.subscriptions.add(this.pane.onDidChangeActiveItem(item => {
|
||||
return this.updateActiveTab();
|
||||
}));
|
||||
|
||||
this.subscriptions.add(atom.config.observe('tabs.tabScrolling', value => this.updateTabScrolling(value)));
|
||||
this.subscriptions.add(atom.config.observe('tabs.tabScrollingThreshold', value => this.updateTabScrollingThreshold(value)));
|
||||
this.subscriptions.add(atom.config.observe('tabs.alwaysShowTabBar', () => this.updateTabBarVisibility()));
|
||||
|
||||
this.updateActiveTab();
|
||||
|
||||
this.element.addEventListener("mousedown", this.onMouseDown.bind(this));
|
||||
this.element.addEventListener("click", this.onClick.bind(this));
|
||||
this.element.addEventListener("auxclick", this.onClick.bind(this));
|
||||
this.element.addEventListener("dblclick", this.onDoubleClick.bind(this));
|
||||
|
||||
this.onDropOnOtherWindow = this.onDropOnOtherWindow.bind(this);
|
||||
ipcRenderer.on('tab:dropped', this.onDropOnOtherWindow);
|
||||
|
||||
}
|
||||
|
||||
destroy() {
|
||||
ipcRenderer.removeListener('tab:dropped', this.onDropOnOtherWindow);
|
||||
this.subscriptions.dispose();
|
||||
return this.element.remove();
|
||||
}
|
||||
|
||||
terminatePendingStates() {
|
||||
for (let tab of this.getTabs()) {
|
||||
if (typeof tab.terminatePendingState === "function") {
|
||||
tab.terminatePendingState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addTabForItem(item, index) {
|
||||
let tabView = new TabView({
|
||||
item,
|
||||
pane: this.pane,
|
||||
tabs: this.tabs,
|
||||
didClickCloseIcon: () => {
|
||||
this.closeTab(tabView);
|
||||
},
|
||||
location: this.location
|
||||
});
|
||||
if (this.isItemMovingBetweenPanes) {
|
||||
tabView.terminatePendingState();
|
||||
}
|
||||
|
||||
this.tabsByElement.set(tabView.element, tabView);
|
||||
this.insertTabAtIndex(tabView, index);
|
||||
|
||||
if (atom.config.get('tabs.addNewTabsAtEnd')) {
|
||||
if (!this.isItemMovingBetweenPanes) {
|
||||
return this.pane.moveItem(item, this.pane.getItems().length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveItemTabToIndex(item, index) {
|
||||
const tabIndex = this.tabs.findIndex(t => t.item === item);
|
||||
if (tabIndex !== -1) {
|
||||
const tab = this.tabs[tabIndex];
|
||||
tab.element.remove();
|
||||
this.tabs.splice(tabIndex, 1);
|
||||
return this.insertTabAtIndex(tab, index);
|
||||
}
|
||||
}
|
||||
|
||||
insertTabAtIndex(tab, index) {
|
||||
let followingTab;
|
||||
if (index != null) {
|
||||
followingTab = this.tabs[index];
|
||||
}
|
||||
if (followingTab) {
|
||||
this.element.insertBefore(tab.element, followingTab.element);
|
||||
this.tabs.splice(index, 0, tab);
|
||||
} else {
|
||||
this.element.appendChild(tab.element);
|
||||
this.tabs.push(tab);
|
||||
}
|
||||
|
||||
tab.updateTitle();
|
||||
return this.updateTabBarVisibility();
|
||||
}
|
||||
|
||||
removeTabForItem(item) {
|
||||
let tab;
|
||||
const tabIndex = this.tabs.findIndex(t => t.item === item);
|
||||
if (tabIndex !== -1) {
|
||||
tab = this.tabs[tabIndex];
|
||||
this.tabs.splice(tabIndex, 1);
|
||||
this.tabsByElement.delete(tab);
|
||||
tab.destroy();
|
||||
}
|
||||
for (tab of this.getTabs()) {
|
||||
tab.updateTitle();
|
||||
}
|
||||
|
||||
return this.updateTabBarVisibility();
|
||||
}
|
||||
|
||||
updateTabBarVisibility() {
|
||||
// Show tab bar if the setting is true or there is more than one tab
|
||||
if (atom.config.get('tabs.alwaysShowTabBar') || (this.pane.getItems().length > 1)) {
|
||||
return this.element.classList.remove('hidden');
|
||||
} else {
|
||||
return this.element.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
getTabs() {
|
||||
return this.tabs.slice();
|
||||
}
|
||||
|
||||
tabAtIndex(index) {
|
||||
return this.tabs[index];
|
||||
}
|
||||
|
||||
tabForItem(item) {
|
||||
return this.tabs.find(t => t.item === item);
|
||||
}
|
||||
|
||||
setActiveTab(tabView) {
|
||||
if ((tabView != null) && (tabView !== this.activeTab)) {
|
||||
if (this.activeTab != null) {
|
||||
this.activeTab.element.classList.remove('active');
|
||||
}
|
||||
this.activeTab = tabView;
|
||||
this.activeTab.element.classList.add('active');
|
||||
return this.activeTab.element.scrollIntoView(false);
|
||||
}
|
||||
}
|
||||
|
||||
getActiveTab() {
|
||||
return this.tabForItem(this.pane.getActiveItem());
|
||||
}
|
||||
|
||||
updateActiveTab() {
|
||||
return this.setActiveTab(this.tabForItem(this.pane.getActiveItem()));
|
||||
}
|
||||
|
||||
closeTab(tab) {
|
||||
if (tab == null) { tab = this.rightClickedTab; }
|
||||
if (tab != null) { return this.pane.destroyItem(tab.item); }
|
||||
}
|
||||
|
||||
openInNewWindow(tab) {
|
||||
let itemURI;
|
||||
if (tab == null) { tab = this.rightClickedTab; }
|
||||
const item = tab != null ? tab.item : undefined;
|
||||
if (item == null) { return; }
|
||||
if (typeof item.getURI === 'function') {
|
||||
itemURI = item.getURI();
|
||||
} else if (typeof item.getPath === 'function') {
|
||||
itemURI = item.getPath();
|
||||
} else if (typeof item.getUri === 'function') {
|
||||
itemURI = item.getUri();
|
||||
}
|
||||
if (itemURI == null) { return; }
|
||||
this.closeTab(tab);
|
||||
for (tab of this.getTabs()) { tab.element.style.maxWidth = ''; }
|
||||
const pathsToOpen = [atom.project.getPaths(), itemURI].reduce(((a, b) => a.concat(b)), []);
|
||||
return atom.open({pathsToOpen, newWindow: true, devMode: atom.devMode, safeMode: atom.safeMode});
|
||||
}
|
||||
|
||||
splitTab(fn) {
|
||||
let item;
|
||||
if (item = this.rightClickedTab != null ? this.rightClickedTab.item : undefined) {
|
||||
let copiedItem;
|
||||
if (copiedItem = typeof item.copy === 'function' ? item.copy() : undefined) {
|
||||
return this.pane[fn]({items: [copiedItem]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeOtherTabs(active) {
|
||||
const tabs = this.getTabs();
|
||||
if (active == null) { active = this.rightClickedTab; }
|
||||
if (active == null) { return; }
|
||||
return tabs.filter((tab) => tab !== active).map((tab) => this.closeTab(tab));
|
||||
}
|
||||
|
||||
closeTabsToRight(active) {
|
||||
const tabs = this.getTabs();
|
||||
if (active == null) { active = this.rightClickedTab; }
|
||||
const index = tabs.indexOf(active);
|
||||
if (index === -1) { return; }
|
||||
return (() => {
|
||||
const result = [];
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const tab = tabs[i];
|
||||
if (i > index) {
|
||||
result.push(this.closeTab(tab));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
|
||||
closeTabsToLeft(active) {
|
||||
const tabs = this.getTabs();
|
||||
if (active == null) { active = this.rightClickedTab; }
|
||||
const index = tabs.indexOf(active);
|
||||
if (index === -1) { return; }
|
||||
return (() => {
|
||||
const result = [];
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const tab = tabs[i];
|
||||
if (i < index) {
|
||||
result.push(this.closeTab(tab));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
|
||||
closeSavedTabs() {
|
||||
return (() => {
|
||||
const result = [];
|
||||
for (let tab of this.getTabs()) {
|
||||
if (!(typeof tab.item.isModified === 'function' ? tab.item.isModified() : undefined)) { result.push(this.closeTab(tab)); } else {
|
||||
result.push(undefined);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
|
||||
closeAllTabs() {
|
||||
return this.getTabs().map((tab) => this.closeTab(tab));
|
||||
}
|
||||
|
||||
getWindowId() {
|
||||
return this.windowId != null ? this.windowId : (this.windowId = atom.getCurrentWindow().id);
|
||||
}
|
||||
|
||||
onDragStart(event) {
|
||||
let itemURI;
|
||||
this.draggedTab = this.tabForElement(event.target);
|
||||
if (!this.draggedTab) { return; }
|
||||
this.lastDropTargetIndex = null;
|
||||
|
||||
event.dataTransfer.setData('atom-tab-event', 'true');
|
||||
|
||||
this.draggedTab.element.classList.add('is-dragging');
|
||||
this.draggedTab.destroyTooltip();
|
||||
|
||||
const tabIndex = this.tabs.indexOf(this.draggedTab);
|
||||
event.dataTransfer.setData('sortable-index', tabIndex);
|
||||
|
||||
const paneIndex = this.paneContainer.getPanes().indexOf(this.pane);
|
||||
event.dataTransfer.setData('from-pane-index', paneIndex);
|
||||
event.dataTransfer.setData('from-pane-id', this.pane.id);
|
||||
event.dataTransfer.setData('from-window-id', this.getWindowId());
|
||||
|
||||
const item = this.pane.getItems()[this.tabs.indexOf(this.draggedTab)];
|
||||
if (item == null) { return; }
|
||||
|
||||
if (typeof item.getURI === 'function') {
|
||||
let left;
|
||||
itemURI = (left = item.getURI()) != null ? left : '';
|
||||
} else if (typeof item.getPath === 'function') {
|
||||
let left1;
|
||||
itemURI = (left1 = item.getPath()) != null ? left1 : '';
|
||||
} else if (typeof item.getUri === 'function') {
|
||||
let left2;
|
||||
itemURI = (left2 = item.getUri()) != null ? left2 : '';
|
||||
}
|
||||
|
||||
if (typeof item.getAllowedLocations === 'function') {
|
||||
for (let location of item.getAllowedLocations()) {
|
||||
event.dataTransfer.setData(`allowed-location-${location}`, 'true');
|
||||
}
|
||||
} else {
|
||||
event.dataTransfer.setData('allow-all-locations', 'true');
|
||||
}
|
||||
|
||||
if (itemURI != null) {
|
||||
event.dataTransfer.setData('text/plain', itemURI);
|
||||
|
||||
if (process.platform === 'darwin') { // see #69
|
||||
if (!this.uriHasProtocol(itemURI)) { itemURI = `file://${itemURI}`; }
|
||||
event.dataTransfer.setData('text/uri-list', itemURI);
|
||||
}
|
||||
|
||||
if ((typeof item.isModified === 'function' ? item.isModified() : undefined) && (item.getText != null)) {
|
||||
event.dataTransfer.setData('has-unsaved-changes', 'true');
|
||||
return event.dataTransfer.setData('modified-text', item.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uriHasProtocol(uri) {
|
||||
try {
|
||||
return (require('url').parse(uri).protocol != null);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onDragLeave(event) {
|
||||
// Do not do anything unless the drag goes outside the tab bar
|
||||
if (!event.currentTarget.contains(event.relatedTarget)) {
|
||||
this.removePlaceholder();
|
||||
this.lastDropTargetIndex = null;
|
||||
return this.getTabs().map((tab) => (tab.element.style.maxWidth = ''));
|
||||
}
|
||||
}
|
||||
|
||||
onDragEnd(event) {
|
||||
if (!this.tabForElement(event.target)) { return; }
|
||||
|
||||
return this.clearDropTarget();
|
||||
}
|
||||
|
||||
onDragOver(event) {
|
||||
let tab;
|
||||
if (!this.isAtomTabEvent(event)) { return; }
|
||||
if (!this.itemIsAllowed(event, this.location)) { return; }
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const newDropTargetIndex = this.getDropTargetIndex(event);
|
||||
if (newDropTargetIndex == null) { return; }
|
||||
if (this.lastDropTargetIndex === newDropTargetIndex) { return; }
|
||||
this.lastDropTargetIndex = newDropTargetIndex;
|
||||
|
||||
this.removeDropTargetClasses();
|
||||
|
||||
const tabs = this.getTabs();
|
||||
const placeholder = this.getPlaceholder();
|
||||
if (placeholder == null) { return; }
|
||||
|
||||
if (newDropTargetIndex < tabs.length) {
|
||||
tab = tabs[newDropTargetIndex];
|
||||
tab.element.classList.add('is-drop-target');
|
||||
return tab.element.parentElement.insertBefore(placeholder, tab.element);
|
||||
} else {
|
||||
if (tab = tabs[newDropTargetIndex - 1]) {
|
||||
let sibling;
|
||||
tab.element.classList.add('drop-target-is-after');
|
||||
if ((sibling = tab.element.nextSibling)) {
|
||||
return tab.element.parentElement.insertBefore(placeholder, sibling);
|
||||
} else {
|
||||
return tab.element.parentElement.appendChild(placeholder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDropOnOtherWindow(event, fromPaneId, fromItemIndex) {
|
||||
if (this.pane.id === fromPaneId) {
|
||||
let itemToRemove;
|
||||
if (itemToRemove = this.pane.getItems()[fromItemIndex]) {
|
||||
this.pane.destroyItem(itemToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
return this.clearDropTarget();
|
||||
}
|
||||
|
||||
clearDropTarget() {
|
||||
if (this.draggedTab != null) {
|
||||
this.draggedTab.element.classList.remove('is-dragging');
|
||||
}
|
||||
if (this.draggedTab != null) {
|
||||
this.draggedTab.updateTooltip();
|
||||
}
|
||||
this.draggedTab = null;
|
||||
this.removeDropTargetClasses();
|
||||
return this.removePlaceholder();
|
||||
}
|
||||
|
||||
onDrop(event) {
|
||||
if (!this.isAtomTabEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const fromWindowId = parseInt(event.dataTransfer.getData('from-window-id'));
|
||||
const fromPaneId = parseInt(event.dataTransfer.getData('from-pane-id'));
|
||||
const fromIndex = parseInt(event.dataTransfer.getData('sortable-index'));
|
||||
const fromPaneIndex = parseInt(event.dataTransfer.getData('from-pane-index'));
|
||||
|
||||
const hasUnsavedChanges = event.dataTransfer.getData('has-unsaved-changes') === 'true';
|
||||
const modifiedText = event.dataTransfer.getData('modified-text');
|
||||
|
||||
const toIndex = this.getDropTargetIndex(event);
|
||||
const toPane = this.pane;
|
||||
|
||||
this.clearDropTarget();
|
||||
|
||||
if (!this.itemIsAllowed(event, this.location)) { return; }
|
||||
|
||||
if (fromWindowId === this.getWindowId()) {
|
||||
let fromPane = this.paneContainer.getPanes()[fromPaneIndex];
|
||||
if ((fromPane != null ? fromPane.id : undefined) !== fromPaneId) {
|
||||
// If dragging from a different pane container, we have to be more
|
||||
// exhaustive in our search.
|
||||
fromPane = Array.from(document.querySelectorAll('atom-pane'))
|
||||
.map(paneEl => paneEl.model)
|
||||
.find(pane => pane.id === fromPaneId);
|
||||
}
|
||||
const item = fromPane.getItems()[fromIndex];
|
||||
if (item != null) { return this.moveItemBetweenPanes(fromPane, fromIndex, toPane, toIndex, item); }
|
||||
} else {
|
||||
const droppedURI = event.dataTransfer.getData('text/plain');
|
||||
atom.workspace.open(droppedURI).then(item => {
|
||||
// Move the item from the pane it was opened on to the target pane
|
||||
// where it was dropped onto
|
||||
const activePane = atom.workspace.getActivePane();
|
||||
const activeItemIndex = activePane.getItems().indexOf(item);
|
||||
this.moveItemBetweenPanes(activePane, activeItemIndex, toPane, toIndex, item);
|
||||
if (hasUnsavedChanges) { if (typeof item.setText === 'function') {
|
||||
item.setText(modifiedText);
|
||||
} }
|
||||
|
||||
if (!isNaN(fromWindowId)) {
|
||||
// Let the window where the drag started know that the tab was dropped
|
||||
const browserWindow = this.browserWindowForId(fromWindowId);
|
||||
return (browserWindow != null ? browserWindow.webContents.send('tab:dropped', fromPaneId, fromIndex) : undefined);
|
||||
}
|
||||
});
|
||||
|
||||
return atom.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Show the tab bar when a tab is being dragged in this pane when alwaysShowTabBar = false
|
||||
onPaneDragEnter(event) {
|
||||
if (!this.isAtomTabEvent(event)) {
|
||||
return;
|
||||
}
|
||||
if (!this.itemIsAllowed(event, this.location)) {
|
||||
return;
|
||||
}
|
||||
if ((this.pane.getItems().length > 1) || atom.config.get('tabs.alwaysShowTabBar')) {
|
||||
return;
|
||||
}
|
||||
if (this.paneElement.contains(event.relatedTarget)) {
|
||||
return this.element.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the tab bar when the dragged tab leaves this pane when alwaysShowTabBar = false
|
||||
onPaneDragLeave(event) {
|
||||
if (!this.isAtomTabEvent(event)) {
|
||||
return;
|
||||
}
|
||||
if (!this.itemIsAllowed(event, this.location)) {
|
||||
return;
|
||||
}
|
||||
if ((this.pane.getItems().length > 1) || atom.config.get('tabs.alwaysShowTabBar')) {
|
||||
return;
|
||||
}
|
||||
if (!this.paneElement.contains(event.relatedTarget)) {
|
||||
return this.element.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
onMouseWheel(event) {
|
||||
if (event.shiftKey || !this.tabScrolling) { return; }
|
||||
|
||||
if (this.wheelDelta == null) { this.wheelDelta = 0; }
|
||||
this.wheelDelta += event.wheelDeltaY;
|
||||
|
||||
if (this.wheelDelta <= -this.tabScrollingThreshold) {
|
||||
this.wheelDelta = 0;
|
||||
return this.pane.activateNextItem();
|
||||
} else if (this.wheelDelta >= this.tabScrollingThreshold) {
|
||||
this.wheelDelta = 0;
|
||||
return this.pane.activatePreviousItem();
|
||||
}
|
||||
}
|
||||
|
||||
onMouseDown(event) {
|
||||
if (!this.pane.isDestroyed()) { this.pane.activate(); }
|
||||
|
||||
const tab = this.tabForElement(event.target);
|
||||
if (!tab) { return; }
|
||||
|
||||
if ((event.button === 2) || ((event.button === 0) && (event.ctrlKey === true))) {
|
||||
if (this.rightClickedTab) {
|
||||
this.rightClickedTab.element.classList.remove('right-clicked');
|
||||
}
|
||||
this.rightClickedTab = tab;
|
||||
this.rightClickedTab.element.classList.add('right-clicked');
|
||||
return event.preventDefault();
|
||||
} else if (event.button === 1) {
|
||||
// This prevents Chromium from activating "scroll mode" when
|
||||
// middle-clicking while some tabs are off-screen.
|
||||
return event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
const tab = this.tabForElement(event.target);
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
if ((event.button === 2) || ((event.button === 0) && (event.ctrlKey === true))) {
|
||||
// Bail out early when receiving this event, because we have already
|
||||
// handled it in the mousedown handler.
|
||||
return;
|
||||
} else if ((event.button === 0) && !event.target.classList.contains('close-icon')) {
|
||||
return this.pane.activateItem(tab.item);
|
||||
} else if (event.button === 1) {
|
||||
return this.pane.destroyItem(tab.item);
|
||||
}
|
||||
}
|
||||
|
||||
onDoubleClick(event) {
|
||||
let tab = this.tabForElement(event.target);
|
||||
if (tab) {
|
||||
return (typeof tab.item.terminatePendingState === 'function' ? tab.item.terminatePendingState() : undefined);
|
||||
} else if (event.target === this.element) {
|
||||
atom.commands.dispatch(this.element, 'application:new-file');
|
||||
return event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
updateTabScrollingThreshold(value) {
|
||||
return this.tabScrollingThreshold = value;
|
||||
}
|
||||
|
||||
updateTabScrolling(value) {
|
||||
if (value === 'platform') {
|
||||
return this.tabScrolling = (process.platform === 'linux');
|
||||
} else {
|
||||
return this.tabScrolling = value;
|
||||
}
|
||||
}
|
||||
|
||||
browserWindowForId(id) {
|
||||
if (BrowserWindow == null) { ({
|
||||
BrowserWindow
|
||||
} = require('electron').remote); }
|
||||
|
||||
return BrowserWindow.fromId(id);
|
||||
}
|
||||
|
||||
moveItemBetweenPanes(fromPane, fromIndex, toPane, toIndex, item) {
|
||||
try {
|
||||
if (toPane === fromPane) {
|
||||
if (fromIndex < toIndex) { toIndex--; }
|
||||
toPane.moveItem(item, toIndex);
|
||||
} else {
|
||||
this.isItemMovingBetweenPanes = true;
|
||||
fromPane.moveItemToPane(item, toPane, toIndex--);
|
||||
}
|
||||
toPane.activateItem(item);
|
||||
return toPane.activate();
|
||||
} finally {
|
||||
this.isItemMovingBetweenPanes = false;
|
||||
}
|
||||
}
|
||||
|
||||
removeDropTargetClasses() {
|
||||
let dropTarget;
|
||||
const workspaceElement = atom.workspace.getElement();
|
||||
for (dropTarget of workspaceElement.querySelectorAll('.tab-bar .is-drop-target')) {
|
||||
dropTarget.classList.remove('is-drop-target');
|
||||
}
|
||||
|
||||
return (() => {
|
||||
const result = [];
|
||||
for (dropTarget of workspaceElement.querySelectorAll('.tab-bar .drop-target-is-after')) {
|
||||
result.push(dropTarget.classList.remove('drop-target-is-after'));
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
|
||||
getDropTargetIndex(event) {
|
||||
const { target } = event;
|
||||
|
||||
if (this.isPlaceholder(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabs = this.getTabs();
|
||||
let tab = this.tabForElement(target);
|
||||
if (tab == null) {
|
||||
tab = tabs[tabs.length - 1];
|
||||
}
|
||||
|
||||
if (tab == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const { left, width } = tab.element.getBoundingClientRect();
|
||||
const elementCenter = left + (width / 2);
|
||||
const elementIndex = tabs.indexOf(tab);
|
||||
|
||||
if (event.pageX < elementCenter) {
|
||||
return elementIndex;
|
||||
} else {
|
||||
return elementIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
getPlaceholder() {
|
||||
if (this.placeholderEl != null) {
|
||||
return this.placeholderEl;
|
||||
}
|
||||
|
||||
this.placeholderEl = document.createElement("li");
|
||||
this.placeholderEl.classList.add("placeholder");
|
||||
return this.placeholderEl;
|
||||
}
|
||||
|
||||
removePlaceholder() {
|
||||
if (this.placeholderEl != null) {
|
||||
this.placeholderEl.remove();
|
||||
}
|
||||
return this.placeholderEl = null;
|
||||
}
|
||||
|
||||
isPlaceholder(element) {
|
||||
return element.classList.contains('placeholder');
|
||||
}
|
||||
|
||||
onMouseEnter() {
|
||||
for (let tab of this.getTabs()) {
|
||||
const { width } = tab.element.getBoundingClientRect();
|
||||
tab.element.style.maxWidth = width.toFixed(2) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
onMouseLeave() {
|
||||
for (let tab of this.getTabs()) {
|
||||
tab.element.style.maxWidth = '';
|
||||
}
|
||||
}
|
||||
|
||||
tabForElement(element) {
|
||||
let currentElement = element;
|
||||
while (currentElement != null) {
|
||||
if (this.tabsByElement.get(currentElement)) {
|
||||
return this.tabsByElement.get(currentElement);
|
||||
} else {
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isAtomTabEvent(event) {
|
||||
for (let item of event.dataTransfer.items) {
|
||||
if (item.type === 'atom-tab-event') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
itemIsAllowed(event, location) {
|
||||
for (let item of event.dataTransfer.items) {
|
||||
if ((item.type === 'allow-all-locations') || (item.type === `allowed-location-${location}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = TabBarView;
|
296
packages/tabs/lib/tab-view.coffee
Normal file
296
packages/tabs/lib/tab-view.coffee
Normal file
@ -0,0 +1,296 @@
|
||||
path = require 'path'
|
||||
{Disposable, CompositeDisposable} = require 'atom'
|
||||
getIconServices = require './get-icon-services'
|
||||
|
||||
layout = require './layout'
|
||||
|
||||
module.exports =
|
||||
class TabView
|
||||
constructor: ({@item, @pane, didClickCloseIcon, @tabs, location}) ->
|
||||
if typeof @item.getPath is 'function'
|
||||
@path = @item.getPath()
|
||||
|
||||
@element = document.createElement('li')
|
||||
@element.setAttribute('is', 'tabs-tab')
|
||||
if ['TextEditor', 'TestView'].indexOf(@item.constructor.name) > -1
|
||||
@element.classList.add('texteditor')
|
||||
@element.classList.add('tab', 'sortable')
|
||||
|
||||
@itemTitle = document.createElement('div')
|
||||
@itemTitle.classList.add('title')
|
||||
@element.appendChild(@itemTitle)
|
||||
|
||||
if location is 'center' or not @item.isPermanentDockItem?()
|
||||
closeIcon = document.createElement('div')
|
||||
closeIcon.classList.add('close-icon')
|
||||
closeIcon.onclick = didClickCloseIcon
|
||||
@element.appendChild(closeIcon)
|
||||
|
||||
@subscriptions = new CompositeDisposable()
|
||||
|
||||
@handleEvents()
|
||||
@updateDataAttributes()
|
||||
@updateTitle()
|
||||
@updateIcon()
|
||||
@updateModifiedStatus()
|
||||
@setupTooltip()
|
||||
|
||||
if @isItemPending()
|
||||
@itemTitle.classList.add('temp')
|
||||
@element.classList.add('pending-tab')
|
||||
|
||||
@element.ondrag = (e) -> layout.drag e
|
||||
@element.ondragend = (e) -> layout.end e
|
||||
|
||||
@element.pane = @pane
|
||||
@element.item = @item
|
||||
@element.itemTitle = @itemTitle
|
||||
@element.path = @path
|
||||
|
||||
handleEvents: ->
|
||||
titleChangedHandler = =>
|
||||
@updateTitle()
|
||||
|
||||
@subscriptions.add @pane.onDidDestroy => @destroy()
|
||||
@subscriptions.add @pane.onItemDidTerminatePendingState (item) =>
|
||||
@clearPending() if item is @item
|
||||
|
||||
if typeof @item.onDidChangeTitle is 'function'
|
||||
onDidChangeTitleDisposable = @item.onDidChangeTitle(titleChangedHandler)
|
||||
if Disposable.isDisposable(onDidChangeTitleDisposable)
|
||||
@subscriptions.add(onDidChangeTitleDisposable)
|
||||
else
|
||||
console.warn "::onDidChangeTitle does not return a valid Disposable!", @item
|
||||
else if typeof @item.on is 'function'
|
||||
#TODO Remove once old events are no longer supported
|
||||
@item.on('title-changed', titleChangedHandler)
|
||||
@subscriptions.add dispose: =>
|
||||
@item.off?('title-changed', titleChangedHandler)
|
||||
|
||||
pathChangedHandler = (@path) =>
|
||||
@updateDataAttributes()
|
||||
@updateTitle()
|
||||
@updateTooltip()
|
||||
@updateIcon()
|
||||
|
||||
if typeof @item.onDidChangePath is 'function'
|
||||
onDidChangePathDisposable = @item.onDidChangePath(pathChangedHandler)
|
||||
if Disposable.isDisposable(onDidChangePathDisposable)
|
||||
@subscriptions.add(onDidChangePathDisposable)
|
||||
else
|
||||
console.warn "::onDidChangePath does not return a valid Disposable!", @item
|
||||
else if typeof @item.on is 'function'
|
||||
#TODO Remove once old events are no longer supported
|
||||
@item.on('path-changed', pathChangedHandler)
|
||||
@subscriptions.add dispose: =>
|
||||
@item.off?('path-changed', pathChangedHandler)
|
||||
|
||||
iconChangedHandler = =>
|
||||
@updateIcon()
|
||||
|
||||
@subscriptions.add getIconServices().onDidChange => @updateIcon()
|
||||
|
||||
if typeof @item.onDidChangeIcon is 'function'
|
||||
onDidChangeIconDisposable = @item.onDidChangeIcon? =>
|
||||
@updateIcon()
|
||||
if Disposable.isDisposable(onDidChangeIconDisposable)
|
||||
@subscriptions.add(onDidChangeIconDisposable)
|
||||
else
|
||||
console.warn "::onDidChangeIcon does not return a valid Disposable!", @item
|
||||
else if typeof @item.on is 'function'
|
||||
#TODO Remove once old events are no longer supported
|
||||
@item.on('icon-changed', iconChangedHandler)
|
||||
@subscriptions.add dispose: =>
|
||||
@item.off?('icon-changed', iconChangedHandler)
|
||||
|
||||
modifiedHandler = =>
|
||||
@updateModifiedStatus()
|
||||
|
||||
if typeof @item.onDidChangeModified is 'function'
|
||||
onDidChangeModifiedDisposable = @item.onDidChangeModified(modifiedHandler)
|
||||
if Disposable.isDisposable(onDidChangeModifiedDisposable)
|
||||
@subscriptions.add(onDidChangeModifiedDisposable)
|
||||
else
|
||||
console.warn "::onDidChangeModified does not return a valid Disposable!", @item
|
||||
else if typeof @item.on is 'function'
|
||||
#TODO Remove once old events are no longer supported
|
||||
@item.on('modified-status-changed', modifiedHandler)
|
||||
@subscriptions.add dispose: =>
|
||||
@item.off?('modified-status-changed', modifiedHandler)
|
||||
|
||||
if typeof @item.onDidSave is 'function'
|
||||
onDidSaveDisposable = @item.onDidSave (event) =>
|
||||
@terminatePendingState()
|
||||
if event.path isnt @path
|
||||
@path = event.path
|
||||
@setupVcsStatus() if atom.config.get 'tabs.enableVcsColoring'
|
||||
|
||||
if Disposable.isDisposable(onDidSaveDisposable)
|
||||
@subscriptions.add(onDidSaveDisposable)
|
||||
else
|
||||
console.warn "::onDidSave does not return a valid Disposable!", @item
|
||||
@subscriptions.add atom.config.observe 'tabs.showIcons', =>
|
||||
@updateIconVisibility()
|
||||
|
||||
@subscriptions.add atom.config.observe 'tabs.enableVcsColoring', (isEnabled) =>
|
||||
if isEnabled and @path? then @setupVcsStatus() else @unsetVcsStatus()
|
||||
|
||||
setupTooltip: ->
|
||||
# Defer creating the tooltip until the tab is moused over
|
||||
onMouseEnter = =>
|
||||
@mouseEnterSubscription.dispose()
|
||||
@hasBeenMousedOver = true
|
||||
@updateTooltip()
|
||||
|
||||
# Trigger again so the tooltip shows
|
||||
@element.dispatchEvent(new CustomEvent('mouseenter', bubbles: true))
|
||||
|
||||
@mouseEnterSubscription = dispose: =>
|
||||
@element.removeEventListener('mouseenter', onMouseEnter)
|
||||
@mouseEnterSubscription = null
|
||||
|
||||
@element.addEventListener('mouseenter', onMouseEnter)
|
||||
|
||||
updateTooltip: ->
|
||||
return unless @hasBeenMousedOver
|
||||
|
||||
@destroyTooltip()
|
||||
|
||||
if @path
|
||||
@tooltip = atom.tooltips.add @element,
|
||||
title: @path
|
||||
html: false
|
||||
delay:
|
||||
show: 1000
|
||||
hide: 100
|
||||
placement: 'bottom'
|
||||
|
||||
destroyTooltip: ->
|
||||
return unless @hasBeenMousedOver
|
||||
@tooltip?.dispose()
|
||||
|
||||
destroy: ->
|
||||
@subscriptions?.dispose()
|
||||
@mouseEnterSubscription?.dispose()
|
||||
@repoSubscriptions?.dispose()
|
||||
@destroyTooltip()
|
||||
@element.remove()
|
||||
|
||||
updateDataAttributes: ->
|
||||
if @path
|
||||
@itemTitle.dataset.name = path.basename(@path)
|
||||
@itemTitle.dataset.path = @path
|
||||
else
|
||||
delete @itemTitle.dataset.name
|
||||
delete @itemTitle.dataset.path
|
||||
|
||||
if itemClass = @item.constructor?.name
|
||||
@element.dataset.type = itemClass
|
||||
else
|
||||
delete @element.dataset.type
|
||||
|
||||
updateTitle: ({updateSiblings, useLongTitle}={}) ->
|
||||
return if @updatingTitle
|
||||
@updatingTitle = true
|
||||
|
||||
if updateSiblings is false
|
||||
title = @item.getTitle()
|
||||
title = @item.getLongTitle?() ? title if useLongTitle
|
||||
@itemTitle.textContent = title
|
||||
else
|
||||
title = @item.getTitle()
|
||||
useLongTitle = false
|
||||
for tab in @tabs when tab isnt this
|
||||
if tab.item.getTitle() is title
|
||||
tab.updateTitle(updateSiblings: false, useLongTitle: true)
|
||||
useLongTitle = true
|
||||
title = @item.getLongTitle?() ? title if useLongTitle
|
||||
|
||||
@itemTitle.textContent = title
|
||||
|
||||
@updatingTitle = false
|
||||
|
||||
updateIcon: ->
|
||||
getIconServices().updateTabIcon(this)
|
||||
|
||||
isItemPending: ->
|
||||
if @pane.getPendingItem?
|
||||
@pane.getPendingItem() is @item
|
||||
else if @item.isPending?
|
||||
@item.isPending()
|
||||
|
||||
terminatePendingState: ->
|
||||
if @pane.clearPendingItem?
|
||||
@pane.clearPendingItem() if @pane.getPendingItem() is @item
|
||||
else if @item.terminatePendingState?
|
||||
@item.terminatePendingState()
|
||||
|
||||
clearPending: ->
|
||||
@itemTitle.classList.remove('temp')
|
||||
@element.classList.remove('pending-tab')
|
||||
|
||||
updateIconVisibility: ->
|
||||
if atom.config.get 'tabs.showIcons'
|
||||
@itemTitle.classList.remove('hide-icon')
|
||||
else
|
||||
@itemTitle.classList.add('hide-icon')
|
||||
|
||||
updateModifiedStatus: ->
|
||||
if @item.isModified?()
|
||||
@element.classList.add('modified') unless @isModified
|
||||
@isModified = true
|
||||
else
|
||||
@element.classList.remove('modified') if @isModified
|
||||
@isModified = false
|
||||
|
||||
setupVcsStatus: ->
|
||||
return unless @path?
|
||||
@repoForPath(@path).then (repo) =>
|
||||
@subscribeToRepo(repo)
|
||||
@updateVcsStatus(repo)
|
||||
|
||||
# Subscribe to the project's repo for changes to the VCS status of the file.
|
||||
subscribeToRepo: (repo) ->
|
||||
return unless repo?
|
||||
|
||||
# Remove previous repo subscriptions.
|
||||
@repoSubscriptions?.dispose()
|
||||
@repoSubscriptions = new CompositeDisposable()
|
||||
|
||||
@repoSubscriptions.add repo.onDidChangeStatus (event) =>
|
||||
@updateVcsStatus(repo, event.pathStatus) if event.path is @path
|
||||
@repoSubscriptions.add repo.onDidChangeStatuses =>
|
||||
@updateVcsStatus(repo)
|
||||
|
||||
repoForPath: ->
|
||||
for dir in atom.project.getDirectories()
|
||||
return atom.project.repositoryForDirectory(dir) if dir.contains @path
|
||||
Promise.resolve(null)
|
||||
|
||||
# Update the VCS status property of this tab using the repo.
|
||||
updateVcsStatus: (repo, status) ->
|
||||
return unless repo?
|
||||
|
||||
newStatus = null
|
||||
if repo.isPathIgnored(@path)
|
||||
newStatus = 'ignored'
|
||||
else
|
||||
status = repo.getCachedPathStatus(@path) unless status?
|
||||
if repo.isStatusModified(status)
|
||||
newStatus = 'modified'
|
||||
else if repo.isStatusNew(status)
|
||||
newStatus = 'added'
|
||||
|
||||
if newStatus isnt @status
|
||||
@status = newStatus
|
||||
@updateVcsColoring()
|
||||
|
||||
updateVcsColoring: ->
|
||||
@itemTitle.classList.remove('status-ignored', 'status-modified', 'status-added')
|
||||
if @status and atom.config.get 'tabs.enableVcsColoring'
|
||||
@itemTitle.classList.add("status-#{@status}")
|
||||
|
||||
unsetVcsStatus: ->
|
||||
@repoSubscriptions?.dispose()
|
||||
delete @status
|
||||
@updateVcsColoring()
|
@ -1,401 +0,0 @@
|
||||
const path = require('path');
|
||||
const {Disposable, CompositeDisposable} = require('atom');
|
||||
const getIconServices = require('./get-icon-services');
|
||||
|
||||
const layout = require('./layout');
|
||||
|
||||
class TabView {
|
||||
constructor({item, pane, didClickCloseIcon, tabs, location}) {
|
||||
this.item = item;
|
||||
this.pane = pane;
|
||||
this.tabs = tabs;
|
||||
if (typeof this.item.getPath === 'function') {
|
||||
this.path = this.item.getPath();
|
||||
}
|
||||
|
||||
this.element = document.createElement('li');
|
||||
this.element.setAttribute('is', 'tabs-tab');
|
||||
if (['TextEditor', 'TestView'].indexOf(this.item.constructor.name) > -1) {
|
||||
this.element.classList.add('texteditor');
|
||||
}
|
||||
this.element.classList.add('tab', 'sortable');
|
||||
|
||||
this.itemTitle = document.createElement('div');
|
||||
this.itemTitle.classList.add('title');
|
||||
this.element.appendChild(this.itemTitle);
|
||||
|
||||
if ((location === 'center') || !(typeof this.item.isPermanentDockItem === 'function' ? this.item.isPermanentDockItem() : undefined)) {
|
||||
const closeIcon = document.createElement('div');
|
||||
closeIcon.classList.add('close-icon');
|
||||
closeIcon.onclick = didClickCloseIcon;
|
||||
this.element.appendChild(closeIcon);
|
||||
}
|
||||
|
||||
this.subscriptions = new CompositeDisposable();
|
||||
|
||||
this.handleEvents();
|
||||
this.updateDataAttributes();
|
||||
this.updateTitle();
|
||||
this.updateIcon();
|
||||
this.updateModifiedStatus();
|
||||
this.setupTooltip();
|
||||
|
||||
if (this.isItemPending()) {
|
||||
this.itemTitle.classList.add('temp');
|
||||
this.element.classList.add('pending-tab');
|
||||
}
|
||||
|
||||
this.element.ondrag = e => layout.drag(e);
|
||||
this.element.ondragend = e => layout.end(e);
|
||||
|
||||
this.element.pane = this.pane;
|
||||
this.element.item = this.item;
|
||||
this.element.itemTitle = this.itemTitle;
|
||||
this.element.path = this.path;
|
||||
}
|
||||
|
||||
handleEvents() {
|
||||
const titleChangedHandler = () => {
|
||||
return this.updateTitle();
|
||||
};
|
||||
|
||||
this.subscriptions.add(this.pane.onDidDestroy(() => this.destroy()));
|
||||
this.subscriptions.add(this.pane.onItemDidTerminatePendingState(item => {
|
||||
if (item === this.item) { return this.clearPending(); }
|
||||
})
|
||||
);
|
||||
|
||||
if (typeof this.item.onDidChangeTitle === 'function') {
|
||||
const onDidChangeTitleDisposable = this.item.onDidChangeTitle(titleChangedHandler);
|
||||
if (Disposable.isDisposable(onDidChangeTitleDisposable)) {
|
||||
this.subscriptions.add(onDidChangeTitleDisposable);
|
||||
} else {
|
||||
console.warn("::onDidChangeTitle does not return a valid Disposable!", this.item);
|
||||
}
|
||||
} else if (typeof this.item.on === 'function') {
|
||||
//TODO Remove once old events are no longer supported
|
||||
this.item.on('title-changed', titleChangedHandler);
|
||||
this.subscriptions.add({dispose: () => {
|
||||
return (typeof this.item.off === 'function' ? this.item.off('title-changed', titleChangedHandler) : undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const pathChangedHandler = path1 => {
|
||||
this.path = path1;
|
||||
this.updateDataAttributes();
|
||||
this.updateTitle();
|
||||
this.updateTooltip();
|
||||
return this.updateIcon();
|
||||
};
|
||||
|
||||
if (typeof this.item.onDidChangePath === 'function') {
|
||||
const onDidChangePathDisposable = this.item.onDidChangePath(pathChangedHandler);
|
||||
if (Disposable.isDisposable(onDidChangePathDisposable)) {
|
||||
this.subscriptions.add(onDidChangePathDisposable);
|
||||
} else {
|
||||
console.warn("::onDidChangePath does not return a valid Disposable!", this.item);
|
||||
}
|
||||
} else if (typeof this.item.on === 'function') {
|
||||
//TODO Remove once old events are no longer supported
|
||||
this.item.on('path-changed', pathChangedHandler);
|
||||
this.subscriptions.add({dispose: () => {
|
||||
return (typeof this.item.off === 'function' ? this.item.off('path-changed', pathChangedHandler) : undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const iconChangedHandler = () => {
|
||||
return this.updateIcon();
|
||||
};
|
||||
|
||||
this.subscriptions.add(getIconServices().onDidChange(() => this.updateIcon()));
|
||||
|
||||
if (typeof this.item.onDidChangeIcon === 'function') {
|
||||
const onDidChangeIconDisposable = typeof this.item.onDidChangeIcon === 'function' ? this.item.onDidChangeIcon(() => {
|
||||
return this.updateIcon();
|
||||
}) : undefined;
|
||||
if (Disposable.isDisposable(onDidChangeIconDisposable)) {
|
||||
this.subscriptions.add(onDidChangeIconDisposable);
|
||||
} else {
|
||||
console.warn("::onDidChangeIcon does not return a valid Disposable!", this.item);
|
||||
}
|
||||
} else if (typeof this.item.on === 'function') {
|
||||
//TODO Remove once old events are no longer supported
|
||||
this.item.on('icon-changed', iconChangedHandler);
|
||||
this.subscriptions.add({dispose: () => {
|
||||
return (typeof this.item.off === 'function' ? this.item.off('icon-changed', iconChangedHandler) : undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const modifiedHandler = () => {
|
||||
return this.updateModifiedStatus();
|
||||
};
|
||||
|
||||
if (typeof this.item.onDidChangeModified === 'function') {
|
||||
const onDidChangeModifiedDisposable = this.item.onDidChangeModified(modifiedHandler);
|
||||
if (Disposable.isDisposable(onDidChangeModifiedDisposable)) {
|
||||
this.subscriptions.add(onDidChangeModifiedDisposable);
|
||||
} else {
|
||||
console.warn("::onDidChangeModified does not return a valid Disposable!", this.item);
|
||||
}
|
||||
} else if (typeof this.item.on === 'function') {
|
||||
//TODO Remove once old events are no longer supported
|
||||
this.item.on('modified-status-changed', modifiedHandler);
|
||||
this.subscriptions.add({dispose: () => {
|
||||
return (typeof this.item.off === 'function' ? this.item.off('modified-status-changed', modifiedHandler) : undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof this.item.onDidSave === 'function') {
|
||||
const onDidSaveDisposable = this.item.onDidSave(event => {
|
||||
this.terminatePendingState();
|
||||
if (event.path !== this.path) {
|
||||
this.path = event.path;
|
||||
if (atom.config.get('tabs.enableVcsColoring')) { return this.setupVcsStatus(); }
|
||||
}
|
||||
});
|
||||
|
||||
if (Disposable.isDisposable(onDidSaveDisposable)) {
|
||||
this.subscriptions.add(onDidSaveDisposable);
|
||||
} else {
|
||||
console.warn("::onDidSave does not return a valid Disposable!", this.item);
|
||||
}
|
||||
}
|
||||
this.subscriptions.add(atom.config.observe('tabs.showIcons', () => {
|
||||
return this.updateIconVisibility();
|
||||
})
|
||||
);
|
||||
|
||||
return this.subscriptions.add(atom.config.observe('tabs.enableVcsColoring', isEnabled => {
|
||||
if (isEnabled && (this.path != null)) { return this.setupVcsStatus(); } else { return this.unsetVcsStatus(); }
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setupTooltip() {
|
||||
// Defer creating the tooltip until the tab is moused over
|
||||
const onMouseEnter = () => {
|
||||
this.mouseEnterSubscription.dispose();
|
||||
this.hasBeenMousedOver = true;
|
||||
this.updateTooltip();
|
||||
|
||||
// Trigger again so the tooltip shows
|
||||
return this.element.dispatchEvent(new CustomEvent('mouseenter', {bubbles: true}));
|
||||
};
|
||||
|
||||
this.mouseEnterSubscription = { dispose: () => {
|
||||
this.element.removeEventListener('mouseenter', onMouseEnter);
|
||||
return this.mouseEnterSubscription = null;
|
||||
}
|
||||
};
|
||||
|
||||
return this.element.addEventListener('mouseenter', onMouseEnter);
|
||||
}
|
||||
|
||||
updateTooltip() {
|
||||
if (!this.hasBeenMousedOver) { return; }
|
||||
|
||||
this.destroyTooltip();
|
||||
|
||||
if (this.path) {
|
||||
return this.tooltip = atom.tooltips.add(this.element, {
|
||||
title: this.path,
|
||||
html: false,
|
||||
delay: {
|
||||
show: 1000,
|
||||
hide: 100
|
||||
},
|
||||
placement: 'bottom'
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
destroyTooltip() {
|
||||
if (!this.hasBeenMousedOver) { return; }
|
||||
return (this.tooltip != null ? this.tooltip.dispose() : undefined);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.subscriptions != null) {
|
||||
this.subscriptions.dispose();
|
||||
}
|
||||
if (this.mouseEnterSubscription != null) {
|
||||
this.mouseEnterSubscription.dispose();
|
||||
}
|
||||
if (this.repoSubscriptions != null) {
|
||||
this.repoSubscriptions.dispose();
|
||||
}
|
||||
this.destroyTooltip();
|
||||
return this.element.remove();
|
||||
}
|
||||
|
||||
updateDataAttributes() {
|
||||
let itemClass;
|
||||
if (this.path) {
|
||||
this.itemTitle.dataset.name = path.basename(this.path);
|
||||
this.itemTitle.dataset.path = this.path;
|
||||
} else {
|
||||
delete this.itemTitle.dataset.name;
|
||||
delete this.itemTitle.dataset.path;
|
||||
}
|
||||
|
||||
if ((itemClass = this.item.constructor != null ? this.item.constructor.name : undefined)) {
|
||||
return this.element.dataset.type = itemClass;
|
||||
} else {
|
||||
return delete this.element.dataset.type;
|
||||
}
|
||||
}
|
||||
|
||||
updateTitle(param) {
|
||||
let title;
|
||||
if (param == null) { param = {}; }
|
||||
let {updateSiblings, useLongTitle} = param;
|
||||
if (this.updatingTitle) { return; }
|
||||
this.updatingTitle = true;
|
||||
|
||||
if (updateSiblings === false) {
|
||||
title = this.item.getTitle();
|
||||
if (useLongTitle) { let left;
|
||||
title = (left = (typeof this.item.getLongTitle === 'function' ? this.item.getLongTitle() : undefined)) != null ? left : title; }
|
||||
this.itemTitle.textContent = title;
|
||||
} else {
|
||||
title = this.item.getTitle();
|
||||
useLongTitle = false;
|
||||
for (let tab of this.tabs) {
|
||||
if (tab !== this) {
|
||||
if (tab.item.getTitle() === title) {
|
||||
tab.updateTitle({updateSiblings: false, useLongTitle: true});
|
||||
useLongTitle = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (useLongTitle) { let left1;
|
||||
title = (left1 = (typeof this.item.getLongTitle === 'function' ? this.item.getLongTitle() : undefined)) != null ? left1 : title; }
|
||||
|
||||
this.itemTitle.textContent = title;
|
||||
}
|
||||
|
||||
return this.updatingTitle = false;
|
||||
}
|
||||
|
||||
updateIcon() {
|
||||
return getIconServices().updateTabIcon(this);
|
||||
}
|
||||
|
||||
isItemPending() {
|
||||
if (this.pane.getPendingItem != null) {
|
||||
return this.pane.getPendingItem() === this.item;
|
||||
} else if (this.item.isPending != null) {
|
||||
return this.item.isPending();
|
||||
}
|
||||
}
|
||||
|
||||
terminatePendingState() {
|
||||
if (this.pane.clearPendingItem != null) {
|
||||
if (this.pane.getPendingItem() === this.item) { return this.pane.clearPendingItem(); }
|
||||
} else if (this.item.terminatePendingState != null) {
|
||||
return this.item.terminatePendingState();
|
||||
}
|
||||
}
|
||||
|
||||
clearPending() {
|
||||
this.itemTitle.classList.remove('temp');
|
||||
return this.element.classList.remove('pending-tab');
|
||||
}
|
||||
|
||||
updateIconVisibility() {
|
||||
if (atom.config.get('tabs.showIcons')) {
|
||||
return this.itemTitle.classList.remove('hide-icon');
|
||||
} else {
|
||||
return this.itemTitle.classList.add('hide-icon');
|
||||
}
|
||||
}
|
||||
|
||||
updateModifiedStatus() {
|
||||
if (typeof this.item.isModified === 'function' ? this.item.isModified() : undefined) {
|
||||
if (!this.isModified) { this.element.classList.add('modified'); }
|
||||
return this.isModified = true;
|
||||
} else {
|
||||
if (this.isModified) { this.element.classList.remove('modified'); }
|
||||
return this.isModified = false;
|
||||
}
|
||||
}
|
||||
|
||||
setupVcsStatus() {
|
||||
if (this.path == null) { return; }
|
||||
return this.repoForPath(this.path).then(repo => {
|
||||
this.subscribeToRepo(repo);
|
||||
return this.updateVcsStatus(repo);
|
||||
});
|
||||
}
|
||||
|
||||
// Subscribe to the project's repo for changes to the VCS status of the file.
|
||||
subscribeToRepo(repo) {
|
||||
if (repo == null) { return; }
|
||||
|
||||
// Remove previous repo subscriptions.
|
||||
if (this.repoSubscriptions != null) {
|
||||
this.repoSubscriptions.dispose();
|
||||
}
|
||||
this.repoSubscriptions = new CompositeDisposable();
|
||||
|
||||
this.repoSubscriptions.add(repo.onDidChangeStatus(event => {
|
||||
if (event.path === this.path) { return this.updateVcsStatus(repo, event.pathStatus); }
|
||||
})
|
||||
);
|
||||
return this.repoSubscriptions.add(repo.onDidChangeStatuses(() => {
|
||||
return this.updateVcsStatus(repo);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
repoForPath() {
|
||||
for (let dir of atom.project.getDirectories()) {
|
||||
if (dir.contains(this.path)) { return atom.project.repositoryForDirectory(dir); }
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// Update the VCS status property of this tab using the repo.
|
||||
updateVcsStatus(repo, status) {
|
||||
if (repo == null) { return; }
|
||||
|
||||
let newStatus = null;
|
||||
if (repo.isPathIgnored(this.path)) {
|
||||
newStatus = 'ignored';
|
||||
} else {
|
||||
if (status == null) { status = repo.getCachedPathStatus(this.path); }
|
||||
if (repo.isStatusModified(status)) {
|
||||
newStatus = 'modified';
|
||||
} else if (repo.isStatusNew(status)) {
|
||||
newStatus = 'added';
|
||||
}
|
||||
}
|
||||
|
||||
if (newStatus !== this.status) {
|
||||
this.status = newStatus;
|
||||
return this.updateVcsColoring();
|
||||
}
|
||||
}
|
||||
|
||||
updateVcsColoring() {
|
||||
this.itemTitle.classList.remove('status-ignored', 'status-modified', 'status-added');
|
||||
if (this.status && atom.config.get('tabs.enableVcsColoring')) {
|
||||
return this.itemTitle.classList.add(`status-${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
unsetVcsStatus() {
|
||||
if (this.repoSubscriptions != null) {
|
||||
this.repoSubscriptions.dispose();
|
||||
}
|
||||
delete this.status;
|
||||
return this.updateVcsColoring();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TabView;
|
Loading…
Reference in New Issue
Block a user