Bundle status-bar

This commit is contained in:
confused_techie 2023-01-06 09:16:43 -08:00
parent 86c5ca6b35
commit 1ee29a5a21
58 changed files with 2075 additions and 0 deletions

1
packages/status-bar/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
spec/fixtures/*.js text eol=lf

View File

@ -0,0 +1,61 @@
# Status Bar package
Display information about the current editor such as cursor position, file path, grammar, current branch, ahead/behind commit counts, and line diff count.
![](https://f.cloud.github.com/assets/671378/2241819/f8418cb8-9ce5-11e3-87e5-109e965986d0.png)
## Configuration
The status bar package accepts the following configuration values:
* `status-bar.cursorPositionFormat` — A string that describes the format to use for the cursor position status bar tile. It defaults to `%L:%C`. In the format string, `%L` represents the 1-based line number and `%C` represents the 1-based column number.
* `status-bar.selectionCountFormat` — A string that describes the format to use for the selection count status bar tile. It defaults to `(%L, %C)`. In the format string, `%L` represents the 1-based line count and `%C` represents the 1-based character count.
## API
This package provides a service that you can use in other Pulsar packages. To use it, include `status-bar` in the `consumedServices` section of your `package.json`:
```json
{
"name": "my-package",
"consumedServices": {
"status-bar": {
"versions": {
"^1.0.0": "consumeStatusBar"
}
}
}
}
```
Then, in your package's main module, call methods on the service:
```coffee
module.exports =
activate: -> # ...
consumeStatusBar: (statusBar) ->
@statusBarTile = statusBar.addLeftTile(item: myElement, priority: 100)
deactivate: ->
# ...
@statusBarTile?.destroy()
@statusBarTile = null
```
The `status-bar` API has four methods:
* `addLeftTile({ item, priority })` - Add a tile to the left side of the status bar. Lower priority tiles are placed further to the left.
* `addRightTile({ item, priority })` - Add a tile to the right side of the status bar. Lower priority tiles are placed further to the right.
The `item` parameter to these methods can be a DOM element, a [jQuery object](http://jquery.com), or a model object for which a view provider has been registered in the [the view registry](https://atom.io/docs/api/latest/ViewRegistry).
* `getLeftTiles()` - Retrieve all of the tiles on the left side of the status bar.
* `getRightTiles()` - Retrieve all of the tiles on the right side of the status bar
All of these methods return `Tile` objects, which have the following methods:
* `getPriority()` - Retrieve the priority that was assigned to the `Tile` when it was created.
* `getItem()` - Retrieve the `Tile`'s item.
* `destroy()` - Remove the `Tile` from the status bar.

View File

@ -0,0 +1,63 @@
{Disposable} = require 'atom'
module.exports =
class CursorPositionView
constructor: ->
@viewUpdatePending = false
@element = document.createElement('status-bar-cursor')
@element.classList.add('cursor-position', 'inline-block')
@goToLineLink = document.createElement('a')
@goToLineLink.classList.add('inline-block')
@element.appendChild(@goToLineLink)
@formatString = atom.config.get('status-bar.cursorPositionFormat') ? '%L:%C'
@activeItemSubscription = atom.workspace.onDidChangeActiveTextEditor (activeEditor) => @subscribeToActiveTextEditor()
@subscribeToConfig()
@subscribeToActiveTextEditor()
@tooltip = atom.tooltips.add(@element, title: => "Line #{@row}, Column #{@column}")
@handleClick()
destroy: ->
@activeItemSubscription.dispose()
@cursorSubscription?.dispose()
@tooltip.dispose()
@configSubscription?.dispose()
@clickSubscription.dispose()
@updateSubscription?.dispose()
subscribeToActiveTextEditor: ->
@cursorSubscription?.dispose()
selectionsMarkerLayer = atom.workspace.getActiveTextEditor()?.selectionsMarkerLayer
@cursorSubscription = selectionsMarkerLayer?.onDidUpdate(@scheduleUpdate.bind(this))
@scheduleUpdate()
subscribeToConfig: ->
@configSubscription?.dispose()
@configSubscription = atom.config.observe 'status-bar.cursorPositionFormat', (value) =>
@formatString = value ? '%L:%C'
@scheduleUpdate()
handleClick: ->
clickHandler = -> atom.commands.dispatch(atom.views.getView(atom.workspace.getActiveTextEditor()), 'go-to-line:toggle')
@element.addEventListener('click', clickHandler)
@clickSubscription = new Disposable => @element.removeEventListener('click', clickHandler)
scheduleUpdate: ->
return if @viewUpdatePending
@viewUpdatePending = true
@updateSubscription = atom.views.updateDocument =>
@viewUpdatePending = false
if position = atom.workspace.getActiveTextEditor()?.getCursorBufferPosition()
@row = position.row + 1
@column = position.column + 1
@goToLineLink.textContent = @formatString.replace('%L', @row).replace('%C', @column)
@element.classList.remove('hide')
else
@goToLineLink.textContent = ''
@element.classList.add('hide')

View File

@ -0,0 +1,118 @@
{Disposable} = require 'atom'
url = require 'url'
fs = require 'fs-plus'
module.exports =
class FileInfoView
constructor: ->
@element = document.createElement('status-bar-file')
@element.classList.add('file-info', 'inline-block')
@currentPath = document.createElement('a')
@currentPath.classList.add('current-path')
@element.appendChild(@currentPath)
@element.currentPath = @currentPath
@element.getActiveItem = @getActiveItem.bind(this)
@activeItemSubscription = atom.workspace.getCenter().onDidChangeActivePaneItem =>
@subscribeToActiveItem()
@subscribeToActiveItem()
@registerTooltip()
clickHandler = (event) =>
isShiftClick = event.shiftKey
@showCopiedTooltip(isShiftClick)
text = @getActiveItemCopyText(isShiftClick)
atom.clipboard.write(text)
setTimeout =>
@clearCopiedTooltip()
, 2000
@element.addEventListener('click', clickHandler)
@clickSubscription = new Disposable => @element.removeEventListener('click', clickHandler)
registerTooltip: ->
@tooltip = atom.tooltips.add(@element, title: ->
"Click to copy absolute file path (Shift + Click to copy relative path)")
clearCopiedTooltip: ->
@copiedTooltip?.dispose()
@registerTooltip()
showCopiedTooltip: (copyRelativePath) ->
@tooltip?.dispose()
@copiedTooltip?.dispose()
text = @getActiveItemCopyText(copyRelativePath)
@copiedTooltip = atom.tooltips.add @element,
title: "Copied: #{text}"
trigger: 'manual'
delay:
show: 0
getActiveItemCopyText: (copyRelativePath) ->
activeItem = @getActiveItem()
path = activeItem?.getPath?()
return activeItem?.getTitle?() or '' if not path?
# Make sure we try to relativize before parsing URLs.
if copyRelativePath
relativized = atom.project.relativize(path)
if relativized isnt path
return relativized
# An item path could be a url, we only want to copy the `path` part
if path?.indexOf('://') > 0
path = url.parse(path).path
path
subscribeToActiveItem: ->
@modifiedSubscription?.dispose()
@titleSubscription?.dispose()
if activeItem = @getActiveItem()
@updateCallback ?= => @update()
if typeof activeItem.onDidChangeTitle is 'function'
@titleSubscription = activeItem.onDidChangeTitle(@updateCallback)
else if typeof activeItem.on is 'function'
#TODO Remove once title-changed event support is removed
activeItem.on('title-changed', @updateCallback)
@titleSubscription = dispose: =>
activeItem.off?('title-changed', @updateCallback)
@modifiedSubscription = activeItem.onDidChangeModified?(@updateCallback)
@update()
destroy: ->
@activeItemSubscription.dispose()
@titleSubscription?.dispose()
@modifiedSubscription?.dispose()
@clickSubscription?.dispose()
@copiedTooltip?.dispose()
@tooltip?.dispose()
getActiveItem: ->
atom.workspace.getCenter().getActivePaneItem()
update: ->
@updatePathText()
@updateBufferHasModifiedText(@getActiveItem()?.isModified?())
updateBufferHasModifiedText: (isModified) ->
if isModified
@element.classList.add('buffer-modified')
@isModified = true
else
@element.classList.remove('buffer-modified')
@isModified = false
updatePathText: ->
if path = @getActiveItem()?.getPath?()
relativized = atom.project.relativize(path)
@currentPath.textContent = if relativized? then fs.tildify(relativized) else path
else if title = @getActiveItem()?.getTitle?()
@currentPath.textContent = title
else
@currentPath.textContent = ''

View File

@ -0,0 +1,222 @@
_ = require "underscore-plus"
{CompositeDisposable, GitRepositoryAsync} = require "atom"
module.exports =
class GitView
constructor: ->
@element = document.createElement('status-bar-git')
@element.classList.add('git-view')
@createBranchArea()
@createCommitsArea()
@createStatusArea()
@activeItemSubscription = atom.workspace.getCenter().onDidChangeActivePaneItem =>
@subscribeToActiveItem()
@projectPathSubscription = atom.project.onDidChangePaths =>
@subscribeToRepositories()
@subscribeToRepositories()
@subscribeToActiveItem()
createBranchArea: ->
@branchArea = document.createElement('div')
@branchArea.classList.add('git-branch', 'inline-block')
@element.appendChild(@branchArea)
@element.branchArea = @branchArea
branchIcon = document.createElement('span')
branchIcon.classList.add('icon', 'icon-git-branch')
@branchArea.appendChild(branchIcon)
@branchLabel = document.createElement('span')
@branchLabel.classList.add('branch-label')
@branchArea.appendChild(@branchLabel)
@element.branchLabel = @branchLabel
createCommitsArea: ->
@commitsArea = document.createElement('div')
@commitsArea.classList.add('git-commits', 'inline-block')
@element.appendChild(@commitsArea)
@commitsAhead = document.createElement('span')
@commitsAhead.classList.add('icon', 'icon-arrow-up', 'commits-ahead-label')
@commitsArea.appendChild(@commitsAhead)
@commitsBehind = document.createElement('span')
@commitsBehind.classList.add('icon', 'icon-arrow-down', 'commits-behind-label')
@commitsArea.appendChild(@commitsBehind)
createStatusArea: ->
@gitStatus = document.createElement('div')
@gitStatus.classList.add('git-status', 'inline-block')
@element.appendChild(@gitStatus)
@gitStatusIcon = document.createElement('span')
@gitStatusIcon.classList.add('icon')
@gitStatus.appendChild(@gitStatusIcon)
@element.gitStatusIcon = @gitStatusIcon
subscribeToActiveItem: ->
activeItem = @getActiveItem()
@savedSubscription?.dispose()
@savedSubscription = activeItem?.onDidSave? => @update()
@update()
subscribeToRepositories: ->
@repositorySubscriptions?.dispose()
@repositorySubscriptions = new CompositeDisposable
for repo in atom.project.getRepositories() when repo?
@repositorySubscriptions.add repo.onDidChangeStatus ({path, status}) =>
@update() if path is @getActiveItemPath()
@repositorySubscriptions.add repo.onDidChangeStatuses =>
@update()
destroy: ->
@activeItemSubscription?.dispose()
@projectPathSubscription?.dispose()
@savedSubscription?.dispose()
@repositorySubscriptions?.dispose()
@branchTooltipDisposable?.dispose()
@commitsAheadTooltipDisposable?.dispose()
@commitsBehindTooltipDisposable?.dispose()
@statusTooltipDisposable?.dispose()
getActiveItemPath: ->
@getActiveItem()?.getPath?()
getRepositoryForActiveItem: ->
[rootDir] = atom.project.relativizePath(@getActiveItemPath())
rootDirIndex = atom.project.getPaths().indexOf(rootDir)
if rootDirIndex >= 0
atom.project.getRepositories()[rootDirIndex]
else
for repo in atom.project.getRepositories() when repo
return repo
getActiveItem: ->
atom.workspace.getCenter().getActivePaneItem()
update: ->
repo = @getRepositoryForActiveItem()
@updateBranchText(repo)
@updateAheadBehindCount(repo)
@updateStatusText(repo)
updateBranchText: (repo) ->
if @showGitInformation(repo)
head = repo.getShortHead(@getActiveItemPath())
@branchLabel.textContent = head
@branchArea.style.display = '' if head
@branchTooltipDisposable?.dispose()
@branchTooltipDisposable = atom.tooltips.add @branchArea, title: "On branch #{head}"
else
@branchArea.style.display = 'none'
showGitInformation: (repo) ->
return false unless repo?
if itemPath = @getActiveItemPath()
atom.project.contains(itemPath)
else
not @getActiveItem()?
updateAheadBehindCount: (repo) ->
unless @showGitInformation(repo)
@commitsArea.style.display = 'none'
return
itemPath = @getActiveItemPath()
{ahead, behind} = repo.getCachedUpstreamAheadBehindCount(itemPath)
if ahead > 0
@commitsAhead.textContent = ahead
@commitsAhead.style.display = ''
@commitsAheadTooltipDisposable?.dispose()
@commitsAheadTooltipDisposable = atom.tooltips.add @commitsAhead, title: "#{_.pluralize(ahead, 'commit')} ahead of upstream"
else
@commitsAhead.style.display = 'none'
if behind > 0
@commitsBehind.textContent = behind
@commitsBehind.style.display = ''
@commitsBehindTooltipDisposable?.dispose()
@commitsBehindTooltipDisposable = atom.tooltips.add @commitsBehind, title: "#{_.pluralize(behind, 'commit')} behind upstream"
else
@commitsBehind.style.display = 'none'
if ahead > 0 or behind > 0
@commitsArea.style.display = ''
else
@commitsArea.style.display = 'none'
clearStatus: ->
@gitStatusIcon.classList.remove('icon-diff-modified', 'status-modified', 'icon-diff-added', 'status-added', 'icon-diff-ignored', 'status-ignored')
updateAsNewFile: ->
@clearStatus()
@gitStatusIcon.classList.add('icon-diff-added', 'status-added')
if textEditor = atom.workspace.getActiveTextEditor()
@gitStatusIcon.textContent = "+#{textEditor.getLineCount()}"
@updateTooltipText("#{_.pluralize(textEditor.getLineCount(), 'line')} in this new file not yet committed")
else
@gitStatusIcon.textContent = ''
@updateTooltipText()
@gitStatus.style.display = ''
updateAsModifiedFile: (repo, path) ->
stats = repo.getDiffStats(path)
@clearStatus()
@gitStatusIcon.classList.add('icon-diff-modified', 'status-modified')
if stats.added and stats.deleted
@gitStatusIcon.textContent = "+#{stats.added}, -#{stats.deleted}"
@updateTooltipText("#{_.pluralize(stats.added, 'line')} added and #{_.pluralize(stats.deleted, 'line')} deleted in this file not yet committed")
else if stats.added
@gitStatusIcon.textContent = "+#{stats.added}"
@updateTooltipText("#{_.pluralize(stats.added, 'line')} added to this file not yet committed")
else if stats.deleted
@gitStatusIcon.textContent = "-#{stats.deleted}"
@updateTooltipText("#{_.pluralize(stats.deleted, 'line')} deleted from this file not yet committed")
else
@gitStatusIcon.textContent = ''
@updateTooltipText()
@gitStatus.style.display = ''
updateAsIgnoredFile: ->
@clearStatus()
@gitStatusIcon.classList.add('icon-diff-ignored', 'status-ignored')
@gitStatusIcon.textContent = ''
@gitStatus.style.display = ''
@updateTooltipText("File is ignored by git")
updateTooltipText: (text) ->
@statusTooltipDisposable?.dispose()
if text
@statusTooltipDisposable = atom.tooltips.add @gitStatusIcon, title: text
updateStatusText: (repo) ->
hideStatus = =>
@clearStatus()
@gitStatus.style.display = 'none'
itemPath = @getActiveItemPath()
if @showGitInformation(repo) and itemPath?
status = repo.getCachedPathStatus(itemPath) ? 0
if repo.isStatusNew(status)
return @updateAsNewFile()
if repo.isStatusModified(status)
return @updateAsModifiedFile(repo, itemPath)
if repo.isPathIgnored(itemPath)
@updateAsIgnoredFile()
else
hideStatus()
else
hideStatus()

View File

@ -0,0 +1,14 @@
module.exports =
class LaunchModeView
constructor: ({safeMode, devMode}={}) ->
@element = document.createElement('status-bar-launch-mode')
@element.classList.add('inline-block', 'icon', 'icon-color-mode')
if devMode
@element.classList.add('text-error')
@tooltipDisposable = atom.tooltips.add(@element, title: 'This window is in dev mode')
else if safeMode
@element.classList.add('text-success')
@tooltipDisposable = atom.tooltips.add(@element, title: 'This window is in safe mode')
detachedCallback: ->
@tooltipDisposable?.dispose()

View File

@ -0,0 +1,121 @@
{CompositeDisposable, Emitter} = require 'atom'
Grim = require 'grim'
StatusBarView = require './status-bar-view'
FileInfoView = require './file-info-view'
CursorPositionView = require './cursor-position-view'
SelectionCountView = require './selection-count-view'
GitView = require './git-view'
LaunchModeView = require './launch-mode-view'
module.exports =
activate: ->
@emitters = new Emitter()
@subscriptions = new CompositeDisposable()
@statusBar = new StatusBarView()
@attachStatusBar()
@subscriptions.add atom.config.onDidChange 'status-bar.fullWidth', =>
@attachStatusBar()
@updateStatusBarVisibility()
@statusBarVisibilitySubscription =
atom.config.observe 'status-bar.isVisible', =>
@updateStatusBarVisibility()
atom.commands.add 'atom-workspace', 'status-bar:toggle', =>
if @statusBarPanel.isVisible()
atom.config.set 'status-bar.isVisible', false
else
atom.config.set 'status-bar.isVisible', true
{safeMode, devMode} = atom.getLoadSettings()
if safeMode or devMode
launchModeView = new LaunchModeView({safeMode, devMode})
@statusBar.addLeftTile(item: launchModeView.element, priority: -1)
@fileInfo = new FileInfoView()
@statusBar.addLeftTile(item: @fileInfo.element, priority: 0)
@cursorPosition = new CursorPositionView()
@statusBar.addLeftTile(item: @cursorPosition.element, priority: 1)
@selectionCount = new SelectionCountView()
@statusBar.addLeftTile(item: @selectionCount.element, priority: 2)
@gitInfo = new GitView()
@gitInfoTile = @statusBar.addRightTile(item: @gitInfo.element, priority: 0)
deactivate: ->
@statusBarVisibilitySubscription?.dispose()
@statusBarVisibilitySubscription = null
@gitInfo?.destroy()
@gitInfo = null
@fileInfo?.destroy()
@fileInfo = null
@cursorPosition?.destroy()
@cursorPosition = null
@selectionCount?.destroy()
@selectionCount = null
@statusBarPanel?.destroy()
@statusBarPanel = null
@statusBar?.destroy()
@statusBar = null
@subscriptions?.dispose()
@subscriptions = null
@emitters?.dispose()
@emitters = null
delete atom.__workspaceView.statusBar if atom.__workspaceView?
updateStatusBarVisibility: ->
if atom.config.get 'status-bar.isVisible'
@statusBarPanel.show()
else
@statusBarPanel.hide()
provideStatusBar: ->
addLeftTile: @statusBar.addLeftTile.bind(@statusBar)
addRightTile: @statusBar.addRightTile.bind(@statusBar)
getLeftTiles: @statusBar.getLeftTiles.bind(@statusBar)
getRightTiles: @statusBar.getRightTiles.bind(@statusBar)
disableGitInfoTile: @gitInfoTile.destroy.bind(@gitInfoTile)
attachStatusBar: ->
@statusBarPanel.destroy() if @statusBarPanel?
panelArgs = {item: @statusBar, priority: 0}
if atom.config.get('status-bar.fullWidth')
@statusBarPanel = atom.workspace.addFooterPanel panelArgs
else
@statusBarPanel = atom.workspace.addBottomPanel panelArgs
# Deprecated
#
# Wrap deprecation calls on the methods returned rather than
# Services API method which would be registered and trigger
# a deprecation call
legacyProvideStatusBar: ->
statusbar = @provideStatusBar()
addLeftTile: (args...) ->
Grim.deprecate("Use version ^1.0.0 of the status-bar Service API.")
statusbar.addLeftTile(args...)
addRightTile: (args...) ->
Grim.deprecate("Use version ^1.0.0 of the status-bar Service API.")
statusbar.addRightTile(args...)
getLeftTiles: ->
Grim.deprecate("Use version ^1.0.0 of the status-bar Service API.")
statusbar.getLeftTiles()
getRightTiles: ->
Grim.deprecate("Use version ^1.0.0 of the status-bar Service API.")
statusbar.getRightTiles()

View File

@ -0,0 +1,58 @@
_ = require 'underscore-plus'
module.exports =
class SelectionCountView
constructor: ->
@element = document.createElement('status-bar-selection')
@element.classList.add('selection-count', 'inline-block')
@tooltipElement = document.createElement('div')
@tooltipDisposable = atom.tooltips.add @element, item: @tooltipElement
@formatString = atom.config.get('status-bar.selectionCountFormat') ? '(%L, %C)'
@activeItemSubscription = atom.workspace.onDidChangeActiveTextEditor => @subscribeToActiveTextEditor()
@subscribeToConfig()
@subscribeToActiveTextEditor()
destroy: ->
@activeItemSubscription.dispose()
@selectionSubscription?.dispose()
@configSubscription?.dispose()
@tooltipDisposable.dispose()
subscribeToConfig: ->
@configSubscription?.dispose()
@configSubscription = atom.config.observe 'status-bar.selectionCountFormat', (value) =>
@formatString = value ? '(%L, %C)'
@scheduleUpdateCount()
subscribeToActiveTextEditor: ->
@selectionSubscription?.dispose()
activeEditor = @getActiveTextEditor()
selectionsMarkerLayer = activeEditor?.selectionsMarkerLayer
@selectionSubscription = selectionsMarkerLayer?.onDidUpdate(@scheduleUpdateCount.bind(this))
@scheduleUpdateCount()
getActiveTextEditor: ->
atom.workspace.getActiveTextEditor()
scheduleUpdateCount: ->
unless @scheduledUpdate
@scheduledUpdate = true
atom.views.updateDocument =>
@updateCount()
@scheduledUpdate = false
updateCount: ->
count = @getActiveTextEditor()?.getSelectedText().length
range = @getActiveTextEditor()?.getSelectedBufferRange()
lineCount = range?.getRowCount()
lineCount -= 1 if range?.end.column is 0
if count > 0
@element.textContent = @formatString.replace('%L', lineCount).replace('%C', count)
@tooltipElement.textContent = "#{_.pluralize(lineCount, 'line')}, #{_.pluralize(count, 'character')} selected"
else
@element.textContent = ''
@tooltipElement.textContent = ''

View File

@ -0,0 +1,107 @@
{Disposable} = require 'atom'
Tile = require './tile'
module.exports =
class StatusBarView
constructor: ->
@element = document.createElement('status-bar')
@element.classList.add('status-bar')
flexboxHackElement = document.createElement('div')
flexboxHackElement.classList.add('flexbox-repaint-hack')
@element.appendChild(flexboxHackElement)
@leftPanel = document.createElement('div')
@leftPanel.classList.add('status-bar-left')
flexboxHackElement.appendChild(@leftPanel)
@element.leftPanel = @leftPanel
@rightPanel = document.createElement('div')
@rightPanel.classList.add('status-bar-right')
flexboxHackElement.appendChild(@rightPanel)
@element.rightPanel = @rightPanel
@leftTiles = []
@rightTiles = []
@element.getLeftTiles = @getLeftTiles.bind(this)
@element.getRightTiles = @getRightTiles.bind(this)
@element.addLeftTile = @addLeftTile.bind(this)
@element.addRightTile = @addRightTile.bind(this)
@bufferSubscriptions = []
@activeItemSubscription = atom.workspace.getCenter().onDidChangeActivePaneItem =>
@unsubscribeAllFromBuffer()
@storeActiveBuffer()
@subscribeAllToBuffer()
@element.dispatchEvent(new CustomEvent('active-buffer-changed', bubbles: true))
@storeActiveBuffer()
destroy: ->
@activeItemSubscription.dispose()
@unsubscribeAllFromBuffer()
@element.remove()
addLeftTile: (options) ->
newItem = options.item
newPriority = options?.priority ? @leftTiles[@leftTiles.length - 1].priority + 1
nextItem = null
for {priority, item}, index in @leftTiles
if priority > newPriority
nextItem = item
break
newTile = new Tile(newItem, newPriority, @leftTiles)
@leftTiles.splice(index, 0, newTile)
newElement = atom.views.getView(newItem)
nextElement = atom.views.getView(nextItem)
@leftPanel.insertBefore(newElement, nextElement)
newTile
addRightTile: (options) ->
newItem = options.item
newPriority = options?.priority ? @rightTiles[0].priority + 1
nextItem = null
for {priority, item}, index in @rightTiles
if priority < newPriority
nextItem = item
break
newTile = new Tile(newItem, newPriority, @rightTiles)
@rightTiles.splice(index, 0, newTile)
newElement = atom.views.getView(newItem)
nextElement = atom.views.getView(nextItem)
@rightPanel.insertBefore(newElement, nextElement)
newTile
getLeftTiles: ->
@leftTiles
getRightTiles: ->
@rightTiles
getActiveBuffer: ->
@buffer
getActiveItem: ->
atom.workspace.getCenter().getActivePaneItem()
storeActiveBuffer: ->
@buffer = @getActiveItem()?.getBuffer?()
subscribeToBuffer: (event, callback) ->
@bufferSubscriptions.push([event, callback])
@buffer.on(event, callback) if @buffer
subscribeAllToBuffer: ->
return unless @buffer
for [event, callback] in @bufferSubscriptions
@buffer.on(event, callback)
unsubscribeAllFromBuffer: ->
return unless @buffer
for [event, callback] in @bufferSubscriptions
@buffer.off(event, callback)

View File

@ -0,0 +1,13 @@
module.exports =
class Tile
constructor: (@item, @priority, @collection) ->
getItem: ->
@item
getPriority: ->
@priority
destroy: ->
@collection.splice(@collection.indexOf(this), 1)
atom.views.getView(@item).remove()

177
packages/status-bar/package-lock.json generated Normal file
View File

@ -0,0 +1,177 @@
{
"name": "status-bar",
"version": "1.8.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "status-bar",
"version": "1.8.17",
"license": "MIT",
"dependencies": {
"fs-plus": "^3.0.1",
"grim": "^2.0.1",
"underscore-plus": "^1.0.0"
},
"engines": {
"atom": "*"
}
},
"node_modules/async": {
"version": "1.5.2",
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
},
"node_modules/balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/event-kit": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.3.tgz",
"integrity": "sha512-b7Qi1JNzY4BfAYfnIRanLk0DOD1gdkWHT4GISIn8Q2tAf3LpU8SP2CMwWaq40imYoKWbtN4ZhbSRxvsnikooZQ=="
},
"node_modules/fs-plus": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.0.2.tgz",
"integrity": "sha1-a19Sp3EolMTd6f2PgfqMYN8EHz0=",
"dependencies": {
"async": "^1.5.2",
"mkdirp": "^0.5.1",
"rimraf": "^2.5.2",
"underscore-plus": "1.x"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"node_modules/glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
}
},
"node_modules/grim": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/grim/-/grim-2.0.2.tgz",
"integrity": "sha512-Qj7hTJRfd87E/gUgfvM0YIH/g2UA2SV6niv6BYXk1o6w4mhgv+QyYM1EjOJQljvzgEj4SqSsRWldXIeKHz3e3Q==",
"dependencies": {
"event-kit": "^2.0.0"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "0.0.8",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"node_modules/mkdirp": {
"version": "0.5.1",
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
"dependencies": {
"minimist": "0.0.8"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"dependencies": {
"glob": "^7.0.5"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/underscore": {
"version": "1.8.3",
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
"node_modules/underscore-plus": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.8.tgz",
"integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==",
"dependencies": {
"underscore": "~1.8.3"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View File

@ -0,0 +1,51 @@
{
"name": "status-bar",
"version": "1.8.17",
"main": "./lib/main",
"description": "Display information about the current editor such as cursor position, file path, grammar, current branch, ahead/behind commits counts, and line diff count.",
"repository": "https://github.com/pulsar-edit/pulsar",
"license": "MIT",
"engines": {
"atom": "*"
},
"dependencies": {
"fs-plus": "^3.0.1",
"grim": "^2.0.1",
"underscore-plus": "^1.0.0"
},
"providedServices": {
"status-bar": {
"description": "A container for indicators at the bottom of the workspace",
"versions": {
"1.1.0": "provideStatusBar",
"0.58.0": "legacyProvideStatusBar"
}
}
},
"configSchema": {
"isVisible": {
"type": "boolean",
"default": true,
"description": "Show status bar at the bottom of the workspace"
},
"fullWidth": {
"order": 1,
"type": "boolean",
"default": true,
"title": "Full-width",
"description": "Fit the status-bar to the window's full-width"
},
"cursorPositionFormat": {
"order": 2,
"type": "string",
"default": "%L:%C",
"description": "Format for the cursor position status bar element, where %L is the line number and %C is the column number"
},
"selectionCountFormat": {
"order": 2,
"type": "string",
"default": "(%L, %C)",
"description": "Format for the selection count status bar element, where %L is the line count and %C is the character count"
}
}
}

View File

@ -0,0 +1,659 @@
fs = require 'fs-plus'
path = require 'path'
os = require 'os'
process = require 'process'
describe "Built-in Status Bar Tiles", ->
[statusBar, workspaceElement, dummyView] = []
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
dummyView = document.createElement("div")
statusBar = null
waitsForPromise ->
atom.packages.activatePackage('status-bar')
runs ->
statusBar = workspaceElement.querySelector("status-bar")
describe "the file info, cursor and selection tiles", ->
[editor, buffer, fileInfo, cursorPosition, selectionCount] = []
beforeEach ->
waitsForPromise ->
atom.workspace.open('sample.js')
runs ->
[launchMode, fileInfo, cursorPosition, selectionCount] =
statusBar.getLeftTiles().map (tile) -> tile.getItem()
editor = atom.workspace.getActiveTextEditor()
buffer = editor.getBuffer()
describe "when associated with an unsaved buffer", ->
it "displays 'untitled' instead of the buffer's path, but still displays the buffer position", ->
waitsForPromise ->
atom.workspace.open()
runs ->
atom.views.performDocumentUpdate()
expect(fileInfo.currentPath.textContent).toBe 'untitled'
expect(cursorPosition.textContent).toBe '1:1'
expect(selectionCount).toBeHidden()
describe "when the associated editor's path changes", ->
it "updates the path in the status bar", ->
waitsForPromise ->
atom.workspace.open('sample.txt')
runs ->
expect(fileInfo.currentPath.textContent).toBe 'sample.txt'
describe "when associated with remote file path", ->
beforeEach ->
jasmine.attachToDOM(workspaceElement)
dummyView.getPath = -> 'remote://server:123/folder/remote_file.txt'
atom.workspace.getActivePane().activateItem(dummyView)
it "updates the path in the status bar", ->
# The remote path isn't relativized in the test because no remote directory provider is registered.
expect(fileInfo.currentPath.textContent).toBe 'remote://server:123/folder/remote_file.txt'
expect(fileInfo.currentPath).toBeVisible()
it "when the path is clicked", ->
fileInfo.currentPath.click()
expect(atom.clipboard.read()).toBe '/folder/remote_file.txt'
it "calls relativize with the remote URL on shift-click", ->
spy = spyOn(atom.project, 'relativize').andReturn 'remote_file.txt'
event = new MouseEvent('click', shiftKey: true)
fileInfo.dispatchEvent(event)
expect(atom.clipboard.read()).toBe 'remote_file.txt'
expect(spy).toHaveBeenCalledWith 'remote://server:123/folder/remote_file.txt'
describe "when file info tile is clicked", ->
it "copies the absolute path into the clipboard if available", ->
waitsForPromise ->
atom.workspace.open('sample.txt')
runs ->
fileInfo.click()
expect(atom.clipboard.read()).toBe fileInfo.getActiveItem().getPath()
describe "when the file info tile is shift-clicked", ->
it "copies the relative path into the clipboard if available", ->
waitsForPromise ->
atom.workspace.open('sample.txt')
runs ->
event = new MouseEvent('click', shiftKey: true)
fileInfo.dispatchEvent(event)
expect(atom.clipboard.read()).toBe 'sample.txt'
describe "when path of an unsaved buffer is clicked", ->
it "copies the 'untitled' into clipboard", ->
waitsForPromise ->
atom.workspace.open()
runs ->
fileInfo.currentPath.click()
expect(atom.clipboard.read()).toBe 'untitled'
describe "when buffer's path is not clicked", ->
it "doesn't display a path tooltip", ->
jasmine.attachToDOM(workspaceElement)
waitsForPromise ->
atom.workspace.open()
runs ->
expect(document.querySelector('.tooltip')).not.toExist()
describe "when buffer's path is clicked", ->
it "displays path tooltip and the tooltip disappears after ~2 seconds", ->
jasmine.attachToDOM(workspaceElement)
waitsForPromise ->
atom.workspace.open()
runs ->
fileInfo.currentPath.click()
expect(document.querySelector('.tooltip')).toBeVisible()
# extra leeway so test won't fail because tooltip disappeared few milliseconds too late
advanceClock(2100)
expect(document.querySelector('.tooltip')).not.toExist()
describe "when saved buffer's path is clicked", ->
it "displays a tooltip containing text 'Copied:' and an absolute native path", ->
jasmine.attachToDOM(workspaceElement)
waitsForPromise ->
atom.workspace.open('sample.txt')
runs ->
fileInfo.currentPath.click()
expect(document.querySelector('.tooltip')).toHaveText "Copied: #{fileInfo.getActiveItem().getPath()}"
it "displays a tooltip containing text 'Copied:' for an absolute Unix path", ->
jasmine.attachToDOM(workspaceElement)
dummyView.getPath = -> '/user/path/for/my/file.txt'
atom.workspace.getActivePane().activateItem(dummyView)
runs ->
fileInfo.currentPath.click()
expect(document.querySelector('.tooltip')).toHaveText "Copied: #{dummyView.getPath()}"
it "displays a tooltip containing text 'Copied:' for an absolute Windows path", ->
jasmine.attachToDOM(workspaceElement)
dummyView.getPath = -> 'c:\\user\\path\\for\\my\\file.txt'
atom.workspace.getActivePane().activateItem(dummyView)
runs ->
fileInfo.currentPath.click()
expect(document.querySelector('.tooltip')).toHaveText "Copied: #{dummyView.getPath()}"
describe "when unsaved buffer's path is clicked", ->
it "displays a tooltip containing text 'Copied: untitled", ->
jasmine.attachToDOM(workspaceElement)
waitsForPromise ->
atom.workspace.open()
runs ->
fileInfo.currentPath.click()
expect(document.querySelector('.tooltip')).toHaveText "Copied: untitled"
describe "when the associated editor's buffer's content changes", ->
it "enables the buffer modified indicator", ->
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
editor.insertText("\n")
advanceClock(buffer.stoppedChangingDelay)
expect(fileInfo.classList.contains('buffer-modified')).toBe(true)
editor.backspace()
describe "when the buffer content has changed from the content on disk", ->
it "disables the buffer modified indicator on save", ->
filePath = path.join(os.tmpdir(), "atom-whitespace.txt")
fs.writeFileSync(filePath, "")
waitsForPromise ->
atom.workspace.open(filePath)
runs ->
editor = atom.workspace.getActiveTextEditor()
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
editor.insertText("\n")
advanceClock(buffer.stoppedChangingDelay)
expect(fileInfo.classList.contains('buffer-modified')).toBe(true)
waitsForPromise ->
# TODO - remove this Promise.resolve once atom/atom#14435 lands.
Promise.resolve(editor.getBuffer().save())
runs ->
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
it "disables the buffer modified indicator if the content matches again", ->
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
editor.insertText("\n")
advanceClock(buffer.stoppedChangingDelay)
expect(fileInfo.classList.contains('buffer-modified')).toBe(true)
editor.backspace()
advanceClock(buffer.stoppedChangingDelay)
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
it "disables the buffer modified indicator when the change is undone", ->
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
editor.insertText("\n")
advanceClock(buffer.stoppedChangingDelay)
expect(fileInfo.classList.contains('buffer-modified')).toBe(true)
editor.undo()
advanceClock(buffer.stoppedChangingDelay)
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
describe "when the buffer changes", ->
it "updates the buffer modified indicator for the new buffer", ->
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
waitsForPromise ->
atom.workspace.open('sample.txt')
runs ->
editor = atom.workspace.getActiveTextEditor()
editor.insertText("\n")
advanceClock(buffer.stoppedChangingDelay)
expect(fileInfo.classList.contains('buffer-modified')).toBe(true)
it "doesn't update the buffer modified indicator for the old buffer", ->
oldBuffer = editor.getBuffer()
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
waitsForPromise ->
atom.workspace.open('sample.txt')
runs ->
oldBuffer.setText("new text")
advanceClock(buffer.stoppedChangingDelay)
expect(fileInfo.classList.contains('buffer-modified')).toBe(false)
describe "when the associated editor's cursor position changes", ->
it "updates the cursor position in the status bar", ->
jasmine.attachToDOM(workspaceElement)
editor.setCursorScreenPosition([1, 2])
atom.views.performDocumentUpdate()
expect(cursorPosition.textContent).toBe '2:3'
it "does not throw an exception if the cursor is moved as the result of the active pane item changing to a non-editor (regression)", ->
waitsForPromise ->
Promise.resolve(atom.packages.deactivatePackage('status-bar')) # Wrapped so works with Promise & non-Promise deactivate
runs ->
atom.workspace.onDidChangeActivePaneItem(-> editor.setCursorScreenPosition([1, 2]))
waitsForPromise ->
atom.packages.activatePackage('status-bar')
runs ->
statusBar = workspaceElement.querySelector("status-bar")
cursorPosition = statusBar.getLeftTiles()[2].getItem()
atom.workspace.getActivePane().activateItem(document.createElement('div'))
expect(editor.getCursorScreenPosition()).toEqual([1, 2])
atom.views.performDocumentUpdate()
expect(cursorPosition).toBeHidden()
describe "when the associated editor's selection changes", ->
it "updates the selection count in the status bar", ->
jasmine.attachToDOM(workspaceElement)
editor.setSelectedBufferRange([[0, 0], [0, 0]])
atom.views.performDocumentUpdate()
expect(selectionCount.textContent).toBe ''
editor.setSelectedBufferRange([[0, 0], [0, 2]])
atom.views.performDocumentUpdate()
expect(selectionCount.textContent).toBe '(1, 2)'
editor.setSelectedBufferRange([[0, 0], [1, 30]])
atom.views.performDocumentUpdate()
expect(selectionCount.textContent).toBe "(2, 60)"
it "does not throw an exception if the cursor is moved as the result of the active pane item changing to a non-editor (regression)", ->
waitsForPromise ->
Promise.resolve(atom.packages.deactivatePackage('status-bar')) # Wrapped so works with Promise & non-Promise deactivate
runs ->
atom.workspace.onDidChangeActivePaneItem(-> editor.setSelectedBufferRange([[1, 2], [1, 3]]))
waitsForPromise ->
atom.packages.activatePackage('status-bar')
runs ->
statusBar = workspaceElement.querySelector("status-bar")
selectionCount = statusBar.getLeftTiles()[3].getItem()
atom.workspace.getActivePane().activateItem(document.createElement('div'))
expect(editor.getSelectedBufferRange()).toEqual([[1, 2], [1, 3]])
atom.views.performDocumentUpdate()
expect(selectionCount).toBeHidden()
describe "when the active pane item does not implement getCursorBufferPosition()", ->
it "hides the cursor position view", ->
jasmine.attachToDOM(workspaceElement)
atom.workspace.getActivePane().activateItem(dummyView)
atom.views.performDocumentUpdate()
expect(cursorPosition).toBeHidden()
describe "when the active pane item implements getTitle() but not getPath()", ->
it "displays the title", ->
jasmine.attachToDOM(workspaceElement)
dummyView.getTitle = -> 'View Title'
atom.workspace.getActivePane().activateItem(dummyView)
expect(fileInfo.currentPath.textContent).toBe 'View Title'
expect(fileInfo.currentPath).toBeVisible()
describe "when the active pane item neither getTitle() nor getPath()", ->
it "hides the path view", ->
jasmine.attachToDOM(workspaceElement)
atom.workspace.getActivePane().activateItem(dummyView)
expect(fileInfo.currentPath).toBeHidden()
describe "when the active pane item's title changes", ->
it "updates the path view with the new title", ->
jasmine.attachToDOM(workspaceElement)
callbacks = []
dummyView.onDidChangeTitle = (fn) ->
callbacks.push(fn)
{
dispose: ->
}
dummyView.getTitle = -> 'View Title'
atom.workspace.getActivePane().activateItem(dummyView)
expect(fileInfo.currentPath.textContent).toBe 'View Title'
dummyView.getTitle = -> 'New Title'
callback() for callback in callbacks
expect(fileInfo.currentPath.textContent).toBe 'New Title'
describe 'the cursor position tile', ->
beforeEach ->
atom.config.set('status-bar.cursorPositionFormat', 'foo %L bar %C')
it 'respects a format string', ->
jasmine.attachToDOM(workspaceElement)
editor.setCursorScreenPosition([1, 2])
atom.views.performDocumentUpdate()
expect(cursorPosition.textContent).toBe 'foo 2 bar 3'
it 'updates when the configuration changes', ->
jasmine.attachToDOM(workspaceElement)
editor.setCursorScreenPosition([1, 2])
atom.views.performDocumentUpdate()
expect(cursorPosition.textContent).toBe 'foo 2 bar 3'
atom.config.set('status-bar.cursorPositionFormat', 'baz %C quux %L')
atom.views.performDocumentUpdate()
expect(cursorPosition.textContent).toBe 'baz 3 quux 2'
describe "when clicked", ->
it "triggers the go-to-line toggle event", ->
eventHandler = jasmine.createSpy('eventHandler')
atom.commands.add('atom-text-editor', 'go-to-line:toggle', eventHandler)
cursorPosition.click()
expect(eventHandler).toHaveBeenCalled()
describe 'the selection count tile', ->
beforeEach ->
atom.config.set('status-bar.selectionCountFormat', '%L foo %C bar selected')
it 'respects a format string', ->
jasmine.attachToDOM(workspaceElement)
editor.setSelectedBufferRange([[0, 0], [1, 30]])
atom.views.performDocumentUpdate()
expect(selectionCount.textContent).toBe "2 foo 60 bar selected"
it 'updates when the configuration changes', ->
jasmine.attachToDOM(workspaceElement)
editor.setSelectedBufferRange([[0, 0], [1, 30]])
atom.views.performDocumentUpdate()
expect(selectionCount.textContent).toBe "2 foo 60 bar selected"
atom.config.set('status-bar.selectionCountFormat', 'Selection: baz %C quux %L')
atom.views.performDocumentUpdate()
expect(selectionCount.textContent).toBe "Selection: baz 60 quux 2"
it 'does not include the next line if the last selected character is a LF', ->
lineEndingRegExp = /\r\n|\n|\r/g
buffer = editor.getBuffer()
buffer.setText(buffer.getText().replace(lineEndingRegExp, '\n'))
jasmine.attachToDOM(workspaceElement)
editor.setSelectedBufferRange([[0, 0], [1, 0]])
atom.views.performDocumentUpdate()
expect(selectionCount.textContent).toBe "1 foo 30 bar selected"
it 'does not include the next line if the last selected character is CRLF', ->
lineEndingRegExp = /\r\n|\n|\r/g
buffer = editor.getBuffer()
buffer.setText(buffer.getText().replace(lineEndingRegExp, '\r\n'))
jasmine.attachToDOM(workspaceElement)
editor.setSelectedBufferRange([[0, 0], [1, 0]])
atom.views.performDocumentUpdate()
expect(selectionCount.textContent).toBe "1 foo 31 bar selected"
describe "the git tile", ->
gitView = null
hover = (element, fn) ->
# FIXME: Only use hoverDefaults once Atom 1.13 is on stable
hoverDelay = atom.tooltips.defaults.delay?.show ? atom.tooltips.hoverDefaults.delay.show
element.dispatchEvent(new CustomEvent('mouseenter', bubbles: false))
element.dispatchEvent(new CustomEvent('mouseover', bubbles: true))
advanceClock(hoverDelay)
fn()
element.dispatchEvent(new CustomEvent('mouseleave', bubbles: false))
element.dispatchEvent(new CustomEvent('mouseout', bubbles: true))
advanceClock(hoverDelay)
setupWorkingDir = (name) ->
dir = atom.project.getDirectories()[0]
target = "#{os.tmpdir()}/#{name}"
targetGit = target + '/.git'
fs.copySync(dir.resolve('git/working-dir'), path.resolve(target))
fs.removeSync(path.resolve(targetGit))
fs.copySync(dir.resolve("git/#{name}.git"), path.resolve(targetGit))
target
beforeEach ->
[gitView] = statusBar.getRightTiles().map (tile) -> tile.getItem()
describe "the git ahead/behind count labels", ->
beforeEach ->
jasmine.attachToDOM(workspaceElement)
it "shows the number of commits that can be pushed/pulled", ->
workingDir = setupWorkingDir('ahead-behind-repo')
atom.project.setPaths([workingDir])
filePath = atom.project.getDirectories()[0].resolve('a.txt')
repo = atom.project.getRepositories()[0]
waitsForPromise ->
atom.workspace.open(filePath)
.then -> repo.refreshStatus()
runs ->
behindElement = document.body.querySelector(".commits-behind-label")
aheadElement = document.body.querySelector(".commits-ahead-label")
expect(aheadElement).toBeVisible()
expect(behindElement).toBeVisible()
expect(aheadElement.textContent).toContain '1'
it "stays hidden when no commits can be pushed/pulled", ->
workingDir = setupWorkingDir('no-ahead-behind-repo')
atom.project.setPaths([workingDir])
filePath = atom.project.getDirectories()[0].resolve('a.txt')
repo = atom.project.getRepositories()[0]
waitsForPromise ->
atom.workspace.open(filePath)
.then -> repo.refreshStatus()
runs ->
behindElement = document.body.querySelector(".commits-behind-label")
aheadElement = document.body.querySelector(".commits-ahead-label")
expect(aheadElement).not.toBeVisible()
expect(behindElement).not.toBeVisible()
describe "the git branch label", ->
projectPath = null
beforeEach ->
projectPath = atom.project.getDirectories()[0].resolve('git/working-dir')
fs.moveSync(path.join(projectPath, 'git.git'), path.join(projectPath, '.git'))
jasmine.attachToDOM(workspaceElement)
afterEach ->
fs.moveSync(path.join(projectPath, '.git'), path.join(projectPath, 'git.git'))
it "displays the current branch for files in repositories", ->
atom.project.setPaths([projectPath])
waitsForPromise ->
atom.workspace.open('a.txt')
runs ->
currentBranch = atom.project.getRepositories()[0].getShortHead()
expect(gitView.branchArea).toBeVisible()
expect(gitView.branchLabel.textContent).toBe currentBranch
atom.workspace.getActivePane().destroyItems()
expect(gitView.branchArea).toBeVisible()
expect(gitView.branchLabel.textContent).toBe currentBranch
atom.workspace.getActivePane().activateItem(dummyView)
runs -> expect(gitView.branchArea).not.toBeVisible()
it "displays the current branch tooltip", ->
atom.project.setPaths([projectPath])
waitsForPromise ->
atom.workspace.open('a.txt')
runs ->
currentBranch = atom.project.getRepositories()[0].getShortHead()
hover gitView.branchArea, ->
expect(document.body.querySelector(".tooltip").innerText)
.toBe("On branch #{currentBranch}")
it "doesn't display the current branch for a file not in a repository", ->
atom.project.setPaths([os.tmpdir()])
waitsForPromise ->
atom.workspace.open(path.join(os.tmpdir(), 'temp.txt'))
runs ->
expect(gitView.branchArea).toBeHidden()
it "doesn't display the current branch for a file outside the current project", ->
waitsForPromise ->
atom.workspace.open(path.join(os.tmpdir(), 'atom-specs', 'not-in-project.txt'))
runs ->
expect(gitView.branchArea).toBeHidden()
describe "the git status label", ->
[repo, filePath, originalPathText, newPath, ignorePath, ignoredPath, projectPath] = []
beforeEach ->
projectPath = atom.project.getDirectories()[0].resolve('git/working-dir')
fs.moveSync(path.join(projectPath, 'git.git'), path.join(projectPath, '.git'))
atom.project.setPaths([projectPath])
filePath = atom.project.getDirectories()[0].resolve('a.txt')
newPath = atom.project.getDirectories()[0].resolve('new.txt')
fs.writeFileSync(newPath, "I'm new here")
ignorePath = path.join(projectPath, '.gitignore')
fs.writeFileSync(ignorePath, 'ignored.txt')
ignoredPath = path.join(projectPath, 'ignored.txt')
fs.writeFileSync(ignoredPath, '')
jasmine.attachToDOM(workspaceElement)
repo = atom.project.getRepositories()[0]
originalPathText = fs.readFileSync(filePath, 'utf8')
waitsForPromise -> repo.refreshStatus()
afterEach ->
fs.writeFileSync(filePath, originalPathText)
fs.removeSync(newPath)
fs.removeSync(ignorePath)
fs.removeSync(ignoredPath)
fs.moveSync(path.join(projectPath, '.git'), path.join(projectPath, 'git.git'))
it "displays the modified icon for a changed file", ->
waitsForPromise ->
atom.workspace.open(filePath)
.then ->
fs.writeFileSync(filePath, "i've changed for the worse")
repo.refreshStatus()
runs ->
expect(gitView.gitStatusIcon).toHaveClass('icon-diff-modified')
it "displays the 1 line added and not committed tooltip", ->
waitsForPromise ->
atom.workspace.open(filePath)
.then ->
fs.writeFileSync(filePath, "i've changed for the worse")
repo.refreshStatus()
runs ->
hover gitView.gitStatusIcon, ->
expect(document.body.querySelector(".tooltip").innerText)
.toBe("1 line added to this file not yet committed")
it "displays the x lines added and not committed tooltip", ->
waitsForPromise ->
atom.workspace.open(filePath)
.then ->
fs.writeFileSync(filePath, "i've changed#{os.EOL}for the worse")
repo.refreshStatus()
runs ->
hover gitView.gitStatusIcon, ->
expect(document.body.querySelector(".tooltip").innerText)
.toBe("2 lines added to this file not yet committed")
it "doesn't display the modified icon for an unchanged file", ->
waitsForPromise ->
atom.workspace.open(filePath)
.then -> repo.refreshStatus()
runs ->
expect(gitView.gitStatusIcon).toHaveText('')
it "displays the new icon for a new file", ->
waitsForPromise ->
atom.workspace.open(newPath)
.then -> repo.refreshStatus()
runs ->
expect(gitView.gitStatusIcon).toHaveClass('icon-diff-added')
hover gitView.gitStatusIcon, ->
expect(document.body.querySelector(".tooltip").innerText)
.toBe("1 line in this new file not yet committed")
it "displays the 1 line added and not committed to new file tooltip", ->
waitsForPromise ->
atom.workspace.open(newPath)
.then -> repo.refreshStatus()
runs ->
hover gitView.gitStatusIcon, ->
expect(document.body.querySelector(".tooltip").innerText)
.toBe("1 line in this new file not yet committed")
it "displays the x lines added and not committed to new file tooltip", ->
fs.writeFileSync(newPath, "I'm new#{os.EOL}here")
waitsForPromise ->
atom.workspace.open(newPath)
.then -> repo.refreshStatus()
runs ->
hover gitView.gitStatusIcon, ->
expect(document.body.querySelector(".tooltip").innerText)
.toBe("2 lines in this new file not yet committed")
it "displays the ignored icon for an ignored file", ->
waitsForPromise ->
atom.workspace.open(ignoredPath)
runs ->
expect(gitView.gitStatusIcon).toHaveClass('icon-diff-ignored')
hover gitView.gitStatusIcon, ->
expect(document.body.querySelector(".tooltip").innerText)
.toBe("File is ignored by git")
it "updates when a status-changed event occurs", ->
waitsForPromise ->
atom.workspace.open(filePath)
.then ->
fs.writeFileSync(filePath, "i've changed for the worse")
repo.refreshStatus()
runs ->
expect(gitView.gitStatusIcon).toHaveClass('icon-diff-modified')
waitsForPromise ->
fs.writeFileSync(filePath, originalPathText)
repo.refreshStatus()
runs ->
expect(gitView.gitStatusIcon).not.toHaveClass('icon-diff-modified')
it "displays the diff stat for modified files", ->
waitsForPromise ->
atom.workspace.open(filePath)
.then ->
fs.writeFileSync(filePath, "i've changed for the worse")
repo.refreshStatus()
runs ->
expect(gitView.gitStatusIcon).toHaveText('+1')
it "displays the diff stat for new files", ->
waitsForPromise ->
atom.workspace.open(newPath)
.then -> repo.refreshStatus()
runs ->
expect(gitView.gitStatusIcon).toHaveText('+1')
it "does not display for files not in the current project", ->
waitsForPromise ->
atom.workspace.open('/tmp/atom-specs/not-in-project.txt')
runs ->
expect(gitView.gitStatusIcon).toBeHidden()

View File

@ -0,0 +1 @@
91c6b2a0a2c0d77bb9ce169154ef4263a3afc96c branch 'master' of https://github.com/as-cii/foo

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1 @@
f97fc7338640a493675f375ed2eeaa2451715415

View File

@ -0,0 +1,13 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/as-cii/foo
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

Binary file not shown.

View File

@ -0,0 +1,2 @@
60a497d3152925c000950e9cd2a874e8314c3155 refs/heads/master
91c6b2a0a2c0d77bb9ce169154ef4263a3afc96c refs/remotes/origin/master

View File

@ -0,0 +1,2 @@
P pack-ce64fd1ffdacf4364f6162466c9c8df5bca62c8b.pack

View File

@ -0,0 +1,3 @@
# pack-refs with: peeled fully-peeled
60a497d3152925c000950e9cd2a874e8314c3155 refs/heads/master
91c6b2a0a2c0d77bb9ce169154ef4263a3afc96c refs/remotes/origin/master

View File

@ -0,0 +1 @@
60a497d3152925c000950e9cd2a874e8314c3155

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true

Binary file not shown.

View File

@ -0,0 +1 @@
ef046e9eecaa5255ea5e9817132d4001724d6ae1

View File

@ -0,0 +1 @@
Initial Commit

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1 @@
7189962af8559c6396aaad5758a2b0129c155529

View File

@ -0,0 +1,14 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[branch "master"]
[remote "origin"]
url = https://github.com/as-cii/foo
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

View File

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -0,0 +1,2 @@
7189962af8559c6396aaad5758a2b0129c155529 refs/heads/master
7189962af8559c6396aaad5758a2b0129c155529 refs/remotes/origin/master

View File

@ -0,0 +1,2 @@
P pack-483aa6a931fd8d7256b902401f1b991daedfe0a5.pack

View File

@ -0,0 +1,3 @@
# pack-refs with: peeled fully-peeled
7189962af8559c6396aaad5758a2b0129c155529 refs/heads/master
7189962af8559c6396aaad5758a2b0129c155529 refs/remotes/origin/master

View File

@ -0,0 +1 @@
7189962af8559c6396aaad5758a2b0129c155529

View File

@ -0,0 +1 @@
undefined

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true

Binary file not shown.

View File

@ -0,0 +1 @@
ef046e9eecaa5255ea5e9817132d4001724d6ae1

View File

@ -0,0 +1 @@
Full of text

View File

@ -0,0 +1,13 @@
var quicksort = function () {
var sort = function(items) {
if (items.length <= 1) return items;
var pivot = items.shift(), current, left = [], right = [];
while(items.length > 0) {
current = items.shift();
current < pivot ? left.push(current) : right.push(current);
}
return sort(left).concat(pivot).concat(sort(right));
};
return sort(Array.apply(this, arguments));
};

View File

@ -0,0 +1 @@
Some text.

View File

@ -0,0 +1,111 @@
describe "Status Bar package", ->
[editor, statusBar, statusBarService, workspaceElement, mainModule] = []
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
waitsForPromise ->
atom.packages.activatePackage('status-bar').then (pack) ->
statusBar = workspaceElement.querySelector("status-bar")
statusBarService = pack.mainModule.provideStatusBar()
{mainModule} = pack
describe "@activate()", ->
it "appends only one status bar", ->
expect(workspaceElement.querySelectorAll('status-bar').length).toBe 1
atom.workspace.getActivePane().splitRight(copyActiveItem: true)
expect(workspaceElement.querySelectorAll('status-bar').length).toBe 1
describe "@deactivate()", ->
it "removes the status bar view", ->
waitsForPromise ->
Promise.resolve(atom.packages.deactivatePackage('status-bar')) # Wrapped so works with Promise & non-Promise deactivate
runs ->
expect(workspaceElement.querySelector('status-bar')).toBeNull()
describe "isVisible option", ->
beforeEach ->
jasmine.attachToDOM(workspaceElement)
describe "when it is true", ->
beforeEach ->
atom.config.set 'status-bar.isVisible', true
it "shows status bar", ->
expect(workspaceElement.querySelector('status-bar').parentNode).toBeVisible()
describe "when it is false", ->
beforeEach ->
atom.config.set 'status-bar.isVisible', false
it "hides status bar", ->
expect(workspaceElement.querySelector('status-bar').parentNode).not.toBeVisible()
describe "when status-bar:toggle is triggered", ->
beforeEach ->
jasmine.attachToDOM(workspaceElement)
atom.config.set 'status-bar.isVisible', true
it "hides or shows the status bar", ->
atom.commands.dispatch(workspaceElement, 'status-bar:toggle')
expect(workspaceElement.querySelector('status-bar').parentNode).not.toBeVisible()
atom.commands.dispatch(workspaceElement, 'status-bar:toggle')
expect(workspaceElement.querySelector('status-bar').parentNode).toBeVisible()
it "toggles the value of isVisible in config file", ->
expect(atom.config.get 'status-bar.isVisible').toBe true
atom.commands.dispatch(workspaceElement, 'status-bar:toggle')
expect(atom.config.get 'status-bar.isVisible').toBe false
atom.commands.dispatch(workspaceElement, 'status-bar:toggle')
expect(atom.config.get 'status-bar.isVisible').toBe true
describe "full-width setting", ->
[containers] = []
beforeEach ->
containers = atom.workspace.panelContainers
jasmine.attachToDOM(workspaceElement)
waitsForPromise ->
atom.workspace.open('sample.js')
it "expects the setting to be enabled by default", ->
expect(atom.config.get('status-bar.fullWidth')).toBeTruthy()
expect(containers.footer.panels).toContain(mainModule.statusBarPanel)
describe "when setting is changed", ->
it "fits status bar to editor's width", ->
atom.config.set('status-bar.fullWidth', false)
expect(containers.bottom.panels).toContain(mainModule.statusBarPanel)
expect(containers.footer.panels).not.toContain(mainModule.statusBarPanel)
it "restores the status-bar location when re-enabling setting", ->
atom.config.set('status-bar.fullWidth', true)
expect(containers.footer.panels).toContain(mainModule.statusBarPanel)
expect(containers.bottom.panels).not.toContain(mainModule.statusBarPanel)
describe "the 'status-bar' service", ->
it "allows tiles to be added, removed, and retrieved", ->
dummyView = document.createElement("div")
tile = statusBarService.addLeftTile(item: dummyView)
expect(statusBar).toContain(dummyView)
expect(statusBarService.getLeftTiles()).toContain(tile)
tile.destroy()
expect(statusBar).not.toContain(dummyView)
expect(statusBarService.getLeftTiles()).not.toContain(tile)
dummyView = document.createElement("div")
tile = statusBarService.addRightTile(item: dummyView)
expect(statusBar).toContain(dummyView)
expect(statusBarService.getRightTiles()).toContain(tile)
tile.destroy()
expect(statusBar).not.toContain(dummyView)
expect(statusBarService.getRightTiles()).not.toContain(tile)
it "allows the git info tile to be disabled", ->
getGitInfoTile = ->
statusBar.getRightTiles().find((tile) -> tile.item.matches('.git-view'))
expect(getGitInfoTile()).not.toBeUndefined()
statusBarService.disableGitInfoTile()
expect(getGitInfoTile()).toBeUndefined()

View File

@ -0,0 +1,103 @@
StatusBarView = require '../lib/status-bar-view'
describe "StatusBarView", ->
statusBarView = null
class TestItem
constructor: (@id) ->
beforeEach ->
statusBarView = new StatusBarView()
atom.views.addViewProvider TestItem, (model) ->
element = document.createElement("item-view")
element.model = model
element
describe "::addLeftTile({item, priority})", ->
it "appends the view for the given item to its left side", ->
testItem1 = new TestItem(1)
testItem2 = new TestItem(2)
testItem3 = new TestItem(3)
tile1 = statusBarView.addLeftTile(item: testItem1, priority: 10)
tile2 = statusBarView.addLeftTile(item: testItem2, priority: 30)
tile3 = statusBarView.addLeftTile(item: testItem3, priority: 20)
{leftPanel} = statusBarView
expect(leftPanel.children[0].nodeName).toBe("ITEM-VIEW")
expect(leftPanel.children[1].nodeName).toBe("ITEM-VIEW")
expect(leftPanel.children[2].nodeName).toBe("ITEM-VIEW")
expect(leftPanel.children[0].model).toBe(testItem1)
expect(leftPanel.children[1].model).toBe(testItem3)
expect(leftPanel.children[2].model).toBe(testItem2)
expect(statusBarView.getLeftTiles()).toEqual([tile1, tile3, tile2])
expect(tile1.getPriority()).toBe(10)
expect(tile1.getItem()).toBe(testItem1)
it "allows the view to be removed", ->
testItem = new TestItem(1)
tile = statusBarView.addLeftTile(item: testItem, priority: 10)
tile.destroy()
expect(statusBarView.leftPanel.children.length).toBe(0)
statusBarView.addLeftTile(item: testItem, priority: 9)
describe "when no priority is given", ->
it "appends the item", ->
testItem1 = new TestItem(1)
testItem2 = new TestItem(2)
statusBarView.addLeftTile(item: testItem1, priority: 1000)
statusBarView.addLeftTile(item: testItem2)
{leftPanel} = statusBarView
expect(leftPanel.children[0].model).toBe(testItem1)
expect(leftPanel.children[1].model).toBe(testItem2)
describe "::addRightTile({item, priority})", ->
it "appends the view for the given item to its right side", ->
testItem1 = new TestItem(1)
testItem2 = new TestItem(2)
testItem3 = new TestItem(3)
tile1 = statusBarView.addRightTile(item: testItem1, priority: 10)
tile2 = statusBarView.addRightTile(item: testItem2, priority: 30)
tile3 = statusBarView.addRightTile(item: testItem3, priority: 20)
{rightPanel} = statusBarView
expect(rightPanel.children[0].nodeName).toBe("ITEM-VIEW")
expect(rightPanel.children[1].nodeName).toBe("ITEM-VIEW")
expect(rightPanel.children[2].nodeName).toBe("ITEM-VIEW")
expect(rightPanel.children[0].model).toBe(testItem2)
expect(rightPanel.children[1].model).toBe(testItem3)
expect(rightPanel.children[2].model).toBe(testItem1)
expect(statusBarView.getRightTiles()).toEqual([tile2, tile3, tile1])
expect(tile1.getPriority()).toBe(10)
expect(tile1.getItem()).toBe(testItem1)
it "allows the view to be removed", ->
testItem = new TestItem(1)
disposable = statusBarView.addRightTile(item: testItem, priority: 10)
disposable.destroy()
expect(statusBarView.rightPanel.children.length).toBe(0)
statusBarView.addRightTile(item: testItem, priority: 11)
describe "when no priority is given", ->
it "prepends the item", ->
testItem1 = new TestItem(1, priority: 1000)
testItem2 = new TestItem(2)
statusBarView.addRightTile(item: testItem1, priority: 1000)
statusBarView.addRightTile(item: testItem2)
{rightPanel} = statusBarView
expect(rightPanel.children[0].model).toBe(testItem2)
expect(rightPanel.children[1].model).toBe(testItem1)

View File

@ -0,0 +1,114 @@
@import "ui-variables";
@import "octicon-mixins";
status-bar {
display: block;
font-size: 11px;
line-height: @component-line-height - 3px;
height: @component-line-height + 1px;
position: relative;
-webkit-user-select: none;
cursor: default;
overflow: hidden;
white-space: nowrap;
min-width: -webkit-min-content;
a, a:hover {
color: @text-color;
}
.flexbox-repaint-hack {
padding: 0 @component-line-height/2;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: space-between;
}
// Use 1/3 of space -> will get cut-off first when narrow
.status-bar-left {
flex: 1 1 33%;
}
// Use 2/3 of space
.status-bar-right {
padding-left: @component-padding;
.inline-block {
margin-right: 0;
margin-left: @component-padding*1.5;
& > .inline-block {
margin-left: 0;
}
}
}
// Add horizontal overflow scrolling
.status-bar-left,
.status-bar-right {
overflow-x: auto;
// prevent badly behaved status bar items from causing vertical scrolling
// in the status bar. They can always implement overflow: auto to scroll
// within themselves.
overflow-y: hidden;
&::-webkit-scrollbar {
display: none;
}
}
// Limit inline-blocks from getting too long
.inline-block {
max-width: 20vw;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// remove padding from buttons
button.inline-block {
padding-left: 0;
padding-right: 0;
}
// No width limit for file-info -> will get cut off if too long
.inline-block.file-info {
max-width: none;
}
.file-info.buffer-modified::after {
content: '*';
}
// All icons are smaller than normal (normal is 16px)
.icon:before {
.icon-size(14px);
position: relative;
top: 1px;
width: auto; // Enable auto-width since not every icon has the same width
margin-right: 4px;
}
.icon-diff-ignored:before,
.commits-ahead-label:before,
.commits-behind-label:before {
margin-right: -1px;
}
.hide,
.current-path:empty,
.cursor-position:empty,
.selection-count:empty {
display: none;
}
.git-view {
display: inline-block;
}
.cursor-position a,
.cursor-position a:hover {
color: @text-color;
}
}