Merge branch 'master' into dh-async-repo

This commit is contained in:
joshaber 2015-11-30 11:44:20 -05:00
commit 3a06953820
38 changed files with 458 additions and 150 deletions

View File

@ -34,9 +34,7 @@ script: script/cibuild
cache:
directories:
- $HOME/.atom/.apm
- $HOME/.atom/.node-gyp/.atom
- $HOME/.atom/.npm
- node_modules
notifications:
email:

View File

@ -4,3 +4,9 @@ See https://atom.io/releases
* The tree-view now sorts directory entries more naturally, in a locale-sensitive way.
* Lines can now be moved up and down with multiple cursors.
* Improved the performance of marker-dependent code paths such as spell-check and find and replace.
* Fixed copying and pasting in native input fields.
* By default, windows with no pane items are now closed via the `core:close` command. The previous behavior can be restored via the `Close Empty Windows` option in settings.
* Fixed an issue where characters were inserted when toggling the settings view on some keyboard layouts.
* Modules can now temporarily override `Error.prepareStackTrace`. There is also an `Error.prototype.getRawStack()` method if you just need access to the raw v8 trace structure.
* Fixed a problem that caused blurry fonts on monitors that have a slightly higher resolution than 96 DPI.

View File

@ -13,6 +13,7 @@ These are just guidelines, not rules, use your best judgment and feel free to pr
[How Can I Contribute?](#how-can-i-contribute)
* [Reporting Bugs](#reporting-bugs)
* [Suggesting Enhancements](#suggesting-enhancements)
* [Your First Code Contribution](#your-first-code-contribution)
* [Pull Requests](#pull-requests)
@ -157,6 +158,60 @@ Include details about your configuration and environment:
* Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]
* Problem happens with all files and projects, not only some files or projects: [Yes/No]
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for Atom, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). If you'd like, you can use [this template](#template-for-submitting-enhancement-suggestions) to structure the information.
#### Before Submitting An Enhancement Suggestion
* **Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](https://atom.io/docs/latest/hacking-atom-debugging#check-atom-and-package-settings).
* **Check if there's already [a package](https://atom.io/packages) which provides that enhancement.**
* **Determine [which repository the enhancement should be suggested in](#atom-and-packages).**
* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#atom-and-packages) your enhancement suggestions is related to, create an issue on that repository and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Atom which the suggestion is related to. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on OSX and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **Explain why this enhancement would be useful** to most Atom users and isn't something that can or should be implemented as a [community package](#atom-and-packages).
* **List some other text editors or applications where this enhancement exists.**
* **Specify which version of Atom you're using.** You can get the exact version by running `atom -v` in your terminal, or by starting Atom and running the `Application: About` command from the [Command Palette](https://github.com/atom/command-palette).
* **Specify the name and version of the OS you're using.**
#### Template For Submitting Enhancement Suggestions
[Short description of suggestion]
**Steps which explain the enhancement**
1. [First Step]
2. [Second Step]
3. [Other Steps...]
**Current and suggested behavior**
[Describe current and suggested behavior here]
**Why would the enhancement be useful to most users**
[Explain why the enhancement would be useful to most users]
[List some other text editors or applications where this enhancement exists]
**Screenshots and GIFs**
![Screenshots and GIFs which demonstrate the steps or part of Atom the enhancement suggestion is related to](url)
**Atom Version:** [Enter Atom version here]
**OS and Version:** [Enter OS name and version here]
### Your First Code Contribution
Unsure where to begin contributing to Atom? You can start by looking through these `beginner` and `help-wanted` issues:
@ -200,6 +255,7 @@ Both issue lists are sorted by total number of comments. While not perfect, numb
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
* Limit the first line to 72 characters or less
* Reference issues and pull requests liberally
* When only changing documentation, include `[ci skip]` in the commit description
* Consider starting the commit message with an applicable emoji:
* :art: `:art:` when improving the format/structure of the code
* :racehorse: `:racehorse:` when improving performance

View File

@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "1.4.1"
"atom-package-manager": "1.5.0"
}
}

View File

@ -50,6 +50,10 @@ if [ $REDIRECT_STDERR ]; then
exec 2> /dev/null
fi
if [ $EXPECT_OUTPUT ]; then
export ELECTRON_ENABLE_LOGGING=1
fi
if [ $OS == 'Mac' ]; then
if [ -n "$BETA_VERSION" ]; then
ATOM_APP_NAME="Atom Beta.app"

View File

@ -284,6 +284,7 @@ module.exports = (grunt) ->
ciTasks.push('download-electron')
ciTasks.push('download-electron-chromedriver')
ciTasks.push('build')
ciTasks.push('fingerprint')
ciTasks.push('dump-symbols') if process.platform isnt 'win32'
ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
ciTasks.push('mkdeb') if process.platform is 'linux'

View File

@ -27,7 +27,7 @@
"grunt-peg": "~1.1.0",
"grunt-shell": "~0.3.1",
"grunt-standard": "^1.0.2",
"legal-eagle": "~0.12.0",
"legal-eagle": "~0.13.0",
"minidump": "~0.9",
"npm": "2.13.3",
"rcedit": "~0.3.0",

View File

@ -0,0 +1,7 @@
var fingerprint = require('../../script/utils/fingerprint')
module.exports = function (grunt) {
grunt.registerTask('fingerprint', 'Fingerpint the node_modules folder for caching on CI', function () {
fingerprint.writeFingerprint()
})
}

View File

@ -36,7 +36,6 @@ unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
The `TextEditor` constructor is no longer public.
To construct a text editor, use `atom.workspace.buildTextEditor()`.
To check if an object is a text editor, look for for the existence of
a public method that you're using (e.g. `::getText`).
To check if an object is a text editor, use `atom.workspace.isTextEditor(object)`.
"""
TextEditor

View File

@ -12,7 +12,7 @@
"url": "https://github.com/atom/atom/issues"
},
"license": "MIT",
"electronVersion": "0.34.3",
"electronVersion": "0.34.5",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^6.1.1",
@ -53,7 +53,7 @@
"service-hub": "^0.7.0",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
"text-buffer": "^8.0.4",
"text-buffer": "8.0.9",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"yargs": "^3.23.0"
@ -65,10 +65,10 @@
"atom-light-ui": "0.43.0",
"base16-tomorrow-dark-theme": "1.0.0",
"base16-tomorrow-light-theme": "1.0.0",
"one-dark-ui": "1.1.5",
"one-dark-ui": "1.1.7",
"one-dark-syntax": "1.1.1",
"one-light-syntax": "1.1.1",
"one-light-ui": "1.1.5",
"one-light-ui": "1.1.7",
"solarized-dark-syntax": "0.39.0",
"solarized-light-syntax": "0.23.0",
"about": "1.1.0",
@ -76,7 +76,7 @@
"autocomplete-atom-api": "0.9.2",
"autocomplete-css": "0.11.0",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.23.0",
"autocomplete-plus": "2.23.1",
"autocomplete-snippets": "1.8.0",
"autoflow": "0.26.0",
"autosave": "0.23.0",
@ -99,12 +99,12 @@
"line-ending-selector": "0.3.0",
"link": "0.31.0",
"markdown-preview": "0.156.2",
"metrics": "0.53.0",
"notifications": "0.61.0",
"metrics": "0.53.1",
"notifications": "0.62.1",
"open-on-github": "0.40.0",
"package-generator": "0.41.0",
"release-notes": "0.53.0",
"settings-view": "0.232.0",
"settings-view": "0.232.1",
"snippets": "1.0.1",
"spell-check": "0.63.0",
"status-bar": "0.80.0",
@ -115,39 +115,39 @@
"tree-view": "0.198.0",
"update-package-dependencies": "0.10.0",
"welcome": "0.33.0",
"whitespace": "0.32.0",
"whitespace": "0.32.1",
"wrap-guide": "0.38.1",
"language-c": "0.49.0",
"language-c": "0.50.1",
"language-clojure": "0.18.0",
"language-coffee-script": "0.43.0",
"language-coffee-script": "0.46.0",
"language-csharp": "0.11.0",
"language-css": "0.35.0",
"language-css": "0.35.1",
"language-gfm": "0.81.0",
"language-git": "0.10.0",
"language-go": "0.40.0",
"language-html": "0.42.0",
"language-hyperlink": "0.15.0",
"language-java": "0.16.1",
"language-javascript": "0.100.0",
"language-java": "0.17.0",
"language-javascript": "0.102.2",
"language-json": "0.17.1",
"language-less": "0.28.3",
"language-less": "0.29.0",
"language-make": "0.20.0",
"language-mustache": "0.13.0",
"language-objective-c": "0.15.0",
"language-perl": "0.31.0",
"language-php": "0.34.0",
"language-property-list": "0.8.0",
"language-python": "0.41.0",
"language-ruby": "0.62.0",
"language-python": "0.42.1",
"language-ruby": "0.64.1",
"language-ruby-on-rails": "0.24.0",
"language-sass": "0.43.0",
"language-sass": "0.44.1",
"language-shellscript": "0.20.0",
"language-source": "0.9.0",
"language-sql": "0.19.0",
"language-text": "0.7.0",
"language-todo": "0.27.0",
"language-toml": "0.16.0",
"language-xml": "0.34.0",
"language-toml": "0.17.0",
"language-xml": "0.34.1",
"language-yaml": "0.24.0"
},
"private": true,

View File

@ -16,6 +16,7 @@ FOR %%a IN (%*) DO (
)
IF "%EXPECT_OUTPUT%"=="YES" (
SET ELECTRON_ENABLE_LOGGING=YES
"%~dp0\..\..\atom.exe" %*
) ELSE (
"%~dp0\..\app\apm\bin\node.exe" "%~dp0\atom.js" %*

View File

@ -18,6 +18,7 @@ done
directory=$(dirname "$0")
if [ $EXPECT_OUTPUT ]; then
export ELECTRON_ENABLE_LOGGING=1
"$directory/../../atom.exe" "$@"
else
"$directory/../app/apm/bin/node.exe" "$directory/atom.js" "$@"

View File

@ -1,5 +1,7 @@
#!/usr/bin/env node
var cp = require('./utils/child-process-wrapper.js');
var crypto = require('crypto')
var fingerprint = require('./utils/fingerprint')
var fs = require('fs');
var path = require('path');
@ -42,6 +44,11 @@ function setEnvironmentVariables() {
}
function removeNodeModules() {
if (fingerprint.fingerprintMatches()) {
console.log('node_modules matches current fingerprint ' + fingerprint.fingerprint() + ' - not removing')
return
}
var fsPlus;
try {
fsPlus = require('fs-plus');
@ -98,8 +105,8 @@ cp.safeExec.bind(global, 'npm install npm --loglevel error', {cwd: path.resolve(
var async = require('async');
var gruntPath = path.join('build', 'node_modules', '.bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
var tasks = [
cp.safeExec.bind(global, 'git clean -dff'),
cp.safeExec.bind(global, gruntPath + ' ci --gruntfile build/Gruntfile.coffee --stack --no-color'),
cp.safeExec.bind(global, 'git clean -dff -e node_modules'), // If we left them behind in removeNodeModules() they are OK to use
cp.safeExec.bind(global, gruntPath + ' ci --gruntfile build/Gruntfile.coffee --stack --no-color')
]
async.series(tasks, function(error) {
process.exit(error ? 1 : 0);

View File

@ -0,0 +1,31 @@
var crypto = require('crypto')
var fs = require('fs')
var path = require('path')
var fingerprintPath = path.resolve(__dirname, '..', '..', 'node_modules', '.atom-ci-fingerprint')
module.exports = {
fingerprint: function () {
var packageJson = fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'))
var body = packageJson.toString() + process.platform + process.version
return crypto.createHash('sha1').update(body).digest('hex')
},
writeFingerprint: function () {
var fingerprint = this.fingerprint()
fs.writeFileSync(fingerprintPath, fingerprint)
console.log('Wrote ci fingerprint:', fingerprintPath, fingerprint)
},
readFingerprint: function() {
if (fs.existsSync(fingerprintPath)) {
return fs.readFileSync(fingerprintPath).toString()
} else {
return null
}
},
fingerprintMatches: function () {
return this.readFingerprint() && this.readFingerprint() === this.fingerprint()
}
}

View File

@ -45,9 +45,11 @@ describe "AtomEnvironment", ->
expect(atom.config.get('editor.showInvisibles')).toBe false
describe "window onerror handler", ->
devToolsPromise = null
beforeEach ->
spyOn atom, 'openDevTools'
spyOn atom, 'executeJavaScriptInDevTools'
devToolsPromise = Promise.resolve()
spyOn(atom, 'openDevTools').andReturn(devToolsPromise)
spyOn(atom, 'executeJavaScriptInDevTools')
it "will open the dev tools when an error is triggered", ->
try
@ -55,8 +57,10 @@ describe "AtomEnvironment", ->
catch e
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
expect(atom.openDevTools).toHaveBeenCalled()
expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled()
waitsForPromise -> devToolsPromise
runs ->
expect(atom.openDevTools).toHaveBeenCalled()
expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled()
describe "::onWillThrowError", ->
willThrowSpy = null
@ -243,6 +247,21 @@ describe "AtomEnvironment", ->
atomEnvironment.destroy()
describe "::destroy()", ->
it "does not throw exceptions when unsubscribing from ipc events (regression)", ->
configDirPath = temp.mkdirSync()
fakeDocument = {
addEventListener: ->
removeEventListener: ->
head: document.createElement('head')
body: document.createElement('body')
}
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document: fakeDocument})
spyOn(atomEnvironment.packages, 'getAvailablePackagePaths').andReturn []
atomEnvironment.startEditorWindow()
atomEnvironment.unloadEditorWindow()
atomEnvironment.destroy()
describe "::openLocations(locations) (called via IPC from browser process)", ->
beforeEach ->
spyOn(atom.workspace, 'open')

View File

@ -69,3 +69,18 @@ describe 'CompileCache', ->
CompileCache.addPathToCache(path.join(fixtures, 'cson.cson'), atomHome)
expect(CSONParser.parse.callCount).toBe 1
describe 'overriding Error.prepareStackTrace', ->
it 'removes the override on the next tick, and always assigns the raw stack', ->
Error.prepareStackTrace = -> 'a-stack-trace'
error = new Error("Oops")
expect(error.stack).toBe 'a-stack-trace'
expect(Array.isArray(error.getRawStack())).toBe true
waits(1)
runs ->
error = new Error("Oops again")
console.log error.stack
expect(error.stack).toContain('compile-cache-spec.coffee')
expect(Array.isArray(error.getRawStack())).toBe true

View File

@ -7,6 +7,7 @@ class FakeLinesYardstick
prepareScreenRowsForMeasurement: ->
@presenter.getPreMeasurementState()
@screenRows = new Set(@presenter.getScreenRows())
getScopedCharacterWidth: (scopeNames, char) ->
@getScopedCharacterWidths(scopeNames)[char]
@ -34,6 +35,8 @@ class FakeLinesYardstick
left = 0
column = 0
return {top, left: 0} unless @screenRows.has(screenPosition.row)
iterator = @model.tokenizedLineForScreenRow(targetRow).getTokenIterator()
while iterator.next()
characterWidths = @getScopedCharacterWidths(iterator.getScopes())

View File

@ -694,6 +694,20 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(explicitHeight: 100, contentFrameWidth: 10 * maxLineLength + 20, baseCharacterWidth: 10, verticalScrollbarWidth: 10)
expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 20 - 10 # subtract vertical scrollbar width
describe "when the longest screen row is the first one and it's hidden", ->
it "doesn't compute an invalid value (regression)", ->
presenter = buildPresenter(tileSize: 2, contentFrameWidth: 10, explicitHeight: 20)
editor.setText """
a very long long long long long long line
b
c
d
e
"""
expectStateUpdate presenter, -> presenter.setScrollTop(40)
expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1
it "updates when the ::contentFrameWidth changes", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
@ -1704,6 +1718,18 @@ describe "TextEditorPresenter", ->
expectUndefinedStateForHighlight(presenter, highlight)
it "does not include highlights that end before the first visible row", ->
editor.setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.")
editor.setSoftWrapped(true)
editor.setWidth(100, true)
editor.setDefaultCharWidth(10)
marker = editor.markBufferRange([[0, 0], [0, 4]], invalidate: 'never')
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
presenter = buildPresenter(explicitHeight: 30, scrollTop: 10, tileSize: 2)
expect(stateForHighlightInTile(presenter, highlight, 0)).toBeUndefined()
it "updates when ::scrollTop changes", ->
editor.setSelectedBufferRanges([
[[6, 2], [6, 4]],

View File

@ -168,7 +168,7 @@ describe "TextEditor", ->
buffer.setPath(undefined)
expect(editor.getLongTitle()).toBe 'untitled'
it "returns <parent-directory>/<filename> when opened files has identical file names", ->
it "returns '<filename> — <parent-directory>' when opened files have identical file names", ->
editor1 = null
editor2 = null
waitsForPromise ->
@ -177,10 +177,10 @@ describe "TextEditor", ->
atom.workspace.open(path.join('sample-theme-2', 'readme')).then (o) ->
editor2 = o
runs ->
expect(editor1.getLongTitle()).toBe 'sample-theme-1/readme'
expect(editor2.getLongTitle()).toBe 'sample-theme-2/readme'
expect(editor1.getLongTitle()).toBe "readme \u2014 sample-theme-1"
expect(editor2.getLongTitle()).toBe "readme \u2014 sample-theme-2"
it "or returns <parent-directory>/.../<filename> when opened files has identical file names", ->
it "returns '<filename> — <parent-directories>' when opened files have identical file and dir names", ->
editor1 = null
editor2 = null
waitsForPromise ->
@ -189,9 +189,20 @@ describe "TextEditor", ->
atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'main.js')).then (o) ->
editor2 = o
runs ->
expect(editor1.getLongTitle()).toBe 'sample-theme-1/.../main.js'
expect(editor2.getLongTitle()).toBe 'sample-theme-2/.../main.js'
expect(editor1.getLongTitle()).toBe "main.js \u2014 sample-theme-1/src/js"
expect(editor2.getLongTitle()).toBe "main.js \u2014 sample-theme-2/src/js"
it "returns '<filename> — <parent-directories>' when opened files have identical file and same parent dir name", ->
editor1 = null
editor2 = null
waitsForPromise ->
atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'main.js')).then (o) ->
editor1 = o
atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'plugin', 'main.js')).then (o) ->
editor2 = o
runs ->
expect(editor1.getLongTitle()).toBe "main.js \u2014 js"
expect(editor2.getLongTitle()).toBe "main.js \u2014 js/plugin"
it "notifies ::onDidChangeTitle observers when the underlying buffer path changes", ->
observed = []

View File

@ -154,7 +154,7 @@ describe "TokenizedBuffer", ->
it "updates tokens to reflect the change", ->
buffer.setTextInRange([[0, 0], [2, 0]], "foo()\n7\n")
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.brace.round.js'])
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'punctuation.definition.arguments.begin.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.js'])
# line 2 is unchanged
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
@ -201,7 +201,7 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.modifier.js'])
# previous line 3 should be combined with input to form line 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
# lines below deleted regions should be shifted upward
@ -245,12 +245,12 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.modifier.js'])
# 3 new lines inserted
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0]).toEqual(value: 'bar', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0]).toEqual(value: 'baz', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0]).toEqual(value: 'bar', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0]).toEqual(value: 'baz', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
# previous line 2 is joined with quux() on line 4
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0]).toEqual(value: 'quux', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0]).toEqual(value: 'quux', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
# previous line 3 is pushed down to become line 5

View File

@ -200,3 +200,34 @@ describe "WindowEventHandler", ->
expect(dispatchedCommands.length).toBe 1
expect(dispatchedCommands[0].type).toBe 'foo-command'
describe "native key bindings", ->
it "correctly dispatches them to active elements with the '.native-key-bindings' class", ->
webContentsSpy = jasmine.createSpyObj("webContents", ["copy", "paste"])
spyOn(atom.applicationDelegate, "getCurrentWindow").andReturn({
webContents: webContentsSpy
})
nativeKeyBindingsInput = document.createElement("input")
nativeKeyBindingsInput.classList.add("native-key-bindings")
jasmine.attachToDOM(nativeKeyBindingsInput)
nativeKeyBindingsInput.focus()
atom.dispatchApplicationMenuCommand("core:copy")
atom.dispatchApplicationMenuCommand("core:paste")
expect(webContentsSpy.copy).toHaveBeenCalled()
expect(webContentsSpy.paste).toHaveBeenCalled()
webContentsSpy.copy.reset()
webContentsSpy.paste.reset()
normalInput = document.createElement("input")
jasmine.attachToDOM(normalInput)
normalInput.focus()
atom.dispatchApplicationMenuCommand("core:copy")
atom.dispatchApplicationMenuCommand("core:paste")
expect(webContentsSpy.copy).not.toHaveBeenCalled()
expect(webContentsSpy.paste).not.toHaveBeenCalled()

View File

@ -76,7 +76,7 @@ describe "Workspace", ->
expect(editor4.getCursorScreenPosition()).toEqual [2, 4]
expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath()
expect(document.title).toBe "#{path.basename(editor3.getPath())} - #{atom.project.getPaths()[0]} - Atom"
expect(document.title).toBe "#{path.basename(editor3.getLongTitle())} - #{atom.project.getPaths()[0]} - Atom"
describe "where there are no open panes or editors", ->
it "constructs the view with no open editors", ->
@ -658,6 +658,13 @@ describe "Workspace", ->
waitsForPromise -> workspace.openLicense()
runs -> expect(workspace.getActivePaneItem().getText()).toMatch /Copyright/
describe "::isTextEditor(obj)", ->
it "returns true when the passed object is an instance of `TextEditor`", ->
expect(workspace.isTextEditor(atom.workspace.buildTextEditor())).toBe(true)
expect(workspace.isTextEditor({getText: ->})).toBe(false)
expect(workspace.isTextEditor(null)).toBe(false)
expect(workspace.isTextEditor(undefined)).toBe(false)
describe "::observeTextEditors()", ->
it "invokes the observer with current and future text editors", ->
observed = []
@ -776,8 +783,8 @@ describe "Workspace", ->
applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom)
})
workspace2.deserialize(atom.workspace.serialize(), atom.deserializers)
item = atom.workspace.getActivePaneItem()
expect(document.title).toBe "#{item.getTitle()} - #{atom.project.getPaths()[0]} - Atom"
item = workspace2.getActivePaneItem()
expect(document.title).toBe "#{item.getLongTitle()} - #{atom.project.getPaths()[0]} - Atom"
workspace2.destroy()
describe "document edited status", ->
@ -1438,11 +1445,12 @@ describe "Workspace", ->
save = -> atom.workspace.saveActivePaneItem()
expect(save).toThrow()
describe "::destroyActivePaneItemOrEmptyPane", ->
describe "::closeActivePaneItemOrEmptyPaneOrWindow", ->
beforeEach ->
spyOn(atom, 'close')
waitsForPromise -> atom.workspace.open()
it "closes the active pane item until all that remains is a single empty pane", ->
it "closes the active pane item, or the active pane if it is empty, or the current window if there is only the empty root pane", ->
atom.config.set('core.destroyEmptyPanes', false)
pane1 = atom.workspace.getActivePane()
@ -1450,19 +1458,22 @@ describe "Workspace", ->
expect(atom.workspace.getPanes().length).toBe 2
expect(pane2.getItems().length).toBe 1
atom.workspace.destroyActivePaneItemOrEmptyPane()
atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
expect(atom.workspace.getPanes().length).toBe 2
expect(pane2.getItems().length).toBe 0
atom.workspace.destroyActivePaneItemOrEmptyPane()
atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
expect(atom.workspace.getPanes().length).toBe 1
expect(pane1.getItems().length).toBe 1
atom.workspace.destroyActivePaneItemOrEmptyPane()
atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
expect(atom.workspace.getPanes().length).toBe 1
expect(pane1.getItems().length).toBe 0
atom.workspace.destroyActivePaneItemOrEmptyPane()
atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
expect(atom.workspace.getPanes().length).toBe 1
atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
expect(atom.close).toHaveBeenCalled()

View File

@ -66,13 +66,42 @@ class ApplicationDelegate
ipc.send("call-window-method", "setFullScreen", fullScreen)
openWindowDevTools: ->
remote.getCurrentWindow().openDevTools()
new Promise (resolve) ->
# Defer DevTools interaction to the next tick, because using them during
# event handling causes some wrong input events to be triggered on
# `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
process.nextTick ->
if remote.getCurrentWindow().isDevToolsOpened()
resolve()
else
remote.getCurrentWindow().once("devtools-opened", -> resolve())
ipc.send("call-window-method", "openDevTools")
closeWindowDevTools: ->
new Promise (resolve) ->
# Defer DevTools interaction to the next tick, because using them during
# event handling causes some wrong input events to be triggered on
# `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
process.nextTick ->
unless remote.getCurrentWindow().isDevToolsOpened()
resolve()
else
remote.getCurrentWindow().once("devtools-closed", -> resolve())
ipc.send("call-window-method", "closeDevTools")
toggleWindowDevTools: ->
remote.getCurrentWindow().toggleDevTools()
new Promise (resolve) =>
# Defer DevTools interaction to the next tick, because using them during
# event handling causes some wrong input events to be triggered on
# `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
process.nextTick =>
if remote.getCurrentWindow().isDevToolsOpened()
@closeWindowDevTools().then(resolve)
else
@openWindowDevTools().then(resolve)
executeJavaScriptInWindowDevTools: (code) ->
remote.getCurrentWindow().executeJavaScriptInDevTools(code)
ipc.send("call-window-method", "executeJavaScriptInDevTools", code)
setWindowDocumentEdited: (edited) ->
ipc.send("call-window-method", "setDocumentEdited", edited)
@ -138,7 +167,7 @@ class ApplicationDelegate
ipc.on('message', outerCallback)
new Disposable ->
ipc.removeEventListener('message', outerCallback)
ipc.removeListener('message', outerCallback)
onUpdateAvailable: (callback) ->
outerCallback = (message, detail) ->
@ -147,17 +176,17 @@ class ApplicationDelegate
ipc.on('message', outerCallback)
new Disposable ->
ipc.removeEventListener('message', outerCallback)
ipc.removeListener('message', outerCallback)
onApplicationMenuCommand: (callback) ->
ipc.on('command', callback)
new Disposable ->
ipc.removeEventListener('command', callback)
ipc.removeListener('command', callback)
onContextMenuCommand: (callback) ->
ipc.on('context-command', callback)
new Disposable ->
ipc.removeEventListener('context-command', callback)
ipc.removeListener('context-command', callback)
didCancelWindowUnload: ->
ipc.send('did-cancel-window-unload')

View File

@ -623,7 +623,7 @@ class AtomEnvironment extends Model
@registerDefaultTargetForKeymaps()
@packages.loadPackages()
@loadStateSync()
@document.body.appendChild(@views.getView(@workspace))
@watchProjectPath()
@ -670,8 +670,7 @@ class AtomEnvironment extends Model
@emitter.emit 'will-throw-error', eventObject
if openDevTools
@openDevTools()
@executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@openDevTools().then => @executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@emitter.emit 'did-throw-error', {message, url, line, column, originalError}
@ -721,10 +720,15 @@ class AtomEnvironment extends Model
###
# Extended: Open the dev tools for the current window.
#
# Returns a {Promise} that resolves when the DevTools have been opened.
openDevTools: ->
@applicationDelegate.openWindowDevTools()
# Extended: Toggle the visibility of the dev tools for the current window.
#
# Returns a {Promise} that resolves when the DevTools have been opened or
# closed.
toggleDevTools: ->
@applicationDelegate.toggleWindowDevTools()

View File

@ -373,6 +373,8 @@ class AtomApplication
# :windowDimensions - Object with height and width keys.
# :window - {AtomWindow} to open file paths in.
openPaths: ({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window}={}) ->
devMode = Boolean(devMode)
safeMode = Boolean(safeMode)
locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom) for pathToOpen in pathsToOpen)
pathsToOpen = (locationToOpen.pathToOpen for locationToOpen in locationsToOpen)

View File

@ -158,25 +158,33 @@ require('source-map-support').install({
}
})
var sourceMapPrepareStackTrace = Error.prepareStackTrace
var prepareStackTrace = sourceMapPrepareStackTrace
var prepareStackTraceWithSourceMapping = Error.prepareStackTrace
// Prevent coffee-script from reassigning Error.prepareStackTrace
Object.defineProperty(Error, 'prepareStackTrace', {
get: function () { return prepareStackTrace },
set: function (newValue) {}
})
let prepareStackTrace = prepareStackTraceWithSourceMapping
// Enable Grim to access the raw stack without reassigning Error.prepareStackTrace
Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native
prepareStackTrace = getRawStack
var result = this.stack
prepareStackTrace = sourceMapPrepareStackTrace
return result
function prepareStackTraceWithRawStackAssignment (error, frames) {
error.rawStack = frames
return prepareStackTrace(error, frames)
}
function getRawStack (_, stack) {
return stack
Object.defineProperty(Error, 'prepareStackTrace', {
get: function () {
return prepareStackTraceWithRawStackAssignment
},
set: function (newValue) {
prepareStackTrace = newValue
process.nextTick(function () {
prepareStackTrace = prepareStackTraceWithSourceMapping
})
}
})
Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native
// Access this.stack to ensure prepareStackTrace has been run on this error
// because it assigns this.rawStack as a side-effect
this.stack
return this.rawStack
}
Object.keys(COMPILERS).forEach(function (extension) {

View File

@ -21,7 +21,6 @@ module.exports =
followSymlinks:
type: 'boolean'
default: true
title: 'Follow symlinks'
description: 'Follow symbolic links when searching files and when opening files with the fuzzy finder.'
disabledPackages:
type: 'array'
@ -54,7 +53,12 @@ module.exports =
destroyEmptyPanes:
type: 'boolean'
default: true
description: 'When the last item of a pane is removed, remove that pane as well.'
title: 'Remove Empty Panes'
description: 'When the last tab of a pane is closed, remove that pane as well.'
closeEmptyWindows:
type: 'boolean'
default: true
description: 'When a window with no open tabs or panes is given the \'Close Tab\' command, close that window.'
fileEncoding:
description: 'Default character set encoding to use when reading and writing files.'
type: 'string'

View File

@ -699,7 +699,7 @@ class Config
@endTransaction()
fn(args...)
result = callback()
new Promise (resolve, reject) =>
new Promise (resolve, reject) ->
result.then(endTransaction(resolve)).catch(endTransaction(reject))
catch error
@endTransaction()

View File

@ -1,8 +1,7 @@
Task = require './task'
# Public: Searches local files for lines matching a specified regex.
#
# Implements thenable so it can be used with `Promise.all()`.
# Searches local files for lines matching a specified regex. Implements `.then()`
# so that it can be used with `Promise.all()`.
class DirectorySearch
constructor: (rootPaths, regex, options) ->
scanHandlerOptions =
@ -22,31 +21,25 @@ class DirectorySearch
@task.terminate()
resolve()
# Public: Implementation of `then()` to satisfy the *thenable* contract.
# This makes it possible to use a `DirectorySearch` with `Promise.all()`.
#
# Returns `Promise`.
then: (args...) ->
@promise.then.apply(@promise, args)
# Public: Cancels the search.
cancel: ->
# This will cause @promise to reject.
@task.cancel()
null
# Default provider for the `atom.directory-searcher` service.
module.exports =
class DefaultDirectorySearcher
# Public: Determines whether this object supports search for a `Directory`.
# Determines whether this object supports search for a `Directory`.
#
# * `directory` {Directory} whose search needs might be supported by this object.
#
# Returns a `boolean` indicating whether this object can search this `Directory`.
canSearchDirectory: (directory) -> true
# Public: Performs a text search for files in the specified `Directory`, subject to the
# Performs a text search for files in the specified `Directory`, subject to the
# specified parameters.
#
# Results are streamed back to the caller by invoking methods on the specified `options`,

View File

@ -24,7 +24,6 @@ module.exports = ({blobStore}) ->
})
atom.displayWindow()
atom.loadStateSync()
atom.startEditorWindow()
# Workaround for focus getting cleared upon window creation

View File

@ -21,16 +21,7 @@ module.exports = ({blobStore}) ->
{testRunnerPath, legacyTestRunnerPath, headless, logFile, testPaths} = getWindowLoadSettings()
if headless
# Override logging in headless mode so it goes to the console, regardless
# of the --enable-logging flag to Electron.
console.log = (args...) ->
ipc.send 'write-to-stdout', args.join(' ') + '\n'
console.warn = (args...) ->
ipc.send 'write-to-stderr', args.join(' ') + '\n'
console.error = (args...) ->
ipc.send 'write-to-stderr', args.join(' ') + '\n'
else
unless headless
# Show window synchronously so a focusout doesn't fire on input elements
# that are focused in the very first spec run.
remote.getCurrentWindow().show()

View File

@ -55,7 +55,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'window:log-deprecation-warnings': -> Grim.logDeprecations()
'window:toggle-auto-indent': -> config.set("editor.autoIndent", not config.get("editor.autoIndent"))
'pane:reopen-closed-item': -> @getModel().reopenItem()
'core:close': -> @getModel().destroyActivePaneItemOrEmptyPane()
'core:close': -> @getModel().closeActivePaneItemOrEmptyPaneOrWindow()
'core:save': -> @getModel().saveActivePaneItem()
'core:save-as': -> @getModel().saveActivePaneItemAs()

View File

@ -377,7 +377,8 @@ class TextEditorPresenter
endRow = @constrainRow(@getEndTileRow() + @tileSize)
screenRows = [startRow...endRow]
if longestScreenRow = @model.getLongestScreenRow()
longestScreenRow = @model.getLongestScreenRow()
if longestScreenRow?
screenRows.push(longestScreenRow)
if @screenRowsToMeasure?
screenRows.push(@screenRowsToMeasure...)
@ -1244,14 +1245,7 @@ class TextEditorPresenter
updateHighlightState: (decorationId, properties, screenRange) ->
return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements()
return if screenRange.isEmpty()
if screenRange.start.row < @startRow
screenRange.start.row = @startRow
screenRange.start.column = 0
if screenRange.end.row >= @endRow
screenRange.end.row = @endRow
screenRange.end.column = 0
@constrainRangeToVisibleRowRange(screenRange)
return if screenRange.isEmpty()
@ -1281,6 +1275,23 @@ class TextEditorPresenter
true
constrainRangeToVisibleRowRange: (screenRange) ->
if screenRange.start.row < @startRow
screenRange.start.row = @startRow
screenRange.start.column = 0
if screenRange.end.row < @startRow
screenRange.end.row = @startRow
screenRange.end.column = 0
if screenRange.start.row >= @endRow
screenRange.start.row = @endRow
screenRange.start.column = 0
if screenRange.end.row >= @endRow
screenRange.end.row = @endRow
screenRange.end.column = 0
repositionRegionWithinTile: (region, tileStartRow) ->
region.top += @scrollTop - tileStartRow * @lineHeight
region.left += @scrollLeft

View File

@ -581,10 +581,7 @@ class TextEditor extends Model
#
# Returns a {String}.
getTitle: ->
if sessionPath = @getPath()
path.basename(sessionPath)
else
'untitled'
@getFileName() ? 'untitled'
# Essential: Get unique title for display in other parts of the UI, such as
# the window title.
@ -593,41 +590,52 @@ class TextEditor extends Model
# If the editor's buffer is saved, its unique title is formatted as one
# of the following,
# * "<filename>" when it is the only editing buffer with this file name.
# * "<unique-dir-prefix>/.../<filename>", where the "..." may be omitted
# if the the direct parent directory is already different.
# * "<filename> — <unique-dir-prefix>" when other buffers have this file name.
#
# Returns a {String}
getLongTitle: ->
if sessionPath = @getPath()
title = @getTitle()
if @getPath()
fileName = @getFileName()
# find text editors with identical file name.
paths = []
allPathSegments = []
for textEditor in atom.workspace.getTextEditors() when textEditor isnt this
if textEditor.getTitle() is title
paths.push(textEditor.getPath())
if paths.length is 0
return title
fileName = path.basename(sessionPath)
if textEditor.getFileName() is fileName
allPathSegments.push(textEditor.getDirectoryPath().split(path.sep))
# find the first directory in all these paths that is unique
nLevel = 0
while (_.some(paths, (apath) -> path.basename(apath) is path.basename(sessionPath)))
sessionPath = path.dirname(sessionPath)
paths = _.map(paths, (apath) -> path.dirname(apath))
nLevel += 1
if allPathSegments.length is 0
return fileName
directory = path.basename sessionPath
if nLevel > 1
path.join(directory, "...", fileName)
else
path.join(directory, fileName)
ourPathSegments = @getDirectoryPath().split(path.sep)
allPathSegments.push ourPathSegments
loop
firstSegment = ourPathSegments[0]
commonBase = _.all(allPathSegments, (pathSegments) -> pathSegments.length > 1 and pathSegments[0] is firstSegment)
if commonBase
pathSegments.shift() for pathSegments in allPathSegments
else
break
"#{fileName} \u2014 #{path.join(pathSegments...)}"
else
'untitled'
# Essential: Returns the {String} path of this editor's text buffer.
getPath: -> @buffer.getPath()
getFileName: ->
if fullPath = @getPath()
path.basename(fullPath)
else
null
getDirectoryPath: ->
if fullPath = @getPath()
path.dirname(fullPath)
else
null
# Extended: Returns the {String} character set encoding of this editor's text
# buffer.
getEncoding: -> @buffer.getEncoding()
@ -678,16 +686,16 @@ class TextEditor extends Model
getSaveDialogOptions: -> {}
checkoutHeadRevision: ->
if filePath = this.getPath()
if @getPath()
checkoutHead = =>
@project.repositoryForDirectory(new Directory(path.dirname(filePath)))
@project.repositoryForDirectory(new Directory(@getDirectoryPath()))
.then (repository) =>
repository?.async.checkoutHeadForEditor(this)
if @config.get('editor.confirmCheckoutHeadRevision')
@applicationDelegate.confirm
message: 'Confirm Checkout HEAD Revision'
detailedMessage: "Are you sure you want to discard all changes to \"#{path.basename(filePath)}\" since the last Git commit?"
detailedMessage: "Are you sure you want to discard all changes to \"#{@getFileName()}\" since the last Git commit?"
buttons:
OK: checkoutHead
Cancel: null

View File

@ -42,9 +42,8 @@ class WindowEventHandler
# `.native-key-bindings` class.
handleNativeKeybindings: ->
bindCommandToAction = (command, action) =>
@addEventListener @document, command, (event) =>
if event.target.webkitMatchesSelector('.native-key-bindings')
@applicationDelegate.getCurrentWindow().webContents[action]()
@subscriptions.add @atomEnvironment.commands.add '.native-key-bindings', command, (event) =>
@applicationDelegate.getCurrentWindow().webContents[action]()
bindCommandToAction('core:copy', 'copy')
bindCommandToAction('core:paste', 'paste')

View File

@ -155,7 +155,7 @@ class Workspace extends Model
projectPaths = @project.getPaths() ? []
if item = @getActivePaneItem()
itemPath = item.getPath?()
itemTitle = item.getTitle?()
itemTitle = item.getLongTitle?() ? item.getTitle?()
projectPath = _.find projectPaths, (projectPath) ->
itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep)
itemTitle ?= "untitled"
@ -518,6 +518,12 @@ class Workspace extends Model
@project.bufferForPath(filePath, options).then (buffer) =>
@buildTextEditor(_.extend({buffer, largeFileMode}, options))
# Public: Returns a {Boolean} that is `true` if `object` is a `TextEditor`.
#
# * `object` An {Object} you want to perform the check against.
isTextEditor: (object) ->
object instanceof TextEditor
# Extended: Create a new text editor.
#
# Returns a {TextEditor}.
@ -675,9 +681,15 @@ class Workspace extends Model
destroyActivePane: ->
@getActivePane()?.destroy()
# Destroy the active pane item or the active pane if it is empty.
destroyActivePaneItemOrEmptyPane: ->
if @getActivePaneItem()? then @destroyActivePaneItem() else @destroyActivePane()
# Close the active pane item, or the active pane if it is empty,
# or the current window if there is only the empty root pane.
closeActivePaneItemOrEmptyPaneOrWindow: ->
if @getActivePaneItem()?
@destroyActivePaneItem()
else if @getPanes().length > 1
@destroyActivePane()
else if @config.get('core.closeEmptyWindows')
atom.close()
# Increase the editor font size by 1px.
increaseFontSize: ->

View File

@ -31,12 +31,18 @@
font-size: @font-size - 2px;
height: auto;
line-height: 1.3em;
&.icon:before {
font-size: @font-size - 2px;
}
}
.btn.btn-sm,
.btn-group-sm > .btn {
padding: @component-padding/4 @component-padding/2;
height: auto;
line-height: 1.3em;
&.icon:before {
font-size: @font-size + 1px;
}
}
.btn.btn-lg,
.btn-group-lg > .btn {
@ -44,6 +50,9 @@
padding: @component-padding - 2px @component-padding + 2px;
height: auto;
line-height: 1.3em;
&.icon:before {
font-size: @font-size + 6px;
}
}
.btn-group > .btn {
@ -63,6 +72,18 @@
border-bottom-right-radius: @component-border-radius;
}
// Icon buttons
.btn.icon {
&:before {
width: initial;
height: initial;
margin-right: .3125em;
}
&:empty:before {
margin-right: 0;
}
}
.btn-toolbar {
> .btn-group + .btn-group, > .btn-group + .btn, > .btn + .btn {
float: none;