Merge branch 'master' into fb-pw-simple-project-config

This commit is contained in:
Philip Weiss
2018-03-09 14:06:14 -08:00
committed by GitHub
21 changed files with 383 additions and 112 deletions

View File

@@ -189,12 +189,12 @@ class BufferedProcess {
output += data
})
wmicProcess.stdout.on('close', () => {
const pidsToKill = output.split(/\s+/)
.filter((pid) => /^\d+$/.test(pid))
.map((pid) => parseInt(pid))
.filter((pid) => pid !== parentPid && pid > 0 && pid < Infinity)
for (let pid of output.split(/\s+/)) {
if (!/^\d{1,10}$/.test(pid)) continue
pid = parseInt(pid, 10)
if (!pid || pid === parentPid) continue
for (let pid of pidsToKill) {
try {
process.kill(pid)
} catch (error) {}

View File

@@ -1106,7 +1106,7 @@ class Config {
deepClone (object) {
if (object instanceof Color) {
return object.clone()
} else if (_.isArray(object)) {
} else if (Array.isArray(object)) {
return object.map(value => this.deepClone(value))
} else if (isPlainObject(object)) {
return _.mapObject(object, (key, value) => [key, this.deepClone(value)])
@@ -1467,7 +1467,7 @@ Config.addSchemaEnforcers({
}
})
let isPlainObject = value => _.isObject(value) && !_.isArray(value) && !_.isFunction(value) && !_.isString(value) && !(value instanceof Color)
let isPlainObject = value => _.isObject(value) && !Array.isArray(value) && !_.isFunction(value) && !_.isString(value) && !(value instanceof Color)
let sortObject = value => {
if (!isPlainObject(value)) { return value }

View File

@@ -1,4 +1,3 @@
const _ = require('underscore-plus')
const {Emitter} = require('event-kit')
let idCounter = 0
@@ -49,7 +48,7 @@ class Decoration {
// 'line-number' is a 'gutter', but a 'gutter' is not a 'line-number'.
static isType (decorationProperties, type) {
// 'line-number' is a special case of 'gutter'.
if (_.isArray(decorationProperties.type)) {
if (Array.isArray(decorationProperties.type)) {
if (decorationProperties.type.includes(type)) {
return true
}

View File

@@ -153,7 +153,10 @@ module.exports = class Dock {
this.state = nextState
this.render(this.state)
const {visible} = this.state
const {hovered, visible} = this.state
if (hovered !== prevState.hovered) {
this.emitter.emit('did-change-hovered', hovered)
}
if (visible !== prevState.visible) {
this.emitter.emit('did-change-visible', visible)
}
@@ -296,7 +299,7 @@ module.exports = class Dock {
}
handleDrag (event) {
if (!this.pointWithinHoverArea({x: event.pageX, y: event.pageY}, false)) {
if (!this.pointWithinHoverArea({x: event.pageX, y: event.pageY}, true)) {
this.draggedOut()
}
}
@@ -313,9 +316,13 @@ module.exports = class Dock {
// Determine whether the cursor is within the dock hover area. This isn't as simple as just using
// mouseenter/leave because we want to be a little more forgiving. For example, if the cursor is
// over the footer, we want to show the bottom dock's toggle button.
pointWithinHoverArea (point, includeButtonWidth = this.state.hovered) {
// over the footer, we want to show the bottom dock's toggle button. Also note that our criteria
// for detecting entry are different than detecting exit but, in order for us to avoid jitter, the
// area considered when detecting exit MUST fully encompass the area considered when detecting
// entry.
pointWithinHoverArea (point, detectingExit) {
const dockBounds = this.innerElement.getBoundingClientRect()
// Copy the bounds object since we can't mutate it.
const bounds = {
top: dockBounds.top,
@@ -324,39 +331,67 @@ module.exports = class Dock {
left: dockBounds.left
}
// Include all panels that are closer to the edge than the dock in our calculations.
// To provide a minimum target, expand the area toward the center a bit.
switch (this.location) {
case 'right':
bounds.left = Math.min(bounds.left, bounds.right - 2)
break
case 'bottom':
bounds.top = Math.min(bounds.top, bounds.bottom - 1)
break
case 'left':
bounds.right = Math.max(bounds.right, bounds.left + 2)
break
}
// Further expand the area to include all panels that are closer to the edge than the dock.
switch (this.location) {
case 'right':
if (!this.isVisible()) bounds.left = bounds.right - 2
bounds.right = Number.POSITIVE_INFINITY
break
case 'bottom':
if (!this.isVisible()) bounds.top = bounds.bottom - 1
bounds.bottom = Number.POSITIVE_INFINITY
break
case 'left':
if (!this.isVisible()) bounds.right = bounds.left + 2
bounds.left = Number.NEGATIVE_INFINITY
break
}
// The area used when detecting "leave" events is actually larger than when detecting entrances.
if (includeButtonWidth) {
// If we're in this area, we know we're within the hover area without having to take further
// measurements.
if (rectContainsPoint(bounds, point)) return true
// If we're within the toggle button, we're definitely in the hover area. Unfortunately, we
// can't do this measurement conditionally (e.g. only if the toggle button is visible) because
// our knowledge of the toggle's button is incomplete due to CSS animations. (We may think the
// toggle button isn't visible when in actuality it is, but is animating to its hidden state.)
//
// Since `point` is always the current mouse position, one possible optimization would be to
// remove it as an argument and determine whether we're inside the toggle button using
// mouseenter/leave events on it. This class would still need to keep track of the mouse
// position (via a mousemove listener) for the other measurements, though.
const toggleButtonBounds = this.toggleButton.getBounds()
if (rectContainsPoint(toggleButtonBounds, point)) return true
// The area used when detecting exit is actually larger than when detecting entrances. Expand
// our bounds and recheck them.
if (detectingExit) {
const hoverMargin = 20
const {width, height} = this.toggleButton.getBounds()
switch (this.location) {
case 'right':
bounds.left -= width + hoverMargin
bounds.left = Math.min(bounds.left, toggleButtonBounds.left) - hoverMargin
break
case 'bottom':
bounds.top -= height + hoverMargin
bounds.top = Math.min(bounds.top, toggleButtonBounds.top) - hoverMargin
break
case 'left':
bounds.right += width + hoverMargin
bounds.right = Math.max(bounds.right, toggleButtonBounds.right) + hoverMargin
break
}
if (rectContainsPoint(bounds, point)) return true
}
return rectContainsPoint(bounds, point)
return false
}
getInitialSize () {
@@ -577,6 +612,16 @@ module.exports = class Dock {
return this.paneContainer.onDidDestroyPaneItem(callback)
}
// Extended: Invoke the given callback when the hovered state of the dock changes.
//
// * `callback` {Function} to be called when the hovered state changes.
// * `hovered` {Boolean} Is the dock now hovered?
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeHovered (callback) {
return this.emitter.on('did-change-hovered', callback)
}
/*
Section: Pane Items
*/
@@ -726,10 +771,7 @@ class DockToggleButton {
}
getBounds () {
if (this.bounds == null) {
this.bounds = this.element.getBoundingClientRect()
}
return this.bounds
return this.innerElement.getBoundingClientRect()
}
update (newProps) {

View File

@@ -201,7 +201,7 @@ class ApplicationMenu {
if (item.command) {
item.accelerator = this.acceleratorForCommand(item.command, keystrokesByCommand)
item.click = () => global.atomApplication.sendCommand(item.command, item.commandDetail)
if (!/^application:/.test(item.command, item.commandDetail)) {
if (!/^application:/.test(item.command)) {
item.metadata.windowSpecific = true
}
}

View File

@@ -58,7 +58,9 @@ class AtomWindow extends EventEmitter {
Object.defineProperty(this.browserWindow, 'loadSettingsJSON', {
get: () => JSON.stringify(Object.assign({
userSettings: this.atomApplication.configFile.get(),
userSettings: !this.isSpec
? this.atomApplication.configFile.get()
: null,
projectSpecification: this.projectSpecification
}, this.loadSettings))
})

View File

@@ -21,7 +21,7 @@ class Notification {
throw new Error(`Notification must be created with string message: ${this.message}`)
}
if (!_.isObject(this.options) || _.isArray(this.options)) {
if (!_.isObject(this.options) || Array.isArray(this.options)) {
throw new Error(`Notification must be created with an options object: ${this.options}`)
}
}

View File

@@ -155,9 +155,17 @@ class Pane {
getFlexScale () { return this.flexScale }
increaseSize () { this.setFlexScale(this.getFlexScale() * 1.1) }
increaseSize () {
if (this.getContainer().getPanes().length > 1) {
this.setFlexScale(this.getFlexScale() * 1.1)
}
}
decreaseSize () { this.setFlexScale(this.getFlexScale() / 1.1) }
decreaseSize () {
if (this.getContainer().getPanes().length > 1) {
this.setFlexScale(this.getFlexScale() / 1.1)
}
}
/*
Section: Event Subscription

View File

@@ -171,6 +171,10 @@ class NativeWatcher {
class AtomNativeWatcher extends NativeWatcher {
async doStart () {
const getRealPath = givenPath => {
if (!givenPath) {
return Promise.resolve(null)
}
return new Promise(resolve => {
fs.realpath(givenPath, (err, resolvedPath) => {
err ? resolve(null) : resolve(resolvedPath)
@@ -239,7 +243,7 @@ class AtomNativeWatcher extends NativeWatcher {
this.subs.add(treeView.onEntryDeleted(async event => {
const realPath = await getRealPath(event.path)
if (!realPath || isOpenInEditor(realPath)) return
if (!realPath || await isOpenInEditor(realPath)) return
this.onEvents([{action: 'deleted', path: realPath}])
}))
@@ -249,7 +253,7 @@ class AtomNativeWatcher extends NativeWatcher {
getRealPath(event.newPath),
getRealPath(event.initialPath)
])
if (!realNewPath || !realOldPath || isOpenInEditor(realNewPath) || isOpenInEditor(realOldPath)) return
if (!realNewPath || !realOldPath || await isOpenInEditor(realNewPath) || await isOpenInEditor(realOldPath)) return
this.onEvents([{action: 'renamed', path: realNewPath, oldPath: realOldPath}])
}))
@@ -492,7 +496,29 @@ class PathWatcher {
// events may include events for paths above this watcher's root path, so filter them to only include the relevant
// ones, then re-broadcast them to our subscribers.
onNativeEvents (events, callback) {
const filtered = events.filter(event => event.path.startsWith(this.normalizedPath))
const isWatchedPath = eventPath => eventPath.startsWith(this.normalizedPath)
const filtered = []
for (let i = 0; i < events.length; i++) {
const event = events[i]
if (event.action === 'renamed') {
const srcWatched = isWatchedPath(event.oldPath)
const destWatched = isWatchedPath(event.path)
if (srcWatched && destWatched) {
filtered.push(event)
} else if (srcWatched && !destWatched) {
filtered.push({action: 'deleted', kind: event.kind, path: event.oldPath})
} else if (!srcWatched && destWatched) {
filtered.push({action: 'created', kind: event.kind, path: event.path})
}
} else {
if (isWatchedPath(event.path)) {
filtered.push(event)
}
}
}
if (filtered.length > 0) {
callback(filtered)

View File

@@ -216,7 +216,7 @@ class Project extends Model {
// To watch paths outside of open projects, use the `watchPaths` function instead; see {PathWatcher}.
//
// When writing tests against functionality that uses this method, be sure to wait for the
// {Promise} returned by {getWatcherPromise()} before manipulating the filesystem to ensure that
// {Promise} returned by {::getWatcherPromise} before manipulating the filesystem to ensure that
// the watcher is receiving events.
//
// * `callback` {Function} to be called with batches of filesystem events reported by

View File

@@ -2811,7 +2811,7 @@ class TextEditorComponent {
setScrollTop (scrollTop) {
if (Number.isNaN(scrollTop) || scrollTop == null) return false
scrollTop = Math.round(Math.max(0, Math.min(this.getMaxScrollTop(), scrollTop)))
scrollTop = roundToPhysicalPixelBoundary(Math.max(0, Math.min(this.getMaxScrollTop(), scrollTop)))
if (scrollTop !== this.scrollTop) {
this.derivedDimensionsCache = {}
this.scrollTopPending = true
@@ -2842,7 +2842,7 @@ class TextEditorComponent {
setScrollLeft (scrollLeft) {
if (Number.isNaN(scrollLeft) || scrollLeft == null) return false
scrollLeft = Math.round(Math.max(0, Math.min(this.getMaxScrollLeft(), scrollLeft)))
scrollLeft = roundToPhysicalPixelBoundary(Math.max(0, Math.min(this.getMaxScrollLeft(), scrollLeft)))
if (scrollLeft !== this.scrollLeft) {
this.scrollLeftPending = true
this.scrollLeft = scrollLeft

View File

@@ -103,7 +103,7 @@ class ThemeManager {
warnForNonExistentThemes () {
let themeNames = this.config.get('core.themes') || []
if (!_.isArray(themeNames)) { themeNames = [themeNames] }
if (!Array.isArray(themeNames)) { themeNames = [themeNames] }
for (let themeName of themeNames) {
if (!themeName || (typeof themeName !== 'string') || !this.packageManager.resolvePackagePath(themeName)) {
console.warn(`Enabled theme '${themeName}' is not installed.`)
@@ -116,7 +116,7 @@ class ThemeManager {
// Returns an array of theme names in the order that they should be activated.
getEnabledThemeNames () {
let themeNames = this.config.get('core.themes') || []
if (!_.isArray(themeNames)) { themeNames = [themeNames] }
if (!Array.isArray(themeNames)) { themeNames = [themeNames] }
themeNames = themeNames.filter((themeName) =>
(typeof themeName === 'string') && this.packageManager.resolvePackagePath(themeName)
)
@@ -138,7 +138,7 @@ class ThemeManager {
if (themeNames.length === 0) {
themeNames = ['one-dark-syntax', 'one-dark-ui']
} else if (themeNames.length === 1) {
if (_.endsWith(themeNames[0], '-ui')) {
if (themeNames[0].endsWith('-ui')) {
themeNames.unshift('one-dark-syntax')
} else {
themeNames.push('one-dark-ui')

View File

@@ -92,7 +92,13 @@ class WorkspaceElement extends HTMLElement {
window.removeEventListener('dragstart', this.handleDragStart)
window.removeEventListener('dragend', this.handleDragEnd, true)
window.removeEventListener('drop', this.handleDrop, true)
})
}),
...[this.model.getLeftDock(), this.model.getRightDock(), this.model.getBottomDock()]
.map(dock => dock.onDidChangeHovered(hovered => {
if (hovered) this.hoveredDock = dock
else if (dock === this.hoveredDock) this.hoveredDock = null
this.checkCleanupDockHoverEvents()
}))
)
this.initializeContent()
this.observeScrollbarStyle()
@@ -186,19 +192,13 @@ class WorkspaceElement extends HTMLElement {
}
updateHoveredDock (mousePosition) {
this.hoveredDock = null
for (let location in this.model.paneContainers) {
if (location !== 'center') {
const dock = this.model.paneContainers[location]
if (!this.hoveredDock && dock.pointWithinHoverArea(mousePosition)) {
this.hoveredDock = dock
dock.setHovered(true)
} else {
dock.setHovered(false)
}
}
}
this.checkCleanupDockHoverEvents()
// If we haven't left the currently hovered dock, don't change anything.
if (this.hoveredDock && this.hoveredDock.pointWithinHoverArea(mousePosition, true)) return
const docks = [this.model.getLeftDock(), this.model.getRightDock(), this.model.getBottomDock()]
const nextHoveredDock =
docks.find(dock => dock !== this.hoveredDock && dock.pointWithinHoverArea(mousePosition))
docks.forEach(dock => { dock.setHovered(dock === nextHoveredDock) })
}
checkCleanupDockHoverEvents () {