mirror of
https://github.com/atom/atom.git
synced 2026-01-21 04:48:12 -05:00
Merge branch 'master' into menu-api
Conflicts: src/atom.coffee src/config.coffee
This commit is contained in:
1
atom.sh
1
atom.sh
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
20
package.json
20
package.json
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(' ', 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(' ', maxDigits - rowValue.length)
|
||||
|
||||
html += """<div class="#{classes}">#{rowValuePadding}#{rowValue}</div>"""
|
||||
|
||||
lastScreenRow = row
|
||||
|
||||
html
|
||||
|
||||
removeLineHighlights: ->
|
||||
return unless @highlightedLineNumbers
|
||||
for line in @highlightedLineNumbers
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
|
||||
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 '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
else match
|
||||
|
||||
2
vendor/apm
vendored
2
vendor/apm
vendored
Submodule vendor/apm updated: 162824eb1a...fcb19e296c
Reference in New Issue
Block a user