Merge branch 'master' into mb-tree-sitter-parsers

This commit is contained in:
Max Brunsfeld 2017-12-27 12:35:51 -08:00
commit 0b6e994ac6
12 changed files with 176 additions and 387 deletions

View File

@ -42,27 +42,11 @@ The `.zip` version will not automatically update.
Using [Chocolatey](https://chocolatey.org)? Run `cinst Atom` to install the latest version of Atom.
### Debian based (Debian, Ubuntu, Linux Mint)
### Linux
Atom is only available for 64-bit Linux systems.
1. Download `atom-amd64.deb` from the [Atom releases page](https://github.com/atom/atom/releases/latest).
2. Run `sudo dpkg --install atom-amd64.deb` on the downloaded package.
3. Launch Atom using the installed `atom` command.
The Linux version does not currently automatically update so you will need to
repeat these steps to upgrade to future releases.
### RPM based (Red Hat, openSUSE, Fedora, CentOS)
Atom is only available for 64-bit Linux systems.
1. Download `atom.x86_64.rpm` from the [Atom releases page](https://github.com/atom/atom/releases/latest).
2. Run `sudo rpm -i atom.x86_64.rpm` on the downloaded package.
3. Launch Atom using the installed `atom` command.
The Linux version does not currently automatically update so you will need to
repeat these steps to upgrade to future releases.
Configure your distribution's package manager to install and update Atom by following the [Linux installation instructions](http://flight-manual.atom.io/getting-started/sections/installing-atom/#platform-linux) in the Flight Manual. You will also find instructions on how to install Atom's official Linux packages without using a package repository, though you will not get automatic updates after installing Atom this way.
### Archive extraction

View File

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

View File

@ -1,285 +1,3 @@
# Atom.io package and update API
This guide describes the web API used by [apm](https://github.com/atom/apm) and
Atom. The vast majority of use cases are met by the `apm` command-line tool,
which does other useful things like incrementing your version in `package.json`
and making sure you have pushed your git tag. In fact, Atom itself shells out to
`apm` rather than hitting the API directly. If you're curious about how Atom
uses `apm`, see the [PackageManager class](https://github.com/atom/settings-view/blob/master/lib/package-manager.coffee)
in the `settings-view` package.
*This API should be considered pre-release and is subject to change (though significant breaking changes are unlikely).*
### Authorization
For calls to the API that require authentication, provide a valid token from your
[Atom.io account page](https://atom.io/account) in the `Authorization` header.
### Media type
All requests that take parameters require `application/json`.
# API Resources
## Packages
### Listing packages
#### GET /api/packages
Parameters:
- **page** (optional)
- **sort** (optional) - One of `downloads`, `created_at`, `updated_at`, `stars`. Defaults to `downloads`
- **direction** (optional) - `asc` or `desc`. Defaults to `desc`. `stars` can only be ordered `desc`
Returns a list of all packages in the following format:
```json
[
{
"releases": {
"latest": "0.6.0"
},
"name": "thedaniel-test-package",
"repository": {
"type": "git",
"url": "https://github.com/thedaniel/test-package"
}
},
...
]
```
Results are paginated 30 at a time, and links to the next and last pages are
provided in the `Link` header:
```
Link: <https://www.atom.io/api/packages?page=1>; rel="self",
<https://www.atom.io/api/packages?page=41>; rel="last",
<https://www.atom.io/api/packages?page=2>; rel="next"
```
By default, results are sorted by download count, descending.
### Searching packages
#### GET /api/packages/search
Parameters:
- **q** (required) - Search query
- **page** (optional)
- **sort** (optional) - One of `downloads`, `created_at`, `updated_at`, `stars`. Defaults to the relevance of the search query.
- **direction** (optional) - `asc` or `desc`. Defaults to `desc`.
Returns results in the same format as [listing packages](#listing-packages).
### Showing package details
#### GET /api/packages/:package_name
Returns package details and versions for a single package
Parameters:
- **engine** (optional) - Only show packages with versions compatible with this
Atom version. Must be valid [SemVer](http://semver.org).
Returns:
```json
{
"releases": {
"latest": "0.6.0"
},
"name": "thedaniel-test-package",
"repository": {
"type": "git",
"url": "https://github.com/thedaniel/test-package"
},
"versions": [
(see single version output below)
...,
]
}
```
### Creating a package
#### POST /api/packages
Create a new package; requires authentication.
The name and version will be fetched from the `package.json`
file in the specified repository. The authenticating user *must* have access
to the indicated repository.
Parameters:
- **repository** - String. The repository containing the plugin, in the form "owner/repo"
Returns:
- **201** - Successfully created, returns created package.
- **400** - Repository is inaccessible, nonexistent, not an atom package. Possible
error messages include:
- That repo does not exist, isn't an atom package, or atombot does not have access
- The package.json at owner/repo isn't valid
- **409** - A package by that name already exists
### Deleting a package
#### DELETE /api/packages/:package_name
Delete a package; requires authentication.
Returns:
- **204** - Success
- **400** - Repository is inaccessible
- **401** - Unauthorized
### Renaming a package
Packages are renamed by publishing a new version with the name changed in `package.json`
See [Creating a new package version](#creating-a-new-package-version) for details.
Requests made to the previous name will forward to the new name.
### Package Versions
#### GET /api/packages/:package_name/versions/:version_name
Returns `package.json` with `dist` key added for e.g. tarball download:
```json
{
"bugs": {
"url": "https://github.com/thedaniel/test-package/issues"
},
"dependencies": {
"async": "~0.2.6",
"pegjs": "~0.7.0",
"season": "~0.13.0"
},
"description": "Expand snippets matching the current prefix with `tab`.",
"dist": {
"tarball": "https://codeload.github.com/..."
},
"engines": {
"atom": "*"
},
"main": "./lib/snippets",
"name": "thedaniel-test-package",
"publishConfig": {
"registry": "https://...",
},
"repository": {
"type": "git",
"url": "https://github.com/thedaniel/test-package.git"
},
"version": "0.6.0"
}
```
### Creating a new package version
#### POST /api/packages/:package_name/versions
Creates a new package version from a git tag; requires authentication. If `rename`
is not `true`, the `name` field in `package.json` *must* match the current package
name.
#### Parameters
- **tag** - A git tag for the version you'd like to create. It's important to note
that the version name will not be taken from the tag, but from the `version`
key in the `package.json` file at that ref. The authenticating user *must* have
access to the package repository.
- **rename** - Boolean indicating whether this version contains a new name for the package.
#### Returns
- **201** - Successfully created. Returns created version.
- **400** - Git tag not found / Repository inaccessible / package.json invalid
- **409** - Version exists
### Deleting a version
#### DELETE /api/packages/:package_name/versions/:version_name
Deletes a package version; requires authentication.
Note that a version cannot be republished with a different tag if it is deleted.
If you need to delete the latest version of a package for e.g. security reasons,
you'll need to increment the version when republishing.
Returns 204 No Content
## Stars
### Listing user stars
#### GET /api/users/:login/stars
List a user's starred packages.
Return value is similar to **GET /api/packages**
#### GET /api/stars
List the authenticated user's starred packages; requires authentication.
Return value is similar to **GET /api/packages**
### Starring a package
#### POST /api/packages/:name/star
Star a package; requires authentication.
Returns a package.
### Unstarring a package
#### DELETE /api/packages/:name/star
Unstar a package; requires authentication.
Returns 204 No Content.
### Listing a package's stargazers
#### GET /api/packages/:name/stargazers
List the users that have starred a package.
Returns a list of user objects:
```json
[
{"login":"aperson"},
{"login":"anotherperson"},
]
```
## Atom updates
### Listing Atom updates
#### GET /api/updates
Atom update feed, following the format expected by [Squirrel](https://github.com/Squirrel/).
Returns:
```json
{
"name": "0.96.0",
"notes": "[HTML release notes]",
"pub_date": "2014-05-19T15:52:06.000Z",
"url": "https://www.atom.io/api/updates/download"
}
```
The information that was here has been moved to [a permanent home inside Atom's Flight Manual.](https://flight-manual.atom.io/atom-server-side-apis/)

View File

@ -97,7 +97,7 @@
"autocomplete-html": "0.8.4",
"autocomplete-plus": "2.39.1",
"autocomplete-snippets": "1.11.2",
"autoflow": "0.29.1",
"autoflow": "0.29.3",
"autosave": "0.24.6",
"background-tips": "0.27.1",
"bookmarks": "0.45.1",
@ -145,7 +145,7 @@
"language-gfm": "0.90.3",
"language-git": "0.19.1",
"language-go": "0.45.0-4",
"language-html": "0.48.4",
"language-html": "0.48.5",
"language-hyperlink": "0.16.3",
"language-java": "0.27.6",
"language-javascript": "0.128.0-4",
@ -160,7 +160,7 @@
"language-python": "0.46.0-2",
"language-ruby": "0.71.4",
"language-ruby-on-rails": "0.25.3",
"language-sass": "0.61.3",
"language-sass": "0.61.4",
"language-shellscript": "0.26.0-3",
"language-source": "0.9.0",
"language-sql": "0.25.9",

View File

@ -1,19 +1,14 @@
/** @babel */
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
const {Emitter, Disposable, CompositeDisposable} = require('event-kit')
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
import {Emitter, Disposable, CompositeDisposable} from 'event-kit'
import {HistoryManager, HistoryProject} from '../src/history-manager'
import StateStore from '../src/state-store'
const {HistoryManager, HistoryProject} = require('../src/history-manager')
const StateStore = require('../src/state-store')
describe("HistoryManager", () => {
let historyManager, commandRegistry, project, stateStore
let commandDisposable, projectDisposable
beforeEach(async () => {
// Do not clobber recent project history
spyOn(atom.applicationDelegate, 'didChangeHistoryManager')
commandDisposable = jasmine.createSpyObj('Disposable', ['dispose'])
commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add'])
commandRegistry.add.andReturn(commandDisposable)
@ -185,11 +180,26 @@ describe("HistoryManager", () => {
})
})
describe("saveState" ,() => {
describe("saveState", () => {
let savedHistory
beforeEach(() => {
// historyManager.saveState is spied on globally to prevent specs from
// modifying the shared project history. Since these tests depend on
// saveState, we unspy it but in turn spy on the state store instead
// so that no data is actually stored to it.
jasmine.unspy(historyManager, 'saveState')
spyOn(historyManager.stateStore, 'save').andCallFake((name, history) => {
savedHistory = history
return Promise.resolve()
})
})
it("saves the state", async () => {
await historyManager.addProject(["/save/state"])
await historyManager.saveState()
const historyManager2 = new HistoryManager({stateStore, project, commands: commandRegistry})
spyOn(historyManager2.stateStore, 'load').andCallFake(name => Promise.resolve(savedHistory))
await historyManager2.loadState()
expect(historyManager2.getProjects()[0].paths).toEqual(['/save/state'])
})

View File

@ -63,7 +63,7 @@ else
beforeEach ->
# Do not clobber recent project history
spyOn(atom.history, 'saveState').andReturn(Promise.resolve())
spyOn(Object.getPrototypeOf(atom.history), 'saveState').andReturn(Promise.resolve())
atom.project.setPaths([specProjectPath])

View File

@ -2840,83 +2840,149 @@ describe('TextEditorComponent', () => {
describe('mouse input', () => {
describe('on the lines', () => {
it('positions the cursor on single-click or when middle/right-clicking', async () => {
for (const button of [0, 1, 2]) {
describe('when there is only one cursor and no selection', () => {
it('positions the cursor on single-click or when middle/right-clicking', async () => {
for (const button of [0, 1, 2]) {
const {component, element, editor} = buildComponent()
const {lineHeight} = component.measurements
editor.setCursorScreenPosition([Infinity, Infinity], {autoscroll: false})
component.didMouseDownOnContent({
detail: 1,
button,
clientX: clientLeftForCharacter(component, 0, 0) - 1,
clientY: clientTopForLine(component, 0) - 1
})
expect(editor.getCursorScreenPosition()).toEqual([0, 0])
const maxRow = editor.getLastScreenRow()
editor.setCursorScreenPosition([Infinity, Infinity], {autoscroll: false})
component.didMouseDownOnContent({
detail: 1,
button,
clientX: clientLeftForCharacter(component, maxRow, editor.lineLengthForScreenRow(maxRow)) + 1,
clientY: clientTopForLine(component, maxRow) + 1
})
expect(editor.getCursorScreenPosition()).toEqual([maxRow, editor.lineLengthForScreenRow(maxRow)])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: clientLeftForCharacter(component, 0, editor.lineLengthForScreenRow(0)) + 1,
clientY: clientTopForLine(component, 0) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([0, editor.lineLengthForScreenRow(0)])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 0) + clientLeftForCharacter(component, 3, 1)) / 2,
clientY: clientTopForLine(component, 1) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([1, 0])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 15)) / 2,
clientY: clientTopForLine(component, 3) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([3, 14])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 15)) / 2 + 1,
clientY: clientTopForLine(component, 3) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([3, 15])
editor.getBuffer().setTextInRange([[3, 14], [3, 15]], '🐣')
await component.getNextUpdatePromise()
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 16)) / 2,
clientY: clientTopForLine(component, 3) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([3, 14])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 16)) / 2 + 1,
clientY: clientTopForLine(component, 3) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([3, 16])
expect(editor.testAutoscrollRequests).toEqual([])
}
})
})
describe('when there is more than one cursor', () => {
it('does not move the cursor when right-clicking', async () => {
const {component, element, editor} = buildComponent()
const {lineHeight} = component.measurements
editor.setCursorScreenPosition([Infinity, Infinity], {autoscroll: false})
editor.setCursorScreenPosition([5, 17], {autoscroll: false})
editor.addCursorAtScreenPosition([2, 4])
component.didMouseDownOnContent({
detail: 1,
button,
button: 2,
clientX: clientLeftForCharacter(component, 0, 0) - 1,
clientY: clientTopForLine(component, 0) - 1
})
expect(editor.getCursorScreenPosition()).toEqual([0, 0])
expect(editor.getCursorScreenPositions()).toEqual([Point.fromObject([5, 17]), Point.fromObject([2, 4])])
})
const maxRow = editor.getLastScreenRow()
editor.setCursorScreenPosition([Infinity, Infinity], {autoscroll: false})
it('does move the cursor when middle-clicking', async () => {
const {component, element, editor} = buildComponent()
const {lineHeight} = component.measurements
editor.setCursorScreenPosition([5, 17], {autoscroll: false})
editor.addCursorAtScreenPosition([2, 4])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: clientLeftForCharacter(component, maxRow, editor.lineLengthForScreenRow(maxRow)) + 1,
clientY: clientTopForLine(component, maxRow) + 1
button: 1,
clientX: clientLeftForCharacter(component, 0, 0) - 1,
clientY: clientTopForLine(component, 0) - 1
})
expect(editor.getCursorScreenPosition()).toEqual([maxRow, editor.lineLengthForScreenRow(maxRow)])
expect(editor.getCursorScreenPositions()).toEqual([Point.fromObject([0, 0])])
})
})
describe('when there are non-empty selections', () => {
it('does not move the cursor when right-clicking', async () => {
const {component, element, editor} = buildComponent()
const {lineHeight} = component.measurements
editor.setCursorScreenPosition([5, 17], {autoscroll: false})
editor.selectRight(3)
component.didMouseDownOnContent({
detail: 1,
button,
clientX: clientLeftForCharacter(component, 0, editor.lineLengthForScreenRow(0)) + 1,
clientY: clientTopForLine(component, 0) + lineHeight / 2
button: 2,
clientX: clientLeftForCharacter(component, 0, 0) - 1,
clientY: clientTopForLine(component, 0) - 1
})
expect(editor.getCursorScreenPosition()).toEqual([0, editor.lineLengthForScreenRow(0)])
expect(editor.getSelectedScreenRange()).toEqual([[5, 17], [5, 20]])
})
it('does move the cursor when middle-clicking', async () => {
const {component, element, editor} = buildComponent()
const {lineHeight} = component.measurements
editor.setCursorScreenPosition([5, 17], {autoscroll: false})
editor.selectRight(3)
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 0) + clientLeftForCharacter(component, 3, 1)) / 2,
clientY: clientTopForLine(component, 1) + lineHeight / 2
button: 1,
clientX: clientLeftForCharacter(component, 0, 0) - 1,
clientY: clientTopForLine(component, 0) - 1
})
expect(editor.getCursorScreenPosition()).toEqual([1, 0])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 15)) / 2,
clientY: clientTopForLine(component, 3) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([3, 14])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 15)) / 2 + 1,
clientY: clientTopForLine(component, 3) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([3, 15])
editor.getBuffer().setTextInRange([[3, 14], [3, 15]], '🐣')
await component.getNextUpdatePromise()
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 16)) / 2,
clientY: clientTopForLine(component, 3) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([3, 14])
component.didMouseDownOnContent({
detail: 1,
button,
clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 16)) / 2 + 1,
clientY: clientTopForLine(component, 3) + lineHeight / 2
})
expect(editor.getCursorScreenPosition()).toEqual([3, 16])
expect(editor.testAutoscrollRequests).toEqual([])
}
expect(editor.getSelectedScreenRange()).toEqual([[0, 0], [0, 0]])
})
})
describe('when the input is for the primary mouse button', () => {

View File

@ -206,12 +206,13 @@ class AtomEnvironment {
this.themes.initialize({configDirPath: this.configDirPath, resourcePath, safeMode, devMode})
this.commandInstaller.initialize(this.getVersion())
this.protocolHandlerInstaller.initialize(this.config, this.notifications)
this.uriHandlerRegistry.registerHostHandler('core', CoreURIHandlers.create(this))
this.autoUpdater.initialize()
this.config.load()
this.protocolHandlerInstaller.initialize(this.config, this.notifications)
this.themes.loadBaseStylesheets()
this.initialStyleElements = this.styles.getSnapshot()
if (params.onlyLoadBaseStyleSheets) this.themes.initialLoadComplete = true

View File

@ -8,6 +8,7 @@ FileRecoveryService = require './file-recovery-service'
ipcHelpers = require '../ipc-helpers'
{BrowserWindow, Menu, app, dialog, ipcMain, shell, screen} = require 'electron'
{CompositeDisposable, Disposable} = require 'event-kit'
crypto = require 'crypto'
fs = require 'fs-plus'
path = require 'path'
os = require 'os'
@ -33,11 +34,16 @@ class AtomApplication
# Public: The entry point into the Atom application.
@open: (options) ->
unless options.socketPath?
username = if process.platform is 'win32' then process.env.USERNAME else process.env.USER
# Lowercasing the ATOM_HOME to make sure that we don't get multiple sockets
# on case-insensitive filesystems due to arbitrary case differences in paths.
atomHomeUnique = path.resolve(process.env.ATOM_HOME).toLowerCase()
hash = crypto.createHash('sha1').update(username).update('|').update(atomHomeUnique)
atomInstanceDigest = hash.digest('hex').substring(0, 32)
if process.platform is 'win32'
userNameSafe = new Buffer(process.env.USERNAME).toString('base64')
options.socketPath = "\\\\.\\pipe\\atom-#{options.version}-#{userNameSafe}-#{process.arch}-sock"
options.socketPath = "\\\\.\\pipe\\atom-#{options.version}-#{process.arch}-#{atomInstanceDigest}-sock"
else
options.socketPath = path.join(os.tmpdir(), "atom-#{options.version}-#{process.env.USER}.sock")
options.socketPath = path.join(os.tmpdir(), "atom-#{options.version}-#{process.arch}-#{atomInstanceDigest}.sock")
# FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
# take a few seconds to trigger 'error' event, it could be a bug of node

View File

@ -1762,11 +1762,13 @@ class TextEditorComponent {
const screenPosition = this.screenPositionForMouseEvent(event)
// All clicks should set the cursor position, but only left-clicks should
// have additional logic.
// On macOS, ctrl-click brings up the context menu so also handle that case.
if (button !== 0 || (platform === 'darwin' && ctrlKey)) {
model.setCursorScreenPosition(screenPosition, {autoscroll: false})
// Always set cursor position on middle-click
// Only set cursor position on right-click if there is one cursor with no selection
const ranges = model.getSelectedBufferRanges()
if (button === 1 || (ranges.length === 1 && ranges[0].isEmpty())) {
model.setCursorScreenPosition(screenPosition, {autoscroll: false})
}
// On Linux, pasting happens on middle click. A textInput event with the
// contents of the selection clipboard will be dispatched by the browser

View File

@ -3630,14 +3630,15 @@ class TextEditor {
return this.buffer.getLanguageMode().rootScopeDescriptor
}
// Essential: Get the syntactic scopeDescriptor for the given position in buffer
// Essential: Get the syntactic {ScopeDescriptor} for the given position in buffer
// coordinates. Useful with {Config::get}.
//
// For example, if called with a position inside the parameter list of an
// anonymous CoffeeScript function, the method returns the following array:
// `["source.coffee", "meta.inline.function.coffee", "variable.parameter.function.coffee"]`
// anonymous CoffeeScript function, this method returns a {ScopeDescriptor} with
// the following scopes array:
// `["source.coffee", "meta.function.inline.coffee", "meta.parameters.coffee", "variable.parameter.function.coffee"]`
//
// * `bufferPosition` A {Point} or {Array} of [row, column].
// * `bufferPosition` A {Point} or {Array} of `[row, column]`.
//
// Returns a {ScopeDescriptor}.
scopeDescriptorForBufferPosition (bufferPosition) {

View File

@ -16,9 +16,10 @@ atom-dock {
.atom-dock-inner {
display: flex;
// Keep the area at least a pixel wide so that you have something to hover
// Keep the area at least 2 pixels wide so that you have something to hover
// over to trigger the toggle button affordance even when fullscreen.
&.left, &.right { min-width: 1px; }
// Needs to be 2 pixels to work on Windows when scaled to 150%. See atom/atom #15728
&.left, &.right { min-width: 2px; }
&.bottom { min-height: 1px; }
&.bottom { width: 100%; }