mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Merge branch 'master' into fb-pw-simple-project-config
This commit is contained in:
@@ -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) {}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
80
src/dock.js
80
src/dock.js
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
}
|
||||
|
||||
12
src/pane.js
12
src/pane.js
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
Reference in New Issue
Block a user