mirror of
https://github.com/atom/atom.git
synced 2026-02-08 13:45:09 -05:00
Merge branch 'master' into ns-use-display-layers
This commit is contained in:
@@ -885,8 +885,6 @@ class AtomEnvironment extends Model
|
||||
else
|
||||
@project.addPath(pathToOpen)
|
||||
|
||||
@applicationDelegate.addRecentDocument(pathToOpen)
|
||||
|
||||
unless fs.isDirectorySync(pathToOpen)
|
||||
@workspace?.open(pathToOpen, {initialLine, initialColumn})
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ var babelVersionDirectory = null
|
||||
var PREFIXES = [
|
||||
'/** @babel */',
|
||||
'"use babel"',
|
||||
'\'use babel\''
|
||||
'\'use babel\'',
|
||||
'/* @flow */'
|
||||
]
|
||||
|
||||
var PREFIX_LENGTH = Math.max.apply(Math, PREFIXES.map(function (prefix) {
|
||||
|
||||
72
src/block-decorations-component.coffee
Normal file
72
src/block-decorations-component.coffee
Normal file
@@ -0,0 +1,72 @@
|
||||
cloneObject = (object) ->
|
||||
clone = {}
|
||||
clone[key] = value for key, value of object
|
||||
clone
|
||||
|
||||
module.exports =
|
||||
class BlockDecorationsComponent
|
||||
constructor: (@container, @views, @presenter, @domElementPool) ->
|
||||
@newState = null
|
||||
@oldState = null
|
||||
@blockDecorationNodesById = {}
|
||||
@domNode = @domElementPool.buildElement("content")
|
||||
@domNode.setAttribute("select", ".atom--invisible-block-decoration")
|
||||
@domNode.style.visibility = "hidden"
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
|
||||
updateSync: (state) ->
|
||||
@newState = state.content
|
||||
@oldState ?= {blockDecorations: {}, width: 0}
|
||||
|
||||
if @newState.width isnt @oldState.width
|
||||
@domNode.style.width = @newState.width + "px"
|
||||
@oldState.width = @newState.width
|
||||
|
||||
for id, blockDecorationState of @oldState.blockDecorations
|
||||
unless @newState.blockDecorations.hasOwnProperty(id)
|
||||
@blockDecorationNodesById[id].remove()
|
||||
delete @blockDecorationNodesById[id]
|
||||
delete @oldState.blockDecorations[id]
|
||||
|
||||
for id, blockDecorationState of @newState.blockDecorations
|
||||
if @oldState.blockDecorations.hasOwnProperty(id)
|
||||
@updateBlockDecorationNode(id)
|
||||
else
|
||||
@oldState.blockDecorations[id] = {}
|
||||
@createAndAppendBlockDecorationNode(id)
|
||||
|
||||
measureBlockDecorations: ->
|
||||
for decorationId, blockDecorationNode of @blockDecorationNodesById
|
||||
style = getComputedStyle(blockDecorationNode)
|
||||
decoration = @newState.blockDecorations[decorationId].decoration
|
||||
marginBottom = parseInt(style.marginBottom) ? 0
|
||||
marginTop = parseInt(style.marginTop) ? 0
|
||||
@presenter.setBlockDecorationDimensions(
|
||||
decoration,
|
||||
blockDecorationNode.offsetWidth,
|
||||
blockDecorationNode.offsetHeight + marginTop + marginBottom
|
||||
)
|
||||
|
||||
createAndAppendBlockDecorationNode: (id) ->
|
||||
blockDecorationState = @newState.blockDecorations[id]
|
||||
blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item)
|
||||
blockDecorationNode.id = "atom--block-decoration-#{id}"
|
||||
@container.appendChild(blockDecorationNode)
|
||||
@blockDecorationNodesById[id] = blockDecorationNode
|
||||
@updateBlockDecorationNode(id)
|
||||
|
||||
updateBlockDecorationNode: (id) ->
|
||||
newBlockDecorationState = @newState.blockDecorations[id]
|
||||
oldBlockDecorationState = @oldState.blockDecorations[id]
|
||||
blockDecorationNode = @blockDecorationNodesById[id]
|
||||
|
||||
if newBlockDecorationState.isVisible
|
||||
blockDecorationNode.classList.remove("atom--invisible-block-decoration")
|
||||
else
|
||||
blockDecorationNode.classList.add("atom--invisible-block-decoration")
|
||||
|
||||
if oldBlockDecorationState.screenRow isnt newBlockDecorationState.screenRow
|
||||
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
oldBlockDecorationState.screenRow = newBlockDecorationState.screenRow
|
||||
@@ -369,6 +369,7 @@ 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()
|
||||
offset = @getWindowOffsetForCurrentPlatform()
|
||||
if dimensions? and offset?
|
||||
|
||||
@@ -212,6 +212,8 @@ class AtomWindow
|
||||
|
||||
isFocused: -> @browserWindow.isFocused()
|
||||
|
||||
isMaximized: -> @browserWindow.isMaximized()
|
||||
|
||||
isMinimized: -> @browserWindow.isMinimized()
|
||||
|
||||
isWebViewFocused: -> @browserWindow.isWebViewFocused()
|
||||
|
||||
@@ -139,7 +139,7 @@ addCommandsToPath = (callback) ->
|
||||
|
||||
atomShCommandPath = path.join(binFolder, 'atom')
|
||||
relativeAtomShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.sh'))
|
||||
atomShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\""
|
||||
atomShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\"\r\necho"
|
||||
|
||||
apmCommandPath = path.join(binFolder, 'apm.cmd')
|
||||
relativeApmPath = path.relative(binFolder, path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd'))
|
||||
|
||||
@@ -203,22 +203,6 @@ ScopeDescriptor = require './scope-descriptor'
|
||||
# maximum: 11.5
|
||||
# ```
|
||||
#
|
||||
# #### object
|
||||
#
|
||||
# Value must be an object. This allows you to nest config options. Sub options
|
||||
# must be under a `properties key`
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
# someSetting:
|
||||
# type: 'object'
|
||||
# properties:
|
||||
# myChildIntOption:
|
||||
# type: 'integer'
|
||||
# minimum: 1.5
|
||||
# maximum: 11.5
|
||||
# ```
|
||||
#
|
||||
# #### color
|
||||
#
|
||||
# Values will be coerced into a {Color} with `red`, `green`, `blue`, and `alpha`
|
||||
@@ -234,13 +218,34 @@ ScopeDescriptor = require './scope-descriptor'
|
||||
# default: 'white'
|
||||
# ```
|
||||
#
|
||||
# #### object / Grouping other types
|
||||
#
|
||||
# A config setting with the type `object` allows grouping a set of config
|
||||
# settings. The group will be visualy separated and has its own group headline.
|
||||
# The sub options must be listed under a `properties` key.
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
# someSetting:
|
||||
# type: 'object'
|
||||
# properties:
|
||||
# myChildIntOption:
|
||||
# type: 'integer'
|
||||
# minimum: 1.5
|
||||
# maximum: 11.5
|
||||
# ```
|
||||
#
|
||||
# ### Other Supported Keys
|
||||
#
|
||||
# #### enum
|
||||
#
|
||||
# All types support an `enum` key. The enum key lets you specify all values
|
||||
# that the config setting can possibly be. `enum` _must_ be an array of values
|
||||
# of your specified type. Schema:
|
||||
# All types support an `enum` key, which lets you specify all the values the
|
||||
# setting can take. `enum` may be an array of allowed values (of the specified
|
||||
# type), or an array of objects with `value` and `description` properties, where
|
||||
# the `value` is an allowed value, and the `description` is a descriptive string
|
||||
# used in the settings view.
|
||||
#
|
||||
# In this example, the setting must be one of the 4 integers:
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
@@ -250,6 +255,20 @@ ScopeDescriptor = require './scope-descriptor'
|
||||
# enum: [2, 4, 6, 8]
|
||||
# ```
|
||||
#
|
||||
# In this example, the setting must be either 'foo' or 'bar', which are
|
||||
# presented using the provided descriptions in the settings pane:
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
# someSetting:
|
||||
# type: 'string'
|
||||
# default: 'foo'
|
||||
# enum: [
|
||||
# {value: 'foo', description: 'Foo mode. You want this.'}
|
||||
# {value: 'bar', description: 'Bar mode. Nobody wants that!'}
|
||||
# ]
|
||||
# ```
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# ```coffee
|
||||
@@ -274,6 +293,9 @@ ScopeDescriptor = require './scope-descriptor'
|
||||
#
|
||||
# Descriptions will be displayed below the title in the settings view.
|
||||
#
|
||||
# For a group of config settings the humanized key or the title and the
|
||||
# description are used for the group headline.
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
# someSetting:
|
||||
@@ -1185,6 +1207,11 @@ Config.addSchemaEnforcers
|
||||
|
||||
validateEnum: (keyPath, value, schema) ->
|
||||
possibleValues = schema.enum
|
||||
|
||||
if Array.isArray(possibleValues)
|
||||
possibleValues = possibleValues.map (value) ->
|
||||
if value.hasOwnProperty('value') then value.value else value
|
||||
|
||||
return value unless possibleValues? and Array.isArray(possibleValues) and possibleValues.length
|
||||
|
||||
for possibleValue in possibleValues
|
||||
|
||||
@@ -136,12 +136,9 @@ class ContextMenuManager
|
||||
|
||||
for itemSet in matchingItemSets
|
||||
for item in itemSet.items
|
||||
continue if item.devMode and not @devMode
|
||||
item = Object.create(item)
|
||||
if typeof item.shouldDisplay is 'function'
|
||||
continue unless item.shouldDisplay(event)
|
||||
item.created?(event)
|
||||
MenuHelpers.merge(currentTargetItems, item, itemSet.specificity)
|
||||
itemForEvent = @cloneItemForEvent(item, event)
|
||||
if itemForEvent
|
||||
MenuHelpers.merge(currentTargetItems, itemForEvent, itemSet.specificity)
|
||||
|
||||
for item in currentTargetItems
|
||||
MenuHelpers.merge(template, item, false)
|
||||
@@ -150,6 +147,19 @@ class ContextMenuManager
|
||||
|
||||
template
|
||||
|
||||
# Returns an object compatible with `::add()` or `null`.
|
||||
cloneItemForEvent: (item, event) ->
|
||||
return null if item.devMode and not @devMode
|
||||
item = Object.create(item)
|
||||
if typeof item.shouldDisplay is 'function'
|
||||
return null unless item.shouldDisplay(event)
|
||||
item.created?(event)
|
||||
if Array.isArray(item.submenu)
|
||||
item.submenu = item.submenu
|
||||
.map((submenuItem) => @cloneItemForEvent(submenuItem, event))
|
||||
.filter((submenuItem) -> submenuItem isnt null)
|
||||
return item
|
||||
|
||||
convertLegacyItemsBySelector: (legacyItemsBySelector, devMode) ->
|
||||
itemsBySelector = {}
|
||||
|
||||
|
||||
@@ -551,7 +551,7 @@ class Cursor extends Model
|
||||
|
||||
# Public: Retrieves the range for the current paragraph.
|
||||
#
|
||||
# A paragraph is defined as a block of text surrounded by empty lines.
|
||||
# A paragraph is defined as a block of text surrounded by empty lines or comments.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getCurrentParagraphBufferRange: ->
|
||||
|
||||
@@ -35,7 +35,6 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
# the marker.
|
||||
module.exports =
|
||||
class Decoration
|
||||
|
||||
# Private: Check if the `decorationProperties.type` matches `type`
|
||||
#
|
||||
# * `decorationProperties` {Object} eg. `{type: 'line-number', class: 'my-new-class'}`
|
||||
@@ -154,6 +153,13 @@ class Decoration
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
|
||||
|
||||
###
|
||||
Section: Utility
|
||||
###
|
||||
|
||||
inspect: ->
|
||||
"<Decoration #{@id}>"
|
||||
|
||||
###
|
||||
Section: Private methods
|
||||
###
|
||||
|
||||
@@ -37,7 +37,7 @@ class DefaultDirectoryProvider
|
||||
# * `uri` {String} The path to the directory to add. This is guaranteed not to
|
||||
# be contained by a {Directory} in `atom.project`.
|
||||
#
|
||||
# Returns a Promise that resolves to:
|
||||
# Returns a {Promise} that resolves to:
|
||||
# * {Directory} if the given URI is compatible with this provider.
|
||||
# * `null` if the given URI is not compatibile with this provider.
|
||||
directoryForURI: (uri) ->
|
||||
|
||||
@@ -12,12 +12,16 @@ class FileSystemBlobStore {
|
||||
}
|
||||
|
||||
constructor (directory) {
|
||||
this.inMemoryBlobs = new Map()
|
||||
this.invalidationKeys = {}
|
||||
this.blobFilename = path.join(directory, 'BLOB')
|
||||
this.blobMapFilename = path.join(directory, 'MAP')
|
||||
this.invalidationKeysFilename = path.join(directory, 'INVKEYS')
|
||||
this.lockFilename = path.join(directory, 'LOCK')
|
||||
this.reset()
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.inMemoryBlobs = new Map()
|
||||
this.invalidationKeys = {}
|
||||
this.storedBlob = new Buffer(0)
|
||||
this.storedBlobMap = {}
|
||||
}
|
||||
@@ -32,9 +36,14 @@ class FileSystemBlobStore {
|
||||
if (!fs.existsSync(this.invalidationKeysFilename)) {
|
||||
return
|
||||
}
|
||||
this.storedBlob = fs.readFileSync(this.blobFilename)
|
||||
this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename))
|
||||
this.invalidationKeys = JSON.parse(fs.readFileSync(this.invalidationKeysFilename))
|
||||
|
||||
try {
|
||||
this.storedBlob = fs.readFileSync(this.blobFilename)
|
||||
this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename))
|
||||
this.invalidationKeys = JSON.parse(fs.readFileSync(this.invalidationKeysFilename))
|
||||
} catch (e) {
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
|
||||
save () {
|
||||
|
||||
1045
src/git-repository-async.js
Normal file
1045
src/git-repository-async.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -77,7 +77,7 @@ class GitRepositoryProvider
|
||||
unless repo
|
||||
repo = GitRepository.open(gitDirPath, {@project, @config})
|
||||
return null unless repo
|
||||
repo.onDidDestroy(=> delete @pathToRepository[gitDirPath])
|
||||
repo.async.onDidDestroy(=> delete @pathToRepository[gitDirPath])
|
||||
@pathToRepository[gitDirPath] = repo
|
||||
repo.refreshIndex()
|
||||
repo.refreshStatus()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
_ = 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'
|
||||
@@ -75,6 +76,13 @@ 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
|
||||
@@ -117,6 +125,10 @@ class GitRepository
|
||||
@subscriptions.dispose()
|
||||
@subscriptions = null
|
||||
|
||||
if @async?
|
||||
@async.destroy()
|
||||
@async = null
|
||||
|
||||
# Public: Invoke the given callback when this GitRepository's destroy() method
|
||||
# is invoked.
|
||||
#
|
||||
@@ -316,6 +328,9 @@ class GitRepository
|
||||
# Returns a {Number} representing the status. This value can be passed to
|
||||
# {::isStatusModified} or {::isStatusNew} to get more information.
|
||||
getPathStatus: (path) ->
|
||||
# Trigger events emitted on the async repo as well
|
||||
@async.refreshStatusForPath(path)
|
||||
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
currentPathStatus = @statuses[relativePath] ? 0
|
||||
@@ -460,27 +475,35 @@ 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: ->
|
||||
@handlerPath ?= require.resolve('./repository-status-handler')
|
||||
asyncRefresh = @async.refreshStatus()
|
||||
syncRefresh = new Promise (resolve, reject) =>
|
||||
@handlerPath ?= require.resolve('./repository-status-handler')
|
||||
|
||||
relativeProjectPaths = @project?.getPaths()
|
||||
.map (path) => @relativize(path)
|
||||
.filter (path) -> path.length > 0
|
||||
relativeProjectPaths = @project?.getPaths()
|
||||
.map (path) => @relativize(path)
|
||||
.filter (path) -> path.length > 0
|
||||
|
||||
@statusTask?.terminate()
|
||||
@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)
|
||||
@statusTask?.terminate()
|
||||
@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)
|
||||
|
||||
@statuses = statuses
|
||||
@upstream = upstream
|
||||
@branch = branch
|
||||
@submodules = submodules
|
||||
@statuses = statuses
|
||||
@upstream = upstream
|
||||
@branch = branch
|
||||
@submodules = submodules
|
||||
|
||||
for submodulePath, submoduleRepo of @getRepo().submodules
|
||||
submoduleRepo.upstream = submodules[submodulePath]?.upstream ? {ahead: 0, behind: 0}
|
||||
for submodulePath, submoduleRepo of @getRepo().submodules
|
||||
submoduleRepo.upstream = submodules[submodulePath]?.upstream ? {ahead: 0, behind: 0}
|
||||
|
||||
unless statusesUnchanged
|
||||
@emitter.emit 'did-change-statuses'
|
||||
resolve()
|
||||
|
||||
unless statusesUnchanged
|
||||
@emitter.emit 'did-change-statuses'
|
||||
|
||||
return Promise.all([asyncRefresh, syncRefresh])
|
||||
|
||||
@@ -96,12 +96,13 @@ class LineNumbersTileComponent
|
||||
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
|
||||
|
||||
buildLineNumberNode: (lineNumberState) ->
|
||||
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex} = lineNumberState
|
||||
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex, blockDecorationsHeight} = lineNumberState
|
||||
|
||||
className = @buildLineNumberClassName(lineNumberState)
|
||||
lineNumberNode = @domElementPool.buildElement("div", className)
|
||||
lineNumberNode.dataset.screenRow = screenRow
|
||||
lineNumberNode.dataset.bufferRow = bufferRow
|
||||
lineNumberNode.style.marginTop = blockDecorationsHeight + "px"
|
||||
|
||||
@setLineNumberInnerNodes(bufferRow, softWrapped, lineNumberNode)
|
||||
lineNumberNode
|
||||
@@ -139,6 +140,10 @@ class LineNumbersTileComponent
|
||||
oldLineNumberState.screenRow = newLineNumberState.screenRow
|
||||
oldLineNumberState.bufferRow = newLineNumberState.bufferRow
|
||||
|
||||
unless oldLineNumberState.blockDecorationsHeight is newLineNumberState.blockDecorationsHeight
|
||||
node.style.marginTop = newLineNumberState.blockDecorationsHeight + "px"
|
||||
oldLineNumberState.blockDecorationsHeight = newLineNumberState.blockDecorationsHeight
|
||||
|
||||
buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) ->
|
||||
className = "line-number"
|
||||
className += " " + decorationClasses.join(' ') if decorationClasses?
|
||||
|
||||
@@ -20,6 +20,8 @@ class LinesTileComponent
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@textNodesByLineId = {}
|
||||
@insertionPointsBeforeLineById = {}
|
||||
@insertionPointsAfterLineById = {}
|
||||
@domNode = @domElementPool.buildElement("div")
|
||||
@domNode.style.position = "absolute"
|
||||
@domNode.style.display = "block"
|
||||
@@ -80,6 +82,9 @@ class LinesTileComponent
|
||||
|
||||
removeLineNode: (id) ->
|
||||
@domElementPool.freeElementAndDescendants(@lineNodesByLineId[id])
|
||||
@removeBlockDecorationInsertionPointBeforeLine(id)
|
||||
@removeBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
delete @lineNodesByLineId[id]
|
||||
delete @textNodesByLineId[id]
|
||||
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
|
||||
@@ -116,6 +121,71 @@ class LinesTileComponent
|
||||
else
|
||||
@domNode.appendChild(lineNode)
|
||||
|
||||
@insertBlockDecorationInsertionPointBeforeLine(id)
|
||||
@insertBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
removeBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
if insertionPoint = @insertionPointsBeforeLineById[id]
|
||||
@domElementPool.freeElementAndDescendants(insertionPoint)
|
||||
delete @insertionPointsBeforeLineById[id]
|
||||
|
||||
insertBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
{hasPrecedingBlockDecorations, screenRow} = @newTileState.lines[id]
|
||||
|
||||
if hasPrecedingBlockDecorations
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
insertionPoint = @domElementPool.buildElement("content")
|
||||
@domNode.insertBefore(insertionPoint, lineNode)
|
||||
@insertionPointsBeforeLineById[id] = insertionPoint
|
||||
insertionPoint.dataset.screenRow = screenRow
|
||||
@updateBlockDecorationInsertionPointBeforeLine(id)
|
||||
|
||||
updateBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
oldLineState = @oldTileState.lines[id]
|
||||
newLineState = @newTileState.lines[id]
|
||||
insertionPoint = @insertionPointsBeforeLineById[id]
|
||||
return unless insertionPoint?
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
insertionPoint.dataset.screenRow = newLineState.screenRow
|
||||
|
||||
precedingBlockDecorationsSelector = newLineState.precedingBlockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',')
|
||||
|
||||
if precedingBlockDecorationsSelector isnt oldLineState.precedingBlockDecorationsSelector
|
||||
insertionPoint.setAttribute("select", precedingBlockDecorationsSelector)
|
||||
oldLineState.precedingBlockDecorationsSelector = precedingBlockDecorationsSelector
|
||||
|
||||
removeBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
if insertionPoint = @insertionPointsAfterLineById[id]
|
||||
@domElementPool.freeElementAndDescendants(insertionPoint)
|
||||
delete @insertionPointsAfterLineById[id]
|
||||
|
||||
insertBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
{hasFollowingBlockDecorations, screenRow} = @newTileState.lines[id]
|
||||
|
||||
if hasFollowingBlockDecorations
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
insertionPoint = @domElementPool.buildElement("content")
|
||||
@domNode.insertBefore(insertionPoint, lineNode.nextSibling)
|
||||
@insertionPointsAfterLineById[id] = insertionPoint
|
||||
insertionPoint.dataset.screenRow = screenRow
|
||||
@updateBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
updateBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
oldLineState = @oldTileState.lines[id]
|
||||
newLineState = @newTileState.lines[id]
|
||||
insertionPoint = @insertionPointsAfterLineById[id]
|
||||
return unless insertionPoint?
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
insertionPoint.dataset.screenRow = newLineState.screenRow
|
||||
|
||||
followingBlockDecorationsSelector = newLineState.followingBlockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',')
|
||||
|
||||
if followingBlockDecorationsSelector isnt oldLineState.followingBlockDecorationsSelector
|
||||
insertionPoint.setAttribute("select", followingBlockDecorationsSelector)
|
||||
oldLineState.followingBlockDecorationsSelector = followingBlockDecorationsSelector
|
||||
|
||||
findNodeNextTo: (node) ->
|
||||
for nextNode, index in @domNode.children
|
||||
continue if index is 0 # skips highlights node
|
||||
@@ -359,12 +429,28 @@ class LinesTileComponent
|
||||
|
||||
oldLineState.decorationClasses = newLineState.decorationClasses
|
||||
|
||||
if not oldLineState.hasPrecedingBlockDecorations and newLineState.hasPrecedingBlockDecorations
|
||||
@insertBlockDecorationInsertionPointBeforeLine(id)
|
||||
else if oldLineState.hasPrecedingBlockDecorations and not newLineState.hasPrecedingBlockDecorations
|
||||
@removeBlockDecorationInsertionPointBeforeLine(id)
|
||||
|
||||
if not oldLineState.hasFollowingBlockDecorations and newLineState.hasFollowingBlockDecorations
|
||||
@insertBlockDecorationInsertionPointAfterLine(id)
|
||||
else if oldLineState.hasFollowingBlockDecorations and not newLineState.hasFollowingBlockDecorations
|
||||
@removeBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
lineNode.dataset.screenRow = newLineState.screenRow
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
@lineIdsByScreenRow[newLineState.screenRow] = id
|
||||
@screenRowsByLineId[id] = newLineState.screenRow
|
||||
|
||||
@updateBlockDecorationInsertionPointBeforeLine(id)
|
||||
@updateBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations
|
||||
oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ TokenIterator = require './token-iterator'
|
||||
|
||||
module.exports =
|
||||
class LinesYardstick
|
||||
constructor: (@model, @lineNodesProvider, grammarRegistry) ->
|
||||
constructor: (@model, @lineNodesProvider, @lineTopIndex, grammarRegistry) ->
|
||||
@tokenIterator = new TokenIterator({grammarRegistry})
|
||||
@rangeForMeasurement = document.createRange()
|
||||
@invalidateCache()
|
||||
@@ -20,8 +20,9 @@ class LinesYardstick
|
||||
screenPositionForPixelPosition: (pixelPosition) ->
|
||||
targetTop = pixelPosition.top
|
||||
targetLeft = pixelPosition.left
|
||||
row = Math.floor(targetTop / @model.getLineHeightInPixels())
|
||||
targetLeft = 0 if row < 0
|
||||
defaultCharWidth = @model.getDefaultCharWidth()
|
||||
row = @lineTopIndex.rowForPixelPosition(targetTop)
|
||||
targetLeft = 0 if targetTop < 0
|
||||
targetLeft = Infinity if row > @model.getLastScreenRow()
|
||||
row = Math.min(row, @model.getLastScreenRow())
|
||||
row = Math.max(0, row)
|
||||
@@ -64,7 +65,7 @@ class LinesYardstick
|
||||
targetRow = screenPosition.row
|
||||
targetColumn = screenPosition.column
|
||||
|
||||
top = targetRow * @model.getLineHeightInPixels()
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow)
|
||||
left = @leftPixelPositionForScreenPosition(targetRow, targetColumn)
|
||||
|
||||
{top, left}
|
||||
|
||||
@@ -158,7 +158,9 @@ class Package
|
||||
false
|
||||
|
||||
# TODO: Remove. Settings view calls this method currently.
|
||||
activateConfig: -> @registerConfigSchemaFromMainModule()
|
||||
activateConfig: ->
|
||||
@requireMainModule()
|
||||
@registerConfigSchemaFromMainModule()
|
||||
|
||||
activateStylesheets: ->
|
||||
return if @stylesheetsActivated
|
||||
|
||||
@@ -36,6 +36,27 @@ class PaneContainerElement extends HTMLElement
|
||||
focusPaneViewOnRight: ->
|
||||
@nearestPaneInDirection('right')?.focus()
|
||||
|
||||
moveActiveItemToPaneAbove: (params) ->
|
||||
@moveActiveItemToNearestPaneInDirection('above', params)
|
||||
|
||||
moveActiveItemToPaneBelow: (params) ->
|
||||
@moveActiveItemToNearestPaneInDirection('below', params)
|
||||
|
||||
moveActiveItemToPaneOnLeft: (params) ->
|
||||
@moveActiveItemToNearestPaneInDirection('left', params)
|
||||
|
||||
moveActiveItemToPaneOnRight: (params) ->
|
||||
@moveActiveItemToNearestPaneInDirection('right', params)
|
||||
|
||||
moveActiveItemToNearestPaneInDirection: (direction, params) ->
|
||||
destPane = @nearestPaneInDirection(direction)?.getModel()
|
||||
return unless destPane?
|
||||
if params?.keepOriginal
|
||||
@model.copyActiveItemToPane(destPane)
|
||||
else
|
||||
@model.moveActiveItemToPane(destPane)
|
||||
destPane.focus()
|
||||
|
||||
nearestPaneInDirection: (direction) ->
|
||||
distance = (pointA, pointB) ->
|
||||
x = pointB.x - pointA.x
|
||||
|
||||
@@ -164,6 +164,15 @@ class PaneContainer extends Model
|
||||
else
|
||||
false
|
||||
|
||||
moveActiveItemToPane: (destPane) ->
|
||||
item = @activePane.getActiveItem()
|
||||
@activePane.moveItemToPane(item, destPane)
|
||||
destPane.setActiveItem(item)
|
||||
|
||||
copyActiveItemToPane: (destPane) ->
|
||||
item = @activePane.copyActiveItem()
|
||||
destPane.activateItem(item)
|
||||
|
||||
destroyEmptyPanes: ->
|
||||
pane.destroy() for pane in @getPanes() when pane.items.length is 0
|
||||
return
|
||||
|
||||
@@ -346,7 +346,6 @@ class Pane extends Model
|
||||
if item?
|
||||
if @activeItem?.isPending?()
|
||||
index = @getActiveItemIndex()
|
||||
@destroyActiveItem() unless item is @activeItem
|
||||
else
|
||||
index = @getActiveItemIndex() + 1
|
||||
@addItem(item, index, false)
|
||||
@@ -366,6 +365,12 @@ class Pane extends Model
|
||||
|
||||
return if item in @items
|
||||
|
||||
if item.isPending?()
|
||||
for existingItem, i in @items
|
||||
if existingItem.isPending?()
|
||||
@destroyItem(existingItem)
|
||||
break
|
||||
|
||||
if typeof item.onDidDestroy is 'function'
|
||||
@itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, false)
|
||||
|
||||
@@ -661,6 +666,8 @@ class Pane extends Model
|
||||
when 'before' then @parent.insertChildBefore(this, newPane)
|
||||
when 'after' then @parent.insertChildAfter(this, newPane)
|
||||
|
||||
@moveItemToPane(@activeItem, newPane) if params?.moveActiveItem
|
||||
|
||||
newPane.activate()
|
||||
newPane
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ class Project extends Model
|
||||
#
|
||||
# * `filePath` A {String} representing a path. If `null`, an "Untitled" buffer is created.
|
||||
#
|
||||
# Returns a promise that resolves to the {TextBuffer}.
|
||||
# Returns a {Promise} that resolves to the {TextBuffer}.
|
||||
bufferForPath: (absoluteFilePath) ->
|
||||
existingBuffer = @findBufferForPath(absoluteFilePath) if absoluteFilePath?
|
||||
if existingBuffer
|
||||
@@ -349,7 +349,7 @@ class Project extends Model
|
||||
# * `absoluteFilePath` A {String} representing a path.
|
||||
# * `text` The {String} text to use as a buffer.
|
||||
#
|
||||
# Returns a promise that resolves to the {TextBuffer}.
|
||||
# Returns a {Promise} that resolves to the {TextBuffer}.
|
||||
buildBuffer: (absoluteFilePath) ->
|
||||
buffer = new TextBuffer({filePath: absoluteFilePath})
|
||||
@addBuffer(buffer)
|
||||
|
||||
@@ -50,6 +50,14 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
|
||||
'window:focus-pane-below': -> @focusPaneViewBelow()
|
||||
'window:focus-pane-on-left': -> @focusPaneViewOnLeft()
|
||||
'window:focus-pane-on-right': -> @focusPaneViewOnRight()
|
||||
'window:move-active-item-to-pane-above': -> @moveActiveItemToPaneAbove()
|
||||
'window:move-active-item-to-pane-below': -> @moveActiveItemToPaneBelow()
|
||||
'window:move-active-item-to-pane-on-left': -> @moveActiveItemToPaneOnLeft()
|
||||
'window:move-active-item-to-pane-on-right': -> @moveActiveItemToPaneOnRight()
|
||||
'window:copy-active-item-to-pane-above': -> @moveActiveItemToPaneAbove(keepOriginal: true)
|
||||
'window:copy-active-item-to-pane-below': -> @moveActiveItemToPaneBelow(keepOriginal: true)
|
||||
'window:copy-active-item-to-pane-on-left': -> @moveActiveItemToPaneOnLeft(keepOriginal: true)
|
||||
'window:copy-active-item-to-pane-on-right': -> @moveActiveItemToPaneOnRight(keepOriginal: true)
|
||||
'window:save-all': -> @getModel().saveAll()
|
||||
'window:toggle-invisibles': -> config.set("editor.showInvisibles", not config.get("editor.showInvisibles"))
|
||||
'window:log-deprecation-warnings': -> Grim.logDeprecations()
|
||||
@@ -65,10 +73,18 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
|
||||
|
||||
commandRegistry.add 'atom-pane',
|
||||
'pane:save-items': -> @getModel().saveItems()
|
||||
'pane:split-left': -> @getModel().splitLeft(copyActiveItem: true)
|
||||
'pane:split-right': -> @getModel().splitRight(copyActiveItem: true)
|
||||
'pane:split-up': -> @getModel().splitUp(copyActiveItem: true)
|
||||
'pane:split-down': -> @getModel().splitDown(copyActiveItem: true)
|
||||
'pane:split-left': -> @getModel().splitLeft()
|
||||
'pane:split-right': -> @getModel().splitRight()
|
||||
'pane:split-up': -> @getModel().splitUp()
|
||||
'pane:split-down': -> @getModel().splitDown()
|
||||
'pane:split-left-and-copy-active-item': -> @getModel().splitLeft(copyActiveItem: true)
|
||||
'pane:split-right-and-copy-active-item': -> @getModel().splitRight(copyActiveItem: true)
|
||||
'pane:split-up-and-copy-active-item': -> @getModel().splitUp(copyActiveItem: true)
|
||||
'pane:split-down-and-copy-active-item': -> @getModel().splitDown(copyActiveItem: true)
|
||||
'pane:split-left-and-move-active-item': -> @getModel().splitLeft(moveActiveItem: true)
|
||||
'pane:split-right-and-move-active-item': -> @getModel().splitRight(moveActiveItem: true)
|
||||
'pane:split-up-and-move-active-item': -> @getModel().splitUp(moveActiveItem: true)
|
||||
'pane:split-down-and-move-active-item': -> @getModel().splitDown(moveActiveItem: true)
|
||||
'pane:close': -> @getModel().close()
|
||||
'pane:close-other-items': -> @getModel().destroyInactiveItems()
|
||||
'pane:increase-size': -> @getModel().increaseSize()
|
||||
|
||||
@@ -13,6 +13,8 @@ ScrollbarCornerComponent = require './scrollbar-corner-component'
|
||||
OverlayManager = require './overlay-manager'
|
||||
DOMElementPool = require './dom-element-pool'
|
||||
LinesYardstick = require './lines-yardstick'
|
||||
BlockDecorationsComponent = require './block-decorations-component'
|
||||
LineTopIndex = require 'line-top-index'
|
||||
|
||||
module.exports =
|
||||
class TextEditorComponent
|
||||
@@ -48,6 +50,9 @@ class TextEditorComponent
|
||||
@observeConfig()
|
||||
@setScrollSensitivity(@config.get('editor.scrollSensitivity'))
|
||||
|
||||
lineTopIndex = new LineTopIndex({
|
||||
defaultLineHeight: @editor.getLineHeightInPixels()
|
||||
})
|
||||
@presenter = new TextEditorPresenter
|
||||
model: @editor
|
||||
tileSize: tileSize
|
||||
@@ -55,11 +60,11 @@ class TextEditorComponent
|
||||
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
|
||||
stoppedScrollingDelay: 200
|
||||
config: @config
|
||||
lineTopIndex: lineTopIndex
|
||||
|
||||
@presenter.onDidUpdateState(@requestUpdate)
|
||||
|
||||
@domElementPool = new DOMElementPool
|
||||
|
||||
@domNode = document.createElement('div')
|
||||
if @useShadowDOM
|
||||
@domNode.classList.add('editor-contents--private')
|
||||
@@ -68,6 +73,7 @@ class TextEditorComponent
|
||||
insertionPoint.setAttribute('select', 'atom-overlay')
|
||||
@domNode.appendChild(insertionPoint)
|
||||
@overlayManager = new OverlayManager(@presenter, @hostElement, @views)
|
||||
@blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool)
|
||||
else
|
||||
@domNode.classList.add('editor-contents')
|
||||
@overlayManager = new OverlayManager(@presenter, @domNode, @views)
|
||||
@@ -82,7 +88,10 @@ class TextEditorComponent
|
||||
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars})
|
||||
@scrollViewNode.appendChild(@linesComponent.getDomNode())
|
||||
|
||||
@linesYardstick = new LinesYardstick(@editor, @linesComponent, @grammars)
|
||||
if @blockDecorationsComponent?
|
||||
@linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode())
|
||||
|
||||
@linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex, @grammars)
|
||||
@presenter.setLinesYardstick(@linesYardstick)
|
||||
|
||||
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
|
||||
@@ -158,6 +167,7 @@ class TextEditorComponent
|
||||
|
||||
@hiddenInputComponent.updateSync(@newState)
|
||||
@linesComponent.updateSync(@newState)
|
||||
@blockDecorationsComponent?.updateSync(@newState)
|
||||
@horizontalScrollbarComponent.updateSync(@newState)
|
||||
@verticalScrollbarComponent.updateSync(@newState)
|
||||
@scrollbarCornerComponent.updateSync(@newState)
|
||||
@@ -177,6 +187,7 @@ class TextEditorComponent
|
||||
|
||||
readAfterUpdateSync: =>
|
||||
@overlayManager?.measureOverlays()
|
||||
@blockDecorationsComponent?.measureBlockDecorations() if @isVisible()
|
||||
|
||||
mountGutterContainerComponent: ->
|
||||
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views})
|
||||
@@ -279,13 +290,13 @@ class TextEditorComponent
|
||||
observeConfig: ->
|
||||
@disposables.add @config.onDidChange 'editor.fontSize', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
@disposables.add @config.onDidChange 'editor.fontFamily', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
@disposables.add @config.onDidChange 'editor.lineHeight', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
onGrammarChanged: =>
|
||||
if @scopedConfigDisposables?
|
||||
@@ -485,6 +496,9 @@ class TextEditorComponent
|
||||
@editor.screenPositionForBufferPosition(bufferPosition)
|
||||
)
|
||||
|
||||
invalidateBlockDecorationDimensions: ->
|
||||
@presenter.invalidateBlockDecorationDimensions(arguments...)
|
||||
|
||||
onMouseDown: (event) =>
|
||||
unless event.button is 0 or (event.button is 1 and process.platform is 'linux')
|
||||
# Only handle mouse down events for left mouse button on all platforms
|
||||
@@ -602,7 +616,7 @@ class TextEditorComponent
|
||||
handleStylingChange: =>
|
||||
@sampleFontStyling()
|
||||
@sampleBackgroundColors()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
handleDragUntilMouseUp: (dragHandler) ->
|
||||
dragging = false
|
||||
@@ -756,7 +770,7 @@ class TextEditorComponent
|
||||
if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
|
||||
@clearPoolAfterUpdate = true
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
sampleBackgroundColors: (suppressUpdate) ->
|
||||
{backgroundColor} = getComputedStyle(@hostElement)
|
||||
@@ -866,7 +880,7 @@ class TextEditorComponent
|
||||
setFontSize: (fontSize) ->
|
||||
@getTopmostDOMNode().style.fontSize = fontSize + 'px'
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
getFontFamily: ->
|
||||
getComputedStyle(@getTopmostDOMNode()).fontFamily
|
||||
@@ -874,16 +888,16 @@ class TextEditorComponent
|
||||
setFontFamily: (fontFamily) ->
|
||||
@getTopmostDOMNode().style.fontFamily = fontFamily
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
@getTopmostDOMNode().style.lineHeight = lineHeight
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
invalidateCharacterWidths: ->
|
||||
invalidateMeasurements: ->
|
||||
@linesYardstick.invalidateCache()
|
||||
@presenter.characterWidthsChanged()
|
||||
@presenter.measurementsChanged()
|
||||
|
||||
setShowIndentGuide: (showIndentGuide) ->
|
||||
@config.set("editor.showIndentGuide", showIndentGuide)
|
||||
|
||||
@@ -347,4 +347,13 @@ class TextEditorElement extends HTMLElement
|
||||
getHeight: ->
|
||||
@offsetHeight
|
||||
|
||||
# Experimental: Invalidate the passed block {Decoration} dimensions, forcing
|
||||
# them to be recalculated and the surrounding content to be adjusted on the
|
||||
# next animation frame.
|
||||
#
|
||||
# * {blockDecoration} A {Decoration} representing the block decoration you
|
||||
# want to update the dimensions of.
|
||||
invalidateBlockDecorationDimensions: ->
|
||||
@component.invalidateBlockDecorationDimensions(arguments...)
|
||||
|
||||
module.exports = TextEditorElement = document.registerElement 'atom-text-editor', prototype: TextEditorElement.prototype
|
||||
|
||||
@@ -14,7 +14,7 @@ class TextEditorPresenter
|
||||
minimumReflowInterval: 200
|
||||
|
||||
constructor: (params) ->
|
||||
{@model, @config} = params
|
||||
{@model, @config, @lineTopIndex} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params
|
||||
{@contentFrameWidth} = params
|
||||
@tokenIterator = @model.displayLayer.buildTokenIterator()
|
||||
@@ -33,6 +33,9 @@ class TextEditorPresenter
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@customGutterDecorationsByGutterName = {}
|
||||
@observedBlockDecorations = new Set()
|
||||
@invalidatedDimensionsByBlockDecoration = new Set()
|
||||
@invalidateAllBlockDecorationsDimensions = false
|
||||
@screenRowsToMeasure = []
|
||||
@transferMeasurementsToModel()
|
||||
@transferMeasurementsFromModel()
|
||||
@@ -92,6 +95,7 @@ class TextEditorPresenter
|
||||
if @shouldUpdateDecorations
|
||||
@fetchDecorations()
|
||||
@updateLineDecorations()
|
||||
@updateBlockDecorations()
|
||||
|
||||
@updateTilesState()
|
||||
|
||||
@@ -136,6 +140,9 @@ class TextEditorPresenter
|
||||
@disposables.add @model.displayLayer.onDidChangeSync (changes) =>
|
||||
for change in changes
|
||||
@invalidateLines(change)
|
||||
startRow = change.start.row
|
||||
endRow = startRow + change.oldExtent.row
|
||||
@spliceBlockDecorationsInRange(startRow, endRow, change.newExtent.row - change.oldExtent.row)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -143,6 +150,11 @@ class TextEditorPresenter
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@disposables.add @model.onDidAddDecoration(@didAddBlockDecoration.bind(this))
|
||||
|
||||
for decoration in @model.getDecorations({type: 'block'})
|
||||
this.didAddBlockDecoration(decoration)
|
||||
|
||||
@disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this))
|
||||
@disposables.add @model.onDidChangePlaceholderText(@emitDidUpdateState.bind(this))
|
||||
@disposables.add @model.onDidChangeMini =>
|
||||
@@ -201,6 +213,7 @@ class TextEditorPresenter
|
||||
highlights: {}
|
||||
overlays: {}
|
||||
cursors: {}
|
||||
blockDecorations: {}
|
||||
gutters: []
|
||||
# Shared state that is copied into ``@state.gutters`.
|
||||
@sharedGutterStyles = {}
|
||||
@@ -293,10 +306,10 @@ class TextEditorPresenter
|
||||
Math.max(0, Math.min(row, @model.getScreenLineCount()))
|
||||
|
||||
getStartTileRow: ->
|
||||
@constrainRow(@tileForRow(@startRow))
|
||||
@constrainRow(@tileForRow(@startRow ? 0))
|
||||
|
||||
getEndTileRow: ->
|
||||
@constrainRow(@tileForRow(@endRow))
|
||||
@constrainRow(@tileForRow(@endRow ? 0))
|
||||
|
||||
isValidScreenRow: (screenRow) ->
|
||||
screenRow >= 0 and screenRow < @model.getScreenLineCount()
|
||||
@@ -336,6 +349,7 @@ class TextEditorPresenter
|
||||
zIndex = 0
|
||||
|
||||
for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize
|
||||
tileEndRow = @constrainRow(tileStartRow + @tileSize)
|
||||
rowsWithinTile = []
|
||||
|
||||
while screenRowIndex >= 0
|
||||
@@ -346,17 +360,21 @@ class TextEditorPresenter
|
||||
|
||||
continue if rowsWithinTile.length is 0
|
||||
|
||||
top = Math.round(@lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow))
|
||||
bottom = Math.round(@lineTopIndex.pixelPositionBeforeBlocksForRow(tileEndRow))
|
||||
height = bottom - top
|
||||
|
||||
tile = @state.content.tiles[tileStartRow] ?= {}
|
||||
tile.top = tileStartRow * @lineHeight - @scrollTop
|
||||
tile.top = top - @scrollTop
|
||||
tile.left = -@scrollLeft
|
||||
tile.height = @tileSize * @lineHeight
|
||||
tile.height = height
|
||||
tile.display = "block"
|
||||
tile.zIndex = zIndex
|
||||
tile.highlights ?= {}
|
||||
|
||||
gutterTile = @lineNumberGutter.tiles[tileStartRow] ?= {}
|
||||
gutterTile.top = tileStartRow * @lineHeight - @scrollTop
|
||||
gutterTile.height = @tileSize * @lineHeight
|
||||
gutterTile.top = top - @scrollTop
|
||||
gutterTile.height = height
|
||||
gutterTile.display = "block"
|
||||
gutterTile.zIndex = zIndex
|
||||
|
||||
@@ -389,15 +407,25 @@ class TextEditorPresenter
|
||||
throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}")
|
||||
|
||||
visibleLineIds[line.id] = true
|
||||
precedingBlockDecorations = @precedingBlockDecorationsByScreenRow[screenRow] ? []
|
||||
followingBlockDecorations = @followingBlockDecorationsByScreenRow[screenRow] ? []
|
||||
if tileState.lines.hasOwnProperty(line.id)
|
||||
lineState = tileState.lines[line.id]
|
||||
lineState.screenRow = screenRow
|
||||
lineState.decorationClasses = @lineDecorationClassesForRow(screenRow)
|
||||
lineState.precedingBlockDecorations = precedingBlockDecorations
|
||||
lineState.followingBlockDecorations = followingBlockDecorations
|
||||
lineState.hasPrecedingBlockDecorations = precedingBlockDecorations.length > 0
|
||||
lineState.hasFollowingBlockDecorations = followingBlockDecorations.length > 0
|
||||
else
|
||||
tileState.lines[line.id] =
|
||||
screenRow: screenRow
|
||||
tokens: line.tokens
|
||||
decorationClasses: @lineDecorationClassesForRow(screenRow)
|
||||
precedingBlockDecorations: precedingBlockDecorations
|
||||
followingBlockDecorations: followingBlockDecorations
|
||||
hasPrecedingBlockDecorations: precedingBlockDecorations.length > 0
|
||||
hasFollowingBlockDecorations: followingBlockDecorations.length > 0
|
||||
|
||||
for id, line of tileState.lines
|
||||
delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id)
|
||||
@@ -534,9 +562,11 @@ class TextEditorPresenter
|
||||
|
||||
continue unless @gutterIsVisible(gutter)
|
||||
for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName]
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.start.row)
|
||||
bottom = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRange.end.row + 1)
|
||||
@customGutterDecorations[gutterName][decorationId] =
|
||||
top: @lineHeight * screenRange.start.row
|
||||
height: @lineHeight * screenRange.getRowCount()
|
||||
top: top
|
||||
height: bottom - top
|
||||
item: properties.item
|
||||
class: properties.class
|
||||
|
||||
@@ -584,8 +614,13 @@ class TextEditorPresenter
|
||||
lineId = @lineIdForScreenRow(screenRow)
|
||||
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
|
||||
foldable = @model.isFoldableAtScreenRow(screenRow)
|
||||
blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow) - @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow)
|
||||
blockDecorationsHeight = blockDecorationsBeforeCurrentScreenRowHeight
|
||||
if screenRow % @tileSize isnt 0
|
||||
blockDecorationsAfterPreviousScreenRowHeight = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) - @lineHeight - @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow - 1)
|
||||
blockDecorationsHeight += blockDecorationsAfterPreviousScreenRowHeight
|
||||
|
||||
tileState.lineNumbers[lineId] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable}
|
||||
tileState.lineNumbers[lineId] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight}
|
||||
visibleLineNumberIds[lineId] = true
|
||||
|
||||
for id of tileState.lineNumbers
|
||||
@@ -596,16 +631,15 @@ class TextEditorPresenter
|
||||
updateStartRow: ->
|
||||
return unless @scrollTop? and @lineHeight?
|
||||
|
||||
startRow = Math.floor(@scrollTop / @lineHeight)
|
||||
@startRow = Math.max(0, startRow)
|
||||
@startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop))
|
||||
|
||||
updateEndRow: ->
|
||||
return unless @scrollTop? and @lineHeight? and @height?
|
||||
|
||||
startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight))
|
||||
visibleLinesCount = Math.ceil(@height / @lineHeight) + 1
|
||||
endRow = startRow + visibleLinesCount
|
||||
@endRow = Math.min(@model.getScreenLineCount(), endRow)
|
||||
@endRow = Math.min(
|
||||
@model.getScreenLineCount(),
|
||||
@lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight - 1) + 1
|
||||
)
|
||||
|
||||
updateRowsPerPage: ->
|
||||
rowsPerPage = Math.floor(@getClientHeight() / @lineHeight)
|
||||
@@ -637,7 +671,7 @@ class TextEditorPresenter
|
||||
updateVerticalDimensions: ->
|
||||
if @lineHeight?
|
||||
oldContentHeight = @contentHeight
|
||||
@contentHeight = @lineHeight * @model.getScreenLineCount()
|
||||
@contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getScreenLineCount()))
|
||||
|
||||
if @contentHeight isnt oldContentHeight
|
||||
@updateHeight()
|
||||
@@ -805,6 +839,7 @@ class TextEditorPresenter
|
||||
didStopScrolling: ->
|
||||
if @mouseWheelScreenRow?
|
||||
@mouseWheelScreenRow = null
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -897,12 +932,15 @@ class TextEditorPresenter
|
||||
@editorWidthInChars = null
|
||||
@updateScrollbarDimensions()
|
||||
@updateClientWidth()
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
setBoundingClientRect: (boundingClientRect) ->
|
||||
unless @clientRectsEqual(@boundingClientRect, boundingClientRect)
|
||||
@boundingClientRect = boundingClientRect
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
clientRectsEqual: (clientRectA, clientRectB) ->
|
||||
@@ -916,6 +954,8 @@ class TextEditorPresenter
|
||||
if @windowWidth isnt width or @windowHeight isnt height
|
||||
@windowWidth = width
|
||||
@windowHeight = height
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -940,6 +980,8 @@ class TextEditorPresenter
|
||||
setLineHeight: (lineHeight) ->
|
||||
unless @lineHeight is lineHeight
|
||||
@lineHeight = lineHeight
|
||||
@model.setLineHeightInPixels(@lineHeight)
|
||||
@lineTopIndex.setDefaultLineHeight(@lineHeight)
|
||||
@restoreScrollTopIfNeeded()
|
||||
@model.setLineHeightInPixels(lineHeight)
|
||||
@shouldUpdateDecorations = true
|
||||
@@ -958,9 +1000,10 @@ class TextEditorPresenter
|
||||
@koreanCharWidth = koreanCharWidth
|
||||
@model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
|
||||
@restoreScrollLeftIfNeeded()
|
||||
@characterWidthsChanged()
|
||||
@measurementsChanged()
|
||||
|
||||
characterWidthsChanged: ->
|
||||
measurementsChanged: ->
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -1057,6 +1100,43 @@ class TextEditorPresenter
|
||||
return unless 0 <= @startRow <= @endRow <= Infinity
|
||||
@decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1)
|
||||
|
||||
updateBlockDecorations: ->
|
||||
@blockDecorationsToRenderById = {}
|
||||
@precedingBlockDecorationsByScreenRow = {}
|
||||
@followingBlockDecorationsByScreenRow = {}
|
||||
visibleDecorationsByMarkerId = @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1)
|
||||
|
||||
if @invalidateAllBlockDecorationsDimensions
|
||||
for decoration in @model.getDecorations(type: 'block')
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
@invalidateAllBlockDecorationsDimensions = false
|
||||
|
||||
for markerId, decorations of visibleDecorationsByMarkerId
|
||||
for decoration in decorations when decoration.isType('block')
|
||||
@updateBlockDecorationState(decoration, true)
|
||||
|
||||
@invalidatedDimensionsByBlockDecoration.forEach (decoration) =>
|
||||
@updateBlockDecorationState(decoration, false)
|
||||
|
||||
for decorationId, decorationState of @state.content.blockDecorations
|
||||
continue if @blockDecorationsToRenderById[decorationId]
|
||||
continue if decorationState.screenRow is @mouseWheelScreenRow
|
||||
|
||||
delete @state.content.blockDecorations[decorationId]
|
||||
|
||||
updateBlockDecorationState: (decoration, isVisible) ->
|
||||
return if @blockDecorationsToRenderById[decoration.getId()]
|
||||
|
||||
screenRow = decoration.getMarker().getHeadScreenPosition().row
|
||||
if decoration.getProperties().position is "before"
|
||||
@precedingBlockDecorationsByScreenRow[screenRow] ?= []
|
||||
@precedingBlockDecorationsByScreenRow[screenRow].push(decoration)
|
||||
else
|
||||
@followingBlockDecorationsByScreenRow[screenRow] ?= []
|
||||
@followingBlockDecorationsByScreenRow[screenRow].push(decoration)
|
||||
@state.content.blockDecorations[decoration.getId()] = {decoration, screenRow, isVisible}
|
||||
@blockDecorationsToRenderById[decoration.getId()] = true
|
||||
|
||||
updateLineDecorations: ->
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@@ -1179,7 +1259,7 @@ class TextEditorPresenter
|
||||
screenRange.end.column = 0
|
||||
|
||||
repositionRegionWithinTile: (region, tileStartRow) ->
|
||||
region.top += @scrollTop - tileStartRow * @lineHeight
|
||||
region.top += @scrollTop - @lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow)
|
||||
region.left += @scrollLeft
|
||||
|
||||
buildHighlightRegions: (screenRange) ->
|
||||
@@ -1249,6 +1329,73 @@ class TextEditorPresenter
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setBlockDecorationDimensions: (decoration, width, height) ->
|
||||
return unless @observedBlockDecorations.has(decoration)
|
||||
|
||||
@lineTopIndex.resizeBlock(decoration.getId(), height)
|
||||
|
||||
@invalidatedDimensionsByBlockDecoration.delete(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
invalidateBlockDecorationDimensions: (decoration) ->
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
spliceBlockDecorationsInRange: (start, end, screenDelta) ->
|
||||
return if screenDelta is 0
|
||||
|
||||
oldExtent = end - start
|
||||
newExtent = end - start + screenDelta
|
||||
invalidatedBlockDecorationIds = @lineTopIndex.splice(start, oldExtent, newExtent)
|
||||
invalidatedBlockDecorationIds.forEach (id) =>
|
||||
decoration = @model.decorationForId(id)
|
||||
newScreenPosition = decoration.getMarker().getHeadScreenPosition()
|
||||
@lineTopIndex.moveBlock(id, newScreenPosition.row)
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
|
||||
didAddBlockDecoration: (decoration) ->
|
||||
return if not decoration.isType('block') or @observedBlockDecorations.has(decoration)
|
||||
|
||||
didMoveDisposable = decoration.getMarker().bufferMarker.onDidChange (markerEvent) =>
|
||||
@didMoveBlockDecoration(decoration, markerEvent)
|
||||
|
||||
didDestroyDisposable = decoration.onDidDestroy =>
|
||||
@disposables.remove(didMoveDisposable)
|
||||
@disposables.remove(didDestroyDisposable)
|
||||
didMoveDisposable.dispose()
|
||||
didDestroyDisposable.dispose()
|
||||
@didDestroyBlockDecoration(decoration)
|
||||
|
||||
isAfter = decoration.getProperties().position is "after"
|
||||
@lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0, isAfter)
|
||||
|
||||
@observedBlockDecorations.add(decoration)
|
||||
@invalidateBlockDecorationDimensions(decoration)
|
||||
@disposables.add(didMoveDisposable)
|
||||
@disposables.add(didDestroyDisposable)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didMoveBlockDecoration: (decoration, markerEvent) ->
|
||||
# Don't move blocks after a text change, because we already splice on buffer
|
||||
# change.
|
||||
return if markerEvent.textChanged
|
||||
|
||||
@lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didDestroyBlockDecoration: (decoration) ->
|
||||
return unless @observedBlockDecorations.has(decoration)
|
||||
|
||||
@lineTopIndex.removeBlock(decoration.getId())
|
||||
@observedBlockDecorations.delete(decoration)
|
||||
@invalidatedDimensionsByBlockDecoration.delete(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
observeCursor: (cursor) ->
|
||||
didChangePositionDisposable = cursor.onDidChangePosition =>
|
||||
@pauseCursorBlinking()
|
||||
@@ -1309,7 +1456,7 @@ class TextEditorPresenter
|
||||
@emitDidUpdateState()
|
||||
|
||||
didChangeFirstVisibleScreenRow: (screenRow) ->
|
||||
@setScrollTop(screenRow * @lineHeight)
|
||||
@setScrollTop(@lineTopIndex.pixelPositionAfterBlocksForRow(screenRow))
|
||||
|
||||
getVerticalScrollMarginInPixels: ->
|
||||
Math.round(@model.getVerticalScrollMargin() * @lineHeight)
|
||||
@@ -1330,8 +1477,8 @@ class TextEditorPresenter
|
||||
|
||||
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
|
||||
|
||||
top = screenRange.start.row * @lineHeight
|
||||
bottom = (screenRange.end.row + 1) * @lineHeight
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.start.row)
|
||||
bottom = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.end.row) + @lineHeight
|
||||
|
||||
if options?.center
|
||||
desiredScrollCenter = (top + bottom) / 2
|
||||
@@ -1403,7 +1550,7 @@ class TextEditorPresenter
|
||||
|
||||
restoreScrollTopIfNeeded: ->
|
||||
unless @scrollTop?
|
||||
@updateScrollTop(@model.getFirstVisibleScreenRow() * @lineHeight)
|
||||
@updateScrollTop(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getFirstVisibleScreenRow()))
|
||||
|
||||
restoreScrollLeftIfNeeded: ->
|
||||
unless @scrollLeft?
|
||||
|
||||
@@ -150,6 +150,7 @@ class TextEditor extends Model
|
||||
firstVisibleScreenColumn: @getFirstVisibleScreenColumn()
|
||||
displayBuffer: @displayBuffer.serialize()
|
||||
selectionsMarkerLayerId: @selectionsMarkerLayer.id
|
||||
pending: @isPending()
|
||||
|
||||
subscribeToBuffer: ->
|
||||
@buffer.retain()
|
||||
@@ -496,6 +497,7 @@ class TextEditor extends Model
|
||||
newEditor = new TextEditor({
|
||||
@buffer, displayBuffer, selectionsMarkerLayer, @tabLength, softTabs,
|
||||
suppressCursorCreation: true, @config, @notificationManager, @packageManager,
|
||||
@firstVisibleScreenRow, @firstVisibleScreenColumn,
|
||||
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
|
||||
})
|
||||
newEditor
|
||||
@@ -576,8 +578,9 @@ class TextEditor extends Model
|
||||
@emitter.on 'did-terminate-pending-state', callback
|
||||
|
||||
terminatePendingState: ->
|
||||
return if not @pending
|
||||
@pending = false
|
||||
@emitter.emit 'did-terminate-pending-state', this
|
||||
@emitter.emit 'did-terminate-pending-state'
|
||||
|
||||
###
|
||||
Section: File Details
|
||||
@@ -704,7 +707,7 @@ class TextEditor extends Model
|
||||
checkoutHead = =>
|
||||
@project.repositoryForDirectory(new Directory(@getDirectoryPath()))
|
||||
.then (repository) =>
|
||||
repository?.checkoutHeadForEditor(this)
|
||||
repository?.async.checkoutHeadForEditor(this)
|
||||
|
||||
if @config.get('editor.confirmCheckoutHeadRevision')
|
||||
@applicationDelegate.confirm
|
||||
@@ -1447,6 +1450,8 @@ class TextEditor extends Model
|
||||
# * __gutter__: A decoration that tracks a {DisplayMarker} in a {Gutter}. Gutter
|
||||
# decorations are created by calling {Gutter::decorateMarker} on the
|
||||
# desired `Gutter` instance.
|
||||
# * __block__: Positions the view associated with the given item before or
|
||||
# after the row of the given `TextEditorMarker`.
|
||||
#
|
||||
# ## Arguments
|
||||
#
|
||||
@@ -1466,11 +1471,14 @@ class TextEditor extends Model
|
||||
# property.
|
||||
# * `gutter` Tracks a {DisplayMarker} in a {Gutter}. Created by calling
|
||||
# {Gutter::decorateMarker} on the desired `Gutter` instance.
|
||||
# * `block` Positions the view associated with the given item before or
|
||||
# after the row of the given `TextEditorMarker`, depending on the `position`
|
||||
# property.
|
||||
# * `class` This CSS class will be applied to the decorated line number,
|
||||
# line, highlight, or overlay.
|
||||
# * `item` (optional) An {HTMLElement} or a model {Object} with a
|
||||
# corresponding view registered. Only applicable to the `gutter` and
|
||||
# `overlay` types.
|
||||
# corresponding view registered. Only applicable to the `gutter`,
|
||||
# `overlay` and `block` types.
|
||||
# * `onlyHead` (optional) If `true`, the decoration will only be applied to
|
||||
# the head of the `DisplayMarker`. Only applicable to the `line` and
|
||||
# `line-number` types.
|
||||
@@ -1480,9 +1488,10 @@ class TextEditor extends Model
|
||||
# * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied
|
||||
# if the associated `DisplayMarker` is non-empty. Only applicable to the
|
||||
# `gutter`, `line`, and `line-number` types.
|
||||
# * `position` (optional) Only applicable to decorations of type `overlay`,
|
||||
# controls where the overlay view is positioned relative to the `DisplayMarker`.
|
||||
# Values can be `'head'` (the default), or `'tail'`.
|
||||
# * `position` (optional) Only applicable to decorations of type `overlay` and `block`,
|
||||
# controls where the view is positioned relative to the `TextEditorMarker`.
|
||||
# Values can be `'head'` (the default) or `'tail'` for overlay decorations, and
|
||||
# `'before'` (the default) or `'after'` for block decorations.
|
||||
#
|
||||
# Returns a {Decoration} object
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
|
||||
@@ -85,9 +85,9 @@ Tooltip.prototype.init = function (element, options) {
|
||||
}
|
||||
}
|
||||
|
||||
this.options.selector ?
|
||||
(this._options = extend({}, this.options, { trigger: 'manual', selector: '' })) :
|
||||
this.fixTitle()
|
||||
this.options.selector
|
||||
? (this._options = extend({}, this.options, { trigger: 'manual', selector: '' }))
|
||||
: this.fixTitle()
|
||||
}
|
||||
|
||||
Tooltip.prototype.getDefaults = function () {
|
||||
@@ -190,9 +190,9 @@ Tooltip.prototype.show = function () {
|
||||
|
||||
if (this.options.animation) tip.classList.add('fade')
|
||||
|
||||
var placement = typeof this.options.placement === 'function' ?
|
||||
this.options.placement.call(this, tip, this.element) :
|
||||
this.options.placement
|
||||
var placement = typeof this.options.placement === 'function'
|
||||
? this.options.placement.call(this, tip, this.element)
|
||||
: this.options.placement
|
||||
|
||||
var autoToken = /\s?auto?\s?/i
|
||||
var autoPlace = autoToken.test(placement)
|
||||
@@ -214,11 +214,11 @@ Tooltip.prototype.show = function () {
|
||||
var orgPlacement = placement
|
||||
var viewportDim = this.viewport.getBoundingClientRect()
|
||||
|
||||
placement = placement === 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
|
||||
placement === 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
|
||||
placement === 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
|
||||
placement === 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
|
||||
placement
|
||||
placement = placement === 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'
|
||||
: placement === 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom'
|
||||
: placement === 'right' && pos.right + actualWidth > viewportDim.width ? 'left'
|
||||
: placement === 'left' && pos.left - actualWidth < viewportDim.left ? 'right'
|
||||
: placement
|
||||
|
||||
tip.classList.remove(orgPlacement)
|
||||
tip.classList.add(placement)
|
||||
@@ -330,10 +330,10 @@ Tooltip.prototype.hasContent = function () {
|
||||
}
|
||||
|
||||
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
|
||||
return placement === 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
||||
placement === 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
||||
placement === 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
|
||||
/* placement === 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
|
||||
return placement === 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 }
|
||||
: placement === 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 }
|
||||
: placement === 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth }
|
||||
:/* placement === 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
|
||||
}
|
||||
|
||||
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
|
||||
|
||||
@@ -74,6 +74,8 @@ class WorkspaceElement extends HTMLElement
|
||||
left: @views.getView(@model.panelContainers.left)
|
||||
right: @views.getView(@model.panelContainers.right)
|
||||
bottom: @views.getView(@model.panelContainers.bottom)
|
||||
header: @views.getView(@model.panelContainers.header)
|
||||
footer: @views.getView(@model.panelContainers.footer)
|
||||
modal: @views.getView(@model.panelContainers.modal)
|
||||
|
||||
@horizontalAxis.insertBefore(@panelContainers.left, @verticalAxis)
|
||||
@@ -82,6 +84,9 @@ class WorkspaceElement extends HTMLElement
|
||||
@verticalAxis.insertBefore(@panelContainers.top, @paneContainer)
|
||||
@verticalAxis.appendChild(@panelContainers.bottom)
|
||||
|
||||
@insertBefore(@panelContainers.header, @horizontalAxis)
|
||||
@appendChild(@panelContainers.footer)
|
||||
|
||||
@appendChild(@panelContainers.modal)
|
||||
|
||||
this
|
||||
@@ -99,6 +104,14 @@ class WorkspaceElement extends HTMLElement
|
||||
|
||||
focusPaneViewOnRight: -> @paneContainer.focusPaneViewOnRight()
|
||||
|
||||
moveActiveItemToPaneAbove: (params) -> @paneContainer.moveActiveItemToPaneAbove(params)
|
||||
|
||||
moveActiveItemToPaneBelow: (params) -> @paneContainer.moveActiveItemToPaneBelow(params)
|
||||
|
||||
moveActiveItemToPaneOnLeft: (params) -> @paneContainer.moveActiveItemToPaneOnLeft(params)
|
||||
|
||||
moveActiveItemToPaneOnRight: (params) -> @paneContainer.moveActiveItemToPaneOnRight(params)
|
||||
|
||||
runPackageSpecs: ->
|
||||
if activePath = @workspace.getActivePaneItem()?.getPath?()
|
||||
[projectPath] = @project.relativizePath(activePath)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
_ = require 'underscore-plus'
|
||||
url = require 'url'
|
||||
path = require 'path'
|
||||
{join} = path
|
||||
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
|
||||
@@ -47,6 +48,8 @@ class Workspace extends Model
|
||||
left: new PanelContainer({location: 'left'})
|
||||
right: new PanelContainer({location: 'right'})
|
||||
bottom: new PanelContainer({location: 'bottom'})
|
||||
header: new PanelContainer({location: 'header'})
|
||||
footer: new PanelContainer({location: 'footer'})
|
||||
modal: new PanelContainer({location: 'modal'})
|
||||
|
||||
@subscribeToEvents()
|
||||
@@ -66,6 +69,8 @@ class Workspace extends Model
|
||||
left: new PanelContainer({location: 'left'})
|
||||
right: new PanelContainer({location: 'right'})
|
||||
bottom: new PanelContainer({location: 'bottom'})
|
||||
header: new PanelContainer({location: 'header'})
|
||||
footer: new PanelContainer({location: 'footer'})
|
||||
modal: new PanelContainer({location: 'modal'})
|
||||
|
||||
@originalFontSize = null
|
||||
@@ -403,12 +408,17 @@ class Workspace extends Model
|
||||
# If `false`, only the active pane will be searched for
|
||||
# an existing item for the same URI. Defaults to `false`.
|
||||
#
|
||||
# Returns a promise that resolves to the {TextEditor} for the file URI.
|
||||
# Returns a {Promise} that resolves to the {TextEditor} for the file URI.
|
||||
open: (uri, options={}) ->
|
||||
searchAllPanes = options.searchAllPanes
|
||||
split = options.split
|
||||
uri = @project.resolvePath(uri)
|
||||
|
||||
# Avoid adding URLs as recent documents to work-around this Spotlight crash:
|
||||
# https://github.com/atom/atom/issues/10071
|
||||
if uri? and not url.parse(uri).protocol?
|
||||
@applicationDelegate.addRecentDocument(uri)
|
||||
|
||||
pane = @paneContainer.paneForURI(uri) if searchAllPanes
|
||||
pane ?= switch split
|
||||
when 'left'
|
||||
@@ -483,6 +493,8 @@ class Workspace extends Model
|
||||
|
||||
Promise.resolve(item)
|
||||
.then (item) =>
|
||||
return item if pane.isDestroyed()
|
||||
|
||||
@itemOpened(item)
|
||||
pane.activateItem(item) if activateItem
|
||||
pane.activate() if activatePane
|
||||
@@ -544,7 +556,7 @@ class Workspace extends Model
|
||||
# Public: Asynchronously reopens the last-closed item's URI if it hasn't already been
|
||||
# reopened.
|
||||
#
|
||||
# Returns a promise that is resolved when the item is opened
|
||||
# Returns a {Promise} that is resolved when the item is opened
|
||||
reopenItem: ->
|
||||
if uri = @destroyedItemURIs.pop()
|
||||
@open(uri)
|
||||
@@ -832,6 +844,44 @@ class Workspace extends Model
|
||||
addTopPanel: (options) ->
|
||||
@addPanel('top', options)
|
||||
|
||||
# Essential: Get an {Array} of all the panel items in the header.
|
||||
getHeaderPanels: ->
|
||||
@getPanels('header')
|
||||
|
||||
# Essential: Adds a panel item to the header.
|
||||
#
|
||||
# * `options` {Object}
|
||||
# * `item` Your panel content. It can be DOM element, a jQuery element, or
|
||||
# a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the
|
||||
# latter. See {ViewRegistry::addViewProvider} for more information.
|
||||
# * `visible` (optional) {Boolean} false if you want the panel to initially be hidden
|
||||
# (default: true)
|
||||
# * `priority` (optional) {Number} Determines stacking order. Lower priority items are
|
||||
# forced closer to the edges of the window. (default: 100)
|
||||
#
|
||||
# Returns a {Panel}
|
||||
addHeaderPanel: (options) ->
|
||||
@addPanel('header', options)
|
||||
|
||||
# Essential: Get an {Array} of all the panel items in the footer.
|
||||
getFooterPanels: ->
|
||||
@getPanels('footer')
|
||||
|
||||
# Essential: Adds a panel item to the footer.
|
||||
#
|
||||
# * `options` {Object}
|
||||
# * `item` Your panel content. It can be DOM element, a jQuery element, or
|
||||
# a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the
|
||||
# latter. See {ViewRegistry::addViewProvider} for more information.
|
||||
# * `visible` (optional) {Boolean} false if you want the panel to initially be hidden
|
||||
# (default: true)
|
||||
# * `priority` (optional) {Number} Determines stacking order. Lower priority items are
|
||||
# forced closer to the edges of the window. (default: 100)
|
||||
#
|
||||
# Returns a {Panel}
|
||||
addFooterPanel: (options) ->
|
||||
@addPanel('footer', options)
|
||||
|
||||
# Essential: Get an {Array} of all the modal panel items
|
||||
getModalPanels: ->
|
||||
@getPanels('modal')
|
||||
@@ -881,7 +931,7 @@ class Workspace extends Model
|
||||
# with number of paths searched.
|
||||
# * `iterator` {Function} callback on each file found.
|
||||
#
|
||||
# Returns a `Promise` with a `cancel()` method that will cancel all
|
||||
# Returns a {Promise} with a `cancel()` method that will cancel all
|
||||
# of the underlying searches that were started as part of this scan.
|
||||
scan: (regex, options={}, iterator) ->
|
||||
if _.isFunction(options)
|
||||
@@ -984,7 +1034,7 @@ class Workspace extends Model
|
||||
# * `iterator` A {Function} callback on each file with replacements:
|
||||
# * `options` {Object} with keys `filePath` and `replacements`.
|
||||
#
|
||||
# Returns a `Promise`.
|
||||
# Returns a {Promise}.
|
||||
replace: (regex, replacementText, filePaths, iterator) ->
|
||||
new Promise (resolve, reject) =>
|
||||
openPaths = (buffer.getPath() for buffer in @project.getBuffers())
|
||||
|
||||
Reference in New Issue
Block a user