Merge branch 'master' into ns-mb-detangle-editor

This commit is contained in:
Max Brunsfeld
2016-08-02 14:12:18 -07:00
22 changed files with 544 additions and 668 deletions

View File

@@ -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
View 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}
}
}
}

View File

@@ -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
View 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
}
}

View File

@@ -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
View 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 = {}
}
}

View File

@@ -3,7 +3,8 @@ Path = require 'path'
exeName = Path.basename(process.execPath)
appPath = "\"#{process.execPath}\""
appName = exeName.replace('atom', 'Atom').replace('beta', 'Beta').replace('.exe', '')
isBeta = appPath.includes(' Beta')
appName = exeName.replace('atom', (if isBeta then 'Atom Beta' else 'Atom' )).replace('.exe', '')
class ShellOption
constructor: (key, parts) ->
@@ -13,11 +14,11 @@ class ShellOption
isRegistered: (callback) =>
new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"})
.get @parts[0].name, (err, val) =>
callback(not err? and val.value is @parts[0].value)
callback(not err? and val? and val.value is @parts[0].value)
register: (callback) =>
doneCount = @parts.length
for part in @parts
@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)
@@ -31,7 +32,7 @@ class ShellOption
update: (callback) =>
new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"})
.get @parts[0].name, (err, val) =>
if err? or not val.value.includes '\\' + exeName
if err? or not val? or val.value.includes '\\' + exeName
callback(err)
else
@register callback

View File

@@ -427,7 +427,7 @@ class Package
return @mainModule if @mainModuleRequired
unless @isCompatible()
console.warn """
Failed to require the main module of '#{@name}' because it requires one or more incompatible native modules (#{_.map(@incompatibleModules, 'name').join(', ')}).
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

View File

@@ -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}"

View File

@@ -18,24 +18,31 @@ class TokenizedBufferIterator
@currentLineLength = currentLine.text.length
@containingTags = @currentLineOpenTags.map (id) => @tokenizedBuffer.grammar.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 = @tokenizedBuffer.grammar.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 = @tokenizedBuffer.grammar.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

View File

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