Merge branch 'master' into menu-api

Conflicts:
	src/atom.coffee
	src/config.coffee
This commit is contained in:
Matt Colyer
2013-10-08 15:49:55 -07:00
20 changed files with 666 additions and 231 deletions

View File

@@ -32,6 +32,7 @@ done
if [ $EXPECT_OUTPUT ]; then
$ATOM_BINARY --executed-from="$(pwd)" --pid=$$ $@
exit $?
else
open -a $ATOM_PATH -n --args --executed-from="$(pwd)" --pid=$$ $@
fi

View File

@@ -1,5 +1,9 @@
require '../src/window'
Atom = require '../src/atom'
window.atom = new Atom()
atom = new Atom()
atom.show() unless atom.getLoadSettings().exitWhenDone
window.atom = atom
{runSpecSuite} = require '../spec/jasmine-helper'
atom.openDevTools()

View File

@@ -1,11 +1,9 @@
require '../spec/spec-helper'
$ = require 'jquery'
_ = require 'underscore'
{Point} = require 'telepath'
Project = require 'project'
fsUtils = require 'fs-utils'
TokenizedBuffer = require 'tokenized-buffer'
{$, _, Point, fs} = require 'atom'
Project = require '../src/project'
fsUtils = require '../src/fs-utils'
TokenizedBuffer = require '../src/tokenized-buffer'
defaultCount = 100
window.pbenchmark = (args...) -> window.benchmark(args..., profile: true)
@@ -13,7 +11,7 @@ window.fbenchmark = (args...) -> window.benchmark(args..., focused: true)
window.fpbenchmark = (args...) -> window.benchmark(args..., profile: true, focused: true)
window.pfbenchmark = window.fpbenchmark
window.benchmarkFixturesProject = new Project(fsUtils.resolveOnLoadPath('benchmark/fixtures'))
window.benchmarkFixturesProject = new Project(fsUtils.resolveOnLoadPath('../benchmark/fixtures'))
beforeEach ->
window.project = window.benchmarkFixturesProject

View File

@@ -1,8 +1,6 @@
require './benchmark-helper'
$ = require 'jquery'
_ = require 'underscore'
TokenizedBuffer = require 'tokenized-buffer'
RootView = require 'root-view'
{$, _, RootView} = require 'atom'
TokenizedBuffer = require '../src/tokenized-buffer'
describe "editor.", ->
editor = null
@@ -12,7 +10,6 @@ describe "editor.", ->
window.rootView = new RootView
window.rootView.attachToDom()
rootView.width(1024)
rootView.height(768)
rootView.open() # open blank editor
@@ -62,6 +59,116 @@ describe "editor.", ->
editor.insertText('"')
editor.backspace()
describe "empty-vs-set-innerHTML.", ->
[firstRow, lastRow] = []
beforeEach ->
firstRow = editor.getFirstVisibleScreenRow()
lastRow = editor.getLastVisibleScreenRow()
benchmark "build-gutter-html.", 1000, ->
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
benchmark "set-innerHTML.", 1000, ->
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
editor.gutter.lineNumbers[0].innerHtml = ''
benchmark "empty.", 1000, ->
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
editor.gutter.lineNumbers.empty()
describe "positionLeftForLineAndColumn.", ->
line = null
beforeEach ->
editor.scrollTop(2000)
editor.resetDisplay()
line = editor.lineElementForScreenRow(106)[0]
describe "one-line.", ->
beforeEach ->
editor.clearCharacterWidthCache()
benchmark "uncached", 5000, ->
editor.positionLeftForLineAndColumn(line, 106, 82)
editor.clearCharacterWidthCache()
benchmark "cached", 5000, ->
editor.positionLeftForLineAndColumn(line, 106, 82)
describe "multiple-lines.", ->
[firstRow, lastRow] = []
beforeEach ->
firstRow = editor.getFirstVisibleScreenRow()
lastRow = editor.getLastVisibleScreenRow()
benchmark "cache-entire-visible-area", 100, ->
for i in [firstRow..lastRow]
line = editor.lineElementForScreenRow(i)[0]
editor.positionLeftForLineAndColumn(line, i, Math.max(0, editor.lineLengthForBufferRow(i)))
describe "text-rendering.", ->
beforeEach ->
editor.scrollTop(2000)
benchmark "resetDisplay", 50, ->
editor.resetDisplay()
benchmark "htmlForScreenRows", 1000, ->
lastRow = editor.getLastScreenRow()
editor.htmlForScreenRows(0, lastRow)
benchmark "htmlForScreenRows.htmlParsing", 50, ->
lastRow = editor.getLastScreenRow()
html = editor.htmlForScreenRows(0, lastRow)
div = document.createElement('div')
div.innerHTML = html
describe "gutter-api.", ->
describe "getLineNumberElementsForClass.", ->
beforeEach ->
editor.gutter.addClassToLine(20, 'omgwow')
editor.gutter.addClassToLine(40, 'omgwow')
benchmark "DOM", 20000, ->
editor.gutter.getLineNumberElementsForClass('omgwow')
benchmark "getLineNumberElement.DOM", 20000, ->
editor.gutter.getLineNumberElement(12)
benchmark "toggle-class", 2000, ->
editor.gutter.addClassToLine(40, 'omgwow')
editor.gutter.removeClassFromLine(40, 'omgwow')
describe "find-then-unset.", ->
classes = ['one', 'two', 'three', 'four']
benchmark "single-class", 200, ->
editor.gutter.addClassToLine(30, 'omgwow')
editor.gutter.addClassToLine(40, 'omgwow')
editor.gutter.removeClassFromAllLines('omgwow')
benchmark "multiple-class", 200, ->
editor.gutter.addClassToLine(30, 'one')
editor.gutter.addClassToLine(30, 'two')
editor.gutter.addClassToLine(40, 'two')
editor.gutter.addClassToLine(40, 'three')
editor.gutter.addClassToLine(40, 'four')
for klass in classes
editor.gutter.removeClassFromAllLines(klass)
describe "line-htmlification.", ->
div = null
html = null
beforeEach ->
lastRow = editor.getLastScreenRow()
html = editor.htmlForScreenRows(0, lastRow)
div = document.createElement('div')
benchmark "setInnerHTML", 1, ->
div.innerHTML = html
describe "9000-line-file.", ->
benchmark "opening.", 5, ->
rootView.open('huge.js')

View File

@@ -1,6 +1,6 @@
{
"name": "atom",
"version": "29.0.0",
"version": "31.0.0",
"main": "./src/main.js",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
"coffee-script": "1.6.2",
"coffeestack": "0.6.0",
"first-mate": "0.2.0",
"git-utils": "0.25.0",
"git-utils": "0.26.0",
"guid": "0.0.10",
"jasmine-focused": "~0.14.0",
"mkdirp": "0.3.5",
@@ -46,17 +46,17 @@
"archive-view": "0.8.0",
"autocomplete": "0.6.0",
"autoflow": "0.3.0",
"bookmarks": "0.4.0",
"bracket-matcher": "0.5.0",
"collaboration": "0.19.0",
"bookmarks": "0.5.0",
"bracket-matcher": "0.6.0",
"collaboration": "0.21.0",
"command-logger": "0.4.0",
"command-palette": "0.4.0",
"editor-stats": "0.3.0",
"exception-reporting": "0.3.0",
"exception-reporting": "0.4.0",
"find-and-replace": "0.24.0",
"fuzzy-finder": "0.7.0",
"gfm": "0.5.0",
"git-diff": "0.4.0",
"git-diff": "0.5.0",
"gists": "0.3.0",
"github-sign-in": "0.7.0",
"go-to-line": "0.4.0",
@@ -66,7 +66,7 @@
"markdown-preview": "0.6.0",
"metrics": "0.8.0",
"package-generator": "0.10.0",
"release-notes": "0.2.0",
"release-notes": "0.3.0",
"settings-view": "0.27.0",
"snippets": "0.6.0",
"spell-check": "0.6.0",
@@ -75,9 +75,9 @@
"tabs": "0.5.0",
"terminal": "0.10.0",
"timecop": "0.5.0",
"to-the-hubs": "0.4.0",
"to-the-hubs": "0.6.0",
"toml": "0.3.0",
"tree-view": "0.8.0",
"tree-view": "0.10.0",
"ui-demo": "0.8.0",
"whitespace": "0.5.0",
"wrap-guide": "0.3.0",

View File

@@ -119,21 +119,21 @@ describe "Editor", ->
it "updates the rendered lines, cursors, selections, scroll position, and event subscriptions to match the given edit session", ->
editor.attachToDom(heightInLines: 5, widthInChars: 30)
editor.setCursorBufferPosition([3, 5])
editor.setCursorBufferPosition([6, 13])
editor.scrollToBottom()
editor.scrollLeft(150)
previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
previousScrollTop = editor.scrollTop()
previousScrollLeft = editor.scrollLeft()
newEditSession.setScrollTop(120)
newEditSession.setScrollTop(900)
newEditSession.setSelectedBufferRange([[40, 0], [43, 1]])
editor.edit(newEditSession)
{ firstRenderedScreenRow, lastRenderedScreenRow } = editor
expect(editor.lineElementForScreenRow(firstRenderedScreenRow).text()).toBe newBuffer.lineForRow(firstRenderedScreenRow)
expect(editor.lineElementForScreenRow(lastRenderedScreenRow).text()).toBe newBuffer.lineForRow(editor.lastRenderedScreenRow)
expect(editor.scrollTop()).toBe 120
expect(editor.scrollTop()).toBe 900
expect(editor.scrollLeft()).toBe 0
expect(editor.getSelectionView().regions[0].position().top).toBe 40 * editor.lineHeight
editor.insertText("hello")
@@ -146,9 +146,9 @@ describe "Editor", ->
expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight
expect(editor.scrollTop()).toBe previousScrollTop
expect(editor.scrollLeft()).toBe previousScrollLeft
expect(editor.getCursorView().position()).toEqual { top: 3 * editor.lineHeight, left: 5 * editor.charWidth }
expect(editor.getCursorView().position()).toEqual { top: 6 * editor.lineHeight, left: 13 * editor.charWidth }
editor.insertText("goodbye")
expect(editor.lineElementForScreenRow(3).text()).toMatch /^ vgoodbyear/
expect(editor.lineElementForScreenRow(6).text()).toMatch /^ currentgoodbye/
it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
filePath = "/tmp/atom-changed-file.txt"
@@ -904,7 +904,8 @@ describe "Editor", ->
it "moves the hiddenInput to the same position with cursor's view", ->
editor.setCursorScreenPosition(row: 2, column: 2)
expect(editor.getCursorView().offset()).toEqual(editor.hiddenInput.offset())
expect(editor.getCursorView()[0].style.left).toEqual(editor.hiddenInput[0].style.left)
expect(editor.getCursorView()[0].style.top).toEqual(editor.hiddenInput[0].style.top)
describe "when the editor is using a variable-width font", ->
beforeEach ->
@@ -1107,8 +1108,8 @@ describe "Editor", ->
expect(span0.children('span:eq(2)')).toMatchSelector '.meta.brace.curly.js'
expect(span0.children('span:eq(2)').text()).toBe "{"
line12 = editor.renderedLines.find('.line:eq(11)')
expect(line12.find('span:eq(2)')).toMatchSelector '.keyword'
line12 = editor.renderedLines.find('.line:eq(11)').children('span:eq(0)')
expect(line12.children('span:eq(1)')).toMatchSelector '.keyword'
it "wraps hard tabs in a span", ->
editor.setText('\t<- hard tab')
@@ -1123,12 +1124,13 @@ describe "Editor", ->
expect(span0_0).toMatchSelector '.leading-whitespace'
expect(span0_0.text()).toBe ' '
it "wraps trailing whitespace in a span", ->
editor.setText('trailing whitespace -> ')
line0 = editor.renderedLines.find('.line:first')
span0_last = line0.children('span:eq(0)').children('span:last')
expect(span0_last).toMatchSelector '.trailing-whitespace'
expect(span0_last.text()).toBe ' '
describe "when the line has trailing whitespace", ->
it "wraps trailing whitespace in a span", ->
editor.setText('trailing whitespace -> ')
line0 = editor.renderedLines.find('.line:first')
span0_last = line0.children('span:eq(0)').children('span:last')
expect(span0_last).toMatchSelector '.trailing-whitespace'
expect(span0_last.text()).toBe ' '
describe "when lines are updated in the buffer", ->
it "syntax highlights the updated lines", ->
@@ -1878,13 +1880,55 @@ describe "Editor", ->
# doesn't allow regular editors to set grammars
expect(-> editor.setGrammar()).toThrow()
describe "when config.editor.showLineNumbers is false", ->
it "doesn't render any line numbers", ->
expect(editor.gutter.lineNumbers).toBeVisible()
config.set("editor.showLineNumbers", false)
expect(editor.gutter.lineNumbers).not.toBeVisible()
describe "using gutter's api", ->
it "can get all the line number elements", ->
elements = editor.gutter.getLineNumberElements()
len = editor.gutter.lastScreenRow - editor.gutter.firstScreenRow + 1
expect(elements).toHaveLength(len)
it "can get a single line number element", ->
element = editor.gutter.getLineNumberElement(3)
expect(element).toBeTruthy()
it "returns falsy when there is no line element", ->
expect(editor.gutter.getLineNumberElement(42)).toHaveLength 0
it "can add and remove classes to all the line numbers", ->
wasAdded = editor.gutter.addClassToAllLines('heyok')
expect(wasAdded).toBe true
elements = editor.gutter.getLineNumberElementsForClass('heyok')
expect($(elements)).toHaveClass('heyok')
editor.gutter.removeClassFromAllLines('heyok')
expect($(editor.gutter.getLineNumberElements())).not.toHaveClass('heyok')
it "can add and remove classes from a single line number", ->
wasAdded = editor.gutter.addClassToLine(3, 'heyok')
expect(wasAdded).toBe true
element = editor.gutter.getLineNumberElement(2)
expect($(element)).not.toHaveClass('heyok')
it "can fetch line numbers by their class", ->
editor.gutter.addClassToLine(1, 'heyok')
editor.gutter.addClassToLine(3, 'heyok')
elements = editor.gutter.getLineNumberElementsForClass('heyok')
expect(elements.length).toBe 2
expect($(elements[0])).toHaveClass 'line-number-1'
expect($(elements[0])).toHaveClass 'heyok'
expect($(elements[1])).toHaveClass 'line-number-3'
expect($(elements[1])).toHaveClass 'heyok'
describe "gutter line highlighting", ->
beforeEach ->
editor.attachToDom(heightInLines: 5.5)
@@ -2160,10 +2204,21 @@ describe "Editor", ->
expect(editor.pixelPositionForBufferPosition([2,7])).toEqual top: 0, left: 0
describe "when the editor is attached and visible", ->
it "returns the top and left pixel positions", ->
beforeEach ->
editor.attachToDom()
it "returns the top and left pixel positions", ->
expect(editor.pixelPositionForBufferPosition([2,7])).toEqual top: 40, left: 70
it "caches the left position", ->
editor.renderedLines.css('font-size', '16px')
expect(editor.pixelPositionForBufferPosition([2,8])).toEqual top: 40, left: 80
# make characters smaller
editor.renderedLines.css('font-size', '15px')
expect(editor.pixelPositionForBufferPosition([2,8])).toEqual top: 40, left: 80
describe "when clicking in the gutter", ->
beforeEach ->
editor.attachToDom()

View File

@@ -18,6 +18,7 @@ atom.themes.loadBaseStylesheets()
atom.themes.requireStylesheet '../static/jasmine'
fixturePackagesPath = path.resolve(__dirname, './fixtures/packages')
atom.packages.packageDirPaths.unshift(fixturePackagesPath)
atom.keymap.loadBundledKeymaps()
[bindingSetsToRestore, bindingSetsByFirstKeystrokeToRestore] = []
@@ -50,7 +51,9 @@ beforeEach ->
bindingSetsByFirstKeystrokeToRestore = _.clone(keymap.bindingSetsByFirstKeystroke)
# reset config before each spec; don't load or save from/to `config.json`
config = new Config()
config = new Config
resourcePath: window.resourcePath
configDirPath: atom.getConfigDirPath()
config.packageDirPaths.unshift(fixturePackagesPath)
spyOn(config, 'load')
spyOn(config, 'save')

View File

@@ -51,7 +51,7 @@ class AtomApplication
version: null
constructor: (options) ->
{@resourcePath, @version} = options
{@resourcePath, @version, @devMode} = options
global.atomApplication = this
@pidsToOpenWindows = {}
@@ -147,7 +147,7 @@ class AtomApplication
app.on 'open-url', (event, urlToOpen) =>
event.preventDefault()
@openUrl(urlToOpen)
@openUrl({urlToOpen, @devMode})
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdateCallback) =>
event.preventDefault()
@@ -221,9 +221,10 @@ class AtomApplication
# + devMode:
# Boolean to control the opened window's dev mode.
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode}={}) ->
[basename, initialLine] = path.basename(pathToOpen).split(':')
pathToOpen = "#{path.dirname(pathToOpen)}/#{basename}"
initialLine -= 1 if initialLine # Convert line numbers to a base of 0
if pathToOpen
[basename, initialLine] = path.basename(pathToOpen).split(':')
pathToOpen = "#{path.dirname(pathToOpen)}/#{basename}"
initialLine -= 1 if initialLine # Convert line numbers to a base of 0
unless devMode
existingWindow = @windowForPath(pathToOpen) unless pidToKillWhenClosed or newWindow
@@ -251,9 +252,11 @@ class AtomApplication
console.log("Killing process #{pid} failed: #{error.code}")
delete @pidsToOpenWindows[pid]
# Private: Handles an atom:// url.
# Private: Open an atom:// url.
#
# Currently only supports atom://session/<session-id> urls.
# The host of the URL being opened is assumed to be the package name
# responsible for opening the URL. A new window will be created with
# that package's `urlMain` as the bootstrap script.
#
# * options
# + urlToOpen:
@@ -261,15 +264,25 @@ class AtomApplication
# + devMode:
# Boolean to control the opened window's dev mode.
openUrl: ({urlToOpen, devMode}) ->
parsedUrl = url.parse(urlToOpen)
if parsedUrl.host is 'session'
sessionId = parsedUrl.path.split('/')[1]
console.log "Joining session #{sessionId}"
if sessionId
bootstrapScript = 'collaboration/lib/bootstrap'
new AtomWindow({bootstrapScript, @resourcePath, sessionId, devMode})
unless @packages?
PackageManager = require './package-manager'
fsUtils = require './fs-utils'
@packages = new PackageManager
configDirPath: fsUtils.absolute('~/.atom')
devMode: devMode
resourcePath: @resourcePath
packageName = url.parse(urlToOpen).host
pack = _.find @packages.getAvailablePackageMetadata(), ({name}) -> name is packageName
if pack?
if pack.urlMain
packagePath = @packages.resolvePackagePath(packageName)
bootstrapScript = path.resolve(packagePath, pack.urlMain)
new AtomWindow({bootstrapScript, @resourcePath, devMode, urlToOpen})
else
console.log "Package '#{pack.name}' does not have a url main: #{urlToOpen}"
else
console.log "Opening unknown url #{urlToOpen}"
console.log "Opening unknown url: #{urlToOpen}"
# Private: Opens up a new {AtomWindow} to run specs within.
#

View File

@@ -30,9 +30,8 @@ class AtomWindow
loadSettings = _.extend({}, settings)
loadSettings.windowState ?= ''
loadSettings.initialPath = pathToOpen
try
if fs.statSync(pathToOpen).isFile()
loadSettings.initialPath = path.dirname(pathToOpen)
if fs.statSyncNoException(pathToOpen).isFile?()
loadSettings.initialPath = path.dirname(pathToOpen)
@browserWindow.loadSettings = loadSettings
@browserWindow.once 'window:loaded', => @loaded = true
@@ -55,6 +54,8 @@ class AtomWindow
false
else if pathToCheck is initialPath
true
else if fs.statSyncNoException(pathToCheck).isDirectory?()
false
else if pathToCheck.indexOf(path.join(initialPath, path.sep)) is 0
true
else

View File

@@ -27,6 +27,9 @@ class Atom
initialize: ->
@unsubscribe()
{devMode, resourcePath} = atom.getLoadSettings()
configDirPath = @getConfigDirPath()
Config = require './config'
Keymap = require './keymap'
PackageManager = require './package-manager'
@@ -36,9 +39,9 @@ class Atom
ContextMenuManager = require './context-menu-manager'
MenuManager = require './menu-manager'
@config = new Config()
@config = new Config({configDirPath, resourcePath})
@keymap = new Keymap()
@packages = new PackageManager()
@packages = new PackageManager({devMode, configDirPath, resourcePath})
#TODO Remove once packages have been updated to not touch atom.packageStates directly
@__defineGetter__ 'packageStates', => @packages.packageStates
@@ -46,7 +49,7 @@ class Atom
@subscribe @packages, 'loaded', => @watchThemes()
@themes = new ThemeManager()
@contextMenu = new ContextMenuManager(@getLoadSettings().devMode)
@contextMenu = new ContextMenuManager(devMode)
@menu = new MenuManager()
@pasteboard = new Pasteboard()
@syntax = deserialize(@getWindowState('syntax')) ? new Syntax()
@@ -215,6 +218,10 @@ class Atom
getHomeDirPath: ->
app.getHomeDir()
# Public: Get the directory path to Atom's configuration area.
getConfigDirPath: ->
@configDirPath ?= fsUtils.absolute('~/.atom')
getWindowStatePath: ->
switch @windowMode
when 'spec'
@@ -246,7 +253,7 @@ class Atom
documentStateJson = @getLoadSettings().windowState
try
documentState = JSON.parse(documentStateJson) if documentStateJson?
documentState = JSON.parse(documentStateJson) if documentStateJson
catch error
console.warn "Error parsing window state: #{windowStatePath}", error.stack, error

View File

@@ -7,8 +7,6 @@ path = require 'path'
async = require 'async'
pathWatcher = require 'pathwatcher'
configDirPath = fsUtils.absolute("~/.atom")
# Public: Used to access all of Atom's configuration details.
#
# A global instance of this class is available to all plugins which can be
@@ -35,28 +33,27 @@ class Config
configFileHasErrors: null
# Private: Created during initialization, available as `global.config`
constructor: ->
@configDirPath = configDirPath
@bundledKeymapsDirPath = path.join(resourcePath, "keymaps")
constructor: ({@configDirPath, @resourcePath}={}) ->
@bundledKeymapsDirPath = path.join(@resourcePath, "keymaps")
@bundledMenusDirPath = path.join(resourcePath, "menus")
@nodeModulesDirPath = path.join(resourcePath, "node_modules")
@nodeModulesDirPath = path.join(@resourcePath, "node_modules")
@bundledPackageDirPaths = [@nodeModulesDirPath]
@lessSearchPaths = [
path.join(resourcePath, 'static', 'variables')
path.join(resourcePath, 'static')
path.join(@resourcePath, 'static', 'variables')
path.join(@resourcePath, 'static')
]
@packageDirPaths = [path.join(configDirPath, "packages")]
@packageDirPaths = [path.join(@configDirPath, "packages")]
if atom.getLoadSettings().devMode
@packageDirPaths.unshift(path.join(configDirPath, "dev", "packages"))
@packageDirPaths.unshift(path.join(@configDirPath, "dev", "packages"))
@userPackageDirPaths = _.clone(@packageDirPaths)
@userStoragePath = path.join(configDirPath, "storage")
@userStoragePath = path.join(@configDirPath, "storage")
@defaultSettings =
core: _.clone(require('./root-view').configDefaults)
editor: _.clone(require('./editor').configDefaults)
@settings = {}
@configFilePath = fsUtils.resolve(configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= path.join(configDirPath, 'config.cson')
@configFilePath = fsUtils.resolve(@configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= path.join(@configDirPath, 'config.cson')
# Private:
initializeConfigDirectory: (done) ->
@@ -68,7 +65,7 @@ class Config
fsUtils.copy(sourcePath, destinationPath, callback)
queue.drain = done
templateConfigDirPath = fsUtils.resolve(window.resourcePath, 'dot-atom')
templateConfigDirPath = fsUtils.resolve(@resourcePath, 'dot-atom')
onConfigDirFile = (sourcePath) =>
relativePath = sourcePath.substring(templateConfigDirPath.length + 1)
destinationPath = path.join(@configDirPath, relativePath)

View File

@@ -54,6 +54,14 @@ class CursorView extends View
@setVisible(@cursor.isVisible() and not @editor.isFoldedAtScreenRow(screenPosition.row))
# Override for speed. The base function checks the computedStyle
isHidden: ->
style = this[0].style
if style.display == 'none' or not @isOnDom()
true
else
false
needsAutoscroll: ->
@cursor.needsAutoscroll

View File

@@ -9,11 +9,16 @@ fsUtils = require './fs-utils'
$ = require './jquery-extensions'
_ = require './underscore-extensions'
MeasureRange = document.createRange()
TextNodeFilter = { acceptNode: -> NodeFilter.FILTER_ACCEPT }
NoScope = ['no-scope']
# Private: Represents the entire visual pane in Atom.
#
# The Editor manages the {EditSession}, which manages the file buffers.
module.exports =
class Editor extends View
@characterWidthCache: {}
@configDefaults:
fontSize: 20
showInvisibles: false
@@ -703,7 +708,12 @@ class Editor extends View
@on 'cursor:moved', =>
return unless @isFocused
cursorView = @getCursorView()
@hiddenInput.offset(cursorView.offset()) if cursorView.is(':visible')
if cursorView.isVisible()
# This is an order of magnitude faster than checking .offset().
style = cursorView[0].style
@hiddenInput[0].style.top = style.top
@hiddenInput[0].style.left = style.left
selectedText = null
@hiddenInput.on 'compositionstart', =>
@@ -961,6 +971,9 @@ class Editor extends View
# fontSize - A {Number} indicating the font size in pixels.
setFontSize: (fontSize) ->
@css('font-size', "#{fontSize}px}")
@clearCharacterWidthCache()
if @isOnDom()
@redraw()
else
@@ -977,6 +990,9 @@ class Editor extends View
# fontFamily - A {String} identifying the CSS `font-family`,
setFontFamily: (fontFamily='') ->
@css('font-family', fontFamily)
@clearCharacterWidthCache()
@redraw()
# Gets the font family for the editor.
@@ -1133,6 +1149,14 @@ class Editor extends View
@layerMinWidth = minWidth
@trigger 'editor:min-width-changed'
# Override for speed. The base function checks computedStyle, unnecessary here.
isHidden: ->
style = this[0].style
if style.display == 'none' or not @isOnDom()
true
else
false
clearRenderedLines: ->
@renderedLines.empty()
@firstRenderedScreenRow = null
@@ -1184,9 +1208,15 @@ class Editor extends View
for cursorView in @getCursorViews()
if cursorView.needsRemoval
cursorView.remove()
else if cursorView.needsUpdate
else if @shouldUpdateCursor(cursorView)
cursorView.updateDisplay()
shouldUpdateCursor: (cursorView) ->
return false unless cursorView.needsUpdate
pos = cursorView.getScreenPosition()
pos.row >= @firstRenderedScreenRow and pos.row <= @lastRenderedScreenRow
updateSelectionViews: ->
if @newSelections.length > 0
@addSelectionView(selection) for selection in @newSelections when not selection.destroyed
@@ -1195,9 +1225,15 @@ class Editor extends View
for selectionView in @getSelectionViews()
if selectionView.needsRemoval
selectionView.remove()
else
else if @shouldUpdateSelection(selectionView)
selectionView.updateDisplay()
shouldUpdateSelection: (selectionView) ->
screenRange = selectionView.getScreenRange()
startRow = screenRange.start.row
endRow = screenRange.end.row
(startRow >= @firstRenderedScreenRow and startRow <= @lastRenderedScreenRow) or (endRow >= @firstRenderedScreenRow and endRow <= @lastRenderedScreenRow)
syncCursorAnimations: ->
for cursorView in @getCursorViews()
do (cursorView) -> cursorView.resetBlinking()
@@ -1229,10 +1265,11 @@ class Editor extends View
if @pendingChanges.length == 0 and @firstRenderedScreenRow and @firstRenderedScreenRow <= renderFrom and renderTo <= @lastRenderedScreenRow
return
@gutter.updateLineNumbers(@pendingChanges, renderFrom, renderTo)
intactRanges = @computeIntactRanges()
@pendingChanges = []
@truncateIntactRanges(intactRanges, renderFrom, renderTo)
changes = @pendingChanges
intactRanges = @computeIntactRanges(renderFrom, renderTo)
@gutter.updateLineNumbers(changes, renderFrom, renderTo)
@clearDirtyRanges(intactRanges)
@fillDirtyRanges(intactRanges, renderFrom, renderTo)
@firstRenderedScreenRow = renderFrom
@@ -1258,7 +1295,7 @@ class Editor extends View
emptyLineChanges
computeIntactRanges: ->
computeIntactRanges: (renderFrom, renderTo) ->
return [] if !@firstRenderedScreenRow? and !@lastRenderedScreenRow?
intactRanges = [{start: @firstRenderedScreenRow, end: @lastRenderedScreenRow, domStart: 0}]
@@ -1293,6 +1330,9 @@ class Editor extends View
domStart: range.domStart + change.end + 1 - range.start
)
intactRanges = newIntactRanges
@truncateIntactRanges(intactRanges, renderFrom, renderTo)
@pendingChanges = []
intactRanges
@@ -1312,35 +1352,35 @@ class Editor extends View
intactRanges.sort (a, b) -> a.domStart - b.domStart
clearDirtyRanges: (intactRanges) ->
renderedLines = @renderedLines[0]
killLine = (line) ->
next = line.nextSibling
renderedLines.removeChild(line)
next
if intactRanges.length == 0
@renderedLines.empty()
else if currentLine = renderedLines.firstChild
@renderedLines[0].innerHTML = ''
else if currentLine = @renderedLines[0].firstChild
domPosition = 0
for intactRange in intactRanges
while intactRange.domStart > domPosition
currentLine = killLine(currentLine)
currentLine = @clearLine(currentLine)
domPosition++
for i in [intactRange.start..intactRange.end]
currentLine = currentLine.nextSibling
domPosition++
while currentLine
currentLine = killLine(currentLine)
currentLine = @clearLine(currentLine)
clearLine: (lineElement) ->
next = lineElement.nextSibling
@renderedLines[0].removeChild(lineElement)
next
fillDirtyRanges: (intactRanges, renderFrom, renderTo) ->
renderedLines = @renderedLines[0]
nextIntact = intactRanges.shift()
currentLine = renderedLines.firstChild
i = 0
nextIntact = intactRanges[i]
currentLine = @renderedLines[0].firstChild
row = renderFrom
while row <= renderTo
if row == nextIntact?.end + 1
nextIntact = intactRanges.shift()
nextIntact = intactRanges[++i]
if !nextIntact or row < nextIntact.start
if nextIntact
dirtyRangeEnd = nextIntact.start - 1
@@ -1348,7 +1388,7 @@ class Editor extends View
dirtyRangeEnd = renderTo
for lineElement in @buildLineElementsForScreenRows(row, dirtyRangeEnd)
renderedLines.insertBefore(lineElement, currentLine)
@renderedLines[0].insertBefore(lineElement, currentLine)
row++
else
currentLine = currentLine.nextSibling
@@ -1369,14 +1409,18 @@ class Editor extends View
#
# Returns a {Number}.
getFirstVisibleScreenRow: ->
Math.floor(@scrollTop() / @lineHeight)
screenRow = Math.floor(@scrollTop() / @lineHeight)
screenRow = 0 if isNaN(screenRow)
screenRow
# Retrieves the number of the row that is visible and currently at the bottom of the editor.
#
# Returns a {Number}.
getLastVisibleScreenRow: ->
calculatedRow = Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1
Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
screenRow = Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
screenRow = 0 if isNaN(screenRow)
screenRow
# Given a row number, identifies if it is currently visible.
#
@@ -1401,11 +1445,11 @@ class Editor extends View
new Array(div.children...)
htmlForScreenRows: (startRow, endRow) ->
htmlLines = []
htmlLines = ''
screenRow = startRow
for line in @activeEditSession.linesForScreenRows(startRow, endRow)
htmlLines.push(@htmlForScreenLine(line, screenRow++))
htmlLines.join('\n\n')
htmlLines += @htmlForScreenLine(line, screenRow++)
htmlLines
htmlForScreenLine: (screenLine, screenRow) ->
{ tokens, text, lineEnding, fold, isSoftWrapped } = screenLine
@@ -1496,28 +1540,92 @@ class Editor extends View
unless existingLineElement
lineElement = @buildLineElementForScreenRow(actualRow)
@renderedLines.append(lineElement)
left = @positionLeftForLineAndColumn(lineElement, column)
left = @positionLeftForLineAndColumn(lineElement, actualRow, column)
unless existingLineElement
@renderedLines[0].removeChild(lineElement)
{ top: row * @lineHeight, left }
positionLeftForLineAndColumn: (lineElement, column) ->
return 0 if column is 0
delta = 0
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, acceptNode: -> NodeFilter.FILTER_ACCEPT)
while textNode = iterator.nextNode()
nextDelta = delta + textNode.textContent.length
if nextDelta >= column
offset = column - delta
break
delta = nextDelta
positionLeftForLineAndColumn: (lineElement, screenRow, column) ->
return 0 if column == 0
range = document.createRange()
range.setEnd(textNode, offset)
range.collapse()
leftPixels = range.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
range.detach()
leftPixels
bufferRow = @bufferRowsForScreenRows(screenRow, screenRow)[0] ? screenRow
tokenizedLine = @activeEditSession.displayBuffer.tokenizedBuffer.tokenizedLines[bufferRow]
left = 0
index = 0
for token in tokenizedLine.tokens
for char in token.value
return left if index >= column
val = @getCharacterWidthCache(token.scopes, char)
if val?
left += val
else
return @measureToColumn(lineElement, tokenizedLine, column)
index++
left
scopesForColumn: (tokenizedLine, column) ->
index = 0
for token in tokenizedLine.tokens
for char in token.value
return token.scopes if index == column
index++
null
measureToColumn: (lineElement, tokenizedLine, column) ->
left = oldLeft = index = 0
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TextNodeFilter)
returnLeft = null
while textNode = iterator.nextNode()
content = textNode.textContent
for char, i in content
# Dont return right away, finish caching the whole line
returnLeft = left if index == column
oldLeft = left
scopes = @scopesForColumn(tokenizedLine, index)
cachedVal = @getCharacterWidthCache(scopes, char)
if cachedVal?
left = oldLeft + cachedVal
else
# i + 1 to measure to the end of the current character
MeasureRange.setEnd(textNode, i + 1)
MeasureRange.collapse()
rects = MeasureRange.getClientRects()
return 0 if rects.length == 0
left = rects[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
@setCharacterWidthCache(scopes, char, left - oldLeft) if scopes?
index++
returnLeft ? left
getCharacterWidthCache: (scopes, char) ->
scopes ?= NoScope
obj = Editor.characterWidthCache
for scope in scopes
obj = obj[scope]
return null unless obj?
obj[char]
setCharacterWidthCache: (scopes, char, val) ->
scopes ?= NoScope
obj = Editor.characterWidthCache
for scope in scopes
obj[scope] ?= {}
obj = obj[scope]
obj[char] = val
clearCharacterWidthCache: ->
Editor.characterWidthCache = {}
pixelOffsetForScreenPosition: (position) ->
{top, left} = @pixelPositionForScreenPosition(position)
@@ -1591,30 +1699,9 @@ class Editor extends View
scopeStack = []
line = []
updateScopeStack = (desiredScopes) ->
excessScopes = scopeStack.length - desiredScopes.length
_.times(excessScopes, popScope) if excessScopes > 0
# pop until common prefix
for i in [scopeStack.length..0]
break if _.isEqual(scopeStack[0...i], desiredScopes[0...i])
popScope()
# push on top of common prefix until scopeStack == desiredScopes
for j in [i...desiredScopes.length]
pushScope(desiredScopes[j])
pushScope = (scope) ->
scopeStack.push(scope)
line.push("<span class=\"#{scope.replace(/\./g, ' ')}\">")
popScope = ->
scopeStack.pop()
line.push("</span>")
attributePairs = []
attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes
line.push("<div #{attributePairs.join(' ')}>")
attributePairs = ''
attributePairs += " #{attributeName}=\"#{value}\"" for attributeName, value of attributes
line.push("<div #{attributePairs}>")
if text == ''
html = Editor.buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini)
@@ -1625,39 +1712,64 @@ class Editor extends View
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
position = 0
for token in tokens
updateScopeStack(token.scopes)
@updateScopeStack(line, scopeStack, token.scopes)
hasLeadingWhitespace = position < firstNonWhitespacePosition
hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition
hasIndentGuide = not mini and showIndentGuide and (hasLeadingWhitespace or lineIsWhitespaceOnly)
line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide}))
position += token.value.length
popScope() while scopeStack.length > 0
@popScope(line, scopeStack) while scopeStack.length > 0
line.push(htmlEolInvisibles) unless text == ''
line.push("<span class='fold-marker'/>") if fold
line.push('</div>')
line.join('')
@updateScopeStack: (line, scopeStack, desiredScopes) ->
excessScopes = scopeStack.length - desiredScopes.length
if excessScopes > 0
@popScope(line, scopeStack) while excessScopes--
# pop until common prefix
for i in [scopeStack.length..0]
break if _.isEqual(scopeStack[0...i], desiredScopes[0...i])
@popScope(line, scopeStack)
# push on top of common prefix until scopeStack == desiredScopes
for j in [i...desiredScopes.length]
@pushScope(line, scopeStack, desiredScopes[j])
null
@pushScope: (line, scopeStack, scope) ->
scopeStack.push(scope)
line.push("<span class=\"#{scope.replace(/\./g, ' ')}\">")
@popScope: (line, scopeStack) ->
scopeStack.pop()
line.push("</span>")
@buildEmptyLineHtml: (showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini) ->
indentCharIndex = 0
if not mini and showIndentGuide
if indentation > 0
tabLength = activeEditSession.getTabLength()
indentGuideHtml = []
indentGuideHtml = ''
for level in [0...indentation]
indentLevelHtml = ["<span class='indent-guide'>"]
indentLevelHtml = "<span class='indent-guide'>"
for characterPosition in [0...tabLength]
if invisible = eolInvisibles.shift()
indentLevelHtml.push("<span class='invisible-character'>#{invisible}</span>")
if invisible = eolInvisibles[indentCharIndex++]
indentLevelHtml += "<span class='invisible-character'>#{invisible}</span>"
else
indentLevelHtml.push(' ')
indentLevelHtml.push("</span>")
indentGuideHtml.push(indentLevelHtml.join(''))
indentLevelHtml += ' '
indentLevelHtml += "</span>"
indentGuideHtml += indentLevelHtml
for invisible in eolInvisibles
indentGuideHtml.push("<span class='invisible-character'>#{invisible}</span>")
indentGuideHtml += "<span class='invisible-character'>#{invisible}</span>"
return indentGuideHtml.join('')
return indentGuideHtml
if htmlEolInvisibles.length > 0
htmlEolInvisibles

View File

@@ -44,6 +44,7 @@ class Git
path: null
statuses: null
upstream: null
branch: null
statusTask: null
# Private: Creates a new `Git` object.
@@ -142,6 +143,12 @@ class Git
# Public: Determine if the given path is new.
isPathNew: (path) -> @isStatusNew(@getPathStatus(path))
# Public: Is the project at the root of this repository?
#
# Returns true if at the root, false if in a subfolder of the repository.
isProjectAtRoot: ->
@projectAtRoot ?= project.relativize(@getWorkingDirectory()) is ''
# Public: Makes a path relative to the repository's working directory.
relativize: (path) -> @getRepo().relativize(path)
@@ -171,6 +178,15 @@ class Git
@getPathStatus(path) if headCheckedOut
headCheckedOut
# Public: Checks out a branch in your repository.
#
# reference - The {String} reference to checkout
# create - A {Boolean} value which, if `true` creates the new reference if it doesn't exist.
#
# Returns a {Boolean} that's `true` if the method was successful.
checkoutReference: (reference, create) ->
@getRepo().checkoutReference(reference, create)
# Public: Retrieves the number of lines added and removed to a path.
#
# This compares the working directory contents of the path to the `HEAD`
@@ -239,6 +255,12 @@ class Git
# Public: ?
getReferenceTarget: (reference) -> @getRepo().getReferenceTarget(reference)
# Public: Gets all the local and remote references.
#
# Returns an object with three keys: `heads`, `remotes`, and `tags`. Each key
# can be an array of strings containing the reference names.
getReferences: -> @getRepo().getReferences()
# Public: ?
getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference)
@@ -247,8 +269,9 @@ class Git
# Private:
refreshStatus: ->
@statusTask = Task.once require.resolve('./repository-status-handler'), @getPath(), ({statuses, upstream}) =>
statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream)
@statusTask = Task.once require.resolve('./repository-status-handler'), @getPath(), ({statuses, upstream, branch}) =>
statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream) and _.isEqual(branch, @branch)
@statuses = statuses
@upstream = upstream
@branch = branch
@trigger 'statuses-changed' unless statusesUnchanged

View File

@@ -62,6 +62,76 @@ class Gutter extends View
setShowLineNumbers: (showLineNumbers) ->
if showLineNumbers then @lineNumbers.show() else @lineNumbers.hide()
# Get all the line-number divs.
#
# Returns a list of {HTMLElement}s.
getLineNumberElements: ->
@lineNumbers[0].childNodes
# Get all the line-number divs.
#
# Returns a list of {HTMLElement}s.
getLineNumberElementsForClass: (klass) ->
@lineNumbers[0].getElementsByClassName(klass)
# Get a single line-number div.
#
# * bufferRow: 0 based line number
#
# Returns a list of {HTMLElement}s that correspond to the bufferRow. More than
# one in the list indicates a wrapped line.
getLineNumberElement: (bufferRow) ->
@getLineNumberElementsForClass("line-number-#{bufferRow}")
# Add a class to all line-number divs.
#
# * klass: string class name
#
# Returns true if the class was added to any lines
addClassToAllLines: (klass)->
elements = @getLineNumberElements()
el.classList.add(klass) for el in elements
!!elements.length
# Remove a class from all line-number divs.
#
# * klass: string class name. Can only be one class name. i.e. 'my-class'
#
# Returns true if the class was removed from any lines
removeClassFromAllLines: (klass)->
# This is faster than calling $.removeClass on all lines, and faster than
# making a new array and iterating through it.
elements = @getLineNumberElementsForClass(klass)
willRemoveClasses = !!elements.length
elements[0].classList.remove(klass) while elements.length > 0
willRemoveClasses
# Add a class to a single line-number div
#
# * bufferRow: 0 based line number
# * klass: string class name
#
# Returns true if there were lines the class was added to
addClassToLine: (bufferRow, klass)->
elements = @getLineNumberElement(bufferRow)
el.classList.add(klass) for el in elements
!!elements.length
# Remove a class from a single line-number div
#
# * bufferRow: 0 based line number
# * klass: string class name
#
# Returns true if there were lines the class was removed from
removeClassFromLine: (bufferRow, klass)->
classesRemoved = false
elements = @getLineNumberElement(bufferRow)
for el in elements
hasClass = el.classList.contains(klass)
classesRemoved |= hasClass
el.classList.remove(klass) if hasClass
classesRemoved
### Internal ###
updateLineNumbers: (changes, renderFrom, renderTo) ->
@@ -78,30 +148,35 @@ class Gutter extends View
@renderLineNumbers(renderFrom, renderTo) if performUpdate
renderLineNumbers: (startScreenRow, endScreenRow) ->
editor = @getEditor()
maxDigits = editor.getLineCount().toString().length
rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow)
cursorScreenRow = editor.getCursorScreenPosition().row
@lineNumbers[0].innerHTML = $$$ ->
for row in rows
if row == lastScreenRow
rowValue = ''
else
rowValue = (row + 1).toString()
classes = ['line-number']
classes.push('fold') if editor.isFoldedAtBufferRow(row)
@div linenumber: row, class: classes.join(' '), =>
rowValuePadding = _.multiplyString('&nbsp;', maxDigits - rowValue.length)
@raw("#{rowValuePadding}#{rowValue}")
lastScreenRow = row
@lineNumbers[0].innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
@firstScreenRow = startScreenRow
@lastScreenRow = endScreenRow
@highlightedRows = null
@highlightLines()
buildLineElementsHtml: (startScreenRow, endScreenRow) =>
editor = @getEditor()
maxDigits = editor.getLineCount().toString().length
rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow)
html = ''
for row in rows
if row == lastScreenRow
rowValue = ''
else
rowValue = (row + 1).toString()
classes = "line-number line-number-#{row}"
classes += ' fold' if editor.isFoldedAtBufferRow(row)
rowValuePadding = _.multiplyString('&nbsp;', maxDigits - rowValue.length)
html += """<div class="#{classes}">#{rowValuePadding}#{rowValue}</div>"""
lastScreenRow = row
html
removeLineHighlights: ->
return unless @highlightedLineNumbers
for line in @highlightedLineNumbers

View File

@@ -38,18 +38,16 @@ $.fn.isVisible = ->
!@isHidden()
$.fn.isHidden = ->
# Implementation taken from jQuery's `:hidden` expression code:
# https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js
#
# We were using a pseudo selector: @is(':hidden'). But jQuery's pseudo
# selector code checks the element's webkitMatchesSelector, which is always
# false, and is really really really slow.
# We used to check @is(':hidden'). But this is much faster than the
# offsetWidth/offsetHeight check + all the pseudo selector mess in jquery.
style = this[0].style
elem = this[0]
return null unless elem
elem.offsetWidth <= 0 and elem.offsetHeight <= 0
if style.display == 'none' or not @isOnDom()
true
else if style.display
false
else
getComputedStyle(this[0]).display == 'none'
$.fn.isDisabled = ->
!!@attr('disabled')

View File

@@ -8,7 +8,11 @@ module.exports =
class PackageManager
_.extend @prototype, EventEmitter
constructor: ->
constructor: ({configDirPath, devMode, @resourcePath}) ->
@packageDirPaths = [path.join(configDirPath, "packages")]
if devMode
@packageDirPaths.unshift(path.join(configDirPath, "dev", "packages"))
@loadedPackages = {}
@activePackages = {}
@packageStates = {}
@@ -83,10 +87,10 @@ class PackageManager
resolvePackagePath: (name) ->
return name if fsUtils.isDirectorySync(name)
packagePath = fsUtils.resolve(config.packageDirPaths..., name)
packagePath = fsUtils.resolve(@packageDirPaths..., name)
return packagePath if fsUtils.isDirectorySync(packagePath)
packagePath = path.join(window.resourcePath, 'node_modules', name)
packagePath = path.join(@resourcePath, 'node_modules', name)
return packagePath if @isInternalPackage(packagePath)
isInternalPackage: (packagePath) ->
@@ -108,11 +112,11 @@ class PackageManager
getAvailablePackagePaths: ->
packagePaths = []
for packageDirPath in config.packageDirPaths
for packageDirPath in @packageDirPaths
for packagePath in fsUtils.listSync(packageDirPath)
packagePaths.push(packagePath) if fsUtils.isDirectorySync(packagePath)
for packagePath in fsUtils.listSync(path.join(window.resourcePath, 'node_modules'))
for packagePath in fsUtils.listSync(path.join(@resourcePath, 'node_modules'))
packagePaths.push(packagePath) if @isInternalPackage(packagePath)
_.uniq(packagePaths)
@@ -122,8 +126,8 @@ class PackageManager
getAvailablePackageMetadata: ->
packages = []
for packagePath in atom.getAvailablePackagePaths()
for packagePath in @getAvailablePackagePaths()
name = path.basename(packagePath)
metadata = atom.getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
packages.push(metadata)
packages

View File

@@ -9,9 +9,11 @@ module.exports = (repoPath) ->
for filePath, status of repo.getStatus()
statuses[path.join(workingDirectoryPath, filePath)] = status
upstream = repo.getAheadBehindCount()
branch = repo.getHead()
repo.release()
else
upstream = {}
statuses = {}
branch = null
{statuses, upstream}
{statuses, upstream, branch}

View File

@@ -1,7 +1,14 @@
_ = require './underscore-extensions'
textUtils = require './text-utils'
whitespaceRegexesByTabLength = {}
WhitespaceRegexesByTabLength = {}
LeadingWhitespaceRegex = /^[ ]+/
TrailingWhitespaceRegex = /[ ]+$/
EscapeRegex = /[&"'<>]/g
CharacterRegex = /./g
StartCharacterRegex = /^./
StartDotRegex = /^\.?/
WhitespaceRegex = /\S/
# Private: Represents a single unit of text as selected by a grammar.
module.exports =
@@ -33,7 +40,7 @@ class Token
[new Token(value: value1, scopes: @scopes), new Token(value: value2, scopes: @scopes)]
whitespaceRegexForTabLength: (tabLength) ->
whitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
WhitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
breakOutAtomicTokens: (tabLength, breakOutLeadingWhitespace) ->
if @hasSurrogatePair
@@ -112,10 +119,10 @@ class Token
)
isOnlyWhitespace: ->
not /\S/.test(@value)
not WhitespaceRegex.test(@value)
matchesScopeSelector: (selector) ->
targetClasses = selector.replace(/^\.?/, '').split('.')
targetClasses = selector.replace(StartDotRegex, '').split('.')
_.any @scopes, (scope) ->
scopeClasses = scope.split('.')
_.isSubset(targetClasses, scopeClasses)
@@ -123,39 +130,59 @@ class Token
getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})->
invisibles ?= {}
html = @value
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
if @isHardTab
classes = []
classes.push('indent-guide') if hasIndentGuide
classes.push('invisible-character') if invisibles.tab
classes.push('hard-tab')
classes = classes.join(' ')
html = html.replace /^./, (match) ->
classes = 'hard-tab'
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if invisibles.tab
html = html.replace StartCharacterRegex, (match) =>
match = invisibles.tab ? match
"<span class='#{classes}'>#{match}</span>"
"<span class='#{classes}'>#{@escapeString(match)}</span>"
else
if hasLeadingWhitespace
classes = []
classes.push('indent-guide') if hasIndentGuide
classes.push('invisible-character') if invisibles.space
classes.push('leading-whitespace')
classes = classes.join(' ')
html = html.replace /^[ ]+/, (match) ->
match = match.replace(/./g, invisibles.space) if invisibles.space
"<span class='#{classes}'>#{match}</span>"
if hasTrailingWhitespace
classes = []
classes.push('indent-guide') if hasIndentGuide and not hasLeadingWhitespace
classes.push('invisible-character') if invisibles.space
classes.push('trailing-whitespace')
classes = classes.join(' ')
html = html.replace /[ ]+$/, (match) ->
match = match.replace(/./g, invisibles.space) if invisibles.space
"<span class='#{classes}'>#{match}</span>"
startIndex = 0
endIndex = html.length
leadingHtml = ''
trailingHtml = ''
if hasLeadingWhitespace and match = LeadingWhitespaceRegex.exec(html)
classes = 'leading-whitespace'
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if invisibles.space
match[0] = match[0].replace(CharacterRegex, invisibles.space) if invisibles.space
leadingHtml = "<span class='#{classes}'>#{match[0]}</span>"
startIndex = match[0].length
if hasTrailingWhitespace and match = TrailingWhitespaceRegex.exec(html)
classes = 'trailing-whitespace'
classes += ' indent-guide' if hasIndentGuide and not hasLeadingWhitespace
classes += ' invisible-character' if invisibles.space
match[0] = match[0].replace(CharacterRegex, invisibles.space) if invisibles.space
trailingHtml = "<span class='#{classes}'>#{match[0]}</span>"
endIndex = match.index
html = leadingHtml + @escapeString(html, startIndex, endIndex) + trailingHtml
html
escapeString: (str, startIndex, endIndex) ->
strLength = str.length
startIndex ?= 0
endIndex ?= strLength
str = str.slice(startIndex, endIndex) if startIndex > 0 or endIndex < strLength
str.replace(EscapeRegex, @escapeStringReplace)
escapeStringReplace: (match) ->
switch match
when '&' then '&amp;'
when '"' then '&quot;'
when "'" then '&#39;'
when '<' then '&lt;'
when '>' then '&gt;'
else match

2
vendor/apm vendored

Submodule vendor/apm updated: 162824eb1a...fcb19e296c