Merge branch 'master' into privilege-escalation

Conflicts:
	src/text-buffer.coffee
This commit is contained in:
Cheng Zhao
2014-02-12 19:32:49 +08:00
33 changed files with 812 additions and 581 deletions

View File

@@ -71,27 +71,3 @@ will only attempt to call deserialize if the two versions match, and otherwise
return undefined. We plan on implementing a migration system in the future, but
this at least protects you from improperly deserializing old state. If you find
yourself in dire need of the migration system, let us know.
### Deferred Package Deserializers
If your package defers loading on startup with an `activationEvents` property in
its `package.cson`, your deserializers won't be loaded until your package is
activated. If you want to deserialize an object from your package on startup,
this could be a problem.
The solution is to also supply a `deferredDeserializers` array in your
`package.cson` with the names of all your deserializers. When Atom attempts to
deserialize some state whose `deserializer` matches one of these names, it will
load your package first so it can register any necessary deserializers before
proceeding.
For example, the markdown preview package doesn't fully load until a preview is
triggered. But if you refresh a window with a preview pane, it loads the
markdown package early so Atom can deserialize the view correctly.
```coffee-script
# markdown-preview/package.cson
'activationEvents': 'markdown-preview:toggle': '.editor'
'deferredDeserializers': ['MarkdownPreviewView']
...
```

View File

@@ -68,6 +68,8 @@
'cmd-=': 'window:increase-font-size'
'cmd-+': 'window:increase-font-size'
'cmd--': 'window:decrease-font-size'
'cmd-_': 'window:decrease-font-size'
'cmd-0': 'window:reset-font-size'
'cmd-k up': 'pane:split-up' # Atom Specific
'cmd-k down': 'pane:split-down' # Atom Specific
@@ -122,7 +124,6 @@
'alt-cmd-z': 'editor:checkout-head-revision'
'cmd-<': 'editor:scroll-to-cursor'
'alt-cmd-ctrl-f': 'editor:fold-selection'
'cmd-=': 'editor:auto-indent'
# Sublime Parity
'cmd-enter': 'editor:newline-below'

View File

@@ -40,6 +40,8 @@
'ctrl-=': 'window:increase-font-size'
'ctrl-+': 'window:increase-font-size'
'ctrl--': 'window:decrease-font-size'
'ctrl-_': 'window:decrease-font-size'
'ctrl-0': 'window:reset-font-size'
'ctrl-k up': 'pane:split-up' # Atom Specific
'ctrl-k down': 'pane:split-down' # Atom Specific
@@ -69,7 +71,6 @@
'alt-ctrl-z': 'editor:checkout-head-revision'
'ctrl-<': 'editor:scroll-to-cursor'
'alt-ctrl-f': 'editor:fold-selection'
'ctrl-=': 'editor:auto-indent'
# Sublime Parity
'ctrl-enter': 'editor:newline-below'

View File

@@ -63,12 +63,12 @@
"base16-tomorrow-dark-theme": "0.11.0",
"solarized-dark-syntax": "0.9.0",
"solarized-light-syntax": "0.5.0",
"archive-view": "0.21.0",
"archive-view": "0.22.0",
"autocomplete": "0.22.0",
"autoflow": "0.13.0",
"autosave": "0.10.0",
"background-tips": "0.6.0",
"bookmarks": "0.18.0",
"autoflow": "0.14.0",
"autosave": "0.11.0",
"background-tips": "0.7.0",
"bookmarks": "0.19.0",
"bracket-matcher": "0.20.0",
"command-logger": "0.11.0",
"command-palette": "0.16.0",
@@ -76,23 +76,23 @@
"editor-stats": "0.13.0",
"exception-reporting": "0.13.0",
"feedback": "0.23.0",
"find-and-replace": "0.81.0",
"find-and-replace": "0.82.0",
"fuzzy-finder": "0.34.0",
"gists": "0.17.0",
"git-diff": "0.24.0",
"github-sign-in": "0.18.0",
"github-sign-in": "0.19.0",
"go-to-line": "0.16.0",
"grammar-selector": "0.19.0",
"image-view": "0.19.0",
"keybinding-resolver": "0.9.0",
"keybinding-resolver": "0.10.0",
"link": "0.17.0",
"markdown-preview": "0.25.1",
"markdown-preview": "0.28.0",
"metrics": "0.26.0",
"package-generator": "0.26.0",
"release-notes": "0.18.0",
"release-notes": "0.20.0",
"settings-view": "0.71.0",
"snippets": "0.27.0",
"spell-check": "0.23.0",
"spell-check": "0.24.0",
"status-bar": "0.32.0",
"styleguide": "0.23.0",
"symbols-view": "0.33.0",
@@ -101,10 +101,10 @@
"timecop": "0.13.0",
"to-the-hubs": "0.19.0",
"tree-view": "0.69.0",
"update-package-dependencies": "0.2.0",
"visual-bell": "0.6.0",
"update-package-dependencies": "0.3.0",
"visual-bell": "0.7.0",
"welcome": "0.4.0",
"whitespace": "0.11.0",
"whitespace": "0.12.0",
"wrap-guide": "0.14.0",
"language-c": "0.4.0",
"language-clojure": "0.1.0",
@@ -136,7 +136,7 @@
"language-sql": "0.3.0",
"language-text": "0.3.0",
"language-todo": "0.3.0",
"language-toml": "0.7.0",
"language-toml": "0.8.0",
"language-xml": "0.3.0",
"language-yaml": "0.2.0"
},

View File

@@ -1,34 +1,49 @@
{View, $, $$} = require '../src/space-pen-extensions'
path = require 'path'
_ = require 'underscore-plus'
{convertStackTrace} = require 'coffeestack'
{View, $, $$} = require '../src/space-pen-extensions'
sourceMaps = {}
formatStackTrace = (stackTrace) ->
formatStackTrace = (message='', stackTrace) ->
return stackTrace unless stackTrace
jasminePattern = /^\s*at\s+.*\(?.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/
firstJasmineLinePattern = /^\s*at \/.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/
convertedLines = []
for line in stackTrace.split('\n')
convertedLines.push(line) unless jasminePattern.test(line)
break if firstJasmineLinePattern.test(line)
convertStackTrace(convertedLines.join('\n'), sourceMaps)
stackTrace = convertStackTrace(convertedLines.join('\n'), sourceMaps)
lines = stackTrace.split('\n')
# Remove first line of stack when it is the same as the error message
errorMatch = lines[0]?.match(/^Error: (.*)/)
lines.shift() if message.trim() is errorMatch?[1]?.trim()
# Remove prefix of lines matching: at [object Object].<anonymous> (path:1:2)
for line, index in lines
prefixMatch = line.match(/at \[object Object\]\.<anonymous> \(([^\)]+)\)/)
lines[index] = "at #{prefixMatch[1]}" if prefixMatch
lines = lines.map (line) -> line.trim()
lines.join('\n')
module.exports =
class AtomReporter extends View
@content: ->
@div id: 'HTMLReporter', class: 'jasmine_reporter', =>
@div outlet: 'specPopup', class: "spec-popup"
@div class: 'spec-reporter', =>
@div outlet: "suites"
@div outlet: 'coreArea', =>
@div outlet: 'coreHeader', class: 'symbolHeader'
@ul outlet: 'coreSummary', class: 'symbolSummary list-unstyled'
@div outlet: 'bundledArea', =>
@div outlet: 'bundledHeader', class: 'symbolHeader'
@ul outlet: 'bundledSummary', class: 'symbolSummary list-unstyled'
@div outlet: 'userArea', =>
@div outlet: 'userHeader', class: 'symbolHeader'
@ul outlet: 'userSummary', class: 'symbolSummary list-unstyled'
@div outlet: "status", class: 'status', =>
@div outlet: 'coreArea', class: 'symbol-area', =>
@div outlet: 'coreHeader', class: 'symbol-header'
@ul outlet: 'coreSummary', class: 'symbol-summary list-unstyled'
@div outlet: 'bundledArea', class: 'symbol-area', =>
@div outlet: 'bundledHeader', class: 'symbol-header'
@ul outlet: 'bundledSummary', class: 'symbol-summary list-unstyled'
@div outlet: 'userArea', class: 'symbol-area', =>
@div outlet: 'userHeader', class: 'symbol-header'
@ul outlet: 'userSummary', class: 'symbol-summary list-unstyled'
@div outlet: "status", class: 'status alert alert-info', =>
@div outlet: "time", class: 'time'
@div outlet: "specCount", class: 'spec-count'
@div outlet: "message", class: 'message'
@@ -45,7 +60,7 @@ class AtomReporter extends View
reportRunnerStarting: (runner) ->
@handleEvents()
@startedAt = new Date()
@startedAt = Date.now()
specs = runner.specs()
@totalSpecCount = specs.length
@addSpecs(specs)
@@ -53,57 +68,29 @@ class AtomReporter extends View
reportRunnerResults: (runner) ->
@updateSpecCounts()
if @failedCount == 0
@message.text "Success!"
@status.addClass('alert-success').removeClass('alert-info') if @failedCount is 0
if @failedCount is 1
@message.text "#{@failedCount} failure"
else
@message.text "Game Over"
@message.text "#{@failedCount} failures"
reportSuiteResults: (suite) ->
reportSpecResults: (spec) ->
@completeSpecCount++
spec.endedAt = new Date().getTime()
spec.endedAt = Date.now()
@specComplete(spec)
@updateStatusView(spec)
reportSpecStarting: (spec) ->
@specStarted(spec)
specFilter: (spec) ->
globalFocusPriority = jasmine.getEnv().focusPriority
parent = spec.parentSuite ? spec.suite
if !globalFocusPriority
true
else if spec.focusPriority >= globalFocusPriority
true
else if not parent
false
else
@specFilter(parent)
handleEvents: ->
$(document).on "mouseover", ".spec-summary", ({currentTarget}) =>
element = $(currentTarget)
description = element.data("description")
return unless description
clearTimeout @timeoutId if @timeoutId?
@specPopup.show()
spec = _.find(window.timedSpecs, ({fullName}) -> description is fullName)
description = "#{description} #{spec.time}ms" if spec
@specPopup.text description
{left, top} = element.offset()
left += 20
top += 20
@specPopup.offset({left, top})
@timeoutId = setTimeout((=> @specPopup.hide()), 3000)
$(document).on "click", ".spec-toggle", ({currentTarget}) =>
element = $(currentTarget)
specFailures = element.parent().find('.spec-failures')
specFailures.toggle()
if specFailures.is(":visible") then element.text "\uf03d" else element.html "\uf03f"
element.toggleClass('folded')
false
updateSpecCounts: ->
@@ -115,7 +102,7 @@ class AtomReporter extends View
updateStatusView: (spec) ->
if @failedCount > 0
@status.addClass('failed') unless @status.hasClass('failed')
@status.addClass('alert-danger').removeClass('alert-info')
@updateSpecCounts()
@@ -123,7 +110,7 @@ class AtomReporter extends View
rootSuite = rootSuite.parentSuite while rootSuite.parentSuite
@message.text rootSuite.description
time = "#{Math.round((spec.endedAt - @startedAt.getTime()) / 10)}"
time = "#{Math.round((spec.endedAt - @startedAt) / 10)}"
time = "0#{time}" if time.length < 3
@time[0].textContent = "#{time[0...-2]}.#{time[-2..]}s"
@@ -145,15 +132,22 @@ class AtomReporter extends View
@userSummary.append symbol
if coreSpecs > 0
@coreHeader.text("Core Specs (#{coreSpecs}):")
@coreHeader.text("Core Specs (#{coreSpecs})")
else
@coreArea.hide()
if bundledPackageSpecs > 0
@bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs}):")
@bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs})")
else
@bundledArea.hide()
if userPackageSpecs > 0
@userHeader.text("User Package Specs (#{userPackageSpecs}):")
if coreSpecs is 0 and bundledPackageSpecs is 0
# Package specs being run, show a more descriptive label
{specDirectory} = specs[0]
packageFolderName = path.basename(path.dirname(specDirectory))
packageName = _.undasherize(_.uncamelcase(packageFolderName))
@userHeader.text("#{packageName} Specs")
else
@userHeader.text("User Package Specs (#{userPackageSpecs})")
else
@userArea.hide()
@@ -163,7 +157,7 @@ class AtomReporter extends View
specComplete: (spec) ->
specSummaryElement = $("#spec-summary-#{spec.id}")
specSummaryElement.removeClass('pending')
specSummaryElement.data("description", spec.getFullName())
specSummaryElement.setTooltip(title: spec.getFullName(), container: '.spec-reporter')
results = spec.results()
if results.skipped
@@ -184,11 +178,9 @@ class SuiteResultView extends View
@div class: 'suite', =>
@div outlet: 'description', class: 'description'
suite: null
initialize: (@suite) ->
@attr('id', "suite-view-#{@suite.id}")
@description.html @suite.description
@description.text(@suite.description)
attach: ->
(@parentSuiteView() or $('.results')).append this
@@ -205,20 +197,22 @@ class SuiteResultView extends View
class SpecResultView extends View
@content: ->
@div class: 'spec', =>
@div "\uf03d", class: 'spec-toggle'
@div class: 'spec-toggle'
@div outlet: 'description', class: 'description'
@div outlet: 'specFailures', class: 'spec-failures'
spec: null
initialize: (@spec) ->
@addClass("spec-view-#{@spec.id}")
@description.text @spec.description
description = @spec.description
description = "it #{description}" if description.indexOf('it ') isnt 0
@description.text(description)
for result in @spec.results().getItems() when not result.passed()
stackTrace = formatStackTrace(result.trace.stack)
stackTrace = formatStackTrace(result.message, result.trace.stack)
@specFailures.append $$ ->
@div result.message, class: 'resultMessage fail'
@div stackTrace, class: 'stackTrace' if stackTrace
@div result.message, class: 'result-message fail'
@pre stackTrace, class: 'stack-trace padded' if stackTrace
attach: ->
@parentSuiteView().append this

View File

@@ -10,25 +10,14 @@ describe "the `atom` global", ->
describe "package lifecycle methods", ->
describe ".loadPackage(name)", ->
describe "when the package has deferred deserializers", ->
it "requires the package's main module if one of its deferred deserializers is referenced", ->
pack = atom.packages.loadPackage('package-with-activation-events')
spyOn(pack, 'activateStylesheets').andCallThrough()
expect(pack.mainModule).toBeNull()
object = atom.deserializers.deserialize({deserializer: 'Foo', data: 5})
expect(pack.mainModule).toBeDefined()
expect(object.constructor.name).toBe 'Foo'
expect(object.data).toBe 5
expect(pack.activateStylesheets).toHaveBeenCalled()
it "continues if the package has an invalid package.json", ->
spyOn(console, 'warn')
atom.config.set("core.disabledPackages", [])
expect(-> atom.packages.loadPackage("package-with-broken-package-json")).not.toThrow()
it "continues if the package has an invalid package.json", ->
spyOn(console, 'warn')
atom.config.set("core.disabledPackages", [])
expect(-> atom.packages.loadPackage("package-with-broken-package-json")).not.toThrow()
it "continues if the package has an invalid keymap", ->
atom.config.set("core.disabledPackages", [])
expect(-> atom.packages.loadPackage("package-with-broken-keymap")).not.toThrow()
it "continues if the package has an invalid keymap", ->
atom.config.set("core.disabledPackages", [])
expect(-> atom.packages.loadPackage("package-with-broken-keymap")).not.toThrow()
describe ".unloadPackage(name)", ->
describe "when the package is active", ->
@@ -355,12 +344,18 @@ describe "the `atom` global", ->
it "absorbs exceptions that are thrown by the package module's serialize methods", ->
spyOn(console, 'error')
atom.packages.activatePackage('package-with-serialize-error', immediate: true)
atom.packages.activatePackage('package-with-serialization', immediate: true)
atom.packages.deactivatePackages()
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
expect(console.error).toHaveBeenCalled()
waitsForPromise ->
atom.packages.activatePackage('package-with-serialize-error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialization')
runs ->
atom.packages.deactivatePackages()
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
expect(console.error).toHaveBeenCalled()
it "removes the package's grammars", ->
waitsForPromise ->
@@ -405,15 +400,22 @@ describe "the `atom` global", ->
describe "textmate packages", ->
it "removes the package's grammars", ->
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
atom.packages.activatePackage('language-ruby', sync: true)
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby"
atom.packages.deactivatePackage('language-ruby')
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
waitsForPromise ->
atom.packages.activatePackage('language-ruby')
runs ->
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby"
atom.packages.deactivatePackage('language-ruby')
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
it "removes the package's scoped properties", ->
atom.packages.activatePackage('language-ruby', sync: true)
atom.packages.deactivatePackage('language-ruby')
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
waitsForPromise ->
atom.packages.activatePackage('language-ruby')
runs ->
atom.packages.deactivatePackage('language-ruby')
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
describe ".activate()", ->
packageActivator = null

View File

@@ -63,6 +63,19 @@ describe "Config", ->
atom.config.toggle('foo.a')
expect(atom.config.get('foo.a')).toBe false
describe ".restoreDefault(keyPath)", ->
it "sets the value of the key path to its default", ->
atom.config.setDefaults('a', b: 3)
atom.config.set('a.b', 4)
expect(atom.config.get('a.b')).toBe 4
atom.config.restoreDefault('a.b')
expect(atom.config.get('a.b')).toBe 3
atom.config.set('a.c', 5)
expect(atom.config.get('a.c')).toBe 5
atom.config.restoreDefault('a.c')
expect(atom.config.get('a.c')).toBeUndefined()
describe ".pushAtKeyPath(keyPath, value)", ->
it "pushes the given value to the array at the key path and updates observers", ->
atom.config.set("foo.bar.baz", ["a"])

View File

@@ -5,12 +5,15 @@ describe "DisplayBuffer", ->
[displayBuffer, buffer, changeHandler, tabLength] = []
beforeEach ->
tabLength = 2
atom.packages.activatePackage('language-javascript', sync: true)
buffer = atom.project.bufferForPathSync('sample.js')
displayBuffer = new DisplayBuffer({buffer, tabLength})
changeHandler = jasmine.createSpy 'changeHandler'
displayBuffer.on 'changed', changeHandler
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
afterEach ->
displayBuffer.destroy()
buffer.release()

View File

@@ -16,11 +16,13 @@ describe "Editor", ->
describe "with default options", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample.js', autoIndent: false)
buffer = editor.buffer
lineLengths = buffer.getLines().map (line) -> line.length
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
describe "when the editor is deserialized", ->
it "restores selections and folds based on markers in the buffer", ->
editor.setSelectedBufferRange([[1, 2], [3, 4]])
@@ -2733,19 +2735,26 @@ describe "Editor", ->
describe "when the editor's grammar has an injection selector", ->
beforeEach ->
atom.packages.activatePackage('language-text', sync: true)
atom.packages.activatePackage('language-javascript', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-text')
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
it "includes the grammar's patterns when the selector matches the current scope in other grammars", ->
atom.packages.activatePackage('language-hyperlink', sync: true)
grammar = atom.syntax.selectGrammar("text.js")
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
waitsForPromise ->
atom.packages.activatePackage('language-hyperlink')
expect(tokens[0].value).toBe "var"
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
runs ->
grammar = atom.syntax.selectGrammar("text.js")
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
expect(tokens[6].value).toBe "http://github.com"
expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
expect(tokens[0].value).toBe "var"
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
expect(tokens[6].value).toBe "http://github.com"
expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
describe "when the grammar is added", ->
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
@@ -2756,11 +2765,13 @@ describe "Editor", ->
expect(tokens[1].value).toBe " http://github.com"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.packages.activatePackage('language-hyperlink', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-hyperlink')
{tokens} = editor.lineForScreenRow(0)
expect(tokens[2].value).toBe "http://github.com"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
runs ->
{tokens} = editor.lineForScreenRow(0)
expect(tokens[2].value).toBe "http://github.com"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
describe "when the grammar is updated", ->
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
@@ -2771,14 +2782,17 @@ describe "Editor", ->
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.packages.activatePackage('package-with-injection-selector', sync: true)
atom.packages.activatePackage('package-with-injection-selector')
{tokens} = editor.lineForScreenRow(0)
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.packages.activatePackage('language-sql', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-sql')
{tokens} = editor.lineForScreenRow(0)
expect(tokens[2].value).toBe "SELECT"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]
runs ->
{tokens} = editor.lineForScreenRow(0)
expect(tokens[2].value).toBe "SELECT"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]

View File

@@ -10,8 +10,6 @@ describe "EditorView", ->
[buffer, editorView, editor, cachedLineHeight, cachedCharWidth] = []
beforeEach ->
atom.packages.activatePackage('language-text', sync: true)
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample.js')
buffer = editor.buffer
editorView = new EditorView(editor)
@@ -26,6 +24,12 @@ describe "EditorView", ->
@width(getCharWidth() * widthInChars) if widthInChars
$('#jasmine-content').append(this)
waitsForPromise ->
atom.packages.activatePackage('language-text', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-javascript', sync: true)
getLineHeight = ->
return cachedLineHeight if cachedLineHeight?
calcDimensions()
@@ -2532,6 +2536,52 @@ describe "EditorView", ->
editorView.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [0,2]
describe "when the line above is folded", ->
it "moves the line around the fold", ->
editor.foldBufferRow(1)
editor.setCursorBufferPosition([10, 0])
editorView.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
expect(buffer.lineForRow(1)).toBe ''
expect(buffer.lineForRow(2)).toBe ' var sort = function(items) {'
expect(editor.isFoldedAtBufferRow(1)).toBe false
expect(editor.isFoldedAtBufferRow(2)).toBe true
describe "when the line being moved is folded", ->
it "moves the fold around the fold above it", ->
editor.setCursorBufferPosition([0, 0])
editor.insertText """
var a = function() {
b = 3;
};
"""
editor.foldBufferRow(0)
editor.foldBufferRow(3)
editor.setCursorBufferPosition([3, 0])
editorView.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
expect(buffer.lineForRow(13)).toBe 'var a = function() {'
editor.logScreenLines()
expect(editor.isFoldedAtBufferRow(0)).toBe true
expect(editor.isFoldedAtBufferRow(13)).toBe true
describe "when the line above is empty and the line above that is folded", ->
it "moves the line to the empty line", ->
editor.foldBufferRow(2)
editor.setCursorBufferPosition([11, 0])
editorView.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [10, 0]
expect(buffer.lineForRow(9)).toBe ' };'
expect(buffer.lineForRow(10)).toBe ' return sort(Array.apply(this, arguments));'
expect(buffer.lineForRow(11)).toBe ''
expect(editor.isFoldedAtBufferRow(2)).toBe true
expect(editor.isFoldedAtBufferRow(10)).toBe false
describe "where there is a selection", ->
describe "when the selection falls inside the line", ->
it "maintains the selection", ->
@@ -2631,6 +2681,54 @@ describe "EditorView", ->
editorView.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [1, 2]
describe "when the line below is folded", ->
it "moves the line around the fold", ->
editor.setCursorBufferPosition([0, 0])
editor.foldBufferRow(1)
editorView.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [9, 0]
expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {'
expect(buffer.lineForRow(9)).toBe 'var quicksort = function () {'
expect(editor.isFoldedAtBufferRow(0)).toBe true
expect(editor.isFoldedAtBufferRow(9)).toBe false
describe "when the line being moved is folded", ->
it "moves the fold around the fold below it", ->
editor.setCursorBufferPosition([0, 0])
editor.insertText """
var a = function() {
b = 3;
};
"""
editor.foldBufferRow(0)
editor.foldBufferRow(3)
editor.setCursorBufferPosition([0, 0])
editorView.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [13, 0]
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
expect(buffer.lineForRow(13)).toBe 'var a = function() {'
expect(editor.isFoldedAtBufferRow(0)).toBe true
expect(editor.isFoldedAtBufferRow(13)).toBe true
describe "when the line below is empty and the line below that is folded", ->
it "moves the line to the empty line", ->
editor.setCursorBufferPosition([0, Infinity])
editor.insertText('\n')
editor.setCursorBufferPosition([0, 0])
editor.foldBufferRow(2)
editorView.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
expect(buffer.lineForRow(0)).toBe ''
expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {'
expect(buffer.lineForRow(2)).toBe ' var sort = function(items) {'
expect(editor.isFoldedAtBufferRow(0)).toBe false
expect(editor.isFoldedAtBufferRow(1)).toBe false
expect(editor.isFoldedAtBufferRow(2)).toBe true
describe "when the cursor is on the last line", ->
it "does not move the line", ->
editor.moveCursorToBottom()

View File

@@ -1,2 +1 @@
'activationEvents': ['activation-event']
'deferredDeserializers': ['Foo']

View File

@@ -1,5 +1 @@
# This package loads async, otherwise it would log errors when it
# is automatically serialized when workspaceView is deactivatated
'main': 'index.coffee'
'activationEvents': ['activation-event']

View File

@@ -6,10 +6,12 @@ describe "LanguageMode", ->
describe "javascript", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample.js', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
describe ".minIndentLevelForRowRange(startRow, endRow)", ->
it "returns the minimum indent level for the given row range", ->
expect(languageMode.minIndentLevelForRowRange(4, 7)).toBe 2
@@ -100,10 +102,12 @@ describe "LanguageMode", ->
describe "coffeescript", ->
beforeEach ->
atom.packages.activatePackage('language-coffee-script', sync: true)
editor = atom.project.openSync('coffee.coffee', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
describe ".toggleLineCommentsForBufferRows(start, end)", ->
it "comments/uncomments lines in the given range", ->
languageMode.toggleLineCommentsForBufferRows(4, 6)
@@ -147,10 +151,12 @@ describe "LanguageMode", ->
describe "css", ->
beforeEach ->
atom.packages.activatePackage('language-css', sync: true)
editor = atom.project.openSync('css.css', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-css')
describe ".toggleLineCommentsForBufferRows(start, end)", ->
it "comments/uncomments lines in the given range", ->
languageMode.toggleLineCommentsForBufferRows(0, 1)
@@ -188,11 +194,15 @@ describe "LanguageMode", ->
describe "less", ->
beforeEach ->
atom.packages.activatePackage('language-less', sync: true)
atom.packages.activatePackage('language-css', sync: true)
editor = atom.project.openSync('sample.less', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-less')
waitsForPromise ->
atom.packages.activatePackage('language-css')
describe "when commenting lines", ->
it "only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart`", ->
languageMode.toggleLineCommentsForBufferRows(0, 0)
@@ -200,10 +210,12 @@ describe "LanguageMode", ->
describe "folding", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample.js', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
it "maintains cursor buffer position when a folding/unfolding", ->
editor.setCursorBufferPosition([5,5])
languageMode.foldAll()
@@ -298,10 +310,12 @@ describe "LanguageMode", ->
describe "folding with comments", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample-with-comments.js', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
describe ".unfoldAll()", ->
it "unfolds every folded line", ->
initialScreenLineCount = editor.getScreenLineCount()
@@ -362,10 +376,12 @@ describe "LanguageMode", ->
describe "css", ->
beforeEach ->
atom.packages.activatePackage('language-source', sync: true)
atom.packages.activatePackage('language-css', sync: true)
editor = atom.project.openSync('css.css', autoIndent: true)
waitsForPromise ->
atom.packages.activatePackage('language-source')
atom.packages.activatePackage('language-css')
describe "suggestedIndentForBufferRow", ->
it "does not return negative values (regression)", ->
editor.setText('.test {\npadding: 0;\n}')

View File

@@ -159,6 +159,16 @@ addCustomMatchers = (spec) ->
@message = -> return "Expected path '" + @actual + "'" + notText + " to exist."
fs.existsSync(@actual)
toHaveFocus: ->
notText = this.isNot and " not" or ""
if not document.hasFocus()
console.error "Specs will fail because the Dev Tools have focus. To fix this close the Dev Tools or click the spec runner."
@message = -> return "Expected element '" + @actual + "' or its descendants" + notText + " to have focus."
element = @actual
element = element.get(0) if element.jquery
element.webkitMatchesSelector(":focus") or element.querySelector(":focus")
window.keyIdentifierForKey = (key) ->
if key.length > 1 # named key
key

View File

@@ -4,10 +4,18 @@ temp = require 'temp'
describe "the `syntax` global", ->
beforeEach ->
atom.packages.activatePackage('language-text', sync: true)
atom.packages.activatePackage('language-javascript', sync: true)
atom.packages.activatePackage('language-coffee-script', sync: true)
atom.packages.activatePackage('language-ruby', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-text')
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
waitsForPromise ->
atom.packages.activatePackage('language-ruby')
describe "serialization", ->
it "remembers grammar overrides by path", ->
@@ -20,29 +28,33 @@ describe "the `syntax` global", ->
describe ".selectGrammar(filePath)", ->
it "can use the filePath to load the correct grammar based on the grammar's filetype", ->
atom.packages.activatePackage('language-git', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-git')
expect(atom.syntax.selectGrammar("file.js").name).toBe "JavaScript" # based on extension (.js)
expect(atom.syntax.selectGrammar(path.join(temp.dir, '.git', 'config')).name).toBe "Git Config" # based on end of the path (.git/config)
expect(atom.syntax.selectGrammar("Rakefile").name).toBe "Ruby" # based on the file's basename (Rakefile)
expect(atom.syntax.selectGrammar("curb").name).toBe "Null Grammar"
expect(atom.syntax.selectGrammar("/hu.git/config").name).toBe "Null Grammar"
runs ->
expect(atom.syntax.selectGrammar("file.js").name).toBe "JavaScript" # based on extension (.js)
expect(atom.syntax.selectGrammar(path.join(temp.dir, '.git', 'config')).name).toBe "Git Config" # based on end of the path (.git/config)
expect(atom.syntax.selectGrammar("Rakefile").name).toBe "Ruby" # based on the file's basename (Rakefile)
expect(atom.syntax.selectGrammar("curb").name).toBe "Null Grammar"
expect(atom.syntax.selectGrammar("/hu.git/config").name).toBe "Null Grammar"
it "uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", ->
filePath = require.resolve("./fixtures/shebang")
expect(atom.syntax.selectGrammar(filePath).name).toBe "Ruby"
it "uses the number of newlines in the first line regex to determine the number of lines to test against", ->
atom.packages.activatePackage('language-property-list', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-property-list')
fileContent = "first-line\n<html>"
expect(atom.syntax.selectGrammar("dummy.coffee", fileContent).name).toBe "CoffeeScript"
runs ->
fileContent = "first-line\n<html>"
expect(atom.syntax.selectGrammar("dummy.coffee", fileContent).name).toBe "CoffeeScript"
fileContent = '<?xml version="1.0" encoding="UTF-8"?>'
expect(atom.syntax.selectGrammar("grammar.tmLanguage", fileContent).name).toBe "Null Grammar"
fileContent = '<?xml version="1.0" encoding="UTF-8"?>'
expect(atom.syntax.selectGrammar("grammar.tmLanguage", fileContent).name).toBe "Null Grammar"
fileContent += '\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
expect(atom.syntax.selectGrammar("grammar.tmLanguage", fileContent).name).toBe "Property List (XML)"
fileContent += '\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
expect(atom.syntax.selectGrammar("grammar.tmLanguage", fileContent).name).toBe "Property List (XML)"
it "doesn't read the file when the file contents are specified", ->
filePath = require.resolve("./fixtures/shebang")

View File

@@ -571,6 +571,23 @@ describe 'TextBuffer', ->
saveBuffer.reload()
expect(events).toEqual ['will-reload', 'reloaded']
it "no longer reports being in conflict", ->
saveBuffer.setText('a')
saveBuffer.save()
saveBuffer.setText('ab')
fs.writeFileSync(saveBuffer.getPath(), 'c')
conflictHandler = jasmine.createSpy('conflictHandler')
saveBuffer.on 'contents-conflicted', conflictHandler
waitsFor ->
conflictHandler.callCount > 0
runs ->
expect(saveBuffer.isInConflict()).toBe true
saveBuffer.save()
expect(saveBuffer.isInConflict()).toBe false
describe "when the buffer has no path", ->
it "throws an exception", ->
saveBuffer = atom.project.bufferForPathSync(null)

View File

@@ -5,11 +5,13 @@ describe "TokenizedBuffer", ->
[tokenizedBuffer, buffer, changeHandler] = []
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
# enable async tokenization
TokenizedBuffer.prototype.chunkSize = 5
jasmine.unspy(TokenizedBuffer.prototype, 'tokenizeInBackground')
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
startTokenizing = (tokenizedBuffer) ->
tokenizedBuffer.setVisible(true)
@@ -311,10 +313,13 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains hard-tabs", ->
beforeEach ->
atom.packages.activatePackage('language-coffee-script', sync: true)
buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({buffer})
startTokenizing(tokenizedBuffer)
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
runs ->
buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({buffer})
startTokenizing(tokenizedBuffer)
afterEach ->
tokenizedBuffer.destroy()
@@ -341,14 +346,17 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains surrogate pairs", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
buffer = atom.project.bufferForPathSync 'sample-with-pairs.js'
buffer.setText """
'abc\uD835\uDF97def'
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer({buffer})
fullyTokenize(tokenizedBuffer)
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
runs ->
buffer = atom.project.bufferForPathSync 'sample-with-pairs.js'
buffer.setText """
'abc\uD835\uDF97def'
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer({buffer})
fullyTokenize(tokenizedBuffer)
afterEach ->
tokenizedBuffer.destroy()
@@ -379,22 +387,30 @@ describe "TokenizedBuffer", ->
describe "when the grammar is updated because a grammar it includes is activated", ->
it "retokenizes the buffer", ->
atom.packages.activatePackage('language-ruby-on-rails', sync: true)
atom.packages.activatePackage('language-ruby', sync: true)
buffer = atom.project.bufferForPathSync()
buffer.setText "<div class='name'><%= User.find(2).full_name %></div>"
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setGrammar(atom.syntax.selectGrammar('test.erb'))
fullyTokenize(tokenizedBuffer)
waitsForPromise ->
atom.packages.activatePackage('language-ruby-on-rails')
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
waitsForPromise ->
atom.packages.activatePackage('language-ruby')
atom.packages.activatePackage('language-html', sync: true)
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
runs ->
buffer = atom.project.bufferForPathSync()
buffer.setText "<div class='name'><%= User.find(2).full_name %></div>"
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setGrammar(atom.syntax.selectGrammar('test.erb'))
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
waitsForPromise ->
atom.packages.activatePackage('language-html')
runs ->
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
describe ".tokenForPosition(position)", ->
afterEach ->

View File

@@ -7,49 +7,125 @@ describe "Workspace", ->
atom.project.setPath(atom.project.resolve('dir'))
workspace = new Workspace
describe "::open(uri)", ->
describe "::open(uri, options)", ->
beforeEach ->
spyOn(workspace.activePane, 'activate')
describe "when called without a uri", ->
it "adds and activates an empty editor on the active pane", ->
editor = null
waitsForPromise ->
workspace.open().then (o) -> editor = o
runs ->
expect(editor.getPath()).toBeUndefined()
expect(workspace.activePane.items).toEqual [editor]
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when called with a uri", ->
describe "when the active pane already has an editor for the given uri", ->
it "activates the existing editor on the active pane", ->
editor1 = workspace.openSync('a')
editor2 = workspace.openSync('b')
spyOn(workspace.activePane, 'activate').andCallThrough()
describe "when the 'searchAllPanes' option is false (default)", ->
describe "when called without a uri", ->
it "adds and activates an empty editor on the active pane", ->
editor = null
waitsForPromise ->
workspace.open('a').then (o) -> editor = o
workspace.open().then (o) -> editor = o
runs ->
expect(editor).toBe editor1
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when the active pane does not have an editor for the given uri", ->
it "adds and activates a new editor for the given path on the active pane", ->
editor = null
waitsForPromise ->
workspace.open('a').then (o) -> editor = o
runs ->
expect(editor.getUri()).toBe 'a'
expect(workspace.activePaneItem).toBe editor
expect(editor.getPath()).toBeUndefined()
expect(workspace.activePane.items).toEqual [editor]
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when called with a uri", ->
describe "when the active pane already has an editor for the given uri", ->
it "activates the existing editor on the active pane", ->
editor1 = workspace.openSync('a')
editor2 = workspace.openSync('b')
editor = null
waitsForPromise ->
workspace.open('a').then (o) -> editor = o
runs ->
expect(editor).toBe editor1
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when the active pane does not have an editor for the given uri", ->
it "adds and activates a new editor for the given path on the active pane", ->
editor = null
waitsForPromise ->
workspace.open('a').then (o) -> editor = o
runs ->
expect(editor.getUri()).toBe 'a'
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.items).toEqual [editor]
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when the 'searchAllPanes' option is true", ->
describe "when an editor for the given uri is already open on an inactive pane", ->
it "activates the existing editor on the inactive pane, then activates that pane", ->
editor1 = workspace.openSync('a')
pane1 = workspace.activePane
pane2 = workspace.activePane.splitRight()
editor2 = workspace.openSync('b')
expect(workspace.activePaneItem).toBe editor2
waitsForPromise ->
workspace.open('a', searchAllPanes: true)
runs ->
expect(workspace.activePane).toBe pane1
expect(workspace.activePaneItem).toBe editor1
describe "when no editor for the given uri is open in any pane", ->
it "opens an editor for the given uri in the active pane", ->
editor = null
waitsForPromise ->
workspace.open('a', searchAllPanes: true).then (o) -> editor = o
runs ->
expect(workspace.activePaneItem).toBe editor
describe "when the 'split' option is set", ->
describe "when the 'split' option is 'left'", ->
it "opens the editor in the leftmost pane of the current pane axis", ->
pane1 = workspace.activePane
pane2 = pane1.splitRight()
expect(workspace.activePane).toBe pane2
editor = null
waitsForPromise ->
workspace.open('a', split: 'left').then (o) -> editor = o
runs ->
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor]
expect(pane2.items).toEqual []
# Focus right pane and reopen the file on the left
waitsForPromise ->
pane2.focus()
workspace.open('a', split: 'left').then (o) -> editor = o
runs ->
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor]
expect(pane2.items).toEqual []
describe "when the 'split' option is 'right'", ->
it "opens the editor in the rightmost pane of the current pane axis", ->
editor = null
pane1 = workspace.activePane
pane2 = null
waitsForPromise ->
workspace.open('a', split: 'right').then (o) -> editor = o
runs ->
pane2 = workspace.getPanes().filter((p) -> p != pane1)[0]
expect(workspace.activePane).toBe pane2
expect(pane1.items).toEqual []
expect(pane2.items).toEqual [editor]
# Focus right pane and reopen the file on the right
waitsForPromise ->
pane1.focus()
workspace.open('a', split: 'right').then (o) -> editor = o
runs ->
expect(workspace.activePane).toBe pane2
expect(pane1.items).toEqual []
expect(pane2.items).toEqual [editor]
describe "::openSync(uri, options)", ->
[activePane, initialItemCount] = []
@@ -92,61 +168,6 @@ describe "Workspace", ->
workspace.openSync('b', activatePane: false)
expect(activePane.activate).not.toHaveBeenCalled()
describe "::openSingletonSync(uri, options)", ->
describe "when an editor for the given uri is already open on the active pane", ->
it "activates the existing editor", ->
editor1 = workspace.openSync('a')
editor2 = workspace.openSync('b')
expect(workspace.activePaneItem).toBe editor2
workspace.openSingletonSync('a')
expect(workspace.activePaneItem).toBe editor1
describe "when an editor for the given uri is already open on an inactive pane", ->
it "activates the existing editor on the inactive pane, then activates that pane", ->
editor1 = workspace.openSync('a')
pane1 = workspace.activePane
pane2 = workspace.activePane.splitRight()
editor2 = workspace.openSync('b')
expect(workspace.activePaneItem).toBe editor2
workspace.openSingletonSync('a')
expect(workspace.activePane).toBe pane1
expect(workspace.activePaneItem).toBe editor1
describe "when no editor for the given uri is open in any pane", ->
it "opens an editor for the given uri in the active pane", ->
editor1 = workspace.openSingletonSync('a')
expect(workspace.activePaneItem).toBe editor1
describe "when the 'split' option is 'left'", ->
it "opens the editor in the leftmost pane of the current pane axis", ->
pane1 = workspace.activePane
pane2 = pane1.splitRight()
expect(workspace.activePane).toBe pane2
editor1 = workspace.openSingletonSync('a', split: 'left')
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor1]
expect(pane2.items).toEqual []
describe "when the 'split' option is 'right'", ->
describe "when the active pane is in a horizontal pane axis", ->
it "activates the editor on the rightmost pane of the current pane axis", ->
pane1 = workspace.activePane
pane2 = pane1.splitRight()
pane1.activate()
editor1 = workspace.openSingletonSync('a', split: 'right')
expect(workspace.activePane).toBe pane2
expect(pane2.items).toEqual [editor1]
expect(pane1.items).toEqual []
describe "when the active pane is not in a horizontal pane axis", ->
it "splits the current pane to the right, then activates the editor on the right pane", ->
pane1 = workspace.activePane
editor1 = workspace.openSingletonSync('a', split: 'right')
pane2 = workspace.activePane
expect(workspace.paneContainer.root.children).toEqual [pane1, pane2]
expect(pane2.items).toEqual [editor1]
expect(pane1.items).toEqual []
describe "::reopenItemSync()", ->
it "opens the uri associated with the last closed pane that isn't currently open", ->
pane = workspace.activePane

View File

@@ -43,11 +43,7 @@ class AtomPackage extends Package
@loadStylesheets()
@loadGrammars()
@loadScopedProperties()
if @metadata.activationEvents?
@registerDeferredDeserializers()
else
@requireMainModule()
@requireMainModule() unless @metadata.activationEvents?
catch e
console.warn "Failed to load package named '#{@name}'", e.stack ? e
@@ -73,21 +69,6 @@ class AtomPackage extends Package
@activationDeferred.promise
# Deprecated
activateSync: ({immediate}={}) ->
@activateResources()
if @metadata.activationEvents? and not immediate
@subscribeToActivationEvents()
else
try
@activateConfig()
@activateStylesheets()
if @requireMainModule()
@mainModule.activate(atom.packages.getPackageState(@name) ? {})
@mainActivated = true
catch e
console.warn "Failed to activate package named '#{@name}'", e.stack
activateNow: ->
try
@activateConfig()
@@ -228,12 +209,6 @@ class AtomPackage extends Package
path.join(@path, 'index')
@mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...])
registerDeferredDeserializers: ->
for deserializerName in @metadata.deferredDeserializers ? []
atom.deserializers.addDeferred deserializerName, =>
@activateStylesheets()
@requireMainModule()
subscribeToActivationEvents: ->
return unless @metadata.activationEvents?
if _.isArray(@metadata.activationEvents)

View File

@@ -150,6 +150,14 @@ class Config
toggle: (keyPath) ->
@set(keyPath, !@get(keyPath))
# Public: Restore the key path to its default value.
#
# keyPath - The {String} name of the key.
#
# Returns the new value.
restoreDefault: (keyPath) ->
@set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath))
# Public: Push the value to the array at the key path.
#
# keyPath - The {String} key path.

View File

@@ -16,7 +16,6 @@ module.exports =
class DeserializerManager
constructor: ->
@deserializers = {}
@deferredDeserializers = {}
# Public: Register the given class(es) as deserializers.
#
@@ -24,13 +23,6 @@ class DeserializerManager
add: (classes...) ->
@deserializers[klass.name] = klass for klass in classes
# Public: Add a deferred deserializer for the given class name.
#
# name - The {String} name of the deserializer.
# fn - The {Function} that creates the deserializer.
addDeferred: (name, fn) ->
@deferredDeserializers[name] = fn
# Public: Remove the given class(es) as deserializers.
#
# classes - One or more classes to remove.
@@ -59,8 +51,4 @@ class DeserializerManager
return unless state?
name = state.get?('deserializer') ? state.deserializer
if @deferredDeserializers[name]
@deferredDeserializers[name]()
delete @deferredDeserializers[name]
@deserializers[name]

View File

@@ -26,6 +26,7 @@ module.exports =
class EditorView extends View
@characterWidthCache: {}
@configDefaults:
fontFamily: ''
fontSize: 20
showInvisibles: false
showIndentGuide: false

View File

@@ -621,7 +621,7 @@ class Editor extends Model
largestFoldStartingAtScreenRow: (screenRow) ->
@displayBuffer.largestFoldStartingAtScreenRow(screenRow)
# Public: Moves the selected line up one row.
# Public: Moves the selected lines up one screen row.
moveLineUp: ->
selection = @getSelectedBufferRange()
return if selection.start.row is 0
@@ -633,29 +633,47 @@ class Editor extends Model
rows = [selection.start.row..selection.end.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
# Move line around the fold that is directly above the selection
precedingScreenRow = @screenPositionForBufferPosition([selection.start.row]).translate([-1])
precedingBufferRow = @bufferPositionForScreenPosition(precedingScreenRow).row
if fold = @largestFoldContainingBufferRow(precedingBufferRow)
insertDelta = fold.getBufferRange().getRowCount()
else
insertDelta = 1
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
bufferRange = fold.getBufferRange()
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow - 1)
endRow = bufferRange.end.row
foldedRows.push(startRow - insertDelta)
else
startRow = row
endRow = row
insertPosition = Point.fromObject([startRow - insertDelta])
endPosition = Point.min([endRow + 1], @buffer.getEofPosition())
lines = @buffer.getTextInRange([[startRow], endPosition])
if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
lines = "#{lines}\n"
@buffer.deleteRows(startRow, endRow)
@buffer.insert([startRow - 1], lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
# Make sure the inserted text doesn't go into an existing fold
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
@destroyFoldsContainingBufferRow(insertPosition.row)
foldedRows.push(insertPosition.row + endRow - startRow + fold.getBufferRange().getRowCount())
@setSelectedBufferRange(selection.translate([-1]), preserveFolds: true)
@buffer.insert(insertPosition, lines)
# Public: Moves the selected line down one row.
# Restore folds that existed before the lines were moved
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
@foldBufferRow(foldedRow)
@setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true)
# Public: Moves the selected lines down one screen row.
moveLineDown: ->
selection = @getSelectedBufferRange()
lastRow = @buffer.getLastRow()
@@ -667,13 +685,21 @@ class Editor extends Model
rows = [selection.end.row..selection.start.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
# Move line around the fold that is directly below the selection
followingScreenRow = @screenPositionForBufferPosition([selection.end.row]).translate([1])
followingBufferRow = @bufferPositionForScreenPosition(followingScreenRow).row
if fold = @largestFoldContainingBufferRow(followingBufferRow)
insertDelta = fold.getBufferRange().getRowCount()
else
insertDelta = 1
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
bufferRange = fold.getBufferRange()
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow + 1)
endRow = bufferRange.end.row
foldedRows.push(endRow + insertDelta)
else
startRow = row
endRow = row
@@ -684,14 +710,23 @@ class Editor extends Model
endPosition = [endRow + 1]
lines = @buffer.getTextInRange([[startRow], endPosition])
@buffer.deleteRows(startRow, endRow)
insertPosition = Point.min([startRow + 1], @buffer.getEofPosition())
insertPosition = Point.min([startRow + insertDelta], @buffer.getEofPosition())
if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
lines = "\n#{lines}"
# Make sure the inserted text doesn't go into an existing fold
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
@destroyFoldsContainingBufferRow(insertPosition.row)
foldedRows.push(insertPosition.row + fold.getBufferRange().getRowCount())
@buffer.insert(insertPosition, lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
# Restore folds that existed before the lines were moved
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
@foldBufferRow(foldedRow)
@setSelectedBufferRange(selection.translate([1]), preserveFolds: true)
@setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true)
# Public: Duplicates the current line.
#

View File

@@ -11,9 +11,8 @@ fs = require 'fs-plus'
# An instance of this class is always available as the `atom.menu` global.
module.exports =
class MenuManager
pendingUpdateOperation: null
constructor: ({@resourcePath}) ->
@pendingUpdateOperation = null
@template = []
atom.keymap.on 'bundled-keymaps-loaded', => @loadPlatformItems()
@@ -32,7 +31,8 @@ class MenuManager
# items - An {Array} of menu item {Object}s containing the keys:
# :label - The {String} menu label.
# :submenu - An optional {Array} of sub menu items.
# :command - An option {String} command to trigger when the item is clicked.
# :command - An optional {String} command to trigger when the item is
# clicked.
#
# Returns nothing.
add: (items) ->
@@ -48,14 +48,21 @@ class MenuManager
includeSelector: (selector) ->
return true if document.body.webkitMatchesSelector(selector)
# Simulate an .editor element attached to a body element that has the same
# classes as the current body element.
# Simulate an .editor element attached to a .workspace element attached to
# a body element that has the same classes as the current body element.
unless @testEditor?
testBody = document.createElement('body')
testBody.classList.add(@classesForElement(document.body)...)
testWorkspace = document.createElement('body')
workspaceClasses = @classesForElement(document.body.querySelector('.workspace')) ? ['.workspace']
testWorkspace.classList.add(workspaceClasses...)
testBody.appendChild(testWorkspace)
@testEditor = document.createElement('div')
@testEditor.classList.add('editor')
testBody = document.createElement('body')
testBody.classList.add(document.body.classList.toString().split(' ')...)
testBody.appendChild(@testEditor)
testWorkspace.appendChild(@testEditor)
@testEditor.webkitMatchesSelector(selector)
@@ -109,3 +116,7 @@ class MenuManager
label.replace(/\&/g, '')
else
label
# Get an {Array} of {String} classes for the given element.
classesForElement: (element) ->
element?.classList.toString().split(' ') ? []

View File

@@ -81,26 +81,15 @@ class PackageManager
@observeDisabledPackages()
# Activate a single package by name
activatePackage: (name, options={}) ->
if options.sync? or options.immediate?
return @activatePackageSync(name, options)
activatePackage: (name) ->
if pack = @getActivePackage(name)
Q(pack)
else
pack = @loadPackage(name)
pack.activate(options).then =>
pack.activate().then =>
@activePackages[pack.name] = pack
pack
# Deprecated
activatePackageSync: (name, options) ->
return pack if pack = @getActivePackage(name)
if pack = @loadPackage(name)
@activePackages[pack.name] = pack
pack.activateSync(options)
pack
# Deactivate all packages
deactivatePackages: ->
@deactivatePackage(pack.name) for pack in @getLoadedPackages()

View File

@@ -27,7 +27,7 @@ class PaneView extends View
'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems',
'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems',
'itemForUri', 'activateItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive',
'activate', toProperty: 'model'
'activate', 'getActiveItem', toProperty: 'model'
previousActiveItem: null

View File

@@ -3,6 +3,7 @@
{Model, Sequence} = require 'theorist'
Serializable = require 'serializable'
PaneAxis = require './pane-axis'
Editor = require './editor'
PaneView = null
# Public: A container for multiple items, one of which is *active* at a given
@@ -85,6 +86,17 @@ class Pane extends Model
getItems: ->
@items.slice()
# Public: Get the active pane item in this pane.
#
# Returns a pane item.
getActiveItem: ->
@activeItem
# Public: Returns an {Editor} if the pane item is an {Editor}, or null
# otherwise.
getActiveEditor: ->
@activeItem if @activeItem instanceof Editor
# Public: Returns the item at the specified index.
itemAtIndex: (index) ->
@items[index]
@@ -105,15 +117,15 @@ class Pane extends Model
else
@activateItemAtIndex(@items.length - 1)
# Public: Returns the index of the current active item.
# Returns the index of the current active item.
getActiveItemIndex: ->
@items.indexOf(@activeItem)
# Public: Makes the item at the given index active.
# Makes the item at the given index active.
activateItemAtIndex: (index) ->
@activateItem(@itemAtIndex(index))
# Public: Makes the given item active, adding the item if necessary.
# Makes the given item active, adding the item if necessary.
activateItem: (item) ->
if item?
@addItem(item)

View File

@@ -137,9 +137,8 @@ class Project extends Model
#
# Returns a promise that resolves to an {Editor}.
open: (filePath, options={}) ->
filePath = @resolve(filePath)
resource = null
_.find @openers, (opener) -> resource = opener(filePath, options)
filePath = @resolve(filePath) ? ''
resource = opener(filePath, options) for opener in @openers when !resource
if resource
Q(resource)
@@ -149,11 +148,10 @@ class Project extends Model
# Only be used in specs
openSync: (filePath, options={}) ->
filePath = @resolve(filePath)
for opener in @openers
return resource if resource = opener(filePath, options)
filePath = @resolve(filePath) ? ''
resource = opener(filePath, options) for opener in @openers when !resource
@buildEditorForBuffer(@bufferForPathSync(filePath), options)
resource or @buildEditorForBuffer(@bufferForPathSync(filePath), options)
# Public: Retrieves all {Editor}s for all open files.
#

View File

@@ -165,14 +165,14 @@ class TextBuffer extends TextBufferCore
# Sets the path for the file.
#
# path - A {String} representing the new file path
setPath: (path) ->
return if path == @getPath()
# filePath - A {String} representing the new file path
setPath: (filePath) ->
return if filePath == @getPath()
@file?.off()
if path
@file = new File(path)
if filePath
@file = new File(filePath)
@subscribeToFile()
else
@file = null
@@ -188,14 +188,15 @@ class TextBuffer extends TextBufferCore
# Saves the buffer at a specific path.
#
# path - The path to save at.
saveAs: (path) ->
unless path then throw new Error("Can't save buffer with no file path")
# filePath - The path to save at.
saveAs: (filePath) ->
unless filePath then throw new Error("Can't save buffer with no file path")
@emit 'will-be-saved', this
@setPath(path)
@setPath(filePath)
@file.write(@getText())
@cachedDiskContents = @getText()
@conflict = false
@emitModifiedStatusChanged(false)
@emit 'saved', this
@@ -212,7 +213,10 @@ class TextBuffer extends TextBufferCore
else
not @isEmpty()
# Identifies if a buffer is in a git conflict with `HEAD`.
# Is the buffer's text in conflict with the text on disk?
#
# This occurs when the buffer's file changes on disk while the buffer has
# unsaved changes.
#
# Returns a {Boolean}.
isInConflict: -> @conflict

View File

@@ -117,6 +117,7 @@ class WorkspaceView extends View
@command 'window:run-package-specs', => ipc.sendChannel('run-package-specs', path.join(atom.project.getPath(), 'spec'))
@command 'window:increase-font-size', => @increaseFontSize()
@command 'window:decrease-font-size', => @decreaseFontSize()
@command 'window:reset-font-size', => @model.resetFontSize()
@command 'window:focus-next-pane', => @focusNextPane()
@command 'window:focus-previous-pane', => @focusPreviousPane()
@@ -229,9 +230,13 @@ class WorkspaceView extends View
@horizontal.append(element)
# Public: Returns the currently focused {PaneView}.
getActivePane: ->
getActivePaneView: ->
@panes.getActivePane()
# Deprecated: Returns the currently focused {PaneView}.
getActivePane: ->
@getActivePaneView()
# Public: Returns the currently focused item from within the focused {PaneView}
getActivePaneItem: ->
@model.activePaneItem

View File

@@ -52,27 +52,43 @@ class Workspace extends Model
#
# filePath - A {String} file path.
# options - An options {Object} (default: {}).
# :initialLine - The buffer line number to open to.
# :initialLine - A {Number} indicating which line number to open to.
# :split - A {String} ('left' or 'right') that opens the filePath in a new
# pane or an existing one if it exists.
# :changeFocus - A {Boolean} that allows the filePath to be opened without
# changing focus.
# :searchAllPanes - A {Boolean} that will open existing editors from any pane
# if the filePath is already open (default: false)
#
# Returns a promise that resolves to the {Editor} for the file URI.
open: (filePath, options={}) ->
changeFocus = options.changeFocus ? true
filePath = atom.project.resolve(filePath)
initialLine = options.initialLine
activePane = @activePane
searchAllPanes = options.searchAllPanes
split = options.split
uri = atom.project.relativize(filePath)
editor = activePane.itemForUri(atom.project.relativize(filePath)) if activePane and filePath
promise = atom.project.open(filePath, {initialLine}) if not editor
pane = switch split
when 'left'
@activePane.findLeftmostSibling()
when 'right'
@activePane.findOrCreateRightmostSibling()
else
if searchAllPanes
@paneContainer.paneForUri(uri) ? @activePane
else
@activePane
Q(editor ? promise)
Q(pane.itemForUri(uri) ? atom.project.open(filePath, options))
.then (editor) =>
if not activePane
activePane = new Pane(items: [editor])
@paneContainer.root = activePane
if not pane
pane = new Pane(items: [editor])
@paneContainer.root = pane
@itemOpened(editor)
activePane.activateItem(editor)
activePane.activate() if changeFocus
pane.activateItem(editor)
pane.activate() if changeFocus
@emit "uri-opened"
editor
.catch (error) ->
@@ -95,8 +111,7 @@ class Workspace extends Model
@activePane.activate() if activatePane
editor
# Public: Synchronously open an editor for the given URI or activate an existing
# editor in any pane if one already exists.
# Deprecated
openSingletonSync: (uri, options={}) ->
{initialLine, split} = options
# TODO: Remove deprecated changeFocus option
@@ -140,6 +155,11 @@ class Workspace extends Model
destroyActivePane: ->
@activePane?.destroy()
# Public: Returns an {Editor} if the active pane item is an {Editor},
# or null otherwise.
getActiveEditor: ->
@activePane?.getActiveEditor()
increaseFontSize: ->
atom.config.set("editor.fontSize", atom.config.get("editor.fontSize") + 1)
@@ -147,6 +167,9 @@ class Workspace extends Model
fontSize = atom.config.get("editor.fontSize")
atom.config.set("editor.fontSize", fontSize - 1) if fontSize > 1
resetFontSize: ->
atom.config.restoreDefault("editor.fontSize")
# Removes the item's uri from the list of potential items to reopen.
itemOpened: (item) ->
if uri = item.getUri?()

View File

@@ -1,171 +1,164 @@
@import "octicon-mixins.less";
@import "octicon-mixins";
@font-face { .octicon-font(); }
#jasmine_content {
position: fixed;
right: 100%;
}
body {
background-color: #ddd;
background-color: #fff;
padding: 0;
}
.spec-popup {
position: absolute;
background-color: #ddd;
border: 2px solid black;
padding: 5px;
font-size: 13px;
display: none;
}
.list-unstyled {
list-style: none;
}
#HTMLReporter { font-size: 11px; font-family: Monaco, Consolas, monospace; line-height: 1.6em; color: #333333; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .symbolHeader {
background-color: #222;
color: #c2c2c2;
font-size: 12px;
margin: 0;
padding: 5px 2px 2px 2px;
}
#HTMLReporter .symbolSummary {
background-color: #222;
overflow: hidden;
margin: 0;
}
#HTMLReporter .symbolSummary li {
float: left;
line-height: 10px;
height: 10px;
width: 10px;
font-size: 10px;
}
#HTMLReporter .symbolSummary li.passed { color: #63AD75 }
#HTMLReporter .symbolSummary li.failed { color: #FF3F05 }
#HTMLReporter .symbolSummary li.skipped { color: #444 }
#HTMLReporter .symbolSummary li.pending { color: #111 }
#HTMLReporter .symbolSummary li:before { content: "\02022"; }
#HTMLReporter .symbolSummary li:hover {
color: white;
}
#HTMLReporter .status {
font-size: 20px;
color: #222;
background-color: #E5FFC0;
line-height: 2em;
padding: 2px;
border: 2px solid #222;
border-left: 0;
border-right: 0;
text-align: center;
}
#HTMLReporter .status.failed {
color: white;
background-color: rgba(204,51,63,1.0);
}
#HTMLReporter .status .spec-count {
float: left;
}
#HTMLReporter .status .message {
}
#HTMLReporter .status .time {
float: right;
}
#HTMLReporter .results .suite + .suite, #HTMLReporter .results .spec + .spec {
border-radius: 0;
}
#HTMLReporter .results .suite, #HTMLReporter .results .spec {
border: 2px solid #222;
border-radius: 7px 0 0 0;
border-right: none;
border-bottom: none;
padding: 5px;
padding-right: 0;
padding-bottom: 0;
}
#HTMLReporter .results .suite:first-child {
border-radius: 0;
border: 2px solid #6A4A3C;
border-top: 0;
border-left: 0;
border-right: 0;
}
#HTMLReporter .results .suite {
border: 2px solid #6A4A3C;
border-radius: 7px 0 0 0;
border-right: none;
border-bottom: none;
padding: 5px;
padding-right: 0;
padding-bottom: 0;
background-color:rgba(204,51,63,0.33);
}
#HTMLReporter .results .spec {
padding: 10px;
background-color:rgba(204,51,63,1.0);
}
#HTMLReporter .results .suite > .suite, #HTMLReporter .results .suite > .spec {
margin-left: 5px
}
#HTMLReporter .results .description {
color: white;
font-size: 15px;
padding-bottom: 10px
}
#HTMLReporter .results .spec .spec-toggle {
font-family: Octicons Regular;
color: white;
font-size: 20px;
float: right;
cursor: pointer;
text-shadow: 3px 3px #222;
opacity: 0;
}
#HTMLReporter .results .spec .spec-toggle:hover {
text-shadow: none;
padding-top: 3px;
}
#HTMLReporter .results .spec:hover .spec-toggle {
opacity: 1;
}
#HTMLReporter .resultMessage {
padding-top: 5px;
color: #fff;
font-size: 15px
}
#HTMLReporter .stackTrace {
font-size: 12px;
padding: 5px;
margin: 5px 0 0 0;
border-radius: 2px;
line-height: 18px;
color: #666666;
border: 1px solid #ddd;
background: white;
white-space: pre;
overflow: auto;
.spec-reporter {
font-size: 11px;
line-height: 1.6em;
color: #333;
.list-unstyled {
list-style: none;
}
.symbol-header {
font-size: 18px;
font-weight: bold;
padding-bottom: 10px;
}
.symbol-area {
padding: 10px;
}
.symbol-summary {
overflow: hidden;
margin: 0;
li {
font-family: Monaco, Consolas, monospace;
float: left;
line-height: 10px;
height: 10px;
width: 10px;
font-size: 10px;
&.passed {
color: #5cb85c;
}
&.failed {
color: #d9534f;
}
&.skipped {
color: #f0ad4e;
}
&.pending {
color: #eee;
}
&:before {
content: "\02022";
}
}
}
.status {
font-size: 20px;
line-height: 2em;
padding: 5px;
border-radius: 0;
text-align: center;
.spec-count {
float: left;
}
.time {
float: right;
}
}
.results {
padding: 10px;
.description {
font-size: 16px;
padding: 5px 0 5px 0;
}
> .suite {
> .description {
font-size: 18px;
font-weight: bold;
}
margin-bottom: 20px;
}
.spec {
margin-top: 5px;
padding: 0 10px 10px 10px;
border-left: 3px solid #d9534f;
.spec-toggle {
.octicon(fold);
float: right;
cursor: pointer;
opacity: 0;
color: #999;
&.folded {
.octicon(unfold);
}
}
.spec-toggle:hover {
color: #333;
}
&:hover .spec-toggle {
opacity: 1;
}
}
.suite > .suite,
.suite > .spec {
margin-left: 10px;
}
}
.result-message {
font-size: 16px;
font-weight: bold;
color: #d9534f;
padding: 5px 0 5px 0;
}
.stack-trace {
font-size: 12px;
margin: 5px 0 0 0;
border-radius: 2px;
line-height: 18px;
color: #666;
border: 1px solid #ddd;
overflow: auto;
}
.tooltip {
.tooltip-inner {
border: 1px solid #ccc;
background: #fff;
color: #666;
max-width: 400px;
}
&.in {
opacity: 1;
}
.tooltip-arrow {
visibility: hidden;
}
}
}

2
vendor/apm vendored

Submodule vendor/apm updated: ce140e6628...0ed2a1ef75