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

@@ -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)