Merge remote-tracking branch 'refs/remotes/origin/master' into wl-electron-37

This commit is contained in:
Wliu
2016-04-29 18:43:48 -04:00
43 changed files with 485 additions and 1021 deletions

View File

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

View File

@@ -57,7 +57,7 @@ module.exports = (grunt) ->
homeDir = process.env.USERPROFILE
contentsDir = shellAppDir
appDir = path.join(shellAppDir, 'resources', 'app')
installDir ?= path.join(process.env.ProgramFiles, appName)
installDir ?= path.join(process.env.LOCALAPPDATA, appName, 'app-dev')
killCommand = 'taskkill /F /IM atom.exe'
else if process.platform is 'darwin'
homeDir = process.env.HOME
@@ -298,6 +298,7 @@ module.exports = (grunt) ->
unless process.platform is 'linux' or grunt.option('no-install')
defaultTasks.push 'install'
grunt.registerTask('default', defaultTasks)
grunt.registerTask('build-and-sign', ['download-electron', 'download-electron-chromedriver', 'build', 'set-version', 'generate-asar', 'codesign:app', 'install'])
getDefaultChannelAndReleaseBranch = (version) ->
if version.match(/dev/) or isBuildingPR()

View File

@@ -54,9 +54,9 @@ module.exports = (grunt) ->
# so that it doesn't becomes larger than it needs to be.
ignoredPaths = [
path.join('git-utils', 'deps')
path.join('nodegit', 'vendor')
path.join('nodegit', 'node_modules', 'node-pre-gyp')
path.join('nodegit', 'node_modules', '.bin')
path.join('ohnogit', 'node_modules', 'nodegit', 'vendor')
path.join('ohnogit', 'node_modules', 'nodegit', 'node_modules', 'node-pre-gyp')
path.join('ohnogit', 'node_modules', 'nodegit', 'node_modules', '.bin')
path.join('oniguruma', 'deps')
path.join('less', 'dist')
path.join('bootstrap', 'docs')
@@ -122,9 +122,9 @@ module.exports = (grunt) ->
# Ignore *.cc and *.h files from native modules
ignoredPaths.push "#{_.escapeRegExp(path.join('ctags', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('git-utils', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'src') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'generate') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'include') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'src') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'generate') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'include') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('keytar', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('nslog', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('oniguruma', 'src') + path.sep)}.*\\.(cc|h)*"

View File

@@ -16,10 +16,22 @@ module.exports = (grunt) ->
{description} = grunt.config.get('atom.metadata')
if process.platform is 'win32'
runas ?= require 'runas'
copyFolder = path.resolve 'script', 'copy-folder.cmd'
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: true) isnt 0
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
done = @async()
fs.access(installDir, fs.W_OK, (err) ->
adminRequired = true if err
if adminRequired
grunt.log.ok("User does not have write access to #{installDir}, elevating to admin")
runas ?= require 'runas'
copyFolder = path.resolve 'script', 'copy-folder.cmd'
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: adminRequired) isnt 0
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
else
grunt.log.ok("Installed into #{installDir}")
done()
)
else if process.platform is 'darwin'
rm installDir
mkdir path.dirname(installDir)

View File

@@ -34,7 +34,7 @@ git clone https://github.com/atom/atom/
cd atom
script/build
```
This will create the Atom application in the `out\Atom` folder as well as copy it to a folder named `Atom` within `Program Files`.
This will create the Atom application in the `out\Atom` folder as well as copy it to a subfolder of your user profile (e.g. `c:\Users\Bob`) called `AppData\Local\atom\app-dev`.
### `script/build` Options
* `--install-dir` - Creates the final built application in this directory. Example (trailing slash is optional):

View File

@@ -18,15 +18,15 @@
# 'ctrl-p': 'core:move-down'
#
# You can find more information about keymaps in these guides:
# * https://atom.io/docs/latest/using-atom-basic-customization#customizing-key-bindings
# * https://atom.io/docs/latest/behind-atom-keymaps-in-depth
# * http://flight-manual.atom.io/using-atom/sections/basic-customization/#_customizing_keybindings
# * http://flight-manual.atom.io/behind-atom/sections/keymaps-in-depth/
#
# If you're having trouble with your keybindings not working, try the
# Keybinding Resolver: `Cmd+.` on OS X and `Ctrl+.` on other platforms. See the
# Debugging Guide for more information:
# * https://atom.io/docs/latest/hacking-atom-debugging#check-the-keybindings
# * http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-the-keybindings
#
# This file uses CoffeeScript Object Notation (CSON).
# If you are unfamiliar with CSON, you can read more about it in the
# Atom Flight Manual:
# https://atom.io/docs/latest/using-atom-basic-customization#cson
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson

View File

@@ -18,4 +18,4 @@
# This file uses CoffeeScript Object Notation (CSON).
# If you are unfamiliar with CSON, you can read more about it in the
# Atom Flight Manual:
# https://atom.io/docs/latest/using-atom-basic-customization#cson
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson

View File

@@ -27,6 +27,7 @@
'ctrl-n': 'application:new-file'
'ctrl-s': 'core:save'
'ctrl-S': 'core:save-as'
'ctrl-f4': 'core:close'
'ctrl-w': 'core:close'
'ctrl-z': 'core:undo'
'ctrl-y': 'core:redo'

View File

@@ -228,10 +228,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -204,10 +204,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -207,10 +207,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -37,9 +37,9 @@
"less-cache": "0.23",
"line-top-index": "0.2.0",
"marked": "^0.3.4",
"nodegit": "0.12.2",
"normalize-package-data": "^2.0.0",
"nslog": "^3",
"ohnogit": "0.0.11",
"oniguruma": "^5",
"pathwatcher": "~6.2",
"property-accessors": "^1.1.3",
@@ -54,7 +54,7 @@
"service-hub": "^0.7.0",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
"text-buffer": "8.4.6",
"text-buffer": "8.5.0",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"yargs": "^3.23.0"
@@ -72,12 +72,12 @@
"one-light-syntax": "1.2.0",
"solarized-dark-syntax": "1.0.2",
"solarized-light-syntax": "1.0.2",
"about": "1.5.0",
"about": "1.5.2",
"archive-view": "0.61.1",
"autocomplete-atom-api": "0.10.0",
"autocomplete-css": "0.11.1",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.29.2",
"autocomplete-plus": "2.30.0",
"autocomplete-snippets": "1.10.0",
"autoflow": "0.27.0",
"autosave": "0.23.1",
@@ -89,7 +89,7 @@
"dev-live-reload": "0.47.0",
"encoding-selector": "0.21.0",
"exception-reporting": "0.38.1",
"fuzzy-finder": "1.0.4",
"fuzzy-finder": "1.0.5",
"git-diff": "1.0.1",
"find-and-replace": "0.198.0",
"go-to-line": "0.30.0",
@@ -101,37 +101,37 @@
"link": "0.31.1",
"markdown-preview": "0.158.0",
"metrics": "0.53.1",
"notifications": "0.63.1",
"notifications": "0.63.2",
"open-on-github": "1.1.0",
"package-generator": "1.0.0",
"settings-view": "0.235.1",
"snippets": "1.0.2",
"spell-check": "0.67.1",
"status-bar": "1.2.3",
"status-bar": "1.2.6",
"styleguide": "0.45.2",
"symbols-view": "0.112.0",
"tabs": "0.92.1",
"tabs": "0.93.1",
"timecop": "0.33.1",
"tree-view": "0.206.0",
"tree-view": "0.206.2",
"update-package-dependencies": "0.10.0",
"welcome": "0.34.0",
"whitespace": "0.32.2",
"wrap-guide": "0.38.1",
"language-c": "0.51.3",
"language-c": "0.51.4",
"language-clojure": "0.20.0",
"language-coffee-script": "0.46.1",
"language-coffee-script": "0.47.0",
"language-csharp": "0.12.1",
"language-css": "0.36.1",
"language-gfm": "0.85.0",
"language-git": "0.12.1",
"language-gfm": "0.86.0",
"language-git": "0.13.0",
"language-go": "0.42.0",
"language-html": "0.44.1",
"language-hyperlink": "0.16.0",
"language-java": "0.17.0",
"language-java": "0.18.0",
"language-javascript": "0.110.0",
"language-json": "0.18.0",
"language-less": "0.29.3",
"language-make": "0.21.1",
"language-make": "0.22.0",
"language-mustache": "0.13.0",
"language-objective-c": "0.15.1",
"language-perl": "0.34.0",
@@ -140,15 +140,15 @@
"language-python": "0.43.1",
"language-ruby": "0.68.5",
"language-ruby-on-rails": "0.25.0",
"language-sass": "0.46.0",
"language-shellscript": "0.21.1",
"language-sass": "0.49.0",
"language-shellscript": "0.22.0",
"language-source": "0.9.0",
"language-sql": "0.20.0",
"language-sql": "0.21.0",
"language-text": "0.7.1",
"language-todo": "0.27.0",
"language-toml": "0.18.0",
"language-xml": "0.34.4",
"language-yaml": "0.25.2"
"language-xml": "0.34.5",
"language-yaml": "0.26.0"
},
"private": true,
"scripts": {

View File

@@ -2,6 +2,7 @@
SET EXPECT_OUTPUT=
SET WAIT=
SET PSARGS=%*
FOR %%a IN (%*) DO (
IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES
@@ -25,7 +26,8 @@ FOR %%a IN (%*) DO (
IF "%EXPECT_OUTPUT%"=="YES" (
SET ELECTRON_ENABLE_LOGGING=YES
IF "%WAIT%"=="YES" (
powershell -noexit "%~dp0\..\..\atom.exe" --pid=$pid %* ; wait-event
powershell -noexit "Start-Process -FilePath \"%~dp0\..\..\atom.exe\" -ArgumentList \"--pid=$pid $env:PSARGS\" ; wait-event"
exit 0
) ELSE (
"%~dp0\..\..\atom.exe" %*
)

View File

@@ -1,2 +1,5 @@
#!/bin/sh
$(dirname "$0")/atom.cmd "$@"
pushd $(dirname "$0") > /dev/null
ATOMCMD=""$(pwd -W)"/atom.cmd"
popd > /dev/null
cmd.exe //c "$ATOMCMD" "$@"

View File

@@ -28,8 +28,10 @@ describe "AtomEnvironment", ->
atom.setSize(originalSize.width, originalSize.height)
it 'sets the size of the window, and can retrieve the size just set', ->
atom.setSize(100, 400)
expect(atom.getSize()).toEqual width: 100, height: 400
newWidth = originalSize.width + 12
newHeight = originalSize.height + 23
atom.setSize(newWidth, newHeight)
expect(atom.getSize()).toEqual width: newWidth, height: newHeight
describe ".isReleasedVersion()", ->
it "returns false if the version is a SHA and true otherwise", ->

View File

@@ -5,6 +5,12 @@
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[branch "master"]
remote = origin
merge = refs/heads/master
[remote "origin"]
url = git@github.com:atom/some-repo-i-guess.git
fetch = +refs/heads/*:refs/remotes/origin/*
[submodule "jstips"]
url = https://github.com/loverajoel/jstips
[submodule "You-Dont-Need-jQuery"]

View File

@@ -0,0 +1 @@
d2b0ad9cbc6f6c4372e8956e5cc5af771b2342e5

View File

@@ -3,7 +3,6 @@
import fs from 'fs-plus'
import path from 'path'
import temp from 'temp'
import Git from 'nodegit'
import {it, beforeEach, afterEach} from './async-spec-helpers'
@@ -47,7 +46,7 @@ describe('GitRepositoryAsync', () => {
let threw = false
try {
await repo.repoPromise
await repo.getRepo()
} catch (e) {
threw = true
}
@@ -56,6 +55,14 @@ describe('GitRepositoryAsync', () => {
})
})
describe('openedPath', () => {
it('is the path passed to .open', () => {
const workingDirPath = copyRepository()
repo = GitRepositoryAsync.open(workingDirPath)
expect(repo.openedPath).toBe(workingDirPath)
})
})
describe('.getRepo()', () => {
beforeEach(() => {
const workingDirectory = copySubmoduleRepository()
@@ -64,19 +71,19 @@ describe('GitRepositoryAsync', () => {
})
it('returns the repository when not given a path', async () => {
const nodeGitRepo1 = await repo.repoPromise
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo()
expect(nodeGitRepo1.workdir()).toBe(nodeGitRepo2.workdir())
})
it('returns the repository when given a non-submodule path', async () => {
const nodeGitRepo1 = await repo.repoPromise
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo('README')
expect(nodeGitRepo1.workdir()).toBe(nodeGitRepo2.workdir())
})
it('returns the submodule repository when given a submodule path', async () => {
const nodeGitRepo1 = await repo.repoPromise
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo('jstips')
expect(nodeGitRepo1.workdir()).not.toBe(nodeGitRepo2.workdir())
@@ -103,7 +110,7 @@ describe('GitRepositoryAsync', () => {
it('returns the repository path for a repository path', async () => {
repo = openFixture('master.git')
const repoPath = await repo.getPath()
expect(repoPath).toBe(path.join(__dirname, 'fixtures', 'git', 'master.git'))
expect(repoPath).toEqualPath(path.join(__dirname, 'fixtures', 'git', 'master.git'))
})
})
@@ -303,7 +310,7 @@ describe('GitRepositoryAsync', () => {
await repo.getPathStatus(filePath)
expect(statusHandler.callCount).toBe(1)
const status = Git.Status.STATUS.WT_MODIFIED
const status = GitRepositoryAsync.Git.Status.STATUS.WT_MODIFIED
expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: status})
fs.writeFileSync(filePath, 'abc')
@@ -878,4 +885,34 @@ describe('GitRepositoryAsync', () => {
})
})
})
describe('.getOriginURL()', () => {
beforeEach(() => {
const workingDirectory = copyRepository('repo-with-submodules')
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the origin URL', async () => {
const url = await repo.getOriginURL()
expect(url).toBe('git@github.com:atom/some-repo-i-guess.git')
})
})
describe('.getUpstreamBranch()', () => {
it('returns null when there is no upstream branch', async () => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
const upstream = await repo.getUpstreamBranch()
expect(upstream).toBe(null)
})
it('returns the upstream branch', async () => {
const workingDirectory = copyRepository('repo-with-submodules')
repo = GitRepositoryAsync.open(workingDirectory)
const upstream = await repo.getUpstreamBranch()
expect(upstream).toBe('refs/remotes/origin/master')
})
})
})

View File

@@ -33,7 +33,7 @@ describe "GitRepository", ->
waitsForPromise ->
repo.async.getPath().then(onSuccess)
runs ->
expect(onSuccess.mostRecentCall.args[0]).toBe(repoPath)
expect(onSuccess.mostRecentCall.args[0]).toEqualPath(repoPath)
describe "new GitRepository(path)", ->
it "throws an exception when no repository is found", ->
@@ -289,6 +289,16 @@ describe "GitRepository", ->
expect(repo.isStatusModified(status)).toBe true
expect(repo.isStatusNew(status)).toBe false
it 'caches statuses that were looked up synchronously', ->
originalContent = 'undefined'
fs.writeFileSync(modifiedPath, 'making this path modified')
repo.getPathStatus('file.txt')
fs.writeFileSync(modifiedPath, originalContent)
waitsForPromise -> repo.refreshStatus()
runs ->
expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeFalsy()
describe "buffer events", ->
[editor] = []

View File

@@ -526,7 +526,7 @@ describe "Project", ->
expect(atom.project.getDirectories()[1].contains(inputPath)).toBe true
expect(atom.project.relativizePath(inputPath)).toEqual [
atom.project.getPaths()[1],
'somewhere/something.txt'
path.join('somewhere', 'something.txt')
]
describe ".contains(path)", ->

View File

@@ -1,66 +0,0 @@
/** @babel */
import ResourcePool from '../src/resource-pool'
import {it} from './async-spec-helpers'
describe('ResourcePool', () => {
let queue
beforeEach(() => {
queue = new ResourcePool([{}])
})
describe('.enqueue', () => {
it('calls the enqueued function', async () => {
let called = false
await queue.enqueue(() => {
called = true
return Promise.resolve()
})
expect(called).toBe(true)
})
it('forwards values from the inner promise', async () => {
const result = await queue.enqueue(() => Promise.resolve(42))
expect(result).toBe(42)
})
it('forwards errors from the inner promise', async () => {
let threw = false
try {
await queue.enqueue(() => Promise.reject(new Error('down with the sickness')))
} catch (e) {
threw = true
}
expect(threw).toBe(true)
})
it('continues to dequeue work after a promise has been rejected', async () => {
try {
await queue.enqueue(() => Promise.reject(new Error('down with the sickness')))
} catch (e) {}
const result = await queue.enqueue(() => Promise.resolve(42))
expect(result).toBe(42)
})
it('queues up work', async () => {
let resolve = null
queue.enqueue(() => {
return new Promise((resolve_, reject) => {
resolve = resolve_
})
})
expect(queue.getQueueDepth()).toBe(0)
queue.enqueue(() => new Promise((resolve, reject) => {}))
expect(queue.getQueueDepth()).toBe(1)
resolve()
waitsFor(() => queue.getQueueDepth() === 0)
})
})
})

View File

@@ -91,3 +91,13 @@ describe "Selection", ->
expect(buffer.lineForRow(0)).toBe " "
expect(buffer.lineForRow(1)).toBe " "
expect(buffer.lineForRow(2)).toBe ""
it "auto-indents if only a newline is inserted", ->
selection.setBufferRange [[2, 0], [3, 0]]
selection.insertText("\n", autoIndent: true)
expect(buffer.lineForRow(2)).toBe " "
it "auto-indents if only a carriage return + newline is inserted", ->
selection.setBufferRange [[2, 0], [3, 0]]
selection.insertText("\r\n", autoIndent: true)
expect(buffer.lineForRow(2)).toBe " "

View File

@@ -172,8 +172,8 @@ jasmine.useRealClock = ->
addCustomMatchers = (spec) ->
spec.addMatchers
toBeInstanceOf: (expected) ->
notText = if @isNot then " not" else ""
this.message = => "Expected #{jasmine.pp(@actual)} to#{notText} be instance of #{expected.name} class"
beOrNotBe = if @isNot then "not be" else "be"
this.message = => "Expected #{jasmine.pp(@actual)} to #{beOrNotBe} instance of #{expected.name} class"
@actual instanceof expected
toHaveLength: (expected) ->
@@ -181,32 +181,38 @@ addCustomMatchers = (spec) ->
this.message = => "Expected object #{@actual} has no length method"
false
else
notText = if @isNot then " not" else ""
this.message = => "Expected object with length #{@actual.length} to#{notText} have length #{expected}"
haveOrNotHave = if @isNot then "not have" else "have"
this.message = => "Expected object with length #{@actual.length} to #{haveOrNotHave} length #{expected}"
@actual.length is expected
toExistOnDisk: (expected) ->
notText = this.isNot and " not" or ""
@message = -> return "Expected path '" + @actual + "'" + notText + " to exist."
toOrNotTo = this.isNot and "not to" or "to"
@message = -> return "Expected path '#{@actual}' #{toOrNotTo} exist."
fs.existsSync(@actual)
toHaveFocus: ->
notText = this.isNot and " not" or ""
toOrNotTo = this.isNot and "not to" or "to"
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."
@message = -> return "Expected element '#{@actual}' or its descendants #{toOrNotTo} have focus."
element = @actual
element = element.get(0) if element.jquery
element is document.activeElement or element.contains(document.activeElement)
toShow: ->
notText = if @isNot then " not" else ""
toOrNotTo = this.isNot and "not to" or "to"
element = @actual
element = element.get(0) if element.jquery
@message = -> return "Expected element '#{element}' or its descendants#{notText} to show."
@message = -> return "Expected element '#{element}' or its descendants #{toOrNotTo} show."
element.style.display in ['block', 'inline-block', 'static', 'fixed']
toEqualPath: (expected) ->
actualPath = path.normalize(@actual)
expectedPath = path.normalize(expected)
@message = -> return "Expected path '#{actualPath}' to be equal to '#{expectedPath}'."
actualPath is expectedPath
window.waitsForPromise = (args...) ->
label = null
if args.length > 1

View File

@@ -4022,15 +4022,33 @@ describe('TextEditorComponent', function () {
})
})
describe('when changing the font', async function () {
it('measures the default char, the korean char, the double width char and the half width char widths', async function () {
expect(editor.getDefaultCharWidth()).toBeCloseTo(12, 0)
describe('when decreasing the fontSize', async function () {
it('decreases the widths of the korean char, the double width char and the half width char', async function () {
originalDefaultCharWidth = editor.getDefaultCharWidth()
koreanDefaultCharWidth = editor.getKoreanCharWidth()
doubleWidthDefaultCharWidth = editor.getDoubleWidthCharWidth()
halfWidthDefaultCharWidth = editor.getHalfWidthCharWidth()
component.setFontSize(10)
await nextViewUpdatePromise()
expect(editor.getDefaultCharWidth()).toBeCloseTo(6, 0)
expect(editor.getKoreanCharWidth()).toBeCloseTo(9, 0)
expect(editor.getDoubleWidthCharWidth()).toBe(10)
expect(editor.getHalfWidthCharWidth()).toBe(5)
expect(editor.getDefaultCharWidth()).toBeLessThan(originalDefaultCharWidth)
expect(editor.getKoreanCharWidth()).toBeLessThan(koreanDefaultCharWidth)
expect(editor.getDoubleWidthCharWidth()).toBeLessThan(doubleWidthDefaultCharWidth)
expect(editor.getHalfWidthCharWidth()).toBeLessThan(halfWidthDefaultCharWidth)
})
})
describe('when increasing the fontSize', function() {
it('increases the widths of the korean char, the double width char and the half width char', async function () {
originalDefaultCharWidth = editor.getDefaultCharWidth()
koreanDefaultCharWidth = editor.getKoreanCharWidth()
doubleWidthDefaultCharWidth = editor.getDoubleWidthCharWidth()
halfWidthDefaultCharWidth = editor.getHalfWidthCharWidth()
component.setFontSize(25)
await nextViewUpdatePromise()
expect(editor.getDefaultCharWidth()).toBeGreaterThan(originalDefaultCharWidth)
expect(editor.getKoreanCharWidth()).toBeGreaterThan(koreanDefaultCharWidth)
expect(editor.getDoubleWidthCharWidth()).toBeGreaterThan(doubleWidthDefaultCharWidth)
expect(editor.getHalfWidthCharWidth()).toBeGreaterThan(halfWidthDefaultCharWidth)
})
})

View File

@@ -182,17 +182,19 @@ describe "TextEditor", ->
expect(editor1.getLongTitle()).toBe "readme \u2014 sample-theme-1"
expect(editor2.getLongTitle()).toBe "readme \u2014 sample-theme-2"
it "returns '<filename> — <parent-directories>' when opened files have identical file and dir names", ->
it "returns '<filename> — <parent-directories>' when opened files have identical file names in subdirectories", ->
editor1 = null
editor2 = null
path1 = path.join('sample-theme-1', 'src', 'js')
path2 = path.join('sample-theme-2', 'src', 'js')
waitsForPromise ->
atom.workspace.open(path.join('sample-theme-1', 'src', 'js', 'main.js')).then (o) ->
atom.workspace.open(path.join(path1, 'main.js')).then (o) ->
editor1 = o
atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'main.js')).then (o) ->
atom.workspace.open(path.join(path2, 'main.js')).then (o) ->
editor2 = o
runs ->
expect(editor1.getLongTitle()).toBe "main.js \u2014 sample-theme-1/src/js"
expect(editor2.getLongTitle()).toBe "main.js \u2014 sample-theme-2/src/js"
expect(editor1.getLongTitle()).toBe "main.js \u2014 #{path1}"
expect(editor2.getLongTitle()).toBe "main.js \u2014 #{path2}"
it "returns '<filename> — <parent-directories>' when opened files have identical file and same parent dir name", ->
editor1 = null
@@ -204,7 +206,7 @@ describe "TextEditor", ->
editor2 = o
runs ->
expect(editor1.getLongTitle()).toBe "main.js \u2014 js"
expect(editor2.getLongTitle()).toBe "main.js \u2014 js/plugin"
expect(editor2.getLongTitle()).toBe "main.js \u2014 " + path.join('js', 'plugin')
it "notifies ::onDidChangeTitle observers when the underlying buffer path changes", ->
observed = []
@@ -1187,14 +1189,10 @@ describe "TextEditor", ->
cursor2 = editor.addCursorAtBufferPosition([1, 4])
expect(cursor2.marker).toBe cursor1.marker
describe '.logCursorScope()', ->
beforeEach ->
spyOn(atom.notifications, 'addInfo')
it 'opens a notification', ->
editor.logCursorScope()
expect(atom.notifications.addInfo).toHaveBeenCalled()
describe '.getCursorScope()', ->
it 'returns the current scope', ->
descriptor = editor.getCursorScope()
expect(descriptor.scopes).toContain('source.js')
describe "selection", ->
selection = null
@@ -5729,28 +5727,6 @@ describe "TextEditor", ->
expect(handler).toHaveBeenCalledWith 'OK'
expect(editor.getPlaceholderText()).toBe 'OK'
describe ".checkoutHeadRevision()", ->
it "reverts to the version of its file checked into the project repository", ->
atom.config.set("editor.confirmCheckoutHeadRevision", false)
editor.setCursorBufferPosition([0, 0])
editor.insertText("---\n")
expect(editor.lineTextForBufferRow(0)).toBe "---"
waitsForPromise ->
editor.checkoutHeadRevision()
runs ->
expect(editor.lineTextForBufferRow(0)).toBe "var quicksort = function () {"
describe "when there's no repository for the editor's file", ->
it "doesn't do anything", ->
editor = atom.workspace.buildTextEditor()
editor.setText("stuff")
editor.checkoutHeadRevision()
waitsForPromise -> editor.checkoutHeadRevision()
describe 'gutters', ->
describe 'the TextEditor constructor', ->
it 'creates a line-number gutter', ->

View File

@@ -175,7 +175,7 @@ describe "atom.themes", ->
expect(styleElementAddedHandler).toHaveBeenCalled()
element = document.querySelector('head style[source-path*="css.css"]')
expect(element.getAttribute('source-path')).toBe atom.themes.stringToId(cssPath)
expect(element.getAttribute('source-path')).toEqualPath atom.themes.stringToId(cssPath)
expect(element.textContent).toBe fs.readFileSync(cssPath, 'utf8')
# doesn't append twice
@@ -194,7 +194,7 @@ describe "atom.themes", ->
expect(document.querySelectorAll('head style').length).toBe lengthBefore + 1
element = document.querySelector('head style[source-path*="sample.less"]')
expect(element.getAttribute('source-path')).toBe atom.themes.stringToId(lessPath)
expect(element.getAttribute('source-path')).toEqualPath atom.themes.stringToId(lessPath)
expect(element.textContent).toBe """
#header {
color: #4d926f;
@@ -213,9 +213,9 @@ describe "atom.themes", ->
it "supports requiring css and less stylesheets without an explicit extension", ->
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'css')
expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toBe atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toEqualPath atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'sample')
expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toBe atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toEqualPath atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
document.querySelector('head style[source-path*="css.css"]').remove()
document.querySelector('head style[source-path*="sample.less"]').remove()

View File

@@ -445,13 +445,12 @@ describe "TokenizedBuffer", ->
expect(screenLine0.text).toBe "# Econ 101#{tabAsSpaces}"
{tokens} = screenLine0
expect(tokens.length).toBe 4
expect(tokens.length).toBe 3
expect(tokens[0].value).toBe "#"
expect(tokens[1].value).toBe " Econ 101"
expect(tokens[2].value).toBe tabAsSpaces
expect(tokens[2].scopes).toEqual tokens[1].scopes
expect(tokens[2].isAtomic).toBeTruthy()
expect(tokens[3].value).toBe ""
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"

View File

@@ -80,7 +80,8 @@ describe "Workspace", ->
expect(untitledEditor.getText()).toBe("An untitled editor.")
expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath()
expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{pathEscaped}///
describe "where there are no open panes or editors", ->
it "constructs the view with no open editors", ->
@@ -833,25 +834,29 @@ describe "Workspace", ->
describe "when there is an active pane item", ->
it "sets the title to the pane item's title plus the project path", ->
item = atom.workspace.getActivePaneItem()
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the title of the active pane item changes", ->
it "updates the window title based on the item's new title", ->
editor = atom.workspace.getActivePaneItem()
editor.buffer.setPath(path.join(temp.dir, 'hi'))
expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the active pane's item changes", ->
it "updates the title to the new item's title plus the project path", ->
atom.workspace.getActivePane().activateNextItem()
item = atom.workspace.getActivePaneItem()
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the last pane item is removed", ->
it "updates the title to contain the project's path", ->
atom.workspace.getActivePane().destroy()
expect(atom.workspace.getActivePaneItem()).toBeUndefined()
expect(document.title).toMatch ///^#{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{pathEscaped}///
describe "when an inactive pane's item changes", ->
it "does not update the title", ->
@@ -875,7 +880,8 @@ describe "Workspace", ->
})
workspace2.deserialize(atom.workspace.serialize(), atom.deserializers)
item = workspace2.getActivePaneItem()
expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{pathEscaped}///
workspace2.destroy()
describe "document edited status", ->
@@ -1610,3 +1616,48 @@ describe "Workspace", ->
runs ->
expect(pane.getPendingItem()).toBeFalsy()
describe "grammar activation", ->
beforeEach ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
it "notifies the workspace of which grammar is used", ->
editor = null
grammarUsed = jasmine.createSpy()
atom.workspace.handleGrammarUsed = grammarUsed
waitsForPromise -> atom.workspace.open('sample-with-comments.js').then (o) -> editor = o
waitsFor -> grammarUsed.callCount is 1
runs ->
expect(grammarUsed.argsForCall[0][0].name).toBe 'JavaScript'
describe ".checkoutHeadRevision()", ->
editor = null
beforeEach ->
atom.config.set("editor.confirmCheckoutHeadRevision", false)
waitsForPromise -> atom.workspace.open('sample-with-comments.js').then (o) -> editor = o
it "reverts to the version of its file checked into the project repository", ->
editor.setCursorBufferPosition([0, 0])
editor.insertText("---\n")
expect(editor.lineTextForBufferRow(0)).toBe "---"
waitsForPromise ->
atom.workspace.checkoutHeadRevision(editor)
runs ->
expect(editor.lineTextForBufferRow(0)).toBe ""
describe "when there's no repository for the editor's file", ->
it "doesn't do anything", ->
editor = atom.workspace.buildTextEditor()
editor.setText("stuff")
atom.workspace.checkoutHeadRevision(editor)
waitsForPromise -> atom.workspace.checkoutHeadRevision(editor)
escapeStringRegex = (str) ->
str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')

View File

@@ -255,7 +255,7 @@ class AtomEnvironment extends Model
@deserializers.add(TextBuffer)
registerDefaultCommands: ->
registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller})
registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller, notificationManager: @notifications, @project, @clipboard})
registerDefaultViewProviders: ->
@views.addViewProvider Workspace, (model, env) ->

View File

@@ -138,7 +138,11 @@ class AtomApplication
return unless @socketPath?
@deleteSocketFile()
server = net.createServer (connection) =>
connection.on 'data', (data) =>
data = ''
connection.on 'data', (chunk) ->
data = data + chunk
connection.on 'end', =>
options = JSON.parse(data)
@openWithOptions(options)
@@ -170,9 +174,6 @@ class AtomApplication
@on 'application:quit', -> app.quit()
@on 'application:new-window', -> @openPath(getLoadSettings())
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
@on 'application:open', -> @promptForPathToOpen('all', getLoadSettings())
@on 'application:open-file', -> @promptForPathToOpen('file', getLoadSettings())
@on 'application:open-folder', -> @promptForPathToOpen('folder', getLoadSettings())
@on 'application:open-dev', -> @promptForPathToOpen('all', devMode: true)
@on 'application:open-safe', -> @promptForPathToOpen('all', safeMode: true)
@on 'application:inspect', ({x, y, atomWindow}) ->
@@ -255,6 +256,14 @@ class AtomApplication
ipcMain.on 'command', (event, command) =>
@emit(command)
ipcMain.on 'open-command', (event, command, args...) =>
defaultPath = args[0] if args.length > 0
switch command
when 'application:open' then @promptForPathToOpen('all', getLoadSettings(), defaultPath)
when 'application:open-file' then @promptForPathToOpen('file', getLoadSettings(), defaultPath)
when 'application:open-folder' then @promptForPathToOpen('folder', getLoadSettings(), defaultPath)
else console.log "Invalid open-command received: " + command
ipcMain.on 'window-command', (event, command, args...) ->
win = BrowserWindow.fromWebContents(event.sender)
win.emit(command, args...)
@@ -653,11 +662,13 @@ class AtomApplication
# :safeMode - A Boolean which controls whether any newly opened windows
# should be in safe mode or not.
# :window - An {AtomWindow} to use for opening a selected file path.
promptForPathToOpen: (type, {devMode, safeMode, window}) ->
@promptForPath type, (pathsToOpen) =>
@openPaths({pathsToOpen, devMode, safeMode, window})
# :path - An optional String which controls the default path to which the
# file dialog opens.
promptForPathToOpen: (type, {devMode, safeMode, window}, path=null) ->
@promptForPath type, ((pathsToOpen) =>
@openPaths({pathsToOpen, devMode, safeMode, window})), path
promptForPath: (type, callback) ->
promptForPath: (type, callback, path) ->
properties =
switch type
when 'file' then ['openFile']
@@ -680,8 +691,8 @@ class AtomApplication
when 'folder' then 'Open Folder'
else 'Open'
if process.platform is 'linux'
if projectPath = @lastFocusedWindow?.projectPath
openOptions.defaultPath = projectPath
# File dialog defaults to project directory of currently active editor
if path?
openOptions.defaultPath = path
dialog.showOpenDialog(parentWindow, openOptions, callback)

View File

@@ -35,7 +35,6 @@ class DisplayBuffer extends Model
state.config = atomEnvironment.config
state.assert = atomEnvironment.assert
state.grammarRegistry = atomEnvironment.grammars
state.packageManager = atomEnvironment.packages
new this(state)
constructor: (params={}) ->
@@ -43,7 +42,7 @@ class DisplayBuffer extends Model
{
tabLength, @editorWidthInChars, @tokenizedBuffer, @foldsMarkerLayer, buffer,
ignoreInvisibles, @largeFileMode, @config, @assert, @grammarRegistry, @packageManager
ignoreInvisibles, @largeFileMode, @config, @assert, @grammarRegistry
} = params
@emitter = new Emitter
@@ -51,7 +50,7 @@ class DisplayBuffer extends Model
@tokenizedBuffer ?= new TokenizedBuffer({
tabLength, buffer, ignoreInvisibles, @largeFileMode, @config,
@grammarRegistry, @packageManager, @assert
@grammarRegistry, @assert
})
@buffer = @tokenizedBuffer.buffer
@charWidthsByScope = {}
@@ -122,7 +121,7 @@ class DisplayBuffer extends Model
foldsMarkerLayer = @foldsMarkerLayer.copy()
new DisplayBuffer({
@buffer, tabLength: @getTabLength(), @largeFileMode, @config, @assert,
@grammarRegistry, @packageManager, foldsMarkerLayer
@grammarRegistry, foldsMarkerLayer
})
updateAllScreenLines: ->

View File

@@ -58,7 +58,7 @@ function getFromShell () {
function needsPatching (options = { platform: process.platform, env: process.env }) {
if (options.platform === 'darwin' && !options.env.PWD) {
let shell = getUserShell()
if (shell.endsWith('csh') || shell.endsWith('tcsh')) {
if (shell.endsWith('csh') || shell.endsWith('tcsh') || shell.endsWith('fish')) {
return false
}
return true
@@ -67,9 +67,20 @@ function needsPatching (options = { platform: process.platform, env: process.env
return false
}
// Fix for #11302 because `process.env` on Windows is a magic object that offers case-insensitive
// environment variable matching. By always cloning to `process.env` we prevent breaking the
// underlying functionality.
function clone (to, from) {
for (var key in to) {
delete to[key]
}
Object.assign(to, from)
}
function normalize (options = {}) {
if (options && options.env) {
process.env = options.env
clone(process.env, options.env)
}
if (!options.env) {
@@ -85,8 +96,8 @@ function normalize (options = {}) {
// in #4126. Retain the original in case someone needs it.
let shellEnv = getFromShell()
if (shellEnv && shellEnv.PATH) {
process._originalEnv = process.env
process.env = shellEnv
process._originalEnv = Object.assign({}, process.env)
clone(process.env, shellEnv)
}
}
}
@@ -96,7 +107,7 @@ function replace (env) {
return
}
process.env = env
clone(process.env, env)
}
export default { getFromShell, needsPatching, normalize, replace }

View File

@@ -1,20 +1,7 @@
'use babel'
import fs from 'fs-plus'
import path from 'path'
import Git from 'nodegit'
import ResourcePool from './resource-pool'
import {Emitter, CompositeDisposable, Disposable} from 'event-kit'
const modifiedStatusFlags = Git.Status.STATUS.WT_MODIFIED | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.WT_TYPECHANGE | Git.Status.STATUS.INDEX_TYPECHANGE
const newStatusFlags = Git.Status.STATUS.WT_NEW | Git.Status.STATUS.INDEX_NEW
const deletedStatusFlags = Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED
const indexStatusFlags = Git.Status.STATUS.INDEX_NEW | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.INDEX_RENAMED | Git.Status.STATUS.INDEX_TYPECHANGE
const ignoredStatusFlags = 1 << 14 // TODO: compose this from libgit2 constants
const submoduleMode = 57344 // TODO: compose this from libgit2 constants
// Just using this for _.isEqual and _.object, we should impl our own here
import _ from 'underscore-plus'
import {Repository} from 'ohnogit'
import {CompositeDisposable, Disposable} from 'event-kit'
// For the most part, this class behaves the same as `GitRepository`, with a few
// notable differences:
@@ -29,39 +16,19 @@ export default class GitRepositoryAsync {
}
static get Git () {
return Git
return Repository.Git
}
// The name of the error thrown when an action is attempted on a destroyed
// repository.
static get DestroyedErrorName () {
return 'GitRepositoryAsync.destroyed'
return Repository.DestroyedErrorName
}
constructor (_path, options = {}) {
// We'll serialize our access manually.
Git.setThreadSafetyStatus(Git.THREAD_SAFETY.DISABLED)
this.repo = Repository.open(_path, options)
this.emitter = new Emitter()
this.subscriptions = new CompositeDisposable()
this.pathStatusCache = {}
this.path = null
// NB: These needs to happen before the following .openRepository call.
this.openedPath = _path
this._openExactPath = options.openExactPath || false
this.repoPromise = this.openRepository()
// NB: We don't currently _use_ the pooled object. But by giving it one
// thing, we're really just serializing all the work. Down the road, we
// could open multiple connections to the repository.
this.repoPool = new ResourcePool([this.repoPromise])
this.isCaseInsensitive = fs.isCaseInsensitive()
this.upstream = {}
this.submodules = {}
this._refreshingPromise = Promise.resolve()
let {refreshOnWindowFocus = true} = options
if (refreshOnWindowFocus) {
@@ -78,23 +45,26 @@ export default class GitRepositoryAsync {
}
}
// This exists to provide backwards compatibility.
get _refreshingPromise () {
return this.repo._refreshingPromise
}
get openedPath () {
return this.repo.openedPath
}
// Public: Destroy this {GitRepositoryAsync} object.
//
// This destroys any tasks and subscriptions and releases the underlying
// libgit2 repository handle. This method is idempotent.
destroy () {
if (this.emitter) {
this.emitter.emit('did-destroy')
this.emitter.dispose()
this.emitter = null
}
this.repo.destroy()
if (this.subscriptions) {
this.subscriptions.dispose()
this.subscriptions = null
}
this.repoPromise = null
}
// Event subscription
@@ -107,7 +77,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidDestroy (callback) {
return this.emitter.on('did-destroy', callback)
return this.repo.onDidDestroy(callback)
}
// Public: Invoke the given callback when a specific file's status has
@@ -122,7 +92,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeStatus (callback) {
return this.emitter.on('did-change-status', callback)
return this.repo.onDidChangeStatus(callback)
}
// Public: Invoke the given callback when a multiple files' statuses have
@@ -134,7 +104,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeStatuses (callback) {
return this.emitter.on('did-change-statuses', callback)
return this.repo.onDidChangeStatuses(callback)
}
// Repository details
@@ -151,25 +121,13 @@ export default class GitRepositoryAsync {
// Public: Returns a {Promise} which resolves to the {String} path of the
// repository.
getPath () {
return this.getRepo().then(repo => {
if (!this.path) {
this.path = repo.path().replace(/\/$/, '')
}
return this.path
})
return this.repo.getPath()
}
// Public: Returns a {Promise} which resolves to the {String} working
// directory path of the repository.
getWorkingDirectory (_path) {
return this.getRepo(_path).then(repo => {
if (!repo.cachedWorkdir) {
repo.cachedWorkdir = repo.workdir()
}
return repo.cachedWorkdir
})
return this.repo.getWorkingDirectory()
}
// Public: Returns a {Promise} that resolves to true if at the root, false if
@@ -191,8 +149,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Promise} which resolves to the relative {String} path.
relativizeToWorkingDirectory (_path) {
return this.getWorkingDirectory()
.then(wd => this.relativize(_path, wd))
return this.repo.relativizeToWorkingDirectory(_path)
}
// Public: Makes a path relative to the repository's working directory.
@@ -202,78 +159,13 @@ export default class GitRepositoryAsync {
//
// Returns the relative {String} path.
relativize (_path, workingDirectory) {
// The original implementation also handled null workingDirectory as it
// pulled it from a sync function that could return null. We require it
// to be passed here.
let openedWorkingDirectory
if (!_path || !workingDirectory) {
return _path
}
// If the opened directory and the workdir differ, this is a symlinked repo
// root, so we have to do all the checks below twice--once against the realpath
// and one against the opened path
const opened = this.openedPath.replace(/\/\.git$/, '')
if (path.relative(opened, workingDirectory) !== '') {
openedWorkingDirectory = opened
}
if (process.platform === 'win32') {
_path = _path.replace(/\\/g, '/')
} else {
if (_path[0] !== '/') {
return _path
}
}
workingDirectory = workingDirectory.replace(/\/$/, '')
// Depending on where the paths come from, they may have a '/private/'
// prefix. Standardize by stripping that out.
_path = _path.replace(/^\/private\//i, '/')
workingDirectory = workingDirectory.replace(/^\/private\//i, '/')
const originalPath = _path
const originalWorkingDirectory = workingDirectory
if (this.isCaseInsensitive) {
_path = _path.toLowerCase()
workingDirectory = workingDirectory.toLowerCase()
}
if (_path.indexOf(workingDirectory) === 0) {
return originalPath.substring(originalWorkingDirectory.length + 1)
} else if (_path === workingDirectory) {
return ''
}
if (openedWorkingDirectory) {
openedWorkingDirectory = openedWorkingDirectory.replace(/\/$/, '')
openedWorkingDirectory = openedWorkingDirectory.replace(/^\/private\//i, '/')
const originalOpenedWorkingDirectory = openedWorkingDirectory
if (this.isCaseInsensitive) {
openedWorkingDirectory = openedWorkingDirectory.toLowerCase()
}
if (_path.indexOf(openedWorkingDirectory) === 0) {
return originalPath.substring(originalOpenedWorkingDirectory.length + 1)
} else if (_path === openedWorkingDirectory) {
return ''
}
}
return _path
return this.repo.relativize(_path, workingDirectory)
}
// Public: Returns a {Promise} which resolves to whether the given branch
// exists.
hasBranch (branch) {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => repo.getBranch(branch))
.then(branch => branch != null)
.catch(_ => false)
})
return this.repo.hasBranch(branch)
}
// Public: Retrieves a shortened version of the HEAD reference value.
@@ -287,11 +179,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Promise} which resolves to a {String}.
getShortHead (_path) {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => repo.getCurrentBranch())
.then(branch => branch.shorthand())
})
return this.repo.getShortHead(_path)
}
// Public: Is the given path a submodule in the repository?
@@ -301,19 +189,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} that resolves true if the given path is a submodule in
// the repository.
isSubmodule (_path) {
return this.relativizeToWorkingDirectory(_path)
.then(relativePath => {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => repo.index())
.then(index => {
const entry = index.getByPath(relativePath)
if (!entry) return false
return entry.mode === submoduleMode
})
})
})
return this.repo.isSubmodule(_path)
}
// Public: Returns the number of commits behind the current branch is from the
@@ -327,18 +203,7 @@ export default class GitRepositoryAsync {
// * `ahead` The {Number} of commits ahead.
// * `behind` The {Number} of commits behind.
getAheadBehindCount (reference, _path) {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => Promise.all([repo, repo.getBranch(reference)]))
.then(([repo, local]) => {
const upstream = Git.Branch.upstream(local)
return Promise.all([repo, local, upstream])
})
.then(([repo, local, upstream]) => {
return Git.Graph.aheadBehind(repo, local.target(), upstream.target())
})
.catch(_ => ({ahead: 0, behind: 0}))
})
return this.repo.getAheadBehindCount(reference, _path)
}
// Public: Get the cached ahead/behind commit counts for the current branch's
@@ -351,15 +216,7 @@ export default class GitRepositoryAsync {
// * `ahead` The {Number} of commits ahead.
// * `behind` The {Number} of commits behind.
getCachedUpstreamAheadBehindCount (_path) {
return this.relativizeToWorkingDirectory(_path)
.then(relativePath => this._submoduleForPath(_path))
.then(submodule => {
if (submodule) {
return submodule.getCachedUpstreamAheadBehindCount(_path)
} else {
return this.upstream
}
})
return this.repo.getCachedUpstreamAheadBehindCount(_path)
}
// Public: Returns the git configuration value specified by the key.
@@ -370,12 +227,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to the {String} git configuration value
// specified by the key.
getConfigValue (key, _path) {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => repo.configSnapshot())
.then(config => config.getStringBuf(key))
.catch(_ => null)
})
return this.repo.getConfigValue(key, _path)
}
// Public: Get the URL for the 'origin' remote.
@@ -386,7 +238,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to the {String} origin url of the
// repository.
getOriginURL (_path) {
return this.getConfigValue('remote.origin.url', _path)
return this.repo.getOriginURL(_path)
}
// Public: Returns the upstream branch for the current HEAD, or null if there
@@ -398,11 +250,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {String} branch name such as
// `refs/remotes/origin/master`.
getUpstreamBranch (_path) {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => repo.getCurrentBranch())
.then(branch => Git.Branch.upstream(branch))
})
return this.repo.getUpstreamBranch(_path)
}
// Public: Gets all the local and remote references.
@@ -415,25 +263,7 @@ export default class GitRepositoryAsync {
// * `remotes` An {Array} of remote reference names.
// * `tags` An {Array} of tag reference names.
getReferences (_path) {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => repo.getReferences(Git.Reference.TYPE.LISTALL))
.then(refs => {
const heads = []
const remotes = []
const tags = []
for (const ref of refs) {
if (ref.isTag()) {
tags.push(ref.name())
} else if (ref.isRemote()) {
remotes.push(ref.name())
} else if (ref.isBranch()) {
heads.push(ref.name())
}
}
return {heads, remotes, tags}
})
})
return this.repo.getReferences(_path)
}
// Public: Get the SHA for the given reference.
@@ -445,11 +275,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to the current {String} SHA for the
// given reference.
getReferenceTarget (reference, _path) {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => Git.Reference.nameToId(repo, reference))
.then(oid => oid.tostrS())
})
return this.repo.getReferenceTarget(reference, _path)
}
// Reading Status
@@ -462,9 +288,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
// is modified.
isPathModified (_path) {
return this.relativizeToWorkingDirectory(_path)
.then(relativePath => this._getStatus([relativePath]))
.then(statuses => statuses.some(status => status.isModified()))
return this.repo.isPathModified(_path)
}
// Public: Resolves true if the given path is new.
@@ -474,9 +298,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
// is new.
isPathNew (_path) {
return this.relativizeToWorkingDirectory(_path)
.then(relativePath => this._getStatus([relativePath]))
.then(statuses => statuses.some(status => status.isNew()))
return this.repo.isPathNew(_path)
}
// Public: Is the given path ignored?
@@ -486,17 +308,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
// is ignored.
isPathIgnored (_path) {
return this.getWorkingDirectory()
.then(wd => {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => {
const relativePath = this.relativize(_path, wd)
return Git.Ignore.pathIsIgnored(repo, relativePath)
})
.then(ignored => Boolean(ignored))
})
})
return this.repo.isPathIgnored(_path)
}
// Get the status of a directory in the repository's working directory.
@@ -507,18 +319,7 @@ export default class GitRepositoryAsync {
// value can be passed to {::isStatusModified} or {::isStatusNew} to get more
// information.
getDirectoryStatus (directoryPath) {
return this.relativizeToWorkingDirectory(directoryPath)
.then(relativePath => {
const pathspec = relativePath + '/**'
return this._getStatus([pathspec])
})
.then(statuses => {
return Promise.all(statuses.map(s => s.statusBit())).then(bits => {
return bits
.filter(b => b > 0)
.reduce((status, bit) => status | bit, 0)
})
})
return this.repo.getDirectoryStatus(directoryPath)
}
// Refresh the status bit for the given path.
@@ -531,27 +332,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {Number} which is the refreshed
// status bit for the path.
refreshStatusForPath (_path) {
let relativePath
return this.getWorkingDirectory()
.then(wd => {
relativePath = this.relativize(_path, wd)
return this._getStatus([relativePath])
})
.then(statuses => {
const cachedStatus = this.pathStatusCache[relativePath] || 0
const status = statuses[0] ? statuses[0].statusBit() : Git.Status.STATUS.CURRENT
if (status !== cachedStatus) {
if (status === Git.Status.STATUS.CURRENT) {
delete this.pathStatusCache[relativePath]
} else {
this.pathStatusCache[relativePath] = status
}
this.emitter.emit('did-change-status', {path: _path, pathStatus: status})
}
return status
})
return this.repo.refreshStatusForPath(_path)
}
// Returns a Promise that resolves to the status bit of a given path if it has
@@ -567,8 +348,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a status {Number} or null if the
// path is not in the cache.
getCachedPathStatus (_path) {
return this.relativizeToWorkingDirectory(_path)
.then(relativePath => this.pathStatusCache[relativePath])
return this.repo.getCachedPathStatus(_path)
}
// Public: Get the cached statuses for the repository.
@@ -576,7 +356,7 @@ export default class GitRepositoryAsync {
// Returns an {Object} of {Number} statuses, keyed by {String} working
// directory-relative file names.
getCachedPathStatuses () {
return this.pathStatusCache
return this.repo.pathStatusCache
}
// Public: Returns true if the given status indicates modification.
@@ -585,7 +365,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Boolean} that's true if the `statusBit` indicates modification.
isStatusModified (statusBit) {
return (statusBit & modifiedStatusFlags) > 0
return this.repo.isStatusModified(statusBit)
}
// Public: Returns true if the given status indicates a new path.
@@ -594,7 +374,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Boolean} that's true if the `statusBit` indicates a new path.
isStatusNew (statusBit) {
return (statusBit & newStatusFlags) > 0
return this.repo.isStatusNew(statusBit)
}
// Public: Returns true if the given status indicates the path is staged.
@@ -604,7 +384,7 @@ export default class GitRepositoryAsync {
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
// staged.
isStatusStaged (statusBit) {
return (statusBit & indexStatusFlags) > 0
return this.repo.isStatusStaged(statusBit)
}
// Public: Returns true if the given status indicates the path is ignored.
@@ -614,7 +394,7 @@ export default class GitRepositoryAsync {
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
// ignored.
isStatusIgnored (statusBit) {
return (statusBit & ignoredStatusFlags) > 0
return this.repo.isStatusIgnored(statusBit)
}
// Public: Returns true if the given status indicates the path is deleted.
@@ -624,7 +404,7 @@ export default class GitRepositoryAsync {
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
// deleted.
isStatusDeleted (statusBit) {
return (statusBit & deletedStatusFlags) > 0
return this.repo.isStatusDeleted(statusBit)
}
// Retrieving Diffs
@@ -640,40 +420,7 @@ export default class GitRepositoryAsync {
// * `added` The {Number} of added lines.
// * `deleted` The {Number} of deleted lines.
getDiffStats (_path) {
return this.getWorkingDirectory(_path)
.then(wd => {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => Promise.all([repo, repo.getHeadCommit()]))
.then(([repo, headCommit]) => Promise.all([repo, headCommit.getTree()]))
.then(([repo, tree]) => {
const options = new Git.DiffOptions()
options.contextLines = 0
options.flags = Git.Diff.OPTION.DISABLE_PATHSPEC_MATCH
options.pathspec = this.relativize(_path, wd)
if (process.platform === 'win32') {
// Ignore eol of line differences on windows so that files checked in
// as LF don't report every line modified when the text contains CRLF
// endings.
options.flags |= Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
}
return Git.Diff.treeToWorkdir(repo, tree, options)
})
.then(diff => this._getDiffLines(diff))
.then(lines => {
const stats = {added: 0, deleted: 0}
for (const line of lines) {
const origin = line.origin()
if (origin === Git.Diff.LINE.ADDITION) {
stats.added++
} else if (origin === Git.Diff.LINE.DELETION) {
stats.deleted++
}
}
return stats
})
})
})
return this.repo.getDiffStats(_path)
}
// Public: Retrieves the line diffs comparing the `HEAD` version of the given
@@ -688,30 +435,7 @@ export default class GitRepositoryAsync {
// * `oldLines` The {Number} of lines in the old hunk.
// * `newLines` The {Number} of lines in the new hunk
getLineDiffs (_path, text) {
return this.getWorkingDirectory(_path)
.then(wd => {
let relativePath = null
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => {
relativePath = this.relativize(_path, wd)
return repo.getHeadCommit()
})
.then(commit => commit.getEntry(relativePath))
.then(entry => entry.getBlob())
.then(blob => {
const options = new Git.DiffOptions()
options.contextLines = 0
if (process.platform === 'win32') {
// Ignore eol of line differences on windows so that files checked in
// as LF don't report every line modified when the text contains CRLF
// endings.
options.flags = Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
}
return this._diffBlobToBuffer(blob, text, options)
})
})
})
return this.repo.getLineDiffs(_path, text)
}
// Checking Out
@@ -732,19 +456,7 @@ export default class GitRepositoryAsync {
// Returns a {Promise} that resolves or rejects depending on whether the
// method was successful.
checkoutHead (_path) {
return this.getWorkingDirectory(_path)
.then(wd => {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => {
const checkoutOptions = new Git.CheckoutOptions()
checkoutOptions.paths = [this.relativize(_path, wd)]
checkoutOptions.checkoutStrategy = Git.Checkout.STRATEGY.FORCE | Git.Checkout.STRATEGY.DISABLE_PATHSPEC_MATCH
return Git.Checkout.head(repo, checkoutOptions)
})
})
})
.then(() => this.refreshStatusForPath(_path))
return this.repo.checkoutHead(_path)
}
// Public: Checks out a branch in your repository.
@@ -755,19 +467,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Promise} that resolves if the method was successful.
checkoutReference (reference, create) {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => repo.checkoutBranch(reference))
})
.catch(error => {
if (create) {
return this._createBranch(reference)
.then(_ => this.checkoutReference(reference, false))
} else {
throw error
}
})
.then(_ => null)
return this.repo.checkoutReference(reference, create)
}
// Private
@@ -786,107 +486,10 @@ export default class GitRepositoryAsync {
return this.checkoutHead(filePath)
}
// Create a new branch with the given name.
// Refreshes the git status.
//
// * `name` The {String} name of the new branch.
//
// Returns a {Promise} which resolves to a {NodeGit.Ref} reference to the
// created branch.
_createBranch (name) {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => Promise.all([repo, repo.getHeadCommit()]))
.then(([repo, commit]) => repo.createBranch(name, commit))
})
}
// Get all the hunks in the diff.
//
// * `diff` The {NodeGit.Diff} whose hunks should be retrieved.
//
// Returns a {Promise} which resolves to an {Array} of {NodeGit.Hunk}.
_getDiffHunks (diff) {
return diff.patches()
.then(patches => Promise.all(patches.map(p => p.hunks()))) // patches :: Array<Patch>
.then(hunks => _.flatten(hunks)) // hunks :: Array<Array<Hunk>>
}
// Get all the lines contained in the diff.
//
// * `diff` The {NodeGit.Diff} use lines should be retrieved.
//
// Returns a {Promise} which resolves to an {Array} of {NodeGit.Line}.
_getDiffLines (diff) {
return this._getDiffHunks(diff)
.then(hunks => Promise.all(hunks.map(h => h.lines())))
.then(lines => _.flatten(lines)) // lines :: Array<Array<Line>>
}
// Diff the given blob and buffer with the provided options.
//
// * `blob` The {NodeGit.Blob}
// * `buffer` The {String} buffer.
// * `options` The {NodeGit.DiffOptions}
//
// Returns a {Promise} which resolves to an {Array} of {Object}s which have
// the following keys:
// * `oldStart` The {Number} of the old starting line.
// * `newStart` The {Number} of the new starting line.
// * `oldLines` The {Number} of old lines.
// * `newLines` The {Number} of new lines.
_diffBlobToBuffer (blob, buffer, options) {
const hunks = []
const hunkCallback = (delta, hunk, payload) => {
hunks.push({
oldStart: hunk.oldStart(),
newStart: hunk.newStart(),
oldLines: hunk.oldLines(),
newLines: hunk.newLines()
})
}
return Git.Diff.blobToBuffer(blob, null, buffer, null, options, null, null, hunkCallback, null)
.then(_ => hunks)
}
// Get the current branch and update this.branch.
//
// Returns a {Promise} which resolves to a {boolean} indicating whether the
// branch name changed.
_refreshBranch () {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => repo.getCurrentBranch())
.then(ref => ref.name())
.then(branchName => {
const changed = branchName !== this.branch
this.branch = branchName
return changed
})
})
}
// Refresh the cached ahead/behind count with the given branch.
//
// * `branchName` The {String} name of the branch whose ahead/behind should be
// used for the refresh.
//
// Returns a {Promise} which will resolve to a {boolean} indicating whether
// the ahead/behind count changed.
_refreshAheadBehindCount (branchName) {
return this.getAheadBehindCount(branchName)
.then(counts => {
const changed = !_.isEqual(counts, this.upstream)
this.upstream = counts
return changed
})
}
// Get the status for this repository.
//
// Returns a {Promise} that will resolve to an object of {String} paths to the
// {Number} status.
_getRepositoryStatus () {
// Returns a {Promise} which will resolve to {null} when refresh is complete.
refreshStatus () {
let projectPathsPromises = [Promise.resolve('')]
if (this.project) {
projectPathsPromises = this.project.getPaths()
@@ -895,163 +498,7 @@ export default class GitRepositoryAsync {
return Promise.all(projectPathsPromises)
.then(paths => paths.map(p => p.length > 0 ? p + '/**' : '*'))
.then(projectPaths => {
return this._getStatus(projectPaths.length > 0 ? projectPaths : null)
})
.then(statuses => {
const statusPairs = statuses.map(status => [status.path(), status.statusBit()])
return _.object(statusPairs)
})
}
// Get the status for the given submodule.
//
// * `submodule` The {GitRepositoryAsync} for the submodule.
//
// Returns a {Promise} which resolves to an {Object}, keyed by {String}
// repo-relative {Number} statuses.
async _getSubmoduleStatus (submodule) {
// At this point, we've called submodule._refreshSubmodules(), which would
// have refreshed the status on *its* submodules, etc. So we know that its
// cached path statuses are up-to-date.
//
// Now we just need to hoist those statuses into our repository by changing
// their paths to be relative to us.
const statuses = submodule.getCachedPathStatuses()
const repoRelativeStatuses = {}
const submoduleRepo = await submodule.getRepo()
const submoduleWorkDir = submoduleRepo.workdir()
for (const relativePath in statuses) {
const statusBit = statuses[relativePath]
const absolutePath = path.join(submoduleWorkDir, relativePath)
const repoRelativePath = await this.relativizeToWorkingDirectory(absolutePath)
repoRelativeStatuses[repoRelativePath] = statusBit
}
return repoRelativeStatuses
}
// Refresh the list of submodules in the repository.
//
// Returns a {Promise} which resolves to an {Object} keyed by {String}
// submodule names with {GitRepositoryAsync} values.
async _refreshSubmodules () {
const repo = await this.getRepo()
const wd = await this.getWorkingDirectory()
const submoduleNames = await repo.getSubmoduleNames()
for (const name of submoduleNames) {
const alreadyExists = Boolean(this.submodules[name])
if (alreadyExists) continue
const submodule = await Git.Submodule.lookup(repo, name)
const absolutePath = path.join(wd, submodule.path())
const submoduleRepo = GitRepositoryAsync.open(absolutePath, {openExactPath: true, refreshOnWindowFocus: false})
this.submodules[name] = submoduleRepo
}
for (const name in this.submodules) {
const repo = this.submodules[name]
const gone = submoduleNames.indexOf(name) < 0
if (gone) {
repo.destroy()
delete this.submodules[name]
} else {
try {
await repo.refreshStatus()
} catch (e) {
// libgit2 will sometimes report submodules that aren't actually valid
// (https://github.com/libgit2/libgit2/issues/3580). So check the
// validity of the submodules by removing any that fail.
repo.destroy()
delete this.submodules[name]
}
}
}
return _.values(this.submodules)
}
// Get the status for the submodules in the repository.
//
// Returns a {Promise} that will resolve to an object of {String} paths to the
// {Number} status.
_getSubmoduleStatuses () {
return this._refreshSubmodules()
.then(repos => {
return Promise.all(repos.map(repo => this._getSubmoduleStatus(repo)))
})
.then(statuses => _.extend({}, ...statuses))
}
// Refresh the cached status.
//
// Returns a {Promise} which will resolve to a {boolean} indicating whether
// any statuses changed.
_refreshStatus () {
return Promise.all([this._getRepositoryStatus(), this._getSubmoduleStatuses()])
.then(([repositoryStatus, submoduleStatus]) => {
const statusesByPath = _.extend({}, repositoryStatus, submoduleStatus)
const changed = !_.isEqual(this.pathStatusCache, statusesByPath)
this.pathStatusCache = statusesByPath
return changed
})
}
// Refreshes the git status.
//
// Returns a {Promise} which will resolve to {null} when refresh is complete.
refreshStatus () {
const status = this._refreshStatus()
const branch = this._refreshBranch()
const aheadBehind = branch.then(() => this._refreshAheadBehindCount(this.branch))
this._refreshingPromise = this._refreshingPromise.then(_ => {
return Promise.all([status, branch, aheadBehind])
.then(([statusChanged, branchChanged, aheadBehindChanged]) => {
if (this.emitter && (statusChanged || branchChanged || aheadBehindChanged)) {
this.emitter.emit('did-change-statuses')
}
return null
})
// Because all these refresh steps happen asynchronously, it's entirely
// possible the repository was destroyed while we were working. In which
// case we should just swallow the error.
.catch(e => {
if (this._isDestroyed()) {
return null
} else {
return Promise.reject(e)
}
})
.catch(e => {
console.error('Error refreshing repository status:')
console.error(e)
return Promise.reject(e)
})
})
return this._refreshingPromise
}
// Get the submodule for the given path.
//
// Returns a {Promise} which resolves to the {GitRepositoryAsync} submodule or
// null if it isn't a submodule path.
async _submoduleForPath (_path) {
let relativePath = await this.relativizeToWorkingDirectory(_path)
for (const submodulePath in this.submodules) {
const submoduleRepo = this.submodules[submodulePath]
if (relativePath === submodulePath) {
return submoduleRepo
} else if (relativePath.indexOf(`${submodulePath}/`) === 0) {
relativePath = relativePath.substring(submodulePath.length + 1)
const innerSubmodule = await submoduleRepo._submoduleForPath(relativePath)
return innerSubmodule || submoduleRepo
}
}
return null
.then(pathspecs => this.repo.refreshStatus(pathspecs))
}
// Get the NodeGit repository for the given path.
@@ -1062,16 +509,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Promise} which resolves to the {NodeGit.Repository}.
getRepo (_path) {
if (this._isDestroyed()) {
const error = new Error('Repository has been destroyed')
error.name = GitRepositoryAsync.DestroyedErrorName
return Promise.reject(error)
}
if (!_path) return this.repoPromise
return this._submoduleForPath(_path)
.then(submodule => submodule ? submodule.getRepo() : this.repoPromise)
return this.repo.getRepo(_path)
}
// Open a new instance of the underlying {NodeGit.Repository}.
@@ -1081,11 +519,7 @@ export default class GitRepositoryAsync {
//
// Returns the new {NodeGit.Repository}.
openRepository () {
if (this._openExactPath) {
return Git.Repository.open(this.openedPath)
} else {
return Git.Repository.openExt(this.openedPath, 0, '')
}
return this.repo.openRepository()
}
// Section: Private
@@ -1095,7 +529,7 @@ export default class GitRepositoryAsync {
//
// Returns a {Boolean}.
_isDestroyed () {
return this.repoPromise == null
return this.repo._isDestroyed()
}
// Subscribe to events on the given buffer.
@@ -1121,28 +555,4 @@ export default class GitRepositoryAsync {
this.subscriptions.add(bufferSubscriptions)
}
// Get the status for the given paths.
//
// * `paths` The {String} paths whose status is wanted. If undefined, get the
// status for the whole repository.
//
// Returns a {Promise} which resolves to an {Array} of {NodeGit.StatusFile}
// statuses for the paths.
_getStatus (paths) {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => {
const opts = {
flags: Git.Status.OPT.INCLUDE_UNTRACKED | Git.Status.OPT.RECURSE_UNTRACKED_DIRS
}
if (paths) {
opts.pathspec = paths
}
return repo.getStatusExt(opts)
})
})
}
}

View File

@@ -166,12 +166,7 @@ class GitRepository
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeStatuses: (callback) ->
@async.onDidChangeStatuses ->
# Defer the callback to the next tick so that we've reset
# `@statusesByPath` by the time it's called. Otherwise reads from within
# the callback could be inconsistent.
# See https://github.com/atom/atom/issues/11396
process.nextTick callback
@emitter.on 'did-change-statuses', callback
###
Section: Repository Details
@@ -496,9 +491,27 @@ class GitRepository
#
# Returns a promise that resolves when the repository has been refreshed.
refreshStatus: ->
statusesChanged = false
# Listen for `did-change-statuses` so we know if something changed. But we
# need to wait to propagate it until after we've set the branch and cleared
# the `statusesByPath` cache. So just set a flag, and we'll emit the event
# after refresh is done.
subscription = @async.onDidChangeStatuses ->
subscription?.dispose()
subscription = null
statusesChanged = true
asyncRefresh = @async.refreshStatus().then =>
@statusesByPath = {}
subscription?.dispose()
subscription = null
@branch = @async?.branch
@statusesByPath = {}
if statusesChanged
@emitter.emit 'did-change-statuses'
syncRefresh = new Promise (resolve, reject) =>
@handlerPath ?= require.resolve('./repository-status-handler')

View File

@@ -391,6 +391,9 @@ class Project extends Model
subscribeToBuffer: (buffer) ->
buffer.onDidDestroy => @removeBuffer(buffer)
buffer.onDidChangePath =>
unless @getPaths().length > 0
@setPaths([path.dirname(buffer.getPath())])
buffer.onWillThrowWatchError ({error, handle}) =>
handle()
@notificationManager.addWarning """

View File

@@ -1,6 +1,6 @@
{ipcRenderer} = require 'electron'
module.exports = ({commandRegistry, commandInstaller, config}) ->
module.exports = ({commandRegistry, commandInstaller, config, notificationManager, project, clipboard}) ->
commandRegistry.add 'atom-workspace',
'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem()
'pane:show-previous-recently-used-item': -> @getModel().getActivePane().activatePreviousRecentlyUsedItem()
@@ -31,9 +31,15 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'application:unhide-all-applications': -> ipcRenderer.send('command', 'application:unhide-all-applications')
'application:new-window': -> ipcRenderer.send('command', 'application:new-window')
'application:new-file': -> ipcRenderer.send('command', 'application:new-file')
'application:open': -> ipcRenderer.send('command', 'application:open')
'application:open-file': -> ipcRenderer.send('command', 'application:open-file')
'application:open-folder': -> ipcRenderer.send('command', 'application:open-folder')
'application:open': ->
defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0]
ipcRenderer.send('open-command', 'application:open', defaultPath)
'application:open-file': ->
defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0]
ipcRenderer.send('open-command', 'application:open-file', defaultPath)
'application:open-folder': ->
defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0]
ipcRenderer.send('open-command', 'application:open-folder', defaultPath)
'application:open-dev': -> ipcRenderer.send('command', 'application:open-dev')
'application:open-safe': -> ipcRenderer.send('command', 'application:open-safe')
'application:add-project-folder': -> atom.addProjectFolder()
@@ -187,9 +193,9 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6)
'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7)
'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8)
'editor:log-cursor-scope': -> @logCursorScope()
'editor:copy-path': -> @copyPathToClipboard(false)
'editor:copy-project-path': -> @copyPathToClipboard(true)
'editor:log-cursor-scope': -> showCursorScope(@getCursorScope(), notificationManager)
'editor:copy-path': -> copyPathToClipboard(this, project, clipboard, false)
'editor:copy-project-path': -> copyPathToClipboard(this, project, clipboard, true)
'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide'))
'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers'))
'editor:scroll-to-cursor': -> @scrollToCursorPosition()
@@ -204,7 +210,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'editor:newline-below': -> @insertNewlineBelow()
'editor:newline-above': -> @insertNewlineAbove()
'editor:toggle-line-comments': -> @toggleLineCommentsInSelection()
'editor:checkout-head-revision': -> @checkoutHeadRevision()
'editor:checkout-head-revision': -> atom.workspace.checkoutHeadRevision(this)
'editor:move-line-up': -> @moveLineUp()
'editor:move-line-down': -> @moveLineDown()
'editor:move-selection-left': -> @moveSelectionLeft()
@@ -232,3 +238,15 @@ stopEventPropagationAndGroupUndo = (config, commandListeners) ->
model.transact config.get('editor.undoGroupingInterval'), ->
commandListener.call(model, event)
newCommandListeners
showCursorScope = (descriptor, notificationManager) ->
list = descriptor.scopes.toString().split(',')
list = list.map (item) -> "* #{item}"
content = "Scopes at Cursor\n#{list.join('\n')}"
notificationManager.addInfo(content, dismissable: true)
copyPathToClipboard = (editor, project, clipboard, relative) ->
if filePath = editor.getPath()
filePath = project.relativize(filePath) if relative
clipboard.write(filePath)

View File

@@ -1,57 +0,0 @@
/** @babel */
// Manages a pool of some resource.
export default class ResourcePool {
constructor (pool) {
this.pool = pool
this.queue = []
}
// Enqueue the given function. The function will be given an object from the
// pool. The function must return a {Promise}.
enqueue (fn) {
let resolve = null
let reject = null
const wrapperPromise = new Promise((resolve_, reject_) => {
resolve = resolve_
reject = reject_
})
this.queue.push(this.wrapFunction(fn, resolve, reject))
this.dequeueIfAble()
return wrapperPromise
}
wrapFunction (fn, resolve, reject) {
return (resource) => {
const promise = fn(resource)
promise
.then(result => {
resolve(result)
this.taskDidComplete(resource)
}, error => {
reject(error)
this.taskDidComplete(resource)
})
}
}
taskDidComplete (resource) {
this.pool.push(resource)
this.dequeueIfAble()
}
dequeueIfAble () {
if (!this.pool.length || !this.queue.length) return
const fn = this.queue.shift()
const resource = this.pool.shift()
fn(resource)
}
getQueueDepth () { return this.queue.length }
}

View File

@@ -378,7 +378,8 @@ class Selection extends Model
indentAdjustment = @editor.indentLevelForLine(precedingText) - options.indentBasis
@adjustIndent(remainingLines, indentAdjustment)
if options.autoIndent and NonWhitespaceRegExp.test(text) and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
textIsAutoIndentable = text is '\n' or text is '\r\n' or NonWhitespaceRegExp.test(text)
if options.autoIndent and textIsAutoIndentable and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
autoIndentFirstLine = true
firstLine = precedingText + firstInsertedLine
desiredIndentLevel = @editor.languageMode.suggestedIndentForLineAtBufferRow(oldBufferRange.start.row, firstLine)

View File

@@ -459,19 +459,21 @@ class TextEditorPresenter
pixelPosition = @pixelPositionForScreenPosition(screenPosition)
top = pixelPosition.top + @lineHeight
left = pixelPosition.left + @gutterWidth
# Fixed positioning.
top = @boundingClientRect.top + pixelPosition.top + @lineHeight
left = @boundingClientRect.left + pixelPosition.left + @gutterWidth
if overlayDimensions = @overlayDimensions[decoration.id]
{itemWidth, itemHeight, contentMargin} = overlayDimensions
rightDiff = left + @boundingClientRect.left + itemWidth + contentMargin - @windowWidth
rightDiff = left + itemWidth + contentMargin - @windowWidth
left -= rightDiff if rightDiff > 0
leftDiff = left + @boundingClientRect.left + contentMargin
leftDiff = left + contentMargin
left -= leftDiff if leftDiff < 0
if top + @boundingClientRect.top + itemHeight > @windowHeight and top - (itemHeight + @lineHeight) >= 0
if top + itemHeight > @windowHeight and
top - (itemHeight + @lineHeight) >= 0
top -= itemHeight + @lineHeight
pixelPosition.top = top

View File

@@ -9,7 +9,6 @@ Cursor = require './cursor'
Model = require './model'
Selection = require './selection'
TextMateScopeSelector = require('first-mate').ScopeSelector
{Directory} = require "pathwatcher"
GutterContainer = require './gutter-container'
TextEditorElement = require './text-editor-element'
@@ -79,14 +78,9 @@ class TextEditor extends Model
state.displayBuffer = displayBuffer
state.selectionsMarkerLayer = displayBuffer.getMarkerLayer(state.selectionsMarkerLayerId)
state.config = atomEnvironment.config
state.notificationManager = atomEnvironment.notifications
state.packageManager = atomEnvironment.packages
state.clipboard = atomEnvironment.clipboard
state.viewRegistry = atomEnvironment.views
state.grammarRegistry = atomEnvironment.grammars
state.project = atomEnvironment.project
state.assert = atomEnvironment.assert.bind(atomEnvironment)
state.applicationDelegate = atomEnvironment.applicationDelegate
editor = new this(state)
if state.registered
disposable = atomEnvironment.textEditors.add(editor)
@@ -99,20 +93,15 @@ class TextEditor extends Model
{
@softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine, initialColumn, tabLength,
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
@project, @assert, @applicationDelegate, grammar, showInvisibles, @autoHeight, @scrollPastEnd
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config, @clipboard, @grammarRegistry,
@assert, grammar, showInvisibles, @autoHeight, @scrollPastEnd
} = params
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
throw new Error("Must pass a notificationManager parameter when constructing TextEditors") unless @notificationManager?
throw new Error("Must pass a packageManager parameter when constructing TextEditors") unless @packageManager?
throw new Error("Must pass a clipboard parameter when constructing TextEditors") unless @clipboard?
throw new Error("Must pass a viewRegistry parameter when constructing TextEditors") unless @viewRegistry?
throw new Error("Must pass a grammarRegistry parameter when constructing TextEditors") unless @grammarRegistry?
throw new Error("Must pass a project parameter when constructing TextEditors") unless @project?
throw new Error("Must pass an assert parameter when constructing TextEditors") unless @assert?
@assert ?= (condition) -> condition
@firstVisibleScreenRow ?= 0
@firstVisibleScreenColumn ?= 0
@emitter = new Emitter
@@ -129,7 +118,7 @@ class TextEditor extends Model
buffer ?= new TextBuffer
@displayBuffer ?= new DisplayBuffer({
buffer, tabLength, softWrapped, ignoreInvisibles: @mini or not showInvisibles, largeFileMode,
@config, @assert, @grammarRegistry, @packageManager
@config, @assert, @grammarRegistry
})
@buffer = @displayBuffer.buffer
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true)
@@ -173,8 +162,6 @@ class TextEditor extends Model
subscribeToBuffer: ->
@buffer.retain()
@disposables.add @buffer.onDidChangePath =>
unless @project.getPaths().length > 0
@project.setPaths([path.dirname(@getPath())])
@emitter.emit 'did-change-title', @getTitle()
@emitter.emit 'did-change-path', @getPath()
@disposables.add @buffer.onDidChangeEncoding =>
@@ -487,12 +474,12 @@ class TextEditor extends Model
onDidChangeScrollTop: (callback) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.")
@viewRegistry.getView(this).onDidChangeScrollTop(callback)
@getElement().onDidChangeScrollTop(callback)
onDidChangeScrollLeft: (callback) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollLeft instead.")
@viewRegistry.getView(this).onDidChangeScrollLeft(callback)
@getElement().onDidChangeScrollLeft(callback)
onDidRequestAutoscroll: (callback) ->
@displayBuffer.onDidRequestAutoscroll(callback)
@@ -520,9 +507,9 @@ class TextEditor extends Model
softTabs = @getSoftTabs()
newEditor = new TextEditor({
@buffer, displayBuffer, selectionsMarkerLayer, @tabLength, softTabs,
suppressCursorCreation: true, @config, @notificationManager, @packageManager,
suppressCursorCreation: true, @config,
@firstVisibleScreenRow, @firstVisibleScreenColumn,
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
@clipboard, @grammarRegistry, @assert
})
newEditor
@@ -682,12 +669,6 @@ class TextEditor extends Model
# Essential: Returns {Boolean} `true` if this editor has no content.
isEmpty: -> @buffer.isEmpty()
# Copies the current file path to the native clipboard.
copyPathToClipboard: (relative = false) ->
if filePath = @getPath()
filePath = atom.project.relativize(filePath) if relative
@clipboard.write(filePath)
###
Section: File Operations
###
@@ -716,25 +697,6 @@ class TextEditor extends Model
# via {Pane::saveItemAs}.
getSaveDialogOptions: -> {}
checkoutHeadRevision: ->
if @getPath()
checkoutHead = =>
@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 \"#{@getFileName()}\" since the last Git commit?"
buttons:
OK: checkoutHead
Cancel: null
else
checkoutHead()
else
Promise.resolve(false)
###
Section: Reading Text
###
@@ -2827,13 +2789,9 @@ class TextEditor extends Model
@commentScopeSelector ?= new TextMateScopeSelector('comment.*')
@commentScopeSelector.matches(@scopeDescriptorForBufferPosition([bufferRow, match.index]).scopes)
logCursorScope: ->
scopeDescriptor = @getLastCursor().getScopeDescriptor()
list = scopeDescriptor.scopes.toString().split(',')
list = list.map (item) -> "* #{item}"
content = "Scopes at Cursor\n#{list.join('\n')}"
@notificationManager.addInfo(content, dismissable: true)
# Get the scope descriptor at the cursor.
getCursorScope: ->
@getLastCursor().getScopeDescriptor()
# {Delegates to: DisplayBuffer.tokenForBufferPosition}
tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
@@ -3138,24 +3096,24 @@ class TextEditor extends Model
scrollToTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
@viewRegistry.getView(this).scrollToTop()
@getElement().scrollToTop()
scrollToBottom: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
@viewRegistry.getView(this).scrollToBottom()
@getElement().scrollToBottom()
scrollToScreenRange: (screenRange, options) -> @displayBuffer.scrollToScreenRange(screenRange, options)
getHorizontalScrollbarHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.")
@viewRegistry.getView(this).getHorizontalScrollbarHeight()
@getElement().getHorizontalScrollbarHeight()
getVerticalScrollbarWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVerticalScrollbarWidth instead.")
@viewRegistry.getView(this).getVerticalScrollbarWidth()
@getElement().getVerticalScrollbarWidth()
pageUp: ->
@moveUp(@getRowsPerPage())
@@ -3222,11 +3180,11 @@ class TextEditor extends Model
pixelPositionForBufferPosition: (bufferPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
@viewRegistry.getView(this).pixelPositionForBufferPosition(bufferPosition)
@getElement().pixelPositionForBufferPosition(bufferPosition)
pixelPositionForScreenPosition: (screenPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead")
@viewRegistry.getView(this).pixelPositionForScreenPosition(screenPosition)
@getElement().pixelPositionForScreenPosition(screenPosition)
getSelectionMarkerAttributes: ->
{type: 'selection', invalidate: 'never'}
@@ -3255,7 +3213,7 @@ class TextEditor extends Model
@displayBuffer.setHeight(height)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setHeight instead.")
@viewRegistry.getView(this).setHeight(height)
@getElement().setHeight(height)
getHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHeight instead.")
@@ -3268,7 +3226,7 @@ class TextEditor extends Model
@displayBuffer.setWidth(width)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setWidth instead.")
@viewRegistry.getView(this).setWidth(width)
@getElement().setWidth(width)
getWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.")
@@ -3312,77 +3270,77 @@ class TextEditor extends Model
getScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.")
@viewRegistry.getView(this).getScrollTop()
@getElement().getScrollTop()
setScrollTop: (scrollTop) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollTop instead.")
@viewRegistry.getView(this).setScrollTop(scrollTop)
@getElement().setScrollTop(scrollTop)
getScrollBottom: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollBottom instead.")
@viewRegistry.getView(this).getScrollBottom()
@getElement().getScrollBottom()
setScrollBottom: (scrollBottom) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollBottom instead.")
@viewRegistry.getView(this).setScrollBottom(scrollBottom)
@getElement().setScrollBottom(scrollBottom)
getScrollLeft: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollLeft instead.")
@viewRegistry.getView(this).getScrollLeft()
@getElement().getScrollLeft()
setScrollLeft: (scrollLeft) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollLeft instead.")
@viewRegistry.getView(this).setScrollLeft(scrollLeft)
@getElement().setScrollLeft(scrollLeft)
getScrollRight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollRight instead.")
@viewRegistry.getView(this).getScrollRight()
@getElement().getScrollRight()
setScrollRight: (scrollRight) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollRight instead.")
@viewRegistry.getView(this).setScrollRight(scrollRight)
@getElement().setScrollRight(scrollRight)
getScrollHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollHeight instead.")
@viewRegistry.getView(this).getScrollHeight()
@getElement().getScrollHeight()
getScrollWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollWidth instead.")
@viewRegistry.getView(this).getScrollWidth()
@getElement().getScrollWidth()
getMaxScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getMaxScrollTop instead.")
@viewRegistry.getView(this).getMaxScrollTop()
@getElement().getMaxScrollTop()
intersectsVisibleRowRange: (startRow, endRow) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")
@viewRegistry.getView(this).intersectsVisibleRowRange(startRow, endRow)
@getElement().intersectsVisibleRowRange(startRow, endRow)
selectionIntersectsVisibleRowRange: (selection) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::selectionIntersectsVisibleRowRange instead.")
@viewRegistry.getView(this).selectionIntersectsVisibleRowRange(selection)
@getElement().selectionIntersectsVisibleRowRange(selection)
screenPositionForPixelPosition: (pixelPosition) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::screenPositionForPixelPosition instead.")
@viewRegistry.getView(this).screenPositionForPixelPosition(pixelPosition)
@getElement().screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::pixelRectForScreenRange instead.")
@viewRegistry.getView(this).pixelRectForScreenRange(screenRange)
@getElement().pixelRectForScreenRange(screenRange)
###
Section: Utility

View File

@@ -29,14 +29,13 @@ class TokenizedBuffer extends Model
state.buffer = atomEnvironment.project.bufferForPathSync(state.bufferPath)
state.config = atomEnvironment.config
state.grammarRegistry = atomEnvironment.grammars
state.packageManager = atomEnvironment.packages
state.assert = atomEnvironment.assert
new this(state)
constructor: (params) ->
{
@buffer, @tabLength, @ignoreInvisibles, @largeFileMode, @config,
@grammarRegistry, @packageManager, @assert, grammarScopeName
@grammarRegistry, @assert, grammarScopeName
} = params
@emitter = new Emitter
@@ -126,7 +125,7 @@ class TokenizedBuffer extends Model
@disposables.add(@configSubscriptions)
@retokenizeLines()
@packageManager.triggerActivationHook("#{grammar.packageName}:grammar-used")
@emitter.emit 'did-change-grammar', grammar
getGrammarSelectionContent: ->

View File

@@ -4,6 +4,7 @@ path = require 'path'
{join} = path
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
fs = require 'fs-plus'
{Directory} = require 'pathwatcher'
DefaultDirectorySearcher = require './default-directory-searcher'
Model = require './model'
TextEditor = require './text-editor'
@@ -550,9 +551,17 @@ class Workspace extends Model
@project.bufferForPath(filePath, options).then (buffer) =>
editor = @buildTextEditor(_.extend({buffer, largeFileMode}, options))
disposable = atom.textEditors.add(editor)
editor.onDidDestroy -> disposable.dispose()
grammarSubscription = editor.observeGrammar(@handleGrammarUsed.bind(this))
editor.onDidDestroy ->
grammarSubscription.dispose()
disposable.dispose()
editor
handleGrammarUsed: (grammar) ->
return unless grammar?
@packageManager.triggerActivationHook("#{grammar.packageName}:grammar-used")
# Public: Returns a {Boolean} that is `true` if `object` is a `TextEditor`.
#
# * `object` An {Object} you want to perform the check against.
@@ -564,8 +573,7 @@ class Workspace extends Model
# Returns a {TextEditor}.
buildTextEditor: (params) ->
params = _.extend({
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
@grammarRegistry, @project, @assert, @applicationDelegate
@config, @clipboard, @grammarRegistry, @assert
}, params)
new TextEditor(params)
@@ -1079,3 +1087,22 @@ class Workspace extends Model
inProcessFinished = true
checkFinished()
checkoutHeadRevision: (editor) ->
if editor.getPath()
checkoutHead = =>
@project.repositoryForDirectory(new Directory(editor.getDirectoryPath()))
.then (repository) =>
repository?.async.checkoutHeadForEditor(editor)
if @config.get('editor.confirmCheckoutHeadRevision')
@applicationDelegate.confirm
message: 'Confirm Checkout HEAD Revision'
detailedMessage: "Are you sure you want to discard all changes to \"#{editor.getFileName()}\" since the last Git commit?"
buttons:
OK: checkoutHead
Cancel: null
else
checkoutHead()
else
Promise.resolve(false)

View File

@@ -15,7 +15,7 @@ atom-text-editor[mini] {
}
atom-overlay {
position: absolute;
position: fixed;
display: block;
z-index: 4;
}