mirror of
https://github.com/atom/atom.git
synced 2026-01-26 07:19:06 -05:00
Merge branch 'master' into brumm-master
This commit is contained in:
@@ -230,11 +230,12 @@ class AtomEnvironment extends Model
|
||||
|
||||
@observeAutoHideMenuBar()
|
||||
|
||||
checkPortableHomeWritable = ->
|
||||
checkPortableHomeWritable = =>
|
||||
responseChannel = "check-portable-home-writable-response"
|
||||
ipcRenderer.on responseChannel, (event, response) ->
|
||||
ipcRenderer.removeAllListeners(responseChannel)
|
||||
atom.notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable
|
||||
@notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable
|
||||
@disposables.add new Disposable -> ipcRenderer.removeAllListeners(responseChannel)
|
||||
ipcRenderer.send('check-portable-home-writable', responseChannel)
|
||||
|
||||
checkPortableHomeWritable()
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
crypto = require 'crypto'
|
||||
clipboard = require './safe-clipboard'
|
||||
|
||||
# Extended: Represents the clipboard used for copying and pasting in Atom.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.clipboard` global.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# ```coffee
|
||||
# atom.clipboard.write('hello')
|
||||
#
|
||||
# console.log(atom.clipboard.read()) # 'hello'
|
||||
# ```
|
||||
module.exports =
|
||||
class Clipboard
|
||||
constructor: ->
|
||||
@reset()
|
||||
|
||||
reset: ->
|
||||
@metadata = null
|
||||
@signatureForMetadata = null
|
||||
|
||||
# Creates an `md5` hash of some text.
|
||||
#
|
||||
# * `text` A {String} to hash.
|
||||
#
|
||||
# Returns a hashed {String}.
|
||||
md5: (text) ->
|
||||
crypto.createHash('md5').update(text, 'utf8').digest('hex')
|
||||
|
||||
# Public: Write the given text to the clipboard.
|
||||
#
|
||||
# The metadata associated with the text is available by calling
|
||||
# {::readWithMetadata}.
|
||||
#
|
||||
# * `text` The {String} to store.
|
||||
# * `metadata` (optional) The additional info to associate with the text.
|
||||
write: (text, metadata) ->
|
||||
@signatureForMetadata = @md5(text)
|
||||
@metadata = metadata
|
||||
clipboard.writeText(text)
|
||||
|
||||
# Public: Read the text from the clipboard.
|
||||
#
|
||||
# Returns a {String}.
|
||||
read: ->
|
||||
clipboard.readText()
|
||||
|
||||
# Public: Read the text from the clipboard and return both the text and the
|
||||
# associated metadata.
|
||||
#
|
||||
# Returns an {Object} with the following keys:
|
||||
# * `text` The {String} clipboard text.
|
||||
# * `metadata` The metadata stored by an earlier call to {::write}.
|
||||
readWithMetadata: ->
|
||||
text = @read()
|
||||
if @signatureForMetadata is @md5(text)
|
||||
{text, @metadata}
|
||||
else
|
||||
{text}
|
||||
70
src/clipboard.js
Normal file
70
src/clipboard.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/** @babel */
|
||||
|
||||
import crypto from 'crypto'
|
||||
import clipboard from './safe-clipboard'
|
||||
|
||||
// Extended: Represents the clipboard used for copying and pasting in Atom.
|
||||
//
|
||||
// An instance of this class is always available as the `atom.clipboard` global.
|
||||
//
|
||||
// ## Examples
|
||||
//
|
||||
// ```coffee
|
||||
// atom.clipboard.write('hello')
|
||||
//
|
||||
// console.log(atom.clipboard.read()) # 'hello'
|
||||
// ```
|
||||
export default class Clipboard {
|
||||
constructor () {
|
||||
this.reset()
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.metadata = null
|
||||
this.signatureForMetadata = null
|
||||
}
|
||||
|
||||
// Creates an `md5` hash of some text.
|
||||
//
|
||||
// * `text` A {String} to hash.
|
||||
//
|
||||
// Returns a hashed {String}.
|
||||
md5 (text) {
|
||||
return crypto.createHash('md5').update(text, 'utf8').digest('hex')
|
||||
}
|
||||
|
||||
// Public: Write the given text to the clipboard.
|
||||
//
|
||||
// The metadata associated with the text is available by calling
|
||||
// {::readWithMetadata}.
|
||||
//
|
||||
// * `text` The {String} to store.
|
||||
// * `metadata` (optional) The additional info to associate with the text.
|
||||
write (text, metadata) {
|
||||
this.signatureForMetadata = this.md5(text)
|
||||
this.metadata = metadata
|
||||
clipboard.writeText(text)
|
||||
}
|
||||
|
||||
// Public: Read the text from the clipboard.
|
||||
//
|
||||
// Returns a {String}.
|
||||
read () {
|
||||
return clipboard.readText()
|
||||
}
|
||||
|
||||
// Public: Read the text from the clipboard and return both the text and the
|
||||
// associated metadata.
|
||||
//
|
||||
// Returns an {Object} with the following keys:
|
||||
// * `text` The {String} clipboard text.
|
||||
// * `metadata` The metadata stored by an earlier call to {::write}.
|
||||
readWithMetadata () {
|
||||
let text = this.read()
|
||||
if (this.signatureForMetadata === this.md5(text)) {
|
||||
return {text, metadata: this.metadata}
|
||||
} else {
|
||||
return {text}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
_ = require 'underscore-plus'
|
||||
ParsedColor = null
|
||||
|
||||
# Essential: A simple color class returned from {Config::get} when the value
|
||||
# at the key path is of type 'color'.
|
||||
module.exports =
|
||||
class Color
|
||||
# Essential: Parse a {String} or {Object} into a {Color}.
|
||||
#
|
||||
# * `value` A {String} such as `'white'`, `#ff00ff`, or
|
||||
# `'rgba(255, 15, 60, .75)'` or an {Object} with `red`, `green`, `blue`,
|
||||
# and `alpha` properties.
|
||||
#
|
||||
# Returns a {Color} or `null` if it cannot be parsed.
|
||||
@parse: (value) ->
|
||||
return null if _.isArray(value) or _.isFunction(value)
|
||||
return null unless _.isObject(value) or _.isString(value)
|
||||
|
||||
ParsedColor ?= require 'color'
|
||||
|
||||
try
|
||||
parsedColor = new ParsedColor(value)
|
||||
catch error
|
||||
return null
|
||||
|
||||
new Color(parsedColor.red(), parsedColor.green(), parsedColor.blue(), parsedColor.alpha())
|
||||
|
||||
constructor: (red, green, blue, alpha) ->
|
||||
Object.defineProperties this,
|
||||
red:
|
||||
set: (newRed) -> red = parseColor(newRed)
|
||||
get: -> red
|
||||
enumerable: true
|
||||
configurable: false
|
||||
green:
|
||||
set: (newGreen) -> green = parseColor(newGreen)
|
||||
get: -> green
|
||||
enumerable: true
|
||||
configurable: false
|
||||
blue:
|
||||
set: (newBlue) -> blue = parseColor(newBlue)
|
||||
get: -> blue
|
||||
enumerable: true
|
||||
configurable: false
|
||||
alpha:
|
||||
set: (newAlpha) -> alpha = parseAlpha(newAlpha)
|
||||
get: -> alpha
|
||||
enumerable: true
|
||||
configurable: false
|
||||
|
||||
@red = red
|
||||
@green = green
|
||||
@blue = blue
|
||||
@alpha = alpha
|
||||
|
||||
# Essential: Returns a {String} in the form `'#abcdef'`.
|
||||
toHexString: ->
|
||||
"##{numberToHexString(@red)}#{numberToHexString(@green)}#{numberToHexString(@blue)}"
|
||||
|
||||
# Essential: Returns a {String} in the form `'rgba(25, 50, 75, .9)'`.
|
||||
toRGBAString: ->
|
||||
"rgba(#{@red}, #{@green}, #{@blue}, #{@alpha})"
|
||||
|
||||
isEqual: (color) ->
|
||||
return true if this is color
|
||||
color = Color.parse(color) unless color instanceof Color
|
||||
return false unless color?
|
||||
color.red is @red and color.blue is @blue and color.green is @green and color.alpha is @alpha
|
||||
|
||||
clone: -> new Color(@red, @green, @blue, @alpha)
|
||||
|
||||
parseColor = (color) ->
|
||||
color = parseInt(color)
|
||||
color = 0 if isNaN(color)
|
||||
color = Math.max(color, 0)
|
||||
color = Math.min(color, 255)
|
||||
color
|
||||
|
||||
parseAlpha = (alpha) ->
|
||||
alpha = parseFloat(alpha)
|
||||
alpha = 1 if isNaN(alpha)
|
||||
alpha = Math.max(alpha, 0)
|
||||
alpha = Math.min(alpha, 1)
|
||||
alpha
|
||||
|
||||
numberToHexString = (number) ->
|
||||
hex = number.toString(16)
|
||||
hex = "0#{hex}" if number < 16
|
||||
hex
|
||||
134
src/color.js
Normal file
134
src/color.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/** @babel */
|
||||
|
||||
let ParsedColor = null
|
||||
|
||||
// Essential: A simple color class returned from {Config::get} when the value
|
||||
// at the key path is of type 'color'.
|
||||
export default class Color {
|
||||
// Essential: Parse a {String} or {Object} into a {Color}.
|
||||
//
|
||||
// * `value` A {String} such as `'white'`, `#ff00ff`, or
|
||||
// `'rgba(255, 15, 60, .75)'` or an {Object} with `red`, `green`, `blue`,
|
||||
// and `alpha` properties.
|
||||
//
|
||||
// Returns a {Color} or `null` if it cannot be parsed.
|
||||
static parse (value) {
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
break
|
||||
case 'object':
|
||||
if (Array.isArray(value)) { return null }
|
||||
break
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
if (!ParsedColor) {
|
||||
ParsedColor = require('color')
|
||||
}
|
||||
|
||||
try {
|
||||
var parsedColor = new ParsedColor(value)
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
|
||||
return new Color(parsedColor.red(), parsedColor.green(), parsedColor.blue(), parsedColor.alpha())
|
||||
}
|
||||
|
||||
constructor (red, green, blue, alpha) {
|
||||
this.red = red
|
||||
this.green = green
|
||||
this.blue = blue
|
||||
this.alpha = alpha
|
||||
}
|
||||
|
||||
set red (red) {
|
||||
this._red = parseColor(red)
|
||||
}
|
||||
|
||||
set green (green) {
|
||||
this._green = parseColor(green)
|
||||
}
|
||||
|
||||
set blue (blue) {
|
||||
this._blue = parseColor(blue)
|
||||
}
|
||||
|
||||
set alpha (alpha) {
|
||||
this._alpha = parseAlpha(alpha)
|
||||
}
|
||||
|
||||
get red () {
|
||||
return this._red
|
||||
}
|
||||
|
||||
get green () {
|
||||
return this._green
|
||||
}
|
||||
|
||||
get blue () {
|
||||
return this._blue
|
||||
}
|
||||
|
||||
get alpha () {
|
||||
return this._alpha
|
||||
}
|
||||
|
||||
// Essential: Returns a {String} in the form `'#abcdef'`.
|
||||
toHexString () {
|
||||
return `#${numberToHexString(this.red)}${numberToHexString(this.green)}${numberToHexString(this.blue)}`
|
||||
}
|
||||
|
||||
// Essential: Returns a {String} in the form `'rgba(25, 50, 75, .9)'`.
|
||||
toRGBAString () {
|
||||
return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`
|
||||
}
|
||||
|
||||
isEqual (color) {
|
||||
if (this === color) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!(color instanceof Color)) {
|
||||
color = Color.parse(color)
|
||||
}
|
||||
|
||||
if (color == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return color.red === this.red && color.blue === this.blue && color.green === this.green && color.alpha === this.alpha
|
||||
}
|
||||
|
||||
clone () {
|
||||
return new Color(this.red, this.green, this.blue, this.alpha)
|
||||
}
|
||||
}
|
||||
|
||||
function parseColor (colorString) {
|
||||
const color = parseInt(colorString, 10)
|
||||
if (isNaN(color)) {
|
||||
return 0
|
||||
} else {
|
||||
return Math.min(Math.max(color, 0), 255)
|
||||
}
|
||||
}
|
||||
|
||||
function parseAlpha (alphaString) {
|
||||
const alpha = parseFloat(alphaString)
|
||||
if (isNaN(alpha)) {
|
||||
return 1
|
||||
} else {
|
||||
return Math.min(Math.max(alpha, 0), 1)
|
||||
}
|
||||
}
|
||||
|
||||
function numberToHexString (number) {
|
||||
const hex = number.toString(16)
|
||||
if (number < 16) {
|
||||
return `0${hex}`
|
||||
} else {
|
||||
return hex
|
||||
}
|
||||
}
|
||||
@@ -786,9 +786,10 @@ class Config
|
||||
rootSchema = properties[key]
|
||||
|
||||
Object.assign rootSchema, schema
|
||||
@setDefaults(keyPath, @extractDefaultsFromSchema(schema))
|
||||
@setScopedDefaultsFromSchema(keyPath, schema)
|
||||
@resetSettingsForSchemaChange()
|
||||
@transact =>
|
||||
@setDefaults(keyPath, @extractDefaultsFromSchema(schema))
|
||||
@setScopedDefaultsFromSchema(keyPath, schema)
|
||||
@resetSettingsForSchemaChange()
|
||||
|
||||
load: ->
|
||||
@initializeConfigDirectory()
|
||||
@@ -958,9 +959,10 @@ class Config
|
||||
setDefaults: (keyPath, defaults) ->
|
||||
if defaults? and isPlainObject(defaults)
|
||||
keys = splitKeyPath(keyPath)
|
||||
for key, childValue of defaults
|
||||
continue unless defaults.hasOwnProperty(key)
|
||||
@setDefaults(keys.concat([key]).join('.'), childValue)
|
||||
@transact =>
|
||||
for key, childValue of defaults
|
||||
continue unless defaults.hasOwnProperty(key)
|
||||
@setDefaults(keys.concat([key]).join('.'), childValue)
|
||||
else
|
||||
try
|
||||
defaults = @makeValueConformToSchema(keyPath, defaults)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
module.exports = (extra) ->
|
||||
# Breakpad on Mac OS X must be running on UI and non-UI processes
|
||||
# Crashpad on Windows and Linux should only be running on non-UI process
|
||||
return if process.type is 'renderer' and process.platform isnt 'darwin'
|
||||
|
||||
{crashReporter} = require 'electron'
|
||||
|
||||
crashReporter.start({
|
||||
productName: 'Atom',
|
||||
companyName: 'GitHub',
|
||||
submitURL: 'http://54.249.141.255:1127/post'
|
||||
extra: extra
|
||||
})
|
||||
10
src/crash-reporter-start.js
Normal file
10
src/crash-reporter-start.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = function (extra) {
|
||||
const {crashReporter} = require('electron')
|
||||
crashReporter.start({
|
||||
productName: 'Atom',
|
||||
companyName: 'GitHub',
|
||||
submitURL: 'https://crashreporter.atom.io',
|
||||
autoSubmit: false,
|
||||
extra: extra
|
||||
})
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
{Disposable} = require 'event-kit'
|
||||
|
||||
# Extended: Manages the deserializers used for serialized state
|
||||
#
|
||||
# An instance of this class is always available as the `atom.deserializers`
|
||||
# global.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# ```coffee
|
||||
# class MyPackageView extends View
|
||||
# atom.deserializers.add(this)
|
||||
#
|
||||
# @deserialize: (state) ->
|
||||
# new MyPackageView(state)
|
||||
#
|
||||
# constructor: (@state) ->
|
||||
#
|
||||
# serialize: ->
|
||||
# @state
|
||||
# ```
|
||||
module.exports =
|
||||
class DeserializerManager
|
||||
constructor: (@atomEnvironment) ->
|
||||
@deserializers = {}
|
||||
|
||||
# Public: Register the given class(es) as deserializers.
|
||||
#
|
||||
# * `deserializers` One or more deserializers to register. A deserializer can
|
||||
# be any object with a `.name` property and a `.deserialize()` method. A
|
||||
# common approach is to register a *constructor* as the deserializer for its
|
||||
# instances by adding a `.deserialize()` class method. When your method is
|
||||
# called, it will be passed serialized state as the first argument and the
|
||||
# {Atom} environment object as the second argument, which is useful if you
|
||||
# wish to avoid referencing the `atom` global.
|
||||
add: (deserializers...) ->
|
||||
@deserializers[deserializer.name] = deserializer for deserializer in deserializers
|
||||
new Disposable =>
|
||||
delete @deserializers[deserializer.name] for deserializer in deserializers
|
||||
return
|
||||
|
||||
getDeserializerCount: ->
|
||||
Object.keys(@deserializers).length
|
||||
|
||||
# Public: Deserialize the state and params.
|
||||
#
|
||||
# * `state` The state {Object} to deserialize.
|
||||
deserialize: (state) ->
|
||||
return unless state?
|
||||
|
||||
if deserializer = @get(state)
|
||||
stateVersion = state.get?('version') ? state.version
|
||||
return if deserializer.version? and deserializer.version isnt stateVersion
|
||||
deserializer.deserialize(state, @atomEnvironment)
|
||||
else
|
||||
console.warn "No deserializer found for", state
|
||||
|
||||
# Get the deserializer for the state.
|
||||
#
|
||||
# * `state` The state {Object} being deserialized.
|
||||
get: (state) ->
|
||||
return unless state?
|
||||
|
||||
name = state.get?('deserializer') ? state.deserializer
|
||||
@deserializers[name]
|
||||
|
||||
clear: ->
|
||||
@deserializers = {}
|
||||
100
src/deserializer-manager.js
Normal file
100
src/deserializer-manager.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/** @babel */
|
||||
|
||||
import {Disposable} from 'event-kit'
|
||||
|
||||
// Extended: Manages the deserializers used for serialized state
|
||||
//
|
||||
// An instance of this class is always available as the `atom.deserializers`
|
||||
// global.
|
||||
//
|
||||
// ## Examples
|
||||
//
|
||||
// ```coffee
|
||||
// class MyPackageView extends View
|
||||
// atom.deserializers.add(this)
|
||||
//
|
||||
// @deserialize: (state) ->
|
||||
// new MyPackageView(state)
|
||||
//
|
||||
// constructor: (@state) ->
|
||||
//
|
||||
// serialize: ->
|
||||
// @state
|
||||
// ```
|
||||
export default class DeserializerManager {
|
||||
constructor (atomEnvironment) {
|
||||
this.atomEnvironment = atomEnvironment
|
||||
this.deserializers = {}
|
||||
}
|
||||
|
||||
// Public: Register the given class(es) as deserializers.
|
||||
//
|
||||
// * `deserializers` One or more deserializers to register. A deserializer can
|
||||
// be any object with a `.name` property and a `.deserialize()` method. A
|
||||
// common approach is to register a *constructor* as the deserializer for its
|
||||
// instances by adding a `.deserialize()` class method. When your method is
|
||||
// called, it will be passed serialized state as the first argument and the
|
||||
// {Atom} environment object as the second argument, which is useful if you
|
||||
// wish to avoid referencing the `atom` global.
|
||||
add (...deserializers) {
|
||||
for (let i = 0; i < deserializers.length; i++) {
|
||||
let deserializer = deserializers[i]
|
||||
this.deserializers[deserializer.name] = deserializer
|
||||
}
|
||||
|
||||
return new Disposable(() => {
|
||||
for (let j = 0; j < deserializers.length; j++) {
|
||||
let deserializer = deserializers[j]
|
||||
delete this.deserializers[deserializer.name]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getDeserializerCount () {
|
||||
return Object.keys(this.deserializers).length
|
||||
}
|
||||
|
||||
// Public: Deserialize the state and params.
|
||||
//
|
||||
// * `state` The state {Object} to deserialize.
|
||||
deserialize (state) {
|
||||
if (state == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const deserializer = this.get(state)
|
||||
if (deserializer) {
|
||||
let stateVersion = (
|
||||
(typeof state.get === 'function') && state.get('version') ||
|
||||
state.version
|
||||
)
|
||||
|
||||
if ((deserializer.version != null) && deserializer.version !== stateVersion) {
|
||||
return
|
||||
}
|
||||
return deserializer.deserialize(state, this.atomEnvironment)
|
||||
} else {
|
||||
return console.warn('No deserializer found for', state)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the deserializer for the state.
|
||||
//
|
||||
// * `state` The state {Object} being deserialized.
|
||||
get (state) {
|
||||
if (state == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let stateDeserializer = (
|
||||
(typeof state.get === 'function') && state.get('deserializer') ||
|
||||
state.deserializer
|
||||
)
|
||||
|
||||
return this.deserializers[stateDeserializer]
|
||||
}
|
||||
|
||||
clear () {
|
||||
this.deserializers = {}
|
||||
}
|
||||
}
|
||||
@@ -1,558 +0,0 @@
|
||||
'use babel'
|
||||
|
||||
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:
|
||||
// * Errors are generally propagated out to the caller instead of being
|
||||
// swallowed within `GitRepositoryAsync`.
|
||||
// * Methods accepting a path shouldn't be given a null path, unless it is
|
||||
// specifically allowed as noted in the method's documentation.
|
||||
export default class GitRepositoryAsync {
|
||||
static open (path, options = {}) {
|
||||
// QUESTION: Should this wrap Git.Repository and reject with a nicer message?
|
||||
return new GitRepositoryAsync(path, options)
|
||||
}
|
||||
|
||||
static get Git () {
|
||||
return Repository.Git
|
||||
}
|
||||
|
||||
// The name of the error thrown when an action is attempted on a destroyed
|
||||
// repository.
|
||||
static get DestroyedErrorName () {
|
||||
return Repository.DestroyedErrorName
|
||||
}
|
||||
|
||||
constructor (_path, options = {}) {
|
||||
this.repo = Repository.open(_path, options)
|
||||
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
|
||||
let {refreshOnWindowFocus = true} = options
|
||||
if (refreshOnWindowFocus) {
|
||||
const onWindowFocus = () => this.refreshStatus()
|
||||
window.addEventListener('focus', onWindowFocus)
|
||||
this.subscriptions.add(new Disposable(() => window.removeEventListener('focus', onWindowFocus)))
|
||||
}
|
||||
|
||||
const {project, subscribeToBuffers} = options
|
||||
this.project = project
|
||||
if (this.project && subscribeToBuffers) {
|
||||
this.project.getBuffers().forEach(buffer => this.subscribeToBuffer(buffer))
|
||||
this.subscriptions.add(this.project.onDidAddBuffer(buffer => this.subscribeToBuffer(buffer)))
|
||||
}
|
||||
}
|
||||
|
||||
// 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 () {
|
||||
this.repo.destroy()
|
||||
|
||||
if (this.subscriptions) {
|
||||
this.subscriptions.dispose()
|
||||
this.subscriptions = null
|
||||
}
|
||||
}
|
||||
|
||||
// Event subscription
|
||||
// ==================
|
||||
|
||||
// Public: Invoke the given callback when this GitRepositoryAsync's destroy()
|
||||
// method is invoked.
|
||||
//
|
||||
// * `callback` {Function}
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidDestroy (callback) {
|
||||
return this.repo.onDidDestroy(callback)
|
||||
}
|
||||
|
||||
// Public: Invoke the given callback when a specific file's status has
|
||||
// changed. When a file is updated, reloaded, etc, and the status changes, this
|
||||
// will be fired.
|
||||
//
|
||||
// * `callback` {Function}
|
||||
// * `event` {Object}
|
||||
// * `path` {String} the old parameters the decoration used to have
|
||||
// * `pathStatus` {Number} representing the status. This value can be passed to
|
||||
// {::isStatusModified} or {::isStatusNew} to get more information.
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeStatus (callback) {
|
||||
return this.repo.onDidChangeStatus(callback)
|
||||
}
|
||||
|
||||
// Public: Invoke the given callback when a multiple files' statuses have
|
||||
// changed. For example, on window focus, the status of all the paths in the
|
||||
// repo is checked. If any of them have changed, this will be fired. Call
|
||||
// {::getPathStatus(path)} to get the status for your path of choice.
|
||||
//
|
||||
// * `callback` {Function}
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeStatuses (callback) {
|
||||
return this.repo.onDidChangeStatuses(callback)
|
||||
}
|
||||
|
||||
// Repository details
|
||||
// ==================
|
||||
|
||||
// Public: A {String} indicating the type of version control system used by
|
||||
// this repository.
|
||||
//
|
||||
// Returns `"git"`.
|
||||
getType () {
|
||||
return 'git'
|
||||
}
|
||||
|
||||
// Public: Returns a {Promise} which resolves to the {String} path of the
|
||||
// repository.
|
||||
getPath () {
|
||||
return this.repo.getPath()
|
||||
}
|
||||
|
||||
// Public: Returns a {Promise} which resolves to the {String} working
|
||||
// directory path of the repository.
|
||||
getWorkingDirectory (_path) {
|
||||
return this.repo.getWorkingDirectory()
|
||||
}
|
||||
|
||||
// Public: Returns a {Promise} that resolves to true if at the root, false if
|
||||
// in a subfolder of the repository.
|
||||
isProjectAtRoot () {
|
||||
if (!this.project) return Promise.resolve(false)
|
||||
|
||||
if (!this.projectAtRoot) {
|
||||
this.projectAtRoot = this.getWorkingDirectory()
|
||||
.then(wd => this.project.relativize(wd) === '')
|
||||
}
|
||||
|
||||
return this.projectAtRoot
|
||||
}
|
||||
|
||||
// Public: Makes a path relative to the repository's working directory.
|
||||
//
|
||||
// * `path` The {String} path to relativize.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the relative {String} path.
|
||||
relativizeToWorkingDirectory (_path) {
|
||||
return this.repo.relativizeToWorkingDirectory(_path)
|
||||
}
|
||||
|
||||
// Public: Makes a path relative to the repository's working directory.
|
||||
//
|
||||
// * `path` The {String} path to relativize.
|
||||
// * `workingDirectory` The {String} working directory path.
|
||||
//
|
||||
// Returns the relative {String} path.
|
||||
relativize (_path, workingDirectory) {
|
||||
return this.repo.relativize(_path, workingDirectory)
|
||||
}
|
||||
|
||||
// Public: Returns a {Promise} which resolves to whether the given branch
|
||||
// exists.
|
||||
hasBranch (branch) {
|
||||
return this.repo.hasBranch(branch)
|
||||
}
|
||||
|
||||
// Public: Retrieves a shortened version of the HEAD reference value.
|
||||
//
|
||||
// This removes the leading segments of `refs/heads`, `refs/tags`, or
|
||||
// `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7
|
||||
// characters.
|
||||
//
|
||||
// * `path` An optional {String} path in the repository to get this information
|
||||
// for, only needed if the repository contains submodules.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {String}.
|
||||
getShortHead (_path) {
|
||||
return this.repo.getShortHead(_path)
|
||||
}
|
||||
|
||||
// Public: Is the given path a submodule in the repository?
|
||||
//
|
||||
// * `path` The {String} path to check.
|
||||
//
|
||||
// Returns a {Promise} that resolves true if the given path is a submodule in
|
||||
// the repository.
|
||||
isSubmodule (_path) {
|
||||
return this.repo.isSubmodule(_path)
|
||||
}
|
||||
|
||||
// Public: Returns the number of commits behind the current branch is from the
|
||||
// its upstream remote branch.
|
||||
//
|
||||
// * `reference` The {String} branch reference name.
|
||||
// * `path` The {String} path in the repository to get this information
|
||||
// for, only needed if the repository contains submodules.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Object} with the following keys:
|
||||
// * `ahead` The {Number} of commits ahead.
|
||||
// * `behind` The {Number} of commits behind.
|
||||
getAheadBehindCount (reference, _path) {
|
||||
return this.repo.getAheadBehindCount(reference, _path)
|
||||
}
|
||||
|
||||
// Public: Get the cached ahead/behind commit counts for the current branch's
|
||||
// upstream branch.
|
||||
//
|
||||
// * `path` An optional {String} path in the repository to get this information
|
||||
// for, only needed if the repository has submodules.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Object} with the following keys:
|
||||
// * `ahead` The {Number} of commits ahead.
|
||||
// * `behind` The {Number} of commits behind.
|
||||
getCachedUpstreamAheadBehindCount (_path) {
|
||||
return this.repo.getCachedUpstreamAheadBehindCount(_path)
|
||||
}
|
||||
|
||||
// Public: Returns the git configuration value specified by the key.
|
||||
//
|
||||
// * `path` An optional {String} path in the repository to get this information
|
||||
// for, only needed if the repository has submodules.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the {String} git configuration value
|
||||
// specified by the key.
|
||||
getConfigValue (key, _path) {
|
||||
return this.repo.getConfigValue(key, _path)
|
||||
}
|
||||
|
||||
// Public: Get the URL for the 'origin' remote.
|
||||
//
|
||||
// * `path` (optional) {String} path in the repository to get this information
|
||||
// for, only needed if the repository has submodules.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the {String} origin url of the
|
||||
// repository.
|
||||
getOriginURL (_path) {
|
||||
return this.repo.getOriginURL(_path)
|
||||
}
|
||||
|
||||
// Public: Returns the upstream branch for the current HEAD, or null if there
|
||||
// is no upstream branch for the current HEAD.
|
||||
//
|
||||
// * `path` An optional {String} path in the repo to get this information for,
|
||||
// only needed if the repository contains submodules.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {String} branch name such as
|
||||
// `refs/remotes/origin/master`.
|
||||
getUpstreamBranch (_path) {
|
||||
return this.repo.getUpstreamBranch(_path)
|
||||
}
|
||||
|
||||
// Public: Gets all the local and remote references.
|
||||
//
|
||||
// * `path` An optional {String} path in the repository to get this information
|
||||
// for, only needed if the repository has submodules.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Object} with the following keys:
|
||||
// * `heads` An {Array} of head reference names.
|
||||
// * `remotes` An {Array} of remote reference names.
|
||||
// * `tags` An {Array} of tag reference names.
|
||||
getReferences (_path) {
|
||||
return this.repo.getReferences(_path)
|
||||
}
|
||||
|
||||
// Public: Get the SHA for the given reference.
|
||||
//
|
||||
// * `reference` The {String} reference to get the target of.
|
||||
// * `path` An optional {String} path in the repo to get the reference target
|
||||
// for. Only needed if the repository contains submodules.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the current {String} SHA for the
|
||||
// given reference.
|
||||
getReferenceTarget (reference, _path) {
|
||||
return this.repo.getReferenceTarget(reference, _path)
|
||||
}
|
||||
|
||||
// Reading Status
|
||||
// ==============
|
||||
|
||||
// Public: Resolves true if the given path is modified.
|
||||
//
|
||||
// * `path` The {String} path to check.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
|
||||
// is modified.
|
||||
isPathModified (_path) {
|
||||
return this.repo.isPathModified(_path)
|
||||
}
|
||||
|
||||
// Public: Resolves true if the given path is new.
|
||||
//
|
||||
// * `path` The {String} path to check.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
|
||||
// is new.
|
||||
isPathNew (_path) {
|
||||
return this.repo.isPathNew(_path)
|
||||
}
|
||||
|
||||
// Public: Is the given path ignored?
|
||||
//
|
||||
// * `path` The {String} path to check.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
|
||||
// is ignored.
|
||||
isPathIgnored (_path) {
|
||||
return this.repo.isPathIgnored(_path)
|
||||
}
|
||||
|
||||
// Get the status of a directory in the repository's working directory.
|
||||
//
|
||||
// * `directoryPath` The {String} path to check.
|
||||
//
|
||||
// Returns a {Promise} resolving to a {Number} representing the status. This
|
||||
// value can be passed to {::isStatusModified} or {::isStatusNew} to get more
|
||||
// information.
|
||||
getDirectoryStatus (directoryPath) {
|
||||
return this.repo.getDirectoryStatus(directoryPath)
|
||||
}
|
||||
|
||||
// Refresh the status bit for the given path.
|
||||
//
|
||||
// Note that if the status of the path has changed, this will emit a
|
||||
// 'did-change-status' event.
|
||||
//
|
||||
// * `path` The {String} path whose status should be refreshed.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {Number} which is the refreshed
|
||||
// status bit for the path.
|
||||
refreshStatusForPath (_path) {
|
||||
return this.repo.refreshStatusForPath(_path)
|
||||
}
|
||||
|
||||
// Returns a Promise that resolves to the status bit of a given path if it has
|
||||
// one, otherwise 'current'.
|
||||
getPathStatus (_path) {
|
||||
return this.refreshStatusForPath(_path)
|
||||
}
|
||||
|
||||
// Public: Get the cached status for the given path.
|
||||
//
|
||||
// * `path` A {String} path in the repository, relative or absolute.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a status {Number} or null if the
|
||||
// path is not in the cache.
|
||||
getCachedPathStatus (_path) {
|
||||
return this.repo.getCachedPathStatus(_path)
|
||||
}
|
||||
|
||||
// Public: Get the cached statuses for the repository.
|
||||
//
|
||||
// Returns an {Object} of {Number} statuses, keyed by {String} working
|
||||
// directory-relative file names.
|
||||
getCachedPathStatuses () {
|
||||
return this.repo.pathStatusCache
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates modification.
|
||||
//
|
||||
// * `statusBit` A {Number} representing the status.
|
||||
//
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates modification.
|
||||
isStatusModified (statusBit) {
|
||||
return this.repo.isStatusModified(statusBit)
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates a new path.
|
||||
//
|
||||
// * `statusBit` A {Number} representing the status.
|
||||
//
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates a new path.
|
||||
isStatusNew (statusBit) {
|
||||
return this.repo.isStatusNew(statusBit)
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates the path is staged.
|
||||
//
|
||||
// * `statusBit` A {Number} representing the status.
|
||||
//
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
|
||||
// staged.
|
||||
isStatusStaged (statusBit) {
|
||||
return this.repo.isStatusStaged(statusBit)
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates the path is ignored.
|
||||
//
|
||||
// * `statusBit` A {Number} representing the status.
|
||||
//
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
|
||||
// ignored.
|
||||
isStatusIgnored (statusBit) {
|
||||
return this.repo.isStatusIgnored(statusBit)
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates the path is deleted.
|
||||
//
|
||||
// * `statusBit` A {Number} representing the status.
|
||||
//
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
|
||||
// deleted.
|
||||
isStatusDeleted (statusBit) {
|
||||
return this.repo.isStatusDeleted(statusBit)
|
||||
}
|
||||
|
||||
// Retrieving Diffs
|
||||
// ================
|
||||
// Public: Retrieves the number of lines added and removed to a path.
|
||||
//
|
||||
// This compares the working directory contents of the path to the `HEAD`
|
||||
// version.
|
||||
//
|
||||
// * `path` The {String} path to check.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Object} with the following keys:
|
||||
// * `added` The {Number} of added lines.
|
||||
// * `deleted` The {Number} of deleted lines.
|
||||
getDiffStats (_path) {
|
||||
return this.repo.getDiffStats(_path)
|
||||
}
|
||||
|
||||
// Public: Retrieves the line diffs comparing the `HEAD` version of the given
|
||||
// path and the given text.
|
||||
//
|
||||
// * `path` The {String} path relative to the repository.
|
||||
// * `text` The {String} to compare against the `HEAD` contents
|
||||
//
|
||||
// Returns an {Array} of hunk {Object}s with the following keys:
|
||||
// * `oldStart` The line {Number} of the old hunk.
|
||||
// * `newStart` The line {Number} of the new hunk.
|
||||
// * `oldLines` The {Number} of lines in the old hunk.
|
||||
// * `newLines` The {Number} of lines in the new hunk
|
||||
getLineDiffs (_path, text) {
|
||||
return this.repo.getLineDiffs(_path, text)
|
||||
}
|
||||
|
||||
// Checking Out
|
||||
// ============
|
||||
|
||||
// Public: Restore the contents of a path in the working directory and index
|
||||
// to the version at `HEAD`.
|
||||
//
|
||||
// This is essentially the same as running:
|
||||
//
|
||||
// ```sh
|
||||
// git reset HEAD -- <path>
|
||||
// git checkout HEAD -- <path>
|
||||
// ```
|
||||
//
|
||||
// * `path` The {String} path to checkout.
|
||||
//
|
||||
// Returns a {Promise} that resolves or rejects depending on whether the
|
||||
// method was successful.
|
||||
checkoutHead (_path) {
|
||||
return this.repo.checkoutHead(_path)
|
||||
}
|
||||
|
||||
// Public: Checks out a branch in your repository.
|
||||
//
|
||||
// * `reference` The {String} reference to checkout.
|
||||
// * `create` A {Boolean} value which, if true creates the new reference if
|
||||
// it doesn't exist.
|
||||
//
|
||||
// Returns a {Promise} that resolves if the method was successful.
|
||||
checkoutReference (reference, create) {
|
||||
return this.repo.checkoutReference(reference, create)
|
||||
}
|
||||
|
||||
// Private
|
||||
// =======
|
||||
|
||||
checkoutHeadForEditor (editor) {
|
||||
const filePath = editor.getPath()
|
||||
if (!filePath) {
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
if (editor.buffer.isModified()) {
|
||||
editor.buffer.reload()
|
||||
}
|
||||
|
||||
return this.checkoutHead(filePath)
|
||||
}
|
||||
|
||||
// Refreshes the git status.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to {null} when refresh is complete.
|
||||
refreshStatus () {
|
||||
let projectPathsPromises = [Promise.resolve('')]
|
||||
if (this.project) {
|
||||
projectPathsPromises = this.project.getPaths()
|
||||
.map(p => this.relativizeToWorkingDirectory(p))
|
||||
}
|
||||
|
||||
return Promise.all(projectPathsPromises)
|
||||
.then(paths => paths.map(p => p.length > 0 ? p + '/**' : '*'))
|
||||
.then(pathspecs => this.repo.refreshStatus(pathspecs))
|
||||
}
|
||||
|
||||
// Get the NodeGit repository for the given path.
|
||||
//
|
||||
// * `path` The optional {String} path within the repository. This is only
|
||||
// needed if you want to get the repository for that path if it is a
|
||||
// submodule.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the {NodeGit.Repository}.
|
||||
getRepo (_path) {
|
||||
return this.repo.getRepo(_path)
|
||||
}
|
||||
|
||||
// Open a new instance of the underlying {NodeGit.Repository}.
|
||||
//
|
||||
// By opening multiple connections to the same underlying repository, users
|
||||
// can safely access the same repository concurrently.
|
||||
//
|
||||
// Returns the new {NodeGit.Repository}.
|
||||
openRepository () {
|
||||
return this.repo.openRepository()
|
||||
}
|
||||
|
||||
// Section: Private
|
||||
// ================
|
||||
|
||||
// Has the repository been destroyed?
|
||||
//
|
||||
// Returns a {Boolean}.
|
||||
_isDestroyed () {
|
||||
return this.repo._isDestroyed()
|
||||
}
|
||||
|
||||
// Subscribe to events on the given buffer.
|
||||
subscribeToBuffer (buffer) {
|
||||
const bufferSubscriptions = new CompositeDisposable()
|
||||
|
||||
const refreshStatusForBuffer = () => {
|
||||
const _path = buffer.getPath()
|
||||
if (_path) {
|
||||
this.refreshStatusForPath(_path)
|
||||
}
|
||||
}
|
||||
|
||||
bufferSubscriptions.add(
|
||||
buffer.onDidSave(refreshStatusForBuffer),
|
||||
buffer.onDidReload(refreshStatusForBuffer),
|
||||
buffer.onDidChangePath(refreshStatusForBuffer),
|
||||
buffer.onDidDestroy(() => {
|
||||
bufferSubscriptions.dispose()
|
||||
this.subscriptions.remove(bufferSubscriptions)
|
||||
})
|
||||
)
|
||||
|
||||
this.subscriptions.add(bufferSubscriptions)
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ class GitRepositoryProvider
|
||||
unless repo
|
||||
repo = GitRepository.open(gitDirPath, {@project, @config})
|
||||
return null unless repo
|
||||
repo.async.onDidDestroy(=> delete @pathToRepository[gitDirPath])
|
||||
repo.onDidDestroy(=> delete @pathToRepository[gitDirPath])
|
||||
@pathToRepository[gitDirPath] = repo
|
||||
repo.refreshIndex()
|
||||
repo.refreshStatus()
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
_ = require 'underscore-plus'
|
||||
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
|
||||
fs = require 'fs-plus'
|
||||
GitRepositoryAsync = require './git-repository-async'
|
||||
GitUtils = require 'git-utils'
|
||||
|
||||
Task = require './task'
|
||||
@@ -76,19 +75,11 @@ class GitRepository
|
||||
unless @repo?
|
||||
throw new Error("No Git repository found searching path: #{path}")
|
||||
|
||||
asyncOptions = _.clone(options)
|
||||
# GitRepository itself will handle these cases by manually calling through
|
||||
# to the async repo.
|
||||
asyncOptions.refreshOnWindowFocus = false
|
||||
asyncOptions.subscribeToBuffers = false
|
||||
@async = GitRepositoryAsync.open(path, asyncOptions)
|
||||
|
||||
@statuses = {}
|
||||
@upstream = {ahead: 0, behind: 0}
|
||||
for submodulePath, submoduleRepo of @repo.submodules
|
||||
submoduleRepo.upstream = {ahead: 0, behind: 0}
|
||||
|
||||
@statusesByPath = {}
|
||||
|
||||
{@project, @config, refreshOnWindowFocus} = options
|
||||
|
||||
refreshOnWindowFocus ?= true
|
||||
@@ -126,10 +117,6 @@ class GitRepository
|
||||
@subscriptions.dispose()
|
||||
@subscriptions = null
|
||||
|
||||
if @async?
|
||||
@async.destroy()
|
||||
@async = null
|
||||
|
||||
# Public: Returns a {Boolean} indicating if this repository has been destroyed.
|
||||
isDestroyed: ->
|
||||
not @repo?
|
||||
@@ -322,7 +309,7 @@ class GitRepository
|
||||
getDirectoryStatus: (directoryPath) ->
|
||||
directoryPath = "#{@relativize(directoryPath)}/"
|
||||
directoryStatus = 0
|
||||
for path, status of Object.assign({}, @async.getCachedPathStatuses(), @statusesByPath)
|
||||
for path, status of @statuses
|
||||
directoryStatus |= status if path.indexOf(directoryPath) is 0
|
||||
directoryStatus
|
||||
|
||||
@@ -335,24 +322,13 @@ class GitRepository
|
||||
getPathStatus: (path) ->
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
|
||||
# This is a bit particular. If a package calls `getPathStatus` like this:
|
||||
# - change the file
|
||||
# - getPathStatus
|
||||
# - change the file
|
||||
# - getPathStatus
|
||||
# We need to preserve the guarantee that each call to `getPathStatus` will
|
||||
# synchronously emit 'did-change-status'. So we need to keep a cache of the
|
||||
# statuses found from this call.
|
||||
currentPathStatus = @getCachedRelativePathStatus(relativePath) ? 0
|
||||
|
||||
# Trigger events emitted on the async repo as well
|
||||
@async.refreshStatusForPath(path)
|
||||
|
||||
currentPathStatus = @statuses[relativePath] ? 0
|
||||
pathStatus = repo.getStatus(repo.relativize(path)) ? 0
|
||||
pathStatus = 0 if repo.isStatusIgnored(pathStatus)
|
||||
@statusesByPath[relativePath] = pathStatus
|
||||
|
||||
if pathStatus > 0
|
||||
@statuses[relativePath] = pathStatus
|
||||
else
|
||||
delete @statuses[relativePath]
|
||||
if currentPathStatus isnt pathStatus
|
||||
@emitter.emit 'did-change-status', {path, pathStatus}
|
||||
|
||||
@@ -364,11 +340,7 @@ class GitRepository
|
||||
#
|
||||
# Returns a status {Number} or null if the path is not in the cache.
|
||||
getCachedPathStatus: (path) ->
|
||||
relativePath = @relativize(path)
|
||||
@getCachedRelativePathStatus(relativePath)
|
||||
|
||||
getCachedRelativePathStatus: (relativePath) ->
|
||||
@statusesByPath[relativePath] ? @async.getCachedPathStatuses()[relativePath]
|
||||
@statuses[@relativize(path)]
|
||||
|
||||
# Public: Returns true if the given status indicates modification.
|
||||
#
|
||||
@@ -492,42 +464,29 @@ class GitRepository
|
||||
|
||||
# Refreshes the current git status in an outside process and asynchronously
|
||||
# updates the relevant properties.
|
||||
#
|
||||
# Returns a promise that resolves when the repository has been refreshed.
|
||||
refreshStatus: ->
|
||||
statusesChanged = false
|
||||
@handlerPath ?= require.resolve('./repository-status-handler')
|
||||
|
||||
# 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
|
||||
relativeProjectPaths = @project?.getPaths()
|
||||
.map (path) => @relativize(path)
|
||||
.filter (path) -> path.length > 0
|
||||
|
||||
statusesChanged = true
|
||||
@statusTask?.terminate()
|
||||
new Promise (resolve) =>
|
||||
@statusTask = Task.once @handlerPath, @getPath(), relativeProjectPaths, ({statuses, upstream, branch, submodules}) =>
|
||||
statusesUnchanged = _.isEqual(statuses, @statuses) and
|
||||
_.isEqual(upstream, @upstream) and
|
||||
_.isEqual(branch, @branch) and
|
||||
_.isEqual(submodules, @submodules)
|
||||
|
||||
asyncRefresh = @async.refreshStatus().then =>
|
||||
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')
|
||||
|
||||
@statusTask?.terminate()
|
||||
@statusTask = Task.once @handlerPath, @getPath(), ({upstream, submodules}) =>
|
||||
@statuses = statuses
|
||||
@upstream = upstream
|
||||
@branch = branch
|
||||
@submodules = submodules
|
||||
|
||||
for submodulePath, submoduleRepo of @getRepo().submodules
|
||||
submoduleRepo.upstream = submodules[submodulePath]?.upstream ? {ahead: 0, behind: 0}
|
||||
|
||||
unless statusesUnchanged
|
||||
@emitter.emit 'did-change-statuses'
|
||||
resolve()
|
||||
|
||||
return Promise.all([asyncRefresh, syncRefresh])
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
{ipcMain} = require 'electron'
|
||||
|
||||
module.exports =
|
||||
class AtomPortable
|
||||
@getPortableAtomHomePath: ->
|
||||
execDirectoryPath = path.dirname(process.execPath)
|
||||
path.join(execDirectoryPath, '..', '.atom')
|
||||
|
||||
@setPortable: (existingAtomHome) ->
|
||||
fs.copySync(existingAtomHome, @getPortableAtomHomePath())
|
||||
|
||||
@isPortableInstall: (platform, environmentAtomHome, defaultHome) ->
|
||||
return false unless platform in ['linux', 'win32']
|
||||
return false if environmentAtomHome
|
||||
return false if not fs.existsSync(@getPortableAtomHomePath())
|
||||
# currently checking only that the directory exists and is writable,
|
||||
# probably want to do some integrity checks on contents in future
|
||||
@isPortableAtomHomePathWritable(defaultHome)
|
||||
|
||||
@isPortableAtomHomePathWritable: (defaultHome) ->
|
||||
writable = false
|
||||
message = ""
|
||||
try
|
||||
writePermissionTestFile = path.join(@getPortableAtomHomePath(), "write.test")
|
||||
fs.writeFileSync(writePermissionTestFile, "test") if not fs.existsSync(writePermissionTestFile)
|
||||
fs.removeSync(writePermissionTestFile)
|
||||
writable = true
|
||||
catch error
|
||||
message = "Failed to use portable Atom home directory (#{@getPortableAtomHomePath()}). Using the default instead (#{defaultHome}). #{error.message}"
|
||||
|
||||
ipcMain.on 'check-portable-home-writable', (event) ->
|
||||
event.sender.send 'check-portable-home-writable-response', {writable, message}
|
||||
writable
|
||||
58
src/main-process/atom-portable.js
Normal file
58
src/main-process/atom-portable.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const fs = require('fs-plus')
|
||||
const path = require('path')
|
||||
const {ipcMain} = require('electron')
|
||||
|
||||
module.exports = class AtomPortable {
|
||||
static getPortableAtomHomePath () {
|
||||
const execDirectoryPath = path.dirname(process.execPath)
|
||||
return path.join(execDirectoryPath, '..', '.atom')
|
||||
}
|
||||
|
||||
static setPortable (existingAtomHome) {
|
||||
fs.copySync(existingAtomHome, this.getPortableAtomHomePath())
|
||||
}
|
||||
|
||||
static isPortableInstall (platform, environmentAtomHome, defaultHome) {
|
||||
if (!['linux', 'win32'].includes(platform)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (environmentAtomHome) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!fs.existsSync(this.getPortableAtomHomePath())) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Currently checking only that the directory exists and is writable,
|
||||
// probably want to do some integrity checks on contents in future.
|
||||
return this.isPortableAtomHomePathWritable(defaultHome)
|
||||
}
|
||||
|
||||
static isPortableAtomHomePathWritable (defaultHome) {
|
||||
let writable = false
|
||||
let message = ''
|
||||
try {
|
||||
const writePermissionTestFile = path.join(this.getPortableAtomHomePath(), 'write.test')
|
||||
|
||||
if (!fs.existsSync(writePermissionTestFile)) {
|
||||
fs.writeFileSync(writePermissionTestFile, 'test')
|
||||
}
|
||||
|
||||
fs.removeSync(writePermissionTestFile)
|
||||
writable = true
|
||||
} catch (error) {
|
||||
message = `Failed to use portable Atom home directory (${this.getPortableAtomHomePath()}). Using the default instead (${defaultHome}). ${error.message}.`
|
||||
}
|
||||
|
||||
ipcMain.on('check-portable-home-writable', function (event) {
|
||||
event.sender.send('check-portable-home-writable-response', {
|
||||
writable: writable,
|
||||
message: message
|
||||
})
|
||||
})
|
||||
|
||||
return writable
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
global.shellStartTime = Date.now()
|
||||
|
||||
process.on 'uncaughtException', (error={}) ->
|
||||
console.log(error.message) if error.message?
|
||||
console.log(error.stack) if error.stack?
|
||||
|
||||
{app} = require 'electron'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
yargs = require 'yargs'
|
||||
previousConsoleLog = console.log
|
||||
startCrashReporter = require('../crash-reporter-start')
|
||||
console.log = require 'nslog'
|
||||
|
||||
start = ->
|
||||
args = parseCommandLine()
|
||||
args.env = process.env
|
||||
setupAtomHome(args)
|
||||
setupCompileCache()
|
||||
if handleStartupEventWithSquirrel()
|
||||
return
|
||||
else if args.test and args.mainProcess
|
||||
console.log = previousConsoleLog
|
||||
testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner'))
|
||||
app.on 'ready', -> testRunner(args.pathsToOpen)
|
||||
return
|
||||
|
||||
# NB: This prevents Win10 from showing dupe items in the taskbar
|
||||
app.setAppUserModelId('com.squirrel.atom.atom')
|
||||
|
||||
addPathToOpen = (event, pathToOpen) ->
|
||||
event.preventDefault()
|
||||
args.pathsToOpen.push(pathToOpen)
|
||||
|
||||
addUrlToOpen = (event, urlToOpen) ->
|
||||
event.preventDefault()
|
||||
args.urlsToOpen.push(urlToOpen)
|
||||
|
||||
app.on 'open-file', addPathToOpen
|
||||
app.on 'open-url', addUrlToOpen
|
||||
app.on 'will-finish-launching', startCrashReporter
|
||||
|
||||
if args.userDataDir?
|
||||
app.setPath('userData', args.userDataDir)
|
||||
else if args.test
|
||||
app.setPath('userData', temp.mkdirSync('atom-test-data'))
|
||||
|
||||
app.on 'ready', ->
|
||||
app.removeListener 'open-file', addPathToOpen
|
||||
app.removeListener 'open-url', addUrlToOpen
|
||||
|
||||
AtomApplication = require path.join(args.resourcePath, 'src', 'main-process', 'atom-application')
|
||||
AtomApplication.open(args)
|
||||
|
||||
console.log("App load time: #{Date.now() - global.shellStartTime}ms") unless args.test
|
||||
|
||||
normalizeDriveLetterName = (filePath) ->
|
||||
if process.platform is 'win32'
|
||||
filePath.replace /^([a-z]):/, ([driveLetter]) -> driveLetter.toUpperCase() + ":"
|
||||
else
|
||||
filePath
|
||||
|
||||
handleStartupEventWithSquirrel = ->
|
||||
return false unless process.platform is 'win32'
|
||||
SquirrelUpdate = require './squirrel-update'
|
||||
squirrelCommand = process.argv[1]
|
||||
SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
|
||||
|
||||
setupAtomHome = ({setPortable}) ->
|
||||
return if process.env.ATOM_HOME
|
||||
|
||||
atomHome = path.join(app.getPath('home'), '.atom')
|
||||
AtomPortable = require './atom-portable'
|
||||
|
||||
if setPortable and not AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)
|
||||
try
|
||||
AtomPortable.setPortable(atomHome)
|
||||
catch error
|
||||
console.log("Failed copying portable directory '#{atomHome}' to '#{AtomPortable.getPortableAtomHomePath()}'")
|
||||
console.log("#{error.message} #{error.stack}")
|
||||
|
||||
if AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)
|
||||
atomHome = AtomPortable.getPortableAtomHomePath()
|
||||
|
||||
try
|
||||
atomHome = fs.realpathSync(atomHome)
|
||||
|
||||
process.env.ATOM_HOME = atomHome
|
||||
|
||||
setupCompileCache = ->
|
||||
compileCache = require('../compile-cache')
|
||||
compileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
|
||||
|
||||
writeFullVersion = ->
|
||||
process.stdout.write """
|
||||
Atom : #{app.getVersion()}
|
||||
Electron: #{process.versions.electron}
|
||||
Chrome : #{process.versions.chrome}
|
||||
Node : #{process.versions.node}
|
||||
|
||||
"""
|
||||
|
||||
parseCommandLine = ->
|
||||
version = app.getVersion()
|
||||
options = yargs(process.argv[1..]).wrap(100)
|
||||
options.usage """
|
||||
Atom Editor v#{version}
|
||||
|
||||
Usage: atom [options] [path ...]
|
||||
|
||||
One or more paths to files or folders may be specified. If there is an
|
||||
existing Atom window that contains all of the given folders, the paths
|
||||
will be opened in that window. Otherwise, they will be opened in a new
|
||||
window.
|
||||
|
||||
Environment Variables:
|
||||
|
||||
ATOM_DEV_RESOURCE_PATH The path from which Atom loads source code in dev mode.
|
||||
Defaults to `~/github/atom`.
|
||||
|
||||
ATOM_HOME The root path for all configuration files and folders.
|
||||
Defaults to `~/.atom`.
|
||||
"""
|
||||
# Deprecated 1.0 API preview flag
|
||||
options.alias('1', 'one').boolean('1').describe('1', 'This option is no longer supported.')
|
||||
options.boolean('include-deprecated-apis').describe('include-deprecated-apis', 'This option is not currently supported.')
|
||||
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
|
||||
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the main process in the foreground.')
|
||||
options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.')
|
||||
options.alias('l', 'log-file').string('l').describe('l', 'Log all output to file.')
|
||||
options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.')
|
||||
options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.')
|
||||
options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.')
|
||||
options.boolean('safe').describe('safe', 'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.')
|
||||
options.boolean('portable').describe('portable', 'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.')
|
||||
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
|
||||
options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.')
|
||||
options.string('timeout').describe('timeout', 'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).')
|
||||
options.alias('v', 'version').boolean('v').describe('v', 'Print the version information.')
|
||||
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
|
||||
options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.')
|
||||
options.string('socket-path')
|
||||
options.string('user-data-dir')
|
||||
options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.')
|
||||
|
||||
args = options.argv
|
||||
|
||||
if args.help
|
||||
process.stdout.write(options.help())
|
||||
process.exit(0)
|
||||
|
||||
if args.version
|
||||
writeFullVersion()
|
||||
process.exit(0)
|
||||
|
||||
addToLastWindow = args['add']
|
||||
executedFrom = args['executed-from']?.toString() ? process.cwd()
|
||||
devMode = args['dev']
|
||||
safeMode = args['safe']
|
||||
pathsToOpen = args._
|
||||
test = args['test']
|
||||
mainProcess = args['main-process']
|
||||
timeout = args['timeout']
|
||||
newWindow = args['new-window']
|
||||
pidToKillWhenClosed = args['pid'] if args['wait']
|
||||
logFile = args['log-file']
|
||||
socketPath = args['socket-path']
|
||||
userDataDir = args['user-data-dir']
|
||||
profileStartup = args['profile-startup']
|
||||
clearWindowState = args['clear-window-state']
|
||||
urlsToOpen = []
|
||||
devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getPath('home'), 'github', 'atom')
|
||||
setPortable = args.portable
|
||||
|
||||
if args['resource-path']
|
||||
devMode = true
|
||||
resourcePath = args['resource-path']
|
||||
|
||||
devMode = true if test
|
||||
resourcePath ?= devResourcePath if devMode
|
||||
|
||||
unless fs.statSyncNoException(resourcePath)
|
||||
resourcePath = path.dirname(path.dirname(__dirname))
|
||||
|
||||
# On Yosemite the $PATH is not inherited by the "open" command, so we have to
|
||||
# explicitly pass it by command line, see http://git.io/YC8_Ew.
|
||||
process.env.PATH = args['path-environment'] if args['path-environment']
|
||||
|
||||
resourcePath = normalizeDriveLetterName(resourcePath)
|
||||
devResourcePath = normalizeDriveLetterName(devResourcePath)
|
||||
|
||||
{resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
|
||||
version, pidToKillWhenClosed, devMode, safeMode, newWindow,
|
||||
logFile, socketPath, userDataDir, profileStartup, timeout, setPortable,
|
||||
clearWindowState, addToLastWindow, mainProcess}
|
||||
|
||||
start()
|
||||
263
src/main-process/main.js
Normal file
263
src/main-process/main.js
Normal file
@@ -0,0 +1,263 @@
|
||||
global.shellStartTime = Date.now()
|
||||
|
||||
process.on('uncaughtException', function (error = {}) {
|
||||
if (error.message != null) {
|
||||
console.log(error.message)
|
||||
}
|
||||
|
||||
if (error.stack != null) {
|
||||
console.log(error.stack)
|
||||
}
|
||||
})
|
||||
|
||||
const {app} = require('electron')
|
||||
const fs = require('fs-plus')
|
||||
const path = require('path')
|
||||
const temp = require('temp')
|
||||
const yargs = require('yargs')
|
||||
const dedent = require('dedent')
|
||||
const startCrashReporter = require('../crash-reporter-start')
|
||||
const previousConsoleLog = console.log
|
||||
console.log = require('nslog')
|
||||
|
||||
function start () {
|
||||
const args = parseCommandLine()
|
||||
args.env = process.env
|
||||
setupAtomHome(args)
|
||||
setupCompileCache()
|
||||
|
||||
if (handleStartupEventWithSquirrel()) {
|
||||
return
|
||||
} else if (args.test && args.mainProcess) {
|
||||
console.log = previousConsoleLog
|
||||
app.on('ready', function () {
|
||||
const testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner'))
|
||||
testRunner(args.pathsToOpen)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// NB: This prevents Win10 from showing dupe items in the taskbar
|
||||
app.setAppUserModelId('com.squirrel.atom.atom')
|
||||
|
||||
function addPathToOpen (event, pathToOpen) {
|
||||
event.preventDefault()
|
||||
args.pathsToOpen.push(pathToOpen)
|
||||
}
|
||||
|
||||
function addUrlToOpen (event, urlToOpen) {
|
||||
event.preventDefault()
|
||||
args.urlsToOpen.push(urlToOpen)
|
||||
}
|
||||
|
||||
app.on('open-file', addPathToOpen)
|
||||
app.on('open-url', addUrlToOpen)
|
||||
app.on('will-finish-launching', startCrashReporter)
|
||||
|
||||
if (args.userDataDir != null) {
|
||||
app.setPath('userData', args.userDataDir)
|
||||
} else if (args.test) {
|
||||
app.setPath('userData', temp.mkdirSync('atom-test-data'))
|
||||
}
|
||||
|
||||
app.on('ready', function () {
|
||||
app.removeListener('open-file', addPathToOpen)
|
||||
app.removeListener('open-url', addUrlToOpen)
|
||||
const AtomApplication = require(path.join(args.resourcePath, 'src', 'main-process', 'atom-application'))
|
||||
AtomApplication.open(args)
|
||||
|
||||
if (!args.test) {
|
||||
console.log(`App load time: ${Date.now() - global.shellStartTime}ms`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeDriveLetterName (filePath) {
|
||||
if (process.platform === 'win32') {
|
||||
return filePath.replace(/^([a-z]):/, ([driveLetter]) => driveLetter.toUpperCase() + ':')
|
||||
} else {
|
||||
return filePath
|
||||
}
|
||||
}
|
||||
|
||||
function handleStartupEventWithSquirrel () {
|
||||
if (process.platform !== 'win32') {
|
||||
return false
|
||||
}
|
||||
|
||||
const SquirrelUpdate = require('./squirrel-update')
|
||||
const squirrelCommand = process.argv[1]
|
||||
return SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
|
||||
}
|
||||
|
||||
function setupAtomHome ({setPortable}) {
|
||||
if (process.env.ATOM_HOME) {
|
||||
return
|
||||
}
|
||||
|
||||
let atomHome = path.join(app.getPath('home'), '.atom')
|
||||
const AtomPortable = require('./atom-portable')
|
||||
|
||||
if (setPortable && !AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) {
|
||||
try {
|
||||
AtomPortable.setPortable(atomHome)
|
||||
} catch (error) {
|
||||
console.log(`Failed copying portable directory '${atomHome}' to '${AtomPortable.getPortableAtomHomePath()}'`)
|
||||
console.log(`${error.message} ${error.stack}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) {
|
||||
atomHome = AtomPortable.getPortableAtomHomePath()
|
||||
}
|
||||
|
||||
try {
|
||||
atomHome = fs.realpathSync(atomHome)
|
||||
} finally {
|
||||
process.env.ATOM_HOME = atomHome
|
||||
}
|
||||
}
|
||||
|
||||
function setupCompileCache () {
|
||||
const CompileCache = require('../compile-cache')
|
||||
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
|
||||
}
|
||||
|
||||
function writeFullVersion () {
|
||||
process.stdout.write(
|
||||
`Atom : ${app.getVersion()}\n` +
|
||||
`Electron: ${process.versions.electron}\n` +
|
||||
`Chrome : ${process.versions.chrome}\n` +
|
||||
`Node : ${process.versions.node}\n`
|
||||
)
|
||||
}
|
||||
|
||||
function parseCommandLine () {
|
||||
const options = yargs(process.argv.slice(1)).wrap(100)
|
||||
const version = app.getVersion()
|
||||
options.usage(
|
||||
dedent`Atom Editor v${version}
|
||||
|
||||
Usage: atom [options] [path ...]
|
||||
|
||||
One or more paths to files or folders may be specified. If there is an
|
||||
existing Atom window that contains all of the given folders, the paths
|
||||
will be opened in that window. Otherwise, they will be opened in a new
|
||||
window.
|
||||
|
||||
Environment Variables:
|
||||
|
||||
ATOM_DEV_RESOURCE_PATH The path from which Atom loads source code in dev mode.
|
||||
Defaults to \`~/github/atom\`.
|
||||
|
||||
ATOM_HOME The root path for all configuration files and folders.
|
||||
Defaults to \`~/.atom\`.`
|
||||
)
|
||||
// Deprecated 1.0 API preview flag
|
||||
options.alias('1', 'one').boolean('1').describe('1', 'This option is no longer supported.')
|
||||
options.boolean('include-deprecated-apis').describe('include-deprecated-apis', 'This option is not currently supported.')
|
||||
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
|
||||
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the main process in the foreground.')
|
||||
options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.')
|
||||
options.alias('l', 'log-file').string('l').describe('l', 'Log all output to file.')
|
||||
options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.')
|
||||
options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.')
|
||||
options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.')
|
||||
options.boolean('safe').describe(
|
||||
'safe',
|
||||
'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.'
|
||||
)
|
||||
options.boolean('portable').describe(
|
||||
'portable',
|
||||
'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.'
|
||||
)
|
||||
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
|
||||
options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.')
|
||||
options.string('timeout').describe(
|
||||
'timeout',
|
||||
'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).'
|
||||
)
|
||||
options.alias('v', 'version').boolean('v').describe('v', 'Print the version information.')
|
||||
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
|
||||
options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.')
|
||||
options.string('socket-path')
|
||||
options.string('user-data-dir')
|
||||
options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.')
|
||||
|
||||
const args = options.argv
|
||||
|
||||
if (args.help) {
|
||||
process.stdout.write(options.help())
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (args.version) {
|
||||
writeFullVersion()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const addToLastWindow = args['add']
|
||||
const safeMode = args['safe']
|
||||
const pathsToOpen = args._
|
||||
const test = args['test']
|
||||
const mainProcess = args['main-process']
|
||||
const timeout = args['timeout']
|
||||
const newWindow = args['new-window']
|
||||
let executedFrom = null
|
||||
if (args['executed-from'] && args['executed-from'].toString()) {
|
||||
executedFrom = args['executed-from'].toString()
|
||||
} else {
|
||||
executedFrom = process.cwd()
|
||||
}
|
||||
|
||||
let pidToKillWhenClosed = null
|
||||
if (args['wait']) {
|
||||
pidToKillWhenClosed = args['pid']
|
||||
}
|
||||
|
||||
const logFile = args['log-file']
|
||||
const socketPath = args['socket-path']
|
||||
const userDataDir = args['user-data-dir']
|
||||
const profileStartup = args['profile-startup']
|
||||
const clearWindowState = args['clear-window-state']
|
||||
const urlsToOpen = []
|
||||
const setPortable = args.portable
|
||||
let devMode = args['dev']
|
||||
let devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH || path.join(app.getPath('home'), 'github', 'atom')
|
||||
let resourcePath = null
|
||||
|
||||
if (args['resource-path']) {
|
||||
devMode = true
|
||||
resourcePath = args['resource-path']
|
||||
}
|
||||
|
||||
if (test) {
|
||||
devMode = true
|
||||
}
|
||||
|
||||
if (devMode && !resourcePath) {
|
||||
resourcePath = devResourcePath
|
||||
}
|
||||
|
||||
if (!fs.statSyncNoException(resourcePath)) {
|
||||
resourcePath = path.dirname(path.dirname(__dirname))
|
||||
}
|
||||
|
||||
if (args['path-environment']) {
|
||||
// On Yosemite the $PATH is not inherited by the "open" command, so we have to
|
||||
// explicitly pass it by command line, see http://git.io/YC8_Ew.
|
||||
process.env.PATH = args['path-environment']
|
||||
}
|
||||
|
||||
resourcePath = normalizeDriveLetterName(resourcePath)
|
||||
devResourcePath = normalizeDriveLetterName(devResourcePath)
|
||||
|
||||
return {
|
||||
resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
|
||||
version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath,
|
||||
userDataDir, profileStartup, timeout, setPortable, clearWindowState,
|
||||
addToLastWindow, mainProcess
|
||||
}
|
||||
}
|
||||
|
||||
start()
|
||||
@@ -1,7 +1,7 @@
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
Spawner = require './spawner'
|
||||
WinRegistry = require './win-registry'
|
||||
WinShell = require './win-shell'
|
||||
WinPowerShell = require './win-powershell'
|
||||
|
||||
appFolder = path.resolve(process.execPath, '..')
|
||||
@@ -125,26 +125,36 @@ exports.restartAtom = (app) ->
|
||||
app.once 'will-quit', -> Spawner.spawn(path.join(binFolder, 'atom.cmd'), args)
|
||||
app.quit()
|
||||
|
||||
updateContextMenus = (callback) ->
|
||||
WinShell.fileContextMenu.update ->
|
||||
WinShell.folderContextMenu.update ->
|
||||
WinShell.folderBackgroundContextMenu.update ->
|
||||
callback()
|
||||
|
||||
# Handle squirrel events denoted by --squirrel-* command line arguments.
|
||||
exports.handleStartupEvent = (app, squirrelCommand) ->
|
||||
switch squirrelCommand
|
||||
when '--squirrel-install'
|
||||
createShortcuts ->
|
||||
WinRegistry.installContextMenu ->
|
||||
addCommandsToPath ->
|
||||
app.quit()
|
||||
addCommandsToPath ->
|
||||
WinShell.fileHandler.register ->
|
||||
updateContextMenus ->
|
||||
app.quit()
|
||||
true
|
||||
when '--squirrel-updated'
|
||||
updateShortcuts ->
|
||||
WinRegistry.installContextMenu ->
|
||||
addCommandsToPath ->
|
||||
addCommandsToPath ->
|
||||
updateContextMenus ->
|
||||
app.quit()
|
||||
true
|
||||
when '--squirrel-uninstall'
|
||||
removeShortcuts ->
|
||||
WinRegistry.uninstallContextMenu ->
|
||||
removeCommandsFromPath ->
|
||||
app.quit()
|
||||
removeCommandsFromPath ->
|
||||
WinShell.fileHandler.deregister ->
|
||||
WinShell.fileContextMenu.deregister ->
|
||||
WinShell.folderContextMenu.deregister ->
|
||||
WinShell.folderBackgroundContextMenu.deregister ->
|
||||
app.quit()
|
||||
true
|
||||
when '--squirrel-obsolete'
|
||||
app.quit()
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
path = require 'path'
|
||||
Spawner = require './spawner'
|
||||
|
||||
if process.env.SystemRoot
|
||||
system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
regPath = path.join(system32Path, 'reg.exe')
|
||||
else
|
||||
regPath = 'reg.exe'
|
||||
|
||||
# Registry keys used for context menu
|
||||
fileKeyPath = 'HKCU\\Software\\Classes\\*\\shell\\Atom'
|
||||
directoryKeyPath = 'HKCU\\Software\\Classes\\directory\\shell\\Atom'
|
||||
backgroundKeyPath = 'HKCU\\Software\\Classes\\directory\\background\\shell\\Atom'
|
||||
applicationsKeyPath = 'HKCU\\Software\\Classes\\Applications\\atom.exe'
|
||||
|
||||
# Spawn reg.exe and callback when it completes
|
||||
spawnReg = (args, callback) ->
|
||||
Spawner.spawn(regPath, args, callback)
|
||||
|
||||
# Install the Open with Atom explorer context menu items via the registry.
|
||||
#
|
||||
# * `callback` The {Function} to call after registry operation is done.
|
||||
# It will be invoked with the same arguments provided by {Spawner.spawn}.
|
||||
#
|
||||
# Returns `undefined`.
|
||||
exports.installContextMenu = (callback) ->
|
||||
addToRegistry = (args, callback) ->
|
||||
args.unshift('add')
|
||||
args.push('/f')
|
||||
spawnReg(args, callback)
|
||||
|
||||
installFileHandler = (callback) ->
|
||||
args = ["#{applicationsKeyPath}\\shell\\open\\command", '/ve', '/d', "\"#{process.execPath}\" \"%1\""]
|
||||
addToRegistry(args, callback)
|
||||
|
||||
installMenu = (keyPath, arg, callback) ->
|
||||
args = [keyPath, '/ve', '/d', 'Open with Atom']
|
||||
addToRegistry args, ->
|
||||
args = [keyPath, '/v', 'Icon', '/d', "\"#{process.execPath}\""]
|
||||
addToRegistry args, ->
|
||||
args = ["#{keyPath}\\command", '/ve', '/d', "\"#{process.execPath}\" \"#{arg}\""]
|
||||
addToRegistry(args, callback)
|
||||
|
||||
installMenu fileKeyPath, '%1', ->
|
||||
installMenu directoryKeyPath, '%1', ->
|
||||
installMenu backgroundKeyPath, '%V', ->
|
||||
installFileHandler(callback)
|
||||
|
||||
# Uninstall the Open with Atom explorer context menu items via the registry.
|
||||
#
|
||||
# * `callback` The {Function} to call after registry operation is done.
|
||||
# It will be invoked with the same arguments provided by {Spawner.spawn}.
|
||||
#
|
||||
# Returns `undefined`.
|
||||
exports.uninstallContextMenu = (callback) ->
|
||||
deleteFromRegistry = (keyPath, callback) ->
|
||||
spawnReg(['delete', keyPath, '/f'], callback)
|
||||
|
||||
deleteFromRegistry fileKeyPath, ->
|
||||
deleteFromRegistry directoryKeyPath, ->
|
||||
deleteFromRegistry backgroundKeyPath, ->
|
||||
deleteFromRegistry(applicationsKeyPath, callback)
|
||||
58
src/main-process/win-shell.coffee
Normal file
58
src/main-process/win-shell.coffee
Normal file
@@ -0,0 +1,58 @@
|
||||
Registry = require 'winreg'
|
||||
Path = require 'path'
|
||||
|
||||
exeName = Path.basename(process.execPath)
|
||||
appPath = "\"#{process.execPath}\""
|
||||
isBeta = appPath.includes(' Beta')
|
||||
appName = exeName.replace('atom', (if isBeta then 'Atom Beta' else 'Atom' )).replace('.exe', '')
|
||||
|
||||
class ShellOption
|
||||
constructor: (key, parts) ->
|
||||
@key = key
|
||||
@parts = parts
|
||||
|
||||
isRegistered: (callback) =>
|
||||
new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"})
|
||||
.get @parts[0].name, (err, val) =>
|
||||
callback(not err? and val? and val.value is @parts[0].value)
|
||||
|
||||
register: (callback) =>
|
||||
doneCount = @parts.length
|
||||
@parts.forEach (part) =>
|
||||
reg = new Registry({hive: 'HKCU', key: if part.key? then "#{@key}\\#{part.key}" else @key})
|
||||
reg.create( -> reg.set part.name, Registry.REG_SZ, part.value, -> callback() if --doneCount is 0)
|
||||
|
||||
deregister: (callback) =>
|
||||
@isRegistered (isRegistered) =>
|
||||
if isRegistered
|
||||
new Registry({hive: 'HKCU', key: @key}).destroy -> callback null, true
|
||||
else
|
||||
callback null, false
|
||||
|
||||
update: (callback) =>
|
||||
new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"})
|
||||
.get @parts[0].name, (err, val) =>
|
||||
if err? or not val? or val.value.includes '\\' + exeName
|
||||
callback(err)
|
||||
else
|
||||
@register callback
|
||||
|
||||
exports.appName = appName
|
||||
|
||||
exports.fileHandler = new ShellOption("\\Software\\Classes\\Applications\\#{exeName}",
|
||||
[{key: 'shell\\open\\command', name: '', value: "#{appPath} \"%1\""}]
|
||||
)
|
||||
|
||||
contextParts = [
|
||||
{key: 'command', name: '', value: "#{appPath} \"%1\""},
|
||||
{name: '', value: "Open with #{appName}"},
|
||||
{name: 'Icon', value: "#{appPath}"}
|
||||
]
|
||||
|
||||
exports.fileContextMenu = new ShellOption("\\Software\\Classes\\*\\shell\\#{appName}", contextParts)
|
||||
|
||||
exports.folderContextMenu = new ShellOption("\\Software\\Classes\\Directory\\shell\\#{appName}", contextParts)
|
||||
|
||||
exports.folderBackgroundContextMenu = new ShellOption("\\Software\\Classes\\Directory\\background\\shell\\#{appName}",
|
||||
JSON.parse(JSON.stringify(contextParts).replace('%1', '%V'))
|
||||
)
|
||||
@@ -33,8 +33,20 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-success`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
@@ -46,8 +58,20 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-info`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
@@ -59,8 +83,20 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-warning`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
@@ -72,12 +108,26 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-error`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
# in the notification header. Defaults to `'flame'`.
|
||||
# * `stack` (optional) A preformatted {String} with stack trace information
|
||||
# describing the location of the error.
|
||||
addError: (message, options) ->
|
||||
@addNotification(new Notification('error', message, options))
|
||||
|
||||
@@ -85,12 +135,26 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-error`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
# in the notification header. Defaults to `'bug'`.
|
||||
# * `stack` (optional) A preformatted {String} with stack trace information
|
||||
# describing the location of the error.
|
||||
addFatalError: (message, options) ->
|
||||
@addNotification(new Notification('fatal', message, options))
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ class Package
|
||||
|
||||
# TODO: Remove. Settings view calls this method currently.
|
||||
activateConfig: ->
|
||||
return if @configSchemaRegisteredOnLoad
|
||||
@requireMainModule()
|
||||
@registerConfigSchemaFromMainModule()
|
||||
|
||||
@@ -426,8 +427,8 @@ class Package
|
||||
return @mainModule if @mainModuleRequired
|
||||
unless @isCompatible()
|
||||
console.warn """
|
||||
Failed to require the main module of '#{@name}' because it requires an incompatible native module.
|
||||
Run `apm rebuild` in the package directory to resolve.
|
||||
Failed to require the main module of '#{@name}' because it requires one or more incompatible native modules (#{_.pluck(@incompatibleModules, 'name').join(', ')}).
|
||||
Run `apm rebuild` in the package directory and restart Atom to resolve.
|
||||
"""
|
||||
return
|
||||
mainModulePath = @getMainModulePath()
|
||||
|
||||
@@ -5,15 +5,32 @@ module.exports = (repoPath, paths = []) ->
|
||||
repo = Git.open(repoPath)
|
||||
|
||||
upstream = {}
|
||||
statuses = {}
|
||||
submodules = {}
|
||||
branch = null
|
||||
|
||||
if repo?
|
||||
# Statuses in main repo
|
||||
workingDirectoryPath = repo.getWorkingDirectory()
|
||||
repoStatus = (if paths.length > 0 then repo.getStatusForPaths(paths) else repo.getStatus())
|
||||
for filePath, status of repoStatus
|
||||
statuses[filePath] = status
|
||||
|
||||
# Statuses in submodules
|
||||
for submodulePath, submoduleRepo of repo.submodules
|
||||
submodules[submodulePath] =
|
||||
branch: submoduleRepo.getHead()
|
||||
upstream: submoduleRepo.getAheadBehindCount()
|
||||
|
||||
workingDirectoryPath = submoduleRepo.getWorkingDirectory()
|
||||
for filePath, status of submoduleRepo.getStatus()
|
||||
absolutePath = path.join(workingDirectoryPath, filePath)
|
||||
# Make path relative to parent repository
|
||||
relativePath = repo.relativize(absolutePath)
|
||||
statuses[relativePath] = status
|
||||
|
||||
upstream = repo.getAheadBehindCount()
|
||||
branch = repo.getHead()
|
||||
repo.release()
|
||||
|
||||
{upstream, submodules}
|
||||
{statuses, upstream, branch, submodules}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
{spliceWithArray} = require 'underscore-plus'
|
||||
|
||||
# Used by the display buffer to map screen rows to buffer rows and vice-versa.
|
||||
# This mapping may not be 1:1 due to folds and soft-wraps. This object maintains
|
||||
# an array of regions, which contain `bufferRows` and `screenRows` fields.
|
||||
#
|
||||
# Rectangular Regions:
|
||||
# If a region has the same number of buffer rows and screen rows, it is referred
|
||||
# to as "rectangular", and represents one or more non-soft-wrapped, non-folded
|
||||
# lines.
|
||||
#
|
||||
# Trapezoidal Regions:
|
||||
# If a region has one buffer row and more than one screen row, it represents a
|
||||
# soft-wrapped line. If a region has one screen row and more than one buffer
|
||||
# row, it represents folded lines
|
||||
module.exports =
|
||||
class RowMap
|
||||
constructor: ->
|
||||
@regions = []
|
||||
|
||||
# Public: Returns a copy of all the regions in the map
|
||||
getRegions: ->
|
||||
@regions.slice()
|
||||
|
||||
# Public: Returns an end-row-exclusive range of screen rows corresponding to
|
||||
# the given buffer row. If the buffer row is soft-wrapped, the range may span
|
||||
# multiple screen rows. Otherwise it will span a single screen row.
|
||||
screenRowRangeForBufferRow: (targetBufferRow) ->
|
||||
{region, bufferRows, screenRows} = @traverseToBufferRow(targetBufferRow)
|
||||
|
||||
if region? and region.bufferRows isnt region.screenRows
|
||||
[screenRows, screenRows + region.screenRows]
|
||||
else
|
||||
screenRows += targetBufferRow - bufferRows
|
||||
[screenRows, screenRows + 1]
|
||||
|
||||
# Public: Returns an end-row-exclusive range of buffer rows corresponding to
|
||||
# the given screen row. If the screen row is the first line of a folded range
|
||||
# of buffer rows, the range may span multiple buffer rows. Otherwise it will
|
||||
# span a single buffer row.
|
||||
bufferRowRangeForScreenRow: (targetScreenRow) ->
|
||||
{region, screenRows, bufferRows} = @traverseToScreenRow(targetScreenRow)
|
||||
if region? and region.bufferRows isnt region.screenRows
|
||||
[bufferRows, bufferRows + region.bufferRows]
|
||||
else
|
||||
bufferRows += targetScreenRow - screenRows
|
||||
[bufferRows, bufferRows + 1]
|
||||
|
||||
# Public: If the given buffer row is part of a folded row range, returns that
|
||||
# row range. Otherwise returns a range spanning only the given buffer row.
|
||||
bufferRowRangeForBufferRow: (targetBufferRow) ->
|
||||
{region, bufferRows} = @traverseToBufferRow(targetBufferRow)
|
||||
if region? and region.bufferRows isnt region.screenRows
|
||||
[bufferRows, bufferRows + region.bufferRows]
|
||||
else
|
||||
[targetBufferRow, targetBufferRow + 1]
|
||||
|
||||
# Public: Given a starting buffer row, the number of buffer rows to replace,
|
||||
# and an array of regions of shape {bufferRows: n, screenRows: m}, splices
|
||||
# the regions at the appropriate location in the map. This method is used by
|
||||
# display buffer to keep the map updated when the underlying buffer changes.
|
||||
spliceRegions: (startBufferRow, bufferRowCount, regions) ->
|
||||
endBufferRow = startBufferRow + bufferRowCount
|
||||
{index, bufferRows} = @traverseToBufferRow(startBufferRow)
|
||||
precedingRows = startBufferRow - bufferRows
|
||||
|
||||
count = 0
|
||||
while region = @regions[index + count]
|
||||
count++
|
||||
bufferRows += region.bufferRows
|
||||
if bufferRows >= endBufferRow
|
||||
followingRows = bufferRows - endBufferRow
|
||||
break
|
||||
|
||||
if precedingRows > 0
|
||||
regions.unshift({bufferRows: precedingRows, screenRows: precedingRows})
|
||||
|
||||
if followingRows > 0
|
||||
regions.push({bufferRows: followingRows, screenRows: followingRows})
|
||||
|
||||
spliceWithArray(@regions, index, count, regions)
|
||||
@mergeAdjacentRectangularRegions(index - 1, index + regions.length)
|
||||
|
||||
traverseToBufferRow: (targetBufferRow) ->
|
||||
bufferRows = 0
|
||||
screenRows = 0
|
||||
for region, index in @regions
|
||||
if (bufferRows + region.bufferRows) > targetBufferRow
|
||||
return {region, index, screenRows, bufferRows}
|
||||
bufferRows += region.bufferRows
|
||||
screenRows += region.screenRows
|
||||
{index, screenRows, bufferRows}
|
||||
|
||||
traverseToScreenRow: (targetScreenRow) ->
|
||||
bufferRows = 0
|
||||
screenRows = 0
|
||||
for region, index in @regions
|
||||
if (screenRows + region.screenRows) > targetScreenRow
|
||||
return {region, index, screenRows, bufferRows}
|
||||
bufferRows += region.bufferRows
|
||||
screenRows += region.screenRows
|
||||
{index, screenRows, bufferRows}
|
||||
|
||||
mergeAdjacentRectangularRegions: (startIndex, endIndex) ->
|
||||
for index in [endIndex..startIndex]
|
||||
if 0 < index < @regions.length
|
||||
leftRegion = @regions[index - 1]
|
||||
rightRegion = @regions[index]
|
||||
leftIsRectangular = leftRegion.bufferRows is leftRegion.screenRows
|
||||
rightIsRectangular = rightRegion.bufferRows is rightRegion.screenRows
|
||||
if leftIsRectangular and rightIsRectangular
|
||||
@regions.splice index - 1, 2,
|
||||
bufferRows: leftRegion.bufferRows + rightRegion.bufferRows
|
||||
screenRows: leftRegion.screenRows + rightRegion.screenRows
|
||||
return
|
||||
|
||||
# Public: Returns an array of strings describing the map's regions.
|
||||
inspect: ->
|
||||
for {bufferRows, screenRows} in @regions
|
||||
"#{bufferRows}:#{screenRows}"
|
||||
@@ -365,7 +365,12 @@ class TextEditorComponent
|
||||
|
||||
onTextInput: (event) =>
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
# WARNING: If we call preventDefault on the input of a space character,
|
||||
# then the browser interprets the spacebar keypress as a page-down command,
|
||||
# causing spaces to scroll elements containing editors. This is impossible
|
||||
# to test.
|
||||
event.preventDefault() if event.data isnt ' '
|
||||
|
||||
return unless @isInputEnabled()
|
||||
|
||||
|
||||
@@ -18,24 +18,31 @@ class TokenizedBufferIterator
|
||||
@currentLineLength = currentLine.text.length
|
||||
@containingTags = @currentLineOpenTags.map (id) => @grammarRegistry.scopeForId(id)
|
||||
currentColumn = 0
|
||||
|
||||
for tag, index in @currentTags
|
||||
if tag >= 0
|
||||
if currentColumn >= position.column and @isAtTagBoundary()
|
||||
if currentColumn is position.column
|
||||
@tagIndex = index
|
||||
break
|
||||
else
|
||||
currentColumn += tag
|
||||
@containingTags.pop() while @closeTags.shift()
|
||||
@containingTags.push(tag) while tag = @openTags.shift()
|
||||
else
|
||||
scopeName = @grammarRegistry.scopeForId(tag)
|
||||
if tag % 2 is 0
|
||||
if @openTags.length > 0
|
||||
@containingTags.push(openTag) while openTag = @openTags.shift()
|
||||
if currentColumn > position.column
|
||||
@tagIndex = index
|
||||
break
|
||||
else
|
||||
@closeTags.push(scopeName)
|
||||
else
|
||||
else
|
||||
scopeName = @grammarRegistry.scopeForId(tag)
|
||||
if tag % 2 is 0 # close tag
|
||||
if @openTags.length > 0
|
||||
if currentColumn is position.column
|
||||
@tagIndex = index
|
||||
break
|
||||
else
|
||||
@containingTags.pop() while @closeTags.shift()
|
||||
@containingTags.push(openTag) while openTag = @openTags.shift()
|
||||
@closeTags.push(scopeName)
|
||||
else # open tag
|
||||
@openTags.push(scopeName)
|
||||
|
||||
@tagIndex ?= @currentTags.length
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Public: Measure how long a function takes to run.
|
||||
#
|
||||
# description - A {String} description that will be logged to the console when
|
||||
# the function completes.
|
||||
# fn - A {Function} to measure the duration of.
|
||||
#
|
||||
# Returns the value returned by the given function.
|
||||
window.measure = (description, fn) ->
|
||||
start = Date.now()
|
||||
value = fn()
|
||||
result = Date.now() - start
|
||||
console.log description, result
|
||||
value
|
||||
|
||||
# Public: Create a dev tools profile for a function.
|
||||
#
|
||||
# description - A {String} description that will be available in the Profiles
|
||||
# tab of the dev tools.
|
||||
# fn - A {Function} to profile.
|
||||
#
|
||||
# Returns the value returned by the given function.
|
||||
window.profile = (description, fn) ->
|
||||
measure description, ->
|
||||
console.profile(description)
|
||||
value = fn()
|
||||
console.profileEnd(description)
|
||||
value
|
||||
30
src/window.js
Normal file
30
src/window.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// Public: Measure how long a function takes to run.
|
||||
//
|
||||
// description - A {String} description that will be logged to the console when
|
||||
// the function completes.
|
||||
// fn - A {Function} to measure the duration of.
|
||||
//
|
||||
// Returns the value returned by the given function.
|
||||
window.measure = function (description, fn) {
|
||||
let start = Date.now()
|
||||
let value = fn()
|
||||
let result = Date.now() - start
|
||||
console.log(description, result)
|
||||
return value
|
||||
}
|
||||
|
||||
// Public: Create a dev tools profile for a function.
|
||||
//
|
||||
// description - A {String} description that will be available in the Profiles
|
||||
// tab of the dev tools.
|
||||
// fn - A {Function} to profile.
|
||||
//
|
||||
// Returns the value returned by the given function.
|
||||
window.profile = function (description, fn) {
|
||||
window.measure(description, function () {
|
||||
console.profile(description)
|
||||
let value = fn()
|
||||
console.profileEnd(description)
|
||||
return value
|
||||
})
|
||||
}
|
||||
@@ -1097,7 +1097,7 @@ class Workspace extends Model
|
||||
checkoutHead = =>
|
||||
@project.repositoryForDirectory(new Directory(editor.getDirectoryPath()))
|
||||
.then (repository) ->
|
||||
repository?.async.checkoutHeadForEditor(editor)
|
||||
repository?.checkoutHeadForEditor(editor)
|
||||
|
||||
if @config.get('editor.confirmCheckoutHeadRevision')
|
||||
@applicationDelegate.confirm
|
||||
|
||||
Reference in New Issue
Block a user