Merge branch 'master' into mb-use-language-mode-api

This commit is contained in:
Max Brunsfeld
2017-11-06 11:32:52 -08:00
23 changed files with 1244 additions and 989 deletions

View File

@@ -32,6 +32,7 @@ ThemeManager = require './theme-manager'
MenuManager = require './menu-manager'
ContextMenuManager = require './context-menu-manager'
CommandInstaller = require './command-installer'
CoreURIHandlers = require './core-uri-handlers'
ProtocolHandlerInstaller = require './protocol-handler-installer'
Project = require './project'
TitleBar = require './title-bar'
@@ -240,6 +241,7 @@ class AtomEnvironment extends Model
@commandInstaller.initialize(@getVersion())
@protocolHandlerInstaller.initialize(@config, @notifications)
@uriHandlerRegistry.registerHostHandler('core', CoreURIHandlers.create(this))
@autoUpdater.initialize()
@config.load()

View File

@@ -107,6 +107,13 @@ module.exports = class CommandRegistry {
// otherwise be generated from the event name.
// * `description`: Used by consumers to display detailed information about
// the command.
// * `hiddenInCommandPalette`: If `true`, this command will not appear in
// the bundled command palette by default, but can still be shown with.
// the `Command Palette: Show Hidden Commands` command. This is a good
// option when you need to register large numbers of commands that don't
// make sense to be executed from the command palette. Please use this
// option conservatively, as it could reduce the discoverability of your
// package's commands.
//
// ## Arguments: Registering Multiple Commands
//

38
src/core-uri-handlers.js Normal file
View File

@@ -0,0 +1,38 @@
function openFile (atom, {query}) {
const {filename, line, column} = query
atom.workspace.open(filename, {
initialLine: parseInt(line || 0, 10),
initialColumn: parseInt(column || 0, 10),
searchAllPanes: true
})
}
function windowShouldOpenFile ({query}) {
const {filename} = query
return (win) => win.containsPath(filename)
}
const ROUTER = {
'/open/file': { handler: openFile, getWindowPredicate: windowShouldOpenFile }
}
module.exports = {
create (atomEnv) {
return function coreURIHandler (parsed) {
const config = ROUTER[parsed.pathname]
if (config) {
config.handler(atomEnv, parsed)
}
}
},
windowPredicate (parsed) {
const config = ROUTER[parsed.pathname]
if (config && config.getWindowPredicate) {
return config.getWindowPredicate(parsed)
} else {
return (win) => true
}
}
}

View File

@@ -594,7 +594,7 @@ class Cursor extends Model {
getCurrentWordBufferRange (options = {}) {
const position = this.getBufferPosition()
const ranges = this.editor.buffer.findAllInRangeSync(
options.wordRegex || this.wordRegExp(),
options.wordRegex || this.wordRegExp(options),
new Range(new Point(position.row, 0), new Point(position.row, Infinity))
)
const range = ranges.find(range =>

View File

@@ -1,15 +1,7 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS104: Avoid inline assignments
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const {join} = require('path')
const path = require('path')
const fs = require('fs-plus')
const _ = require('underscore-plus')
const {Emitter, Disposable, CompositeDisposable} = require('event-kit')
const fs = require('fs-plus')
const path = require('path')
const GitUtils = require('git-utils')
let nextId = 0
@@ -241,15 +233,15 @@ class GitRepository {
// * `path` The {String} path to check.
//
// Returns a {Boolean}.
isSubmodule (path) {
if (!path) return false
isSubmodule (filePath) {
if (!filePath) return false
const repo = this.getRepo(path)
if (repo.isSubmodule(repo.relativize(path))) {
const repo = this.getRepo(filePath)
if (repo.isSubmodule(repo.relativize(filePath))) {
return true
} else {
// Check if the path is a working directory in a repo that isn't the root.
return repo !== this.getRepo() && repo.relativize(join(path, 'dir')) === 'dir'
// Check if the filePath is a working directory in a repo that isn't the root.
return repo !== this.getRepo() && repo.relativize(path.join(filePath, 'dir')) === 'dir'
}
}

View File

@@ -128,7 +128,7 @@ class ApplicationMenu
]
focusedWindow: ->
_.find global.atomApplication.windows, (atomWindow) -> atomWindow.isFocused()
_.find global.atomApplication.getAllWindows(), (atomWindow) -> atomWindow.isFocused()
# Combines a menu template with the appropriate keystroke.
#

View File

@@ -67,7 +67,7 @@ class AtomApplication
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, @logFile, @userDataDir} = options
@socketPath = null if options.test or options.benchmark or options.benchmarkTest
@pidsToOpenWindows = {}
@windows = []
@windowStack = new WindowStack()
@config = new Config({enablePersistence: true})
@config.setSchema null, {type: 'object', properties: _.clone(ConfigSchema)}
@@ -114,7 +114,7 @@ class AtomApplication
@launch(options)
destroy: ->
windowsClosePromises = @windows.map (window) ->
windowsClosePromises = @getAllWindows().map (window) ->
window.close()
window.closedPromise
Promise.all(windowsClosePromises).then(=> @disposable.dispose())
@@ -162,8 +162,8 @@ class AtomApplication
# Public: Removes the {AtomWindow} from the global window list.
removeWindow: (window) ->
@windows.splice(@windows.indexOf(window), 1)
if @windows.length is 0
@windowStack.removeWindow(window)
if @getAllWindows().length is 0
@applicationMenu?.enableWindowSpecificItems(false)
if process.platform in ['win32', 'linux']
app.quit()
@@ -172,22 +172,28 @@ class AtomApplication
# Public: Adds the {AtomWindow} to the global window list.
addWindow: (window) ->
@windows.push window
@windowStack.addWindow(window)
@applicationMenu?.addWindow(window.browserWindow)
window.once 'window:loaded', =>
@autoUpdateManager?.emitUpdateAvailableEvent(window)
unless window.isSpec
focusHandler = => @lastFocusedWindow = window
focusHandler = => @windowStack.touch(window)
blurHandler = => @saveState(false)
window.browserWindow.on 'focus', focusHandler
window.browserWindow.on 'blur', blurHandler
window.browserWindow.once 'closed', =>
@lastFocusedWindow = null if window is @lastFocusedWindow
@windowStack.removeWindow(window)
window.browserWindow.removeListener 'focus', focusHandler
window.browserWindow.removeListener 'blur', blurHandler
window.browserWindow.webContents.once 'did-finish-load', => @saveState(false)
getAllWindows: =>
@windowStack.all().slice()
getLastFocusedWindow: (predicate) =>
@windowStack.getLastFocusedWindow(predicate)
# Creates server to listen for additional atom application launches.
#
# You can run the atom command multiple times, but after the first launch
@@ -276,7 +282,7 @@ class AtomApplication
else
event.preventDefault()
@quitting = true
windowUnloadPromises = @windows.map((window) -> window.prepareToUnload())
windowUnloadPromises = @getAllWindows().map((window) -> window.prepareToUnload())
Promise.all(windowUnloadPromises).then((windowUnloadedResults) ->
didUnloadAllWindows = windowUnloadedResults.every((didUnloadWindow) -> didUnloadWindow)
app.quit() if didUnloadAllWindows
@@ -309,7 +315,7 @@ class AtomApplication
event.sender.send('did-resolve-proxy', requestId, proxy)
@disposable.add ipcHelpers.on ipcMain, 'did-change-history-manager', (event) =>
for atomWindow in @windows
for atomWindow in @getAllWindows()
webContents = atomWindow.browserWindow.webContents
if webContents isnt event.sender
webContents.send('did-change-history-manager')
@@ -483,7 +489,7 @@ class AtomApplication
# Returns the {AtomWindow} for the given paths.
windowForPaths: (pathsToOpen, devMode) ->
_.find @windows, (atomWindow) ->
_.find @getAllWindows(), (atomWindow) ->
atomWindow.devMode is devMode and atomWindow.containsPaths(pathsToOpen)
# Returns the {AtomWindow} for the given ipcMain event.
@@ -491,11 +497,11 @@ class AtomApplication
@atomWindowForBrowserWindow(BrowserWindow.fromWebContents(sender))
atomWindowForBrowserWindow: (browserWindow) ->
@windows.find((atomWindow) -> atomWindow.browserWindow is browserWindow)
@getAllWindows().find((atomWindow) -> atomWindow.browserWindow is browserWindow)
# Public: Returns the currently focused {AtomWindow} or undefined if none.
focusedWindow: ->
_.find @windows, (atomWindow) -> atomWindow.isFocused()
_.find @getAllWindows(), (atomWindow) -> atomWindow.isFocused()
# Get the platform-specific window offset for new windows.
getWindowOffsetForCurrentPlatform: ->
@@ -507,8 +513,8 @@ class AtomApplication
# Get the dimensions for opening a new window by cascading as appropriate to
# the platform.
getDimensionsForNewWindow: ->
return if (@focusedWindow() ? @lastFocusedWindow)?.isMaximized()
dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions()
return if (@focusedWindow() ? @getLastFocusedWindow())?.isMaximized()
dimensions = (@focusedWindow() ? @getLastFocusedWindow())?.getDimensions()
offset = @getWindowOffsetForCurrentPlatform()
if dimensions? and offset?
dimensions.x += offset
@@ -554,7 +560,7 @@ class AtomApplication
existingWindow = @windowForPaths(pathsToOpen, devMode)
stats = (fs.statSyncNoException(pathToOpen) for pathToOpen in pathsToOpen)
unless existingWindow?
if currentWindow = window ? @lastFocusedWindow
if currentWindow = window ? @getLastFocusedWindow()
existingWindow = currentWindow if (
addToLastWindow or
currentWindow.devMode is devMode and
@@ -583,7 +589,7 @@ class AtomApplication
windowDimensions ?= @getDimensionsForNewWindow()
openedWindow = new AtomWindow(this, @fileRecoveryService, {initialPaths, locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState, env})
openedWindow.focus()
@lastFocusedWindow = openedWindow
@windowStack.addWindow(openedWindow)
if pidToKillWhenClosed?
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
@@ -617,9 +623,10 @@ class AtomApplication
saveState: (allowEmpty=false) ->
return if @quitting
states = []
for window in @windows
for window in @getAllWindows()
unless window.isSpec
states.push({initialPaths: window.representedDirectoryPaths})
states.reverse()
if states.length > 0 or allowEmpty
@storageFolder.storeSync('application.json', states)
@emit('application:did-save-state')
@@ -648,30 +655,39 @@ class AtomApplication
# :devMode - Boolean to control the opened window's dev mode.
# :safeMode - Boolean to control the opened window's safe mode.
openUrl: ({urlToOpen, devMode, safeMode, env}) ->
parsedUrl = url.parse(urlToOpen)
parsedUrl = url.parse(urlToOpen, true)
return unless parsedUrl.protocol is "atom:"
pack = @findPackageWithName(parsedUrl.host, devMode)
if pack?.urlMain
@openPackageUrlMain(parsedUrl.host, pack.urlMain, urlToOpen, devMode, safeMode, env)
else
@openPackageUriHandler(urlToOpen, devMode, safeMode, env)
@openPackageUriHandler(urlToOpen, parsedUrl, devMode, safeMode, env)
openPackageUriHandler: (url, devMode, safeMode, env) ->
resourcePath = @resourcePath
if devMode
try
windowInitializationScript = require.resolve(path.join(@devResourcePath, 'src', 'initialize-application-window'))
resourcePath = @devResourcePath
openPackageUriHandler: (url, parsedUrl, devMode, safeMode, env) ->
bestWindow = null
if parsedUrl.host is 'core'
predicate = require('../core-uri-handlers').windowPredicate(parsedUrl)
bestWindow = @getLastFocusedWindow (win) ->
not win.isSpecWindow() and predicate(win)
windowInitializationScript ?= require.resolve('../initialize-application-window')
if @lastFocusedWindow?
@lastFocusedWindow.sendURIMessage url
bestWindow ?= @getLastFocusedWindow (win) -> not win.isSpecWindow()
if bestWindow?
bestWindow.sendURIMessage url
bestWindow.focus()
else
resourcePath = @resourcePath
if devMode
try
windowInitializationScript = require.resolve(path.join(@devResourcePath, 'src', 'initialize-application-window'))
resourcePath = @devResourcePath
windowInitializationScript ?= require.resolve('../initialize-application-window')
windowDimensions = @getDimensionsForNewWindow()
@lastFocusedWindow = new AtomWindow(this, @fileRecoveryService, {resourcePath, windowInitializationScript, devMode, safeMode, windowDimensions, env})
@lastFocusedWindow.on 'window:loaded', =>
@lastFocusedWindow.sendURIMessage url
win = new AtomWindow(this, @fileRecoveryService, {resourcePath, windowInitializationScript, devMode, safeMode, windowDimensions, env})
@windowStack.addWindow(win)
win.on 'window:loaded', ->
win.sendURIMessage url
findPackageWithName: (packageName, devMode) ->
_.find @getPackageManager(devMode).getAvailablePackageMetadata(), ({name}) -> name is packageName
@@ -867,7 +883,7 @@ class AtomApplication
disableZoomOnDisplayChange: ->
outerCallback = =>
for window in @windows
for window in @getAllWindows()
window.disableZoom()
# Set the limits every time a display is added or removed, otherwise the
@@ -878,3 +894,24 @@ class AtomApplication
new Disposable ->
screen.removeListener('display-added', outerCallback)
screen.removeListener('display-removed', outerCallback)
class WindowStack
constructor: (@windows = []) ->
addWindow: (window) =>
@removeWindow(window)
@windows.unshift(window)
touch: (window) =>
@addWindow(window)
removeWindow: (window) =>
currentIndex = @windows.indexOf(window)
@windows.splice(currentIndex, 1) if currentIndex > -1
getLastFocusedWindow: (predicate) =>
predicate ?= (win) -> true
@windows.find(predicate)
all: =>
@windows

View File

@@ -138,4 +138,4 @@ class AutoUpdateManager
detail: message
getWindows: ->
global.atomApplication.windows
global.atomApplication.getAllWindows()

View File

@@ -341,13 +341,21 @@ class Project extends Model {
}
this.rootDirectories.push(directory)
this.watcherPromisesByPath[directory.getPath()] = watchPath(directory.getPath(), {}, events => {
const didChangeCallback = events => {
// Stop event delivery immediately on removal of a rootDirectory, even if its watcher
// promise has yet to resolve at the time of removal
if (this.rootDirectories.includes(directory)) {
this.emitter.emit('did-change-files', events)
}
})
}
// We'll use the directory's custom onDidChangeFiles callback, if available.
// CustomDirectory::onDidChangeFiles should match the signature of
// Project::onDidChangeFiles below (although it may resolve asynchronously)
this.watcherPromisesByPath[directory.getPath()] =
directory.onDidChangeFiles != null
? Promise.resolve(directory.onDidChangeFiles(didChangeCallback))
: watchPath(directory.getPath(), {}, didChangeCallback)
for (let watchedPath in this.watcherPromisesByPath) {
if (!this.rootDirectories.find(dir => dir.getPath() === watchedPath)) {

View File

@@ -126,7 +126,6 @@ class TextEditorComponent {
this.blockDecorationResizeObserver = new ResizeObserver(this.didResizeBlockDecorations.bind(this))
this.lineComponentsByScreenLineId = new Map()
this.overlayComponents = new Set()
this.overlayDimensionsByElement = new WeakMap()
this.shouldRenderDummyScrollbars = true
this.remeasureScrollbars = false
this.pendingAutoscroll = null
@@ -803,15 +802,9 @@ class TextEditorComponent {
{
key: overlayProps.element,
overlayComponents: this.overlayComponents,
measuredDimensions: this.overlayDimensionsByElement.get(overlayProps.element),
didResize: (overlayComponent) => {
this.updateOverlayToRender(overlayProps)
overlayComponent.update(Object.assign(
{
measuredDimensions: this.overlayDimensionsByElement.get(overlayProps.element)
},
overlayProps
))
overlayComponent.update(overlayProps)
}
},
overlayProps
@@ -1357,7 +1350,6 @@ class TextEditorComponent {
let wrapperTop = contentClientRect.top + this.pixelPositionAfterBlocksForRow(row) + this.getLineHeight()
let wrapperLeft = contentClientRect.left + this.pixelLeftForRowAndColumn(row, column)
const clientRect = element.getBoundingClientRect()
this.overlayDimensionsByElement.set(element, clientRect)
if (avoidOverflow !== false) {
const computedStyle = window.getComputedStyle(element)
@@ -4226,17 +4218,26 @@ class OverlayComponent {
this.element.style.zIndex = 4
this.element.style.top = (this.props.pixelTop || 0) + 'px'
this.element.style.left = (this.props.pixelLeft || 0) + 'px'
this.currentContentRect = null
// Synchronous DOM updates in response to resize events might trigger a
// "loop limit exceeded" error. We disconnect the observer before
// potentially mutating the DOM, and then reconnect it on the next tick.
// Note: ResizeObserver calls its callback when .observe is called
this.resizeObserver = new ResizeObserver((entries) => {
const {contentRect} = entries[0]
if (contentRect.width !== this.props.measuredDimensions.width || contentRect.height !== this.props.measuredDimensions.height) {
if (
this.currentContentRect &&
(this.currentContentRect.width !== contentRect.width ||
this.currentContentRect.height !== contentRect.height)
) {
this.resizeObserver.disconnect()
this.props.didResize(this)
process.nextTick(() => { this.resizeObserver.observe(this.props.element) })
}
this.currentContentRect = contentRect
})
this.didAttach()
this.props.overlayComponents.add(this)

View File

@@ -1,56 +0,0 @@
module.exports =
class TokenIterator
constructor: (@tokenizedBuffer) ->
reset: (@line) ->
@index = null
@startColumn = 0
@endColumn = 0
@scopes = @line.openScopes.map (id) => @tokenizedBuffer.grammar.scopeForId(id)
@scopeStarts = @scopes.slice()
@scopeEnds = []
this
next: ->
{tags} = @line
if @index?
@startColumn = @endColumn
@scopeEnds.length = 0
@scopeStarts.length = 0
@index++
else
@index = 0
while @index < tags.length
tag = tags[@index]
if tag < 0
scope = @tokenizedBuffer.grammar.scopeForId(tag)
if tag % 2 is 0
if @scopeStarts[@scopeStarts.length - 1] is scope
@scopeStarts.pop()
else
@scopeEnds.push(scope)
@scopes.pop()
else
@scopeStarts.push(scope)
@scopes.push(scope)
@index++
else
@endColumn += tag
@text = @line.text.substring(@startColumn, @endColumn)
return true
false
getScopes: -> @scopes
getScopeStarts: -> @scopeStarts
getScopeEnds: -> @scopeEnds
getText: -> @text
getBufferStart: -> @startColumn
getBufferEnd: -> @endColumn

79
src/token-iterator.js Normal file
View File

@@ -0,0 +1,79 @@
module.exports =
class TokenIterator {
constructor (tokenizedBuffer) {
this.tokenizedBuffer = tokenizedBuffer
}
reset (line) {
this.line = line
this.index = null
this.startColumn = 0
this.endColumn = 0
this.scopes = this.line.openScopes.map(id => this.tokenizedBuffer.grammar.scopeForId(id))
this.scopeStarts = this.scopes.slice()
this.scopeEnds = []
return this
}
next () {
const {tags} = this.line
if (this.index != null) {
this.startColumn = this.endColumn
this.scopeEnds.length = 0
this.scopeStarts.length = 0
this.index++
} else {
this.index = 0
}
while (this.index < tags.length) {
const tag = tags[this.index]
if (tag < 0) {
const scope = this.tokenizedBuffer.grammar.scopeForId(tag)
if ((tag % 2) === 0) {
if (this.scopeStarts[this.scopeStarts.length - 1] === scope) {
this.scopeStarts.pop()
} else {
this.scopeEnds.push(scope)
}
this.scopes.pop()
} else {
this.scopeStarts.push(scope)
this.scopes.push(scope)
}
this.index++
} else {
this.endColumn += tag
this.text = this.line.text.substring(this.startColumn, this.endColumn)
return true
}
}
return false
}
getScopes () {
return this.scopes
}
getScopeStarts () {
return this.scopeStarts
}
getScopeEnds () {
return this.scopeEnds
}
getText () {
return this.text
}
getBufferStart () {
return this.startColumn
}
getBufferEnd () {
return this.endColumn
}
}