mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge branch 'summit'
Conflicts: src/app/config.coffee src/atom-application.coffee src/main.coffee
This commit is contained in:
@@ -7,6 +7,7 @@ remote = require 'remote'
|
||||
crypto = require 'crypto'
|
||||
path = require 'path'
|
||||
dialog = remote.require 'dialog'
|
||||
app = remote.require 'app'
|
||||
telepath = require 'telepath'
|
||||
ThemeManager = require 'theme-manager'
|
||||
|
||||
@@ -116,7 +117,7 @@ window.atom =
|
||||
_.uniq(packagePaths)
|
||||
|
||||
getAvailablePackageNames: ->
|
||||
path.basename(packagePath) for packagePath in @getAvailablePackagePaths()
|
||||
_.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
|
||||
|
||||
getAvailablePackageMetadata: ->
|
||||
packages = []
|
||||
@@ -184,7 +185,7 @@ window.atom =
|
||||
remote.getCurrentWindow().hide()
|
||||
|
||||
exit: (status) ->
|
||||
remote.require('app').exit(status)
|
||||
app.exit(status)
|
||||
|
||||
toggleFullScreen: ->
|
||||
@setFullScreen(!@isFullScreen())
|
||||
@@ -195,6 +196,9 @@ window.atom =
|
||||
isFullScreen: ->
|
||||
remote.getCurrentWindow().isFullScreen()
|
||||
|
||||
getHomeDirPath: ->
|
||||
app.getHomeDir()
|
||||
|
||||
getWindowStatePath: ->
|
||||
switch @windowMode
|
||||
when 'spec'
|
||||
@@ -231,12 +235,12 @@ window.atom =
|
||||
console.warn "Error parsing window state: #{windowStatePath}", error.stack, error
|
||||
|
||||
windowState ?= {}
|
||||
telepath.Document.create(windowState, site: telepath.createSite(1))
|
||||
site.deserializeDocument(windowState) ? site.createDocument({})
|
||||
|
||||
saveWindowState: ->
|
||||
windowStateJson = JSON.stringify(@getWindowState().toObject())
|
||||
windowStateJson = JSON.stringify(@getWindowState().serialize(), null, 2)
|
||||
if windowStatePath = @getWindowStatePath()
|
||||
fsUtils.writeSync(windowStatePath, windowStateJson)
|
||||
fsUtils.writeSync(windowStatePath, "#{windowStateJson}\n")
|
||||
else
|
||||
@getLoadSettings().windowState = windowStateJson
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Range = require 'range'
|
||||
{Range} = require 'telepath'
|
||||
_ = require 'underscore'
|
||||
|
||||
### Internal ###
|
||||
@@ -19,16 +19,16 @@ class BufferChangeOperation
|
||||
do: ->
|
||||
@buffer.pauseEvents()
|
||||
@pauseMarkerObservation()
|
||||
@markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
|
||||
# @markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
|
||||
if @oldRange?
|
||||
@oldText = @buffer.getTextInRange(@oldRange)
|
||||
@newRange = @calculateNewRange(@oldRange, @newText)
|
||||
@newRange = Range.fromText(@oldRange.start, @newText)
|
||||
newRange = @changeBuffer
|
||||
oldRange: @oldRange
|
||||
newRange: @newRange
|
||||
oldText: @oldText
|
||||
newText: @newText
|
||||
@restoreMarkers(@markersToRestoreOnRedo) if @markersToRestoreOnRedo
|
||||
# @restoreMarkers(@markersToRestoreOnRedo) if @markersToRestoreOnRedo
|
||||
@buffer.resumeEvents()
|
||||
@resumeMarkerObservation()
|
||||
newRange
|
||||
@@ -36,67 +36,19 @@ class BufferChangeOperation
|
||||
undo: ->
|
||||
@buffer.pauseEvents()
|
||||
@pauseMarkerObservation()
|
||||
@markersToRestoreOnRedo = @invalidateMarkers(@newRange)
|
||||
# @markersToRestoreOnRedo = @invalidateMarkers(@newRange)
|
||||
if @oldRange?
|
||||
@changeBuffer
|
||||
oldRange: @newRange
|
||||
newRange: @oldRange
|
||||
oldText: @newText
|
||||
newText: @oldText
|
||||
@restoreMarkers(@markersToRestoreOnUndo)
|
||||
# @restoreMarkers(@markersToRestoreOnUndo)
|
||||
@buffer.resumeEvents()
|
||||
@resumeMarkerObservation()
|
||||
|
||||
splitLines: (text) ->
|
||||
lines = text.split('\n')
|
||||
lineEndings = []
|
||||
for line, index in lines
|
||||
if _.endsWith(line, '\r')
|
||||
lines[index] = line[...-1]
|
||||
lineEndings[index] = '\r\n'
|
||||
else
|
||||
lineEndings[index] = '\n'
|
||||
{lines, lineEndings}
|
||||
|
||||
changeBuffer: ({ oldRange, newRange, newText, oldText }) ->
|
||||
{ prefix, suffix } = @buffer.prefixAndSuffixForRange(oldRange)
|
||||
{lines, lineEndings} = @splitLines(newText)
|
||||
lastLineIndex = lines.length - 1
|
||||
|
||||
if lines.length == 1
|
||||
lines = [prefix + newText + suffix]
|
||||
else
|
||||
lines[0] = prefix + lines[0]
|
||||
lines[lastLineIndex] += suffix
|
||||
|
||||
startRow = oldRange.start.row
|
||||
endRow = oldRange.end.row
|
||||
|
||||
normalizeLineEndings = @options.normalizeLineEndings ? true
|
||||
if normalizeLineEndings and suggestedLineEnding = @buffer.suggestedLineEndingForRow(startRow)
|
||||
lineEndings[index] = suggestedLineEnding for index in [0..lastLineIndex]
|
||||
|
||||
_.spliceWithArray(@buffer.lines, startRow, endRow - startRow + 1, lines)
|
||||
_.spliceWithArray(@buffer.lineEndings, startRow, endRow - startRow + 1, lineEndings)
|
||||
@buffer.cachedMemoryContents = null
|
||||
@buffer.conflict = false if @buffer.conflict and !@buffer.isModified()
|
||||
|
||||
event = { oldRange, newRange, oldText, newText }
|
||||
@updateMarkers(event)
|
||||
@buffer.trigger 'changed', event
|
||||
@buffer.scheduleModifiedEvents()
|
||||
|
||||
newRange
|
||||
|
||||
calculateNewRange: (oldRange, newText) ->
|
||||
newRange = new Range(oldRange.start.copy(), oldRange.start.copy())
|
||||
{lines} = @splitLines(newText)
|
||||
if lines.length == 1
|
||||
newRange.end.column += newText.length
|
||||
else
|
||||
lastLineIndex = lines.length - 1
|
||||
newRange.end.row += lastLineIndex
|
||||
newRange.end.column = lines[lastLineIndex].length
|
||||
@buffer.text.change(oldRange, newText)
|
||||
newRange
|
||||
|
||||
invalidateMarkers: (oldRange) ->
|
||||
@@ -109,9 +61,6 @@ class BufferChangeOperation
|
||||
marker.resumeEvents() for marker in @buffer.getMarkers(includeInvalid: true)
|
||||
@buffer.trigger 'markers-updated' if @oldRange?
|
||||
|
||||
updateMarkers: (bufferChange) ->
|
||||
marker.handleBufferChange(bufferChange) for marker in @buffer.getMarkers()
|
||||
|
||||
restoreMarkers: (markersToRestore) ->
|
||||
for [id, previousRange] in markersToRestore
|
||||
if validMarker = @buffer.validMarkers[id]
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
_ = require 'underscore'
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
EventEmitter = require 'event-emitter'
|
||||
|
||||
module.exports =
|
||||
class BufferMarker
|
||||
headPosition: null
|
||||
tailPosition: null
|
||||
suppressObserverNotification: false
|
||||
invalidationStrategy: null
|
||||
|
||||
### Internal ###
|
||||
|
||||
constructor: ({@id, @buffer, range, @invalidationStrategy, @attributes, noTail, reverse}) ->
|
||||
@invalidationStrategy ?= 'contains'
|
||||
@setRange(range, {noTail, reverse})
|
||||
|
||||
### Public ###
|
||||
|
||||
# Sets the marker's range, potentialy modifying both its head and tail.
|
||||
#
|
||||
# range - The new {Range} the marker should cover
|
||||
# options - A hash of options with the following keys:
|
||||
# reverse: if `true`, the marker is reversed; that is, its tail is "above" the head
|
||||
# noTail: if `true`, the marker doesn't have a tail
|
||||
setRange: (range, options={}) ->
|
||||
@consolidateObserverNotifications false, =>
|
||||
range = Range.fromObject(range)
|
||||
if options.reverse
|
||||
@setTailPosition(range.end) unless options.noTail
|
||||
@setHeadPosition(range.start)
|
||||
else
|
||||
@setTailPosition(range.start) unless options.noTail
|
||||
@setHeadPosition(range.end)
|
||||
|
||||
# Identifies if the ending position of a marker is greater than the starting position.
|
||||
#
|
||||
# This can happen when, for example, you highlight text "up" in a {Buffer}.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isReversed: ->
|
||||
@tailPosition? and @headPosition.isLessThan(@tailPosition)
|
||||
|
||||
# Checks that the marker's attributes match the given attributes
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
matchesAttributes: (queryAttributes) ->
|
||||
for key, value of queryAttributes
|
||||
switch key
|
||||
when 'startRow'
|
||||
return false unless @getRange().start.row == value
|
||||
when 'endRow'
|
||||
return false unless @getRange().end.row == value
|
||||
when 'containsRange'
|
||||
return false unless @getRange().containsRange(value, exclusive: true)
|
||||
when 'containsRow'
|
||||
return false unless @getRange().containsRow(value)
|
||||
else
|
||||
return false unless _.isEqual(@attributes[key], value)
|
||||
true
|
||||
|
||||
# Identifies if the marker's head position is equal to its tail.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isRangeEmpty: ->
|
||||
@getHeadPosition().isEqual(@getTailPosition())
|
||||
|
||||
# Retrieves the {Range} between a marker's head and its tail.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getRange: ->
|
||||
if @tailPosition
|
||||
new Range(@getTailPosition(), @getHeadPosition())
|
||||
else
|
||||
new Range(@getHeadPosition(), @getHeadPosition())
|
||||
|
||||
# Retrieves the position of the marker's head.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getHeadPosition: -> @headPosition?.copy()
|
||||
|
||||
# Retrieves the position of the marker's tail.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getTailPosition: -> @tailPosition?.copy() ? @getHeadPosition()
|
||||
|
||||
# Sets the position of the marker's head.
|
||||
#
|
||||
# newHeadPosition - The new {Point} to place the head
|
||||
# options - A hash with the following keys:
|
||||
# clip: if `true`, the point is [clipped]{Buffer.clipPosition}
|
||||
# bufferChanged: if `true`, indicates that the {Buffer} should trigger an event that it's changed
|
||||
#
|
||||
# Returns a {Point} representing the new head position.
|
||||
setHeadPosition: (newHeadPosition, options={}) ->
|
||||
oldHeadPosition = @getHeadPosition()
|
||||
newHeadPosition = Point.fromObject(newHeadPosition)
|
||||
newHeadPosition = @buffer.clipPosition(newHeadPosition) if options.clip ? true
|
||||
return if newHeadPosition.isEqual(@headPosition)
|
||||
@headPosition = newHeadPosition
|
||||
bufferChanged = !!options.bufferChanged
|
||||
@notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged})
|
||||
@headPosition
|
||||
|
||||
# Sets the position of the marker's tail.
|
||||
#
|
||||
# newHeadPosition - The new {Point} to place the tail
|
||||
# options - A hash with the following keys:
|
||||
# clip: if `true`, the point is [clipped]{Buffer.clipPosition}
|
||||
# bufferChanged: if `true`, indicates that the {Buffer} should trigger an event that it's changed
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
setTailPosition: (newTailPosition, options={}) ->
|
||||
oldTailPosition = @getTailPosition()
|
||||
newTailPosition = Point.fromObject(newTailPosition)
|
||||
newTailPosition = @buffer.clipPosition(newTailPosition) if options.clip ? true
|
||||
return if newTailPosition.isEqual(@tailPosition)
|
||||
@tailPosition = newTailPosition
|
||||
bufferChanged = !!options.bufferChanged
|
||||
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged})
|
||||
@tailPosition
|
||||
|
||||
# Retrieves the starting position of the marker.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartPosition: ->
|
||||
@getRange().start
|
||||
|
||||
# Retrieves the ending position of the marker.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndPosition: ->
|
||||
@getRange().end
|
||||
|
||||
# Sets the marker's tail to the same position as the marker's head.
|
||||
#
|
||||
# This only works if there isn't already a tail position.
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
placeTail: ->
|
||||
@setTailPosition(@getHeadPosition()) unless @tailPosition
|
||||
|
||||
# Removes the tail from the marker.
|
||||
clearTail: ->
|
||||
oldTailPosition = @getTailPosition()
|
||||
@tailPosition = null
|
||||
newTailPosition = @getTailPosition()
|
||||
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
|
||||
|
||||
# Identifies if a {Point} is within the marker.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
containsPoint: (point) ->
|
||||
@getRange().containsPoint(point)
|
||||
|
||||
# Destroys the marker
|
||||
destroy: ->
|
||||
@buffer.destroyMarker(@id)
|
||||
@trigger 'destroyed'
|
||||
|
||||
# Returns a {Boolean} indicating whether the marker is valid. Markers can be
|
||||
# invalidated when a region surrounding them in the buffer is changed.
|
||||
isValid: ->
|
||||
@buffer.getMarker(@id)?
|
||||
|
||||
# Returns a {Boolean} indicating whether the marker has been destroyed. A marker
|
||||
# can be invalid without being destroyed, in which case undoing the invalidating
|
||||
# operation would restore the marker. Once a marker is destroyed by calling
|
||||
# {BufferMarker.destroy}, no undo/redo operation can ever bring it back.
|
||||
isDestroyed: ->
|
||||
not (@buffer.validMarkers[@id]? or @buffer.invalidMarkers[@id]?)
|
||||
|
||||
### Internal ###
|
||||
|
||||
tryToInvalidate: (changedRange) ->
|
||||
previousRange = @getRange()
|
||||
if changedRange
|
||||
betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false)
|
||||
containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true)
|
||||
containsEnd = changedRange.containsPoint(@getEndPosition(), exclusive: true)
|
||||
switch @invalidationStrategy
|
||||
when 'between'
|
||||
@invalidate() if betweenStartAndEnd or containsStart or containsEnd
|
||||
when 'contains'
|
||||
@invalidate() if containsStart or containsEnd
|
||||
when 'never'
|
||||
if containsStart or containsEnd
|
||||
if containsStart and containsEnd
|
||||
@setRange([changedRange.end, changedRange.end])
|
||||
else if containsStart
|
||||
@setRange([changedRange.end, @getEndPosition()])
|
||||
else
|
||||
@setRange([@getStartPosition(), changedRange.start])
|
||||
[@id, previousRange]
|
||||
|
||||
handleBufferChange: (bufferChange) ->
|
||||
@consolidateObserverNotifications true, =>
|
||||
@setHeadPosition(@updatePosition(@headPosition, bufferChange, true), clip: false, bufferChanged: true)
|
||||
@setTailPosition(@updatePosition(@tailPosition, bufferChange, false), clip: false, bufferChanged: true) if @tailPosition
|
||||
|
||||
updatePosition: (position, bufferChange, isHead) ->
|
||||
{ oldRange, newRange } = bufferChange
|
||||
|
||||
return position if not isHead and oldRange.start.isEqual(position)
|
||||
return position if position.isLessThan(oldRange.end)
|
||||
|
||||
newRow = newRange.end.row
|
||||
newColumn = newRange.end.column
|
||||
|
||||
if position.row == oldRange.end.row
|
||||
newColumn += position.column - oldRange.end.column
|
||||
else
|
||||
newColumn = position.column
|
||||
newRow += position.row - oldRange.end.row
|
||||
|
||||
[newRow, newColumn]
|
||||
|
||||
notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged} = {}) ->
|
||||
return if @suppressObserverNotification
|
||||
|
||||
if newHeadPosition? and newTailPosition?
|
||||
return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition)
|
||||
else if newHeadPosition?
|
||||
return if _.isEqual(newHeadPosition, oldHeadPosition)
|
||||
else if newTailPosition?
|
||||
return if _.isEqual(newTailPosition, oldTailPosition)
|
||||
|
||||
oldHeadPosition ?= @getHeadPosition()
|
||||
newHeadPosition ?= @getHeadPosition()
|
||||
oldTailPosition ?= @getTailPosition()
|
||||
newTailPosition ?= @getTailPosition()
|
||||
valid = @isValid()
|
||||
@trigger 'changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}
|
||||
|
||||
consolidateObserverNotifications: (bufferChanged, fn) ->
|
||||
@suppressObserverNotification = true
|
||||
oldHeadPosition = @getHeadPosition()
|
||||
oldTailPosition = @getTailPosition()
|
||||
fn()
|
||||
newHeadPosition = @getHeadPosition()
|
||||
newTailPosition = @getTailPosition()
|
||||
@suppressObserverNotification = false
|
||||
@notifyObservers({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged})
|
||||
|
||||
invalidate: ->
|
||||
delete @buffer.validMarkers[@id]
|
||||
@buffer.invalidMarkers[@id] = this
|
||||
@notifyObservers(bufferChanged: true)
|
||||
|
||||
revalidate: ->
|
||||
delete @buffer.invalidMarkers[@id]
|
||||
@buffer.validMarkers[@id] = this
|
||||
@notifyObservers(bufferChanged: true)
|
||||
|
||||
_.extend BufferMarker.prototype, EventEmitter
|
||||
@@ -12,9 +12,9 @@ nodeModulesDirPath = path.join(resourcePath, "node_modules")
|
||||
bundledThemesDirPath = path.join(resourcePath, "themes")
|
||||
userThemesDirPath = path.join(configDirPath, "themes")
|
||||
userPackagesDirPath = path.join(configDirPath, "packages")
|
||||
userStoragePath = path.join(configDirPath, ".storage")
|
||||
packageDirPaths = [userPackagesDirPath]
|
||||
packageDirPaths.unshift(path.join(configDirPath, "dev", "packages")) if atom.getLoadSettings().devMode
|
||||
userPackageDirPaths = [userPackagesDirPath]
|
||||
userPackageDirPaths.unshift(path.join(configDirPath, "dev", "packages")) if atom.getLoadSettings().devMode
|
||||
userStoragePath = path.join(configDirPath, "storage")
|
||||
|
||||
# Public: Handles all of Atom's configuration details.
|
||||
#
|
||||
@@ -26,8 +26,8 @@ class Config
|
||||
themeDirPaths: [userThemesDirPath, bundledThemesDirPath]
|
||||
bundledPackageDirPaths: [nodeModulesDirPath]
|
||||
nodeModulesDirPath: nodeModulesDirPath
|
||||
packageDirPaths: packageDirPaths
|
||||
userPackagesDirPath: userPackagesDirPath
|
||||
packageDirPaths: _.clone(userPackageDirPaths)
|
||||
userPackageDirPaths: userPackageDirPaths
|
||||
userStoragePath: userStoragePath
|
||||
lessSearchPaths: [path.join(resourcePath, 'static'), path.join(resourcePath, 'vendor')]
|
||||
defaultSettings: null
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{View} = require 'space-pen'
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
{Point, Range} = require 'telepath'
|
||||
_ = require 'underscore'
|
||||
|
||||
### Internal ###
|
||||
@@ -31,6 +30,9 @@ class CursorView extends View
|
||||
@cursor.on 'destroyed.cursor-view', =>
|
||||
@needsRemoval = true
|
||||
|
||||
if @cursor.marker.isRemote()
|
||||
@addClass("site-#{@cursor.marker.getOriginSiteId()}")
|
||||
|
||||
remove: ->
|
||||
@editor.removeCursorView(this)
|
||||
@cursor.off('.cursor-view')
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
{Point, Range} = require 'telepath'
|
||||
EventEmitter = require 'event-emitter'
|
||||
_ = require 'underscore'
|
||||
|
||||
@@ -22,27 +21,28 @@ class Cursor
|
||||
@updateVisibility()
|
||||
{oldHeadScreenPosition, newHeadScreenPosition} = e
|
||||
{oldHeadBufferPosition, newHeadBufferPosition} = e
|
||||
{bufferChanged} = e
|
||||
{textChanged} = e
|
||||
return if oldHeadScreenPosition.isEqual(newHeadScreenPosition)
|
||||
|
||||
@needsAutoscroll ?= @isLastCursor() and !bufferChanged
|
||||
@needsAutoscroll ?= @isLastCursor() and !textChanged
|
||||
|
||||
movedEvent =
|
||||
oldBufferPosition: oldHeadBufferPosition
|
||||
oldScreenPosition: oldHeadScreenPosition
|
||||
newBufferPosition: newHeadBufferPosition
|
||||
newScreenPosition: newHeadScreenPosition
|
||||
bufferChanged: bufferChanged
|
||||
textChanged: textChanged
|
||||
|
||||
@trigger 'moved', movedEvent
|
||||
@editSession.trigger 'cursor-moved', movedEvent
|
||||
@marker.on 'destroyed', =>
|
||||
@destroyed = true
|
||||
@editSession.removeCursor(this)
|
||||
@trigger 'destroyed'
|
||||
@needsAutoscroll = true
|
||||
|
||||
destroy: ->
|
||||
@destroyed = true
|
||||
@marker.destroy()
|
||||
@editSession.removeCursor(this)
|
||||
@trigger 'destroyed'
|
||||
|
||||
changePosition: (options, fn) ->
|
||||
@goalColumn = null
|
||||
@@ -139,9 +139,7 @@ class Cursor
|
||||
|
||||
# Deselects whatever the cursor is selecting.
|
||||
clearSelection: ->
|
||||
if @selection
|
||||
@selection.goalBufferRange = null
|
||||
@selection.clear() unless @selection.retainSelection
|
||||
@selection?.clear()
|
||||
|
||||
# Retrieves the cursor's screen row.
|
||||
#
|
||||
@@ -427,7 +425,7 @@ class Cursor
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getIndentLevel: ->
|
||||
if @editSession.softTabs
|
||||
if @editSession.getSoftTabs()
|
||||
@getBufferColumn() / @editSession.getTabLength()
|
||||
else
|
||||
@getBufferColumn()
|
||||
|
||||
@@ -49,7 +49,9 @@ class Directory
|
||||
# pathToCheck - the {String} path to check.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
contains: (pathToCheck='') ->
|
||||
contains: (pathToCheck) ->
|
||||
return false unless pathToCheck
|
||||
|
||||
if pathToCheck.indexOf(path.join(@getPath(), path.sep)) is 0
|
||||
true
|
||||
else if pathToCheck.indexOf(path.join(@getRealPath(), path.sep)) is 0
|
||||
@@ -62,7 +64,9 @@ class Directory
|
||||
# fullPath - The {String} path to convert.
|
||||
#
|
||||
# Returns a {String}.
|
||||
relativize: (fullPath='') ->
|
||||
relativize: (fullPath) ->
|
||||
return fullPath unless fullPath
|
||||
|
||||
if fullPath is @getPath()
|
||||
''
|
||||
else if fullPath.indexOf(path.join(@getPath(), path.sep)) is 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Range = require 'range'
|
||||
{Range} = require 'telepath'
|
||||
_ = require 'underscore'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Subscriber = require 'subscriber'
|
||||
@@ -6,18 +6,30 @@ Subscriber = require 'subscriber'
|
||||
module.exports =
|
||||
class DisplayBufferMarker
|
||||
bufferMarkerSubscription: null
|
||||
headScreenPosition: null
|
||||
tailScreenPosition: null
|
||||
valid: true
|
||||
oldHeadBufferPosition: null
|
||||
oldHeadScreenPosition: null
|
||||
oldTailBufferPosition: null
|
||||
oldTailScreenPosition: null
|
||||
wasValid: true
|
||||
|
||||
### Internal ###
|
||||
|
||||
constructor: ({@bufferMarker, @displayBuffer}) ->
|
||||
@id = @bufferMarker.id
|
||||
@observeBufferMarker()
|
||||
@oldHeadBufferPosition = @getHeadBufferPosition()
|
||||
@oldHeadScreenPosition = @getHeadScreenPosition()
|
||||
@oldTailBufferPosition = @getTailBufferPosition()
|
||||
@oldTailScreenPosition = @getTailScreenPosition()
|
||||
@wasValid = @isValid()
|
||||
|
||||
@subscribe @bufferMarker, 'destroyed', => @destroyed()
|
||||
@subscribe @bufferMarker, 'changed', (event) => @notifyObservers(event)
|
||||
|
||||
### Public ###
|
||||
|
||||
copy: (attributes) ->
|
||||
@displayBuffer.getMarker(@bufferMarker.copy(attributes).id)
|
||||
|
||||
# Gets the screen range of the display marker.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
@@ -48,7 +60,7 @@ class DisplayBufferMarker
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getHeadScreenPosition: ->
|
||||
@headScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
|
||||
@displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Sets the screen position of the marker's head.
|
||||
#
|
||||
@@ -75,7 +87,7 @@ class DisplayBufferMarker
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getTailScreenPosition: ->
|
||||
@tailScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
|
||||
@displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Sets the screen position of the marker's tail.
|
||||
#
|
||||
@@ -103,13 +115,16 @@ class DisplayBufferMarker
|
||||
# This only works if there isn't already a tail position.
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
placeTail: ->
|
||||
@bufferMarker.placeTail()
|
||||
plantTail: ->
|
||||
@bufferMarker.plantTail()
|
||||
|
||||
# Removes the tail from the marker.
|
||||
clearTail: ->
|
||||
@bufferMarker.clearTail()
|
||||
|
||||
hasTail: ->
|
||||
@bufferMarker.hasTail()
|
||||
|
||||
# Returns whether the head precedes the tail in the buffer
|
||||
isReversed: ->
|
||||
@bufferMarker.isReversed()
|
||||
@@ -126,14 +141,50 @@ class DisplayBufferMarker
|
||||
isDestroyed: ->
|
||||
@bufferMarker.isDestroyed()
|
||||
|
||||
getOriginSiteId: ->
|
||||
@bufferMarker.getOriginSiteId()
|
||||
|
||||
isLocal: ->
|
||||
@bufferMarker.isLocal()
|
||||
|
||||
isRemote: ->
|
||||
@bufferMarker.isRemote()
|
||||
|
||||
getAttributes: ->
|
||||
@bufferMarker.getAttributes()
|
||||
|
||||
setAttributes: (attributes) ->
|
||||
@bufferMarker.setAttributes(attributes)
|
||||
|
||||
matchesAttributes: (attributes) ->
|
||||
@bufferMarker.matchesAttributes(attributes)
|
||||
for key, value of attributes
|
||||
return false unless @matchesAttribute(key, value)
|
||||
true
|
||||
|
||||
matchesAttribute: (key, value) ->
|
||||
switch key
|
||||
when 'startBufferRow'
|
||||
key = 'startRow'
|
||||
when 'endBufferRow'
|
||||
key = 'endRow'
|
||||
when 'containsBufferRange'
|
||||
key = 'containsRange'
|
||||
when 'containsBufferPosition'
|
||||
key = 'containsPosition'
|
||||
@bufferMarker.matchesAttribute(key, value)
|
||||
|
||||
# Destroys the marker
|
||||
destroy: ->
|
||||
@bufferMarker.destroy()
|
||||
@unsubscribe()
|
||||
|
||||
isEqual: (other) ->
|
||||
return false unless other instanceof @constructor
|
||||
@bufferMarker.isEqual(other.bufferMarker)
|
||||
|
||||
compare: (other) ->
|
||||
@bufferMarker.compare(other.bufferMarker)
|
||||
|
||||
# Returns a {String} representation of the marker
|
||||
inspect: ->
|
||||
"DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange().inspect()})"
|
||||
@@ -144,54 +195,37 @@ class DisplayBufferMarker
|
||||
delete @displayBuffer.markers[@id]
|
||||
@trigger 'destroyed'
|
||||
|
||||
observeBufferMarker: ->
|
||||
@subscribe @bufferMarker, 'destroyed', => @destroyed()
|
||||
notifyObservers: ({textChanged}) ->
|
||||
textChanged ?= false
|
||||
|
||||
@getHeadScreenPosition() # memoize current value
|
||||
@getTailScreenPosition() # memoize current value
|
||||
@subscribe @bufferMarker, 'changed', ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}) =>
|
||||
@notifyObservers
|
||||
oldHeadBufferPosition: oldHeadPosition
|
||||
newHeadBufferPosition: newHeadPosition
|
||||
oldTailBufferPosition: oldTailPosition
|
||||
newTailBufferPosition: newTailPosition
|
||||
bufferChanged: bufferChanged
|
||||
valid: valid
|
||||
newHeadBufferPosition = @getHeadBufferPosition()
|
||||
newHeadScreenPosition = @getHeadScreenPosition()
|
||||
newTailBufferPosition = @getTailBufferPosition()
|
||||
newTailScreenPosition = @getTailScreenPosition()
|
||||
isValid = @isValid()
|
||||
|
||||
notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged, valid} = {}) ->
|
||||
return unless @valid or @isValid()
|
||||
|
||||
oldHeadScreenPosition = @getHeadScreenPosition()
|
||||
newHeadScreenPosition = oldHeadScreenPosition
|
||||
oldTailScreenPosition = @getTailScreenPosition()
|
||||
newTailScreenPosition = oldTailScreenPosition
|
||||
valid ?= true
|
||||
|
||||
if valid
|
||||
@headScreenPosition = null
|
||||
newHeadScreenPosition = @getHeadScreenPosition()
|
||||
@tailScreenPosition = null
|
||||
newTailScreenPosition = @getTailScreenPosition()
|
||||
|
||||
validChanged = valid isnt @valid
|
||||
headScreenPositionChanged = not _.isEqual(newHeadScreenPosition, oldHeadScreenPosition)
|
||||
tailScreenPositionChanged = not _.isEqual(newTailScreenPosition, oldTailScreenPosition)
|
||||
return unless validChanged or headScreenPositionChanged or tailScreenPositionChanged
|
||||
|
||||
oldHeadBufferPosition ?= @getHeadBufferPosition()
|
||||
newHeadBufferPosition = @getHeadBufferPosition() ? oldHeadBufferPosition
|
||||
oldTailBufferPosition ?= @getTailBufferPosition()
|
||||
newTailBufferPosition = @getTailBufferPosition() ? oldTailBufferPosition
|
||||
@valid = valid
|
||||
changed = false
|
||||
changed = true unless _.isEqual(newHeadBufferPosition, @oldHeadBufferPosition)
|
||||
changed = true unless _.isEqual(newHeadScreenPosition, @oldHeadScreenPosition)
|
||||
changed = true unless _.isEqual(newTailBufferPosition, @oldTailBufferPosition)
|
||||
changed = true unless _.isEqual(newTailScreenPosition, @oldTailScreenPosition)
|
||||
changed = true unless _.isEqual(isValid, @wasValid)
|
||||
return unless changed
|
||||
|
||||
@trigger 'changed', {
|
||||
oldHeadScreenPosition, newHeadScreenPosition,
|
||||
oldTailScreenPosition, newTailScreenPosition,
|
||||
oldHeadBufferPosition, newHeadBufferPosition,
|
||||
oldTailBufferPosition, newTailBufferPosition,
|
||||
bufferChanged
|
||||
valid
|
||||
@oldHeadScreenPosition, newHeadScreenPosition,
|
||||
@oldTailScreenPosition, newTailScreenPosition,
|
||||
@oldHeadBufferPosition, newHeadBufferPosition,
|
||||
@oldTailBufferPosition, newTailBufferPosition,
|
||||
textChanged,
|
||||
isValid
|
||||
}
|
||||
|
||||
@oldHeadBufferPosition = newHeadBufferPosition
|
||||
@oldHeadScreenPosition = newHeadScreenPosition
|
||||
@oldTailBufferPosition = newTailBufferPosition
|
||||
@oldTailScreenPosition = newTailScreenPosition
|
||||
@wasValid = isValid
|
||||
|
||||
_.extend DisplayBufferMarker.prototype, EventEmitter
|
||||
_.extend DisplayBufferMarker.prototype, Subscriber
|
||||
|
||||
@@ -1,37 +1,64 @@
|
||||
_ = require 'underscore'
|
||||
guid = require 'guid'
|
||||
telepath = require 'telepath'
|
||||
{Point, Range} = telepath
|
||||
TokenizedBuffer = require 'tokenized-buffer'
|
||||
RowMap = require 'row-map'
|
||||
Point = require 'point'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Range = require 'range'
|
||||
Fold = require 'fold'
|
||||
Token = require 'token'
|
||||
DisplayBufferMarker = require 'display-buffer-marker'
|
||||
Subscriber = require 'subscriber'
|
||||
|
||||
DefaultSoftWrapColumn = 1000000
|
||||
|
||||
module.exports =
|
||||
class DisplayBuffer
|
||||
@idCounter: 1
|
||||
screenLines: null
|
||||
rowMap: null
|
||||
tokenizedBuffer: null
|
||||
markers: null
|
||||
foldsByMarkerId: null
|
||||
|
||||
### Internal ###
|
||||
@acceptsDocuments: true
|
||||
registerDeserializer(this)
|
||||
|
||||
@deserialize: (state) ->
|
||||
new DisplayBuffer(state)
|
||||
|
||||
constructor: (optionsOrState) ->
|
||||
if optionsOrState instanceof telepath.Document
|
||||
@state = optionsOrState
|
||||
@id = @state.get('id')
|
||||
@tokenizedBuffer = deserialize(@state.get('tokenizedBuffer'))
|
||||
@buffer = @tokenizedBuffer.buffer
|
||||
else
|
||||
{@buffer, softWrapColumn} = optionsOrState
|
||||
@id = guid.create().toString()
|
||||
@tokenizedBuffer = new TokenizedBuffer(optionsOrState)
|
||||
@state = site.createDocument
|
||||
deserializer: @constructor.name
|
||||
id: @id
|
||||
tokenizedBuffer: @tokenizedBuffer.getState()
|
||||
softWrapColumn: softWrapColumn ? DefaultSoftWrapColumn
|
||||
|
||||
constructor: (@buffer, options={}) ->
|
||||
@id = @constructor.idCounter++
|
||||
@tokenizedBuffer = new TokenizedBuffer(@buffer, options)
|
||||
@softWrapColumn = options.softWrapColumn ? Infinity
|
||||
@markers = {}
|
||||
@foldsByMarkerId = {}
|
||||
@updateAllScreenLines()
|
||||
|
||||
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
|
||||
@tokenizedBuffer.on 'grammar-changed', (grammar) => @trigger 'grammar-changed', grammar
|
||||
@tokenizedBuffer.on 'changed', @handleTokenizedBufferChange
|
||||
@subscribe @buffer, 'markers-updated', @handleMarkersUpdated
|
||||
@subscribe @buffer, 'marker-created', @handleMarkerCreated
|
||||
@subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated
|
||||
@subscribe @buffer, 'marker-created', @handleBufferMarkerCreated
|
||||
|
||||
serialize: -> @state.clone()
|
||||
getState: -> @state
|
||||
|
||||
copy: ->
|
||||
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()})
|
||||
for marker in @findMarkers(displayBufferId: @id)
|
||||
marker.copy(displayBufferId: newDisplayBuffer.id)
|
||||
newDisplayBuffer
|
||||
|
||||
updateAllScreenLines: ->
|
||||
@maxLineLength = 0
|
||||
@@ -56,7 +83,8 @@ class DisplayBuffer
|
||||
# Defines the limit at which the buffer begins to soft wrap text.
|
||||
#
|
||||
# softWrapColumn - A {Number} defining the soft wrap limit.
|
||||
setSoftWrapColumn: (@softWrapColumn) ->
|
||||
setSoftWrapColumn: (softWrapColumn) ->
|
||||
@state.set('softWrapColumn', softWrapColumn)
|
||||
start = 0
|
||||
end = @getLastRow()
|
||||
@updateAllScreenLines()
|
||||
@@ -64,6 +92,9 @@ class DisplayBuffer
|
||||
bufferDelta = 0
|
||||
@triggerChanged({ start, end, screenDelta, bufferDelta })
|
||||
|
||||
getSoftWrapColumn: ->
|
||||
@state.get('softWrapColumn')
|
||||
|
||||
# Gets the screen line for the given screen row.
|
||||
#
|
||||
# screenRow - A {Number} indicating the screen row.
|
||||
@@ -107,9 +138,15 @@ class DisplayBuffer
|
||||
createFold: (startRow, endRow) ->
|
||||
foldMarker =
|
||||
@findFoldMarker({startRow, endRow}) ?
|
||||
@buffer.markRange([[startRow, 0], [endRow, Infinity]], @foldMarkerAttributes())
|
||||
@buffer.markRange([[startRow, 0], [endRow, Infinity]], @getFoldMarkerAttributes())
|
||||
@foldForMarker(foldMarker)
|
||||
|
||||
isFoldedAtBufferRow: (bufferRow) ->
|
||||
@largestFoldContainingBufferRow(bufferRow)?
|
||||
|
||||
isFoldedAtScreenRow: (screenRow) ->
|
||||
@largestFoldContainingBufferRow(@bufferRowForScreenRow(screenRow))?
|
||||
|
||||
# Destroys the fold with the given id
|
||||
destroyFoldWithId: (id) ->
|
||||
@foldsByMarkerId[id]?.destroy()
|
||||
@@ -370,7 +407,7 @@ class DisplayBuffer
|
||||
#
|
||||
# Returns a {Number} representing the `line` position where the wrap would take place.
|
||||
# Returns `null` if a wrap wouldn't occur.
|
||||
findWrapColumn: (line, softWrapColumn=@softWrapColumn) ->
|
||||
findWrapColumn: (line, softWrapColumn=@getSoftWrapColumn()) ->
|
||||
return unless line.length > softWrapColumn
|
||||
|
||||
if /\s/.test(line[softWrapColumn])
|
||||
@@ -396,8 +433,7 @@ class DisplayBuffer
|
||||
#
|
||||
# Returns the {DisplayBufferMarker} (if it exists).
|
||||
getMarker: (id) ->
|
||||
marker = @markers[id]
|
||||
unless marker?
|
||||
unless marker = @markers[id]
|
||||
if bufferMarker = @buffer.getMarker(id)
|
||||
marker = new DisplayBufferMarker({bufferMarker, displayBuffer: this})
|
||||
@markers[id] = marker
|
||||
@@ -407,7 +443,10 @@ class DisplayBuffer
|
||||
#
|
||||
# Returns an {Array} of existing {DisplayBufferMarker}s.
|
||||
getMarkers: ->
|
||||
_.values(@markers)
|
||||
@buffer.getMarkers().map ({id}) => @getMarker(id)
|
||||
|
||||
getMarkerCount: ->
|
||||
@buffer.getMarkerCount()
|
||||
|
||||
# Constructs a new marker at the given screen range.
|
||||
#
|
||||
@@ -470,21 +509,16 @@ class DisplayBuffer
|
||||
#
|
||||
# Returns an {Array} of {DisplayBufferMarker}s
|
||||
findMarkers: (attributes) ->
|
||||
{ startBufferRow, endBufferRow, containsBufferRange, containsBufferRow } = attributes
|
||||
attributes.startRow = startBufferRow if startBufferRow?
|
||||
attributes.endRow = endBufferRow if endBufferRow?
|
||||
attributes.containsRange = containsBufferRange if containsBufferRange?
|
||||
attributes.containsRow = containsBufferRow if containsBufferRow?
|
||||
attributes = _.omit(attributes, ['startBufferRow', 'endBufferRow', 'containsBufferRange', 'containsBufferRow'])
|
||||
@buffer.findMarkers(attributes).map ({id}) => @getMarker(id)
|
||||
markers = @getMarkers().filter (marker) -> marker.matchesAttributes(attributes)
|
||||
markers.sort (a, b) -> a.compare(b)
|
||||
|
||||
findFoldMarker: (attributes) ->
|
||||
@findFoldMarkers(attributes)[0]
|
||||
|
||||
findFoldMarkers: (attributes) ->
|
||||
@buffer.findMarkers(@foldMarkerAttributes(attributes))
|
||||
@buffer.findMarkers(@getFoldMarkerAttributes(attributes))
|
||||
|
||||
foldMarkerAttributes: (attributes={}) ->
|
||||
getFoldMarkerAttributes: (attributes={}) ->
|
||||
_.extend(attributes, class: 'fold', displayBufferId: @id)
|
||||
|
||||
pauseMarkerObservers: ->
|
||||
@@ -492,10 +526,11 @@ class DisplayBuffer
|
||||
|
||||
resumeMarkerObservers: ->
|
||||
marker.resumeEvents() for marker in @getMarkers()
|
||||
@trigger 'markers-updated'
|
||||
|
||||
refreshMarkerScreenPositions: ->
|
||||
for marker in @getMarkers()
|
||||
marker.notifyObservers(bufferChanged: false)
|
||||
marker.notifyObservers(textChanged: false)
|
||||
|
||||
destroy: ->
|
||||
marker.unsubscribe() for marker in @getMarkers()
|
||||
@@ -608,15 +643,18 @@ class DisplayBuffer
|
||||
@longestScreenRow = maxLengthCandidatesStartRow + screenRow
|
||||
@maxLineLength = length
|
||||
|
||||
handleMarkersUpdated: =>
|
||||
event = @pendingChangeEvent
|
||||
@pendingChangeEvent = null
|
||||
@triggerChanged(event, false)
|
||||
handleBufferMarkersUpdated: =>
|
||||
if event = @pendingChangeEvent
|
||||
@pendingChangeEvent = null
|
||||
@triggerChanged(event, false)
|
||||
|
||||
handleMarkerCreated: (marker) =>
|
||||
new Fold(this, marker) if marker.matchesAttributes(@foldMarkerAttributes())
|
||||
handleBufferMarkerCreated: (marker) =>
|
||||
@createFoldForMarker(marker) if marker.matchesAttributes(@getFoldMarkerAttributes())
|
||||
@trigger 'marker-created', @getMarker(marker.id)
|
||||
|
||||
createFoldForMarker: (marker) ->
|
||||
new Fold(this, marker)
|
||||
|
||||
foldForMarker: (marker) ->
|
||||
@foldsByMarkerId[marker.id]
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ _ = require 'underscore'
|
||||
fsUtils = require 'fs-utils'
|
||||
path = require 'path'
|
||||
telepath = require 'telepath'
|
||||
Point = require 'point'
|
||||
guid = require 'guid'
|
||||
{Point, Range} = telepath
|
||||
Buffer = require 'text-buffer'
|
||||
LanguageMode = require 'language-mode'
|
||||
DisplayBuffer = require 'display-buffer'
|
||||
@@ -10,7 +11,6 @@ Cursor = require 'cursor'
|
||||
Selection = require 'selection'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Subscriber = require 'subscriber'
|
||||
Range = require 'range'
|
||||
TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
|
||||
# An `EditSession` manages the states between {Editor}s, {Buffer}s, and the project as a whole.
|
||||
@@ -22,60 +22,60 @@ class EditSession
|
||||
|
||||
### Internal ###
|
||||
|
||||
@version: 1
|
||||
@version: 4
|
||||
|
||||
@deserialize: (state) ->
|
||||
new EditSession(state)
|
||||
|
||||
id: null
|
||||
languageMode: null
|
||||
displayBuffer: null
|
||||
cursors: null
|
||||
remoteCursors: null
|
||||
selections: null
|
||||
softTabs: true
|
||||
softWrap: false
|
||||
remoteSelections: null
|
||||
suppressSelectionMerging: false
|
||||
|
||||
constructor: (optionsOrState) ->
|
||||
@cursors = []
|
||||
@remoteCursors = []
|
||||
@selections = []
|
||||
@remoteSelections = []
|
||||
if optionsOrState instanceof telepath.Document
|
||||
@state = optionsOrState
|
||||
{tabLength, softTabs, @softWrap} = @state.toObject()
|
||||
@buffer = deserialize(@state.get('buffer'))
|
||||
@id = @state.get('id')
|
||||
displayBuffer = deserialize(@state.get('displayBuffer'))
|
||||
@setBuffer(displayBuffer.buffer)
|
||||
@setDisplayBuffer(displayBuffer)
|
||||
for marker in @findMarkers(@getSelectionMarkerAttributes())
|
||||
marker.setAttributes(preserveFolds: true)
|
||||
@addSelection(marker)
|
||||
@setScrollTop(@state.get('scrollTop'))
|
||||
@setScrollLeft(@state.get('scrollLeft'))
|
||||
cursorScreenPosition = @state.getObject('cursorScreenPosition')
|
||||
registerEditSession = true
|
||||
else
|
||||
{@buffer, tabLength, softTabs, @softWrap} = optionsOrState
|
||||
@state = telepath.Document.create
|
||||
{buffer, displayBuffer, tabLength, softTabs, softWrap, suppressCursorCreation} = optionsOrState
|
||||
@id = guid.create().toString()
|
||||
displayBuffer ?= new DisplayBuffer({buffer, tabLength})
|
||||
@state = site.createDocument
|
||||
deserializer: 'EditSession'
|
||||
version: @constructor.version
|
||||
id: @id
|
||||
bufferId: buffer.id
|
||||
displayBuffer: displayBuffer.getState()
|
||||
softWrap: softWrap ? false
|
||||
softTabs: buffer.usesSoftTabs() ? softTabs ? true
|
||||
scrollTop: 0
|
||||
scrollLeft: 0
|
||||
cursorScreenPosition = [0, 0]
|
||||
@setBuffer(buffer)
|
||||
@setDisplayBuffer(displayBuffer)
|
||||
|
||||
if @getCursors().length is 0 and not suppressCursorCreation
|
||||
position = _.last(@getRemoteCursors())?.getBufferPosition() ? [0, 0]
|
||||
@addCursorAtBufferPosition(position)
|
||||
|
||||
@softTabs = @buffer.usesSoftTabs() ? softTabs ? true
|
||||
@languageMode = new LanguageMode(this, @buffer.getExtension())
|
||||
@displayBuffer = new DisplayBuffer(@buffer, { @languageMode, tabLength })
|
||||
@cursors = []
|
||||
@selections = []
|
||||
@addCursorAtScreenPosition(cursorScreenPosition)
|
||||
|
||||
@buffer.retain()
|
||||
@subscribe @buffer, "path-changed", =>
|
||||
project.setPath(path.dirname(@getPath())) unless project.getPath()?
|
||||
@trigger "title-changed"
|
||||
@trigger "path-changed"
|
||||
@subscribe @buffer, "contents-conflicted", => @trigger "contents-conflicted"
|
||||
@subscribe @buffer, "markers-updated", => @mergeCursors()
|
||||
@subscribe @buffer, "modified-status-changed", => @trigger "modified-status-changed"
|
||||
|
||||
@preserveCursorPositionOnBufferReload()
|
||||
|
||||
@subscribe @displayBuffer, "changed", (e) =>
|
||||
@trigger 'screen-lines-changed', e
|
||||
|
||||
@displayBuffer.on 'grammar-changed', => @handleGrammarChange()
|
||||
|
||||
@state.observe ({key, newValue}) =>
|
||||
@state.on 'changed', ({key, newValue}) =>
|
||||
switch key
|
||||
when 'scrollTop'
|
||||
@trigger 'scroll-top-changed', newValue
|
||||
@@ -84,6 +84,22 @@ class EditSession
|
||||
|
||||
project.addEditSession(this) if registerEditSession
|
||||
|
||||
setBuffer: (@buffer) ->
|
||||
@buffer.retain()
|
||||
@subscribe @buffer, "path-changed", =>
|
||||
project.setPath(path.dirname(@getPath())) unless project.getPath()?
|
||||
@trigger "title-changed"
|
||||
@trigger "path-changed"
|
||||
@subscribe @buffer, "contents-conflicted", => @trigger "contents-conflicted"
|
||||
@subscribe @buffer, "modified-status-changed", => @trigger "modified-status-changed"
|
||||
@preserveCursorPositionOnBufferReload()
|
||||
|
||||
setDisplayBuffer: (@displayBuffer) ->
|
||||
@subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
|
||||
@subscribe @displayBuffer, "changed", (e) => @trigger 'screen-lines-changed', e
|
||||
@subscribe @displayBuffer, "markers-updated", => @mergeIntersectingSelections()
|
||||
@subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
|
||||
|
||||
getViewClass: ->
|
||||
require 'editor'
|
||||
|
||||
@@ -99,22 +115,21 @@ class EditSession
|
||||
@trigger 'destroyed'
|
||||
@off()
|
||||
|
||||
serialize: ->
|
||||
@state.set
|
||||
buffer: @buffer.serialize()
|
||||
scrollTop: @getScrollTop()
|
||||
scrollLeft: @getScrollLeft()
|
||||
tabLength: @getTabLength()
|
||||
softTabs: @softTabs
|
||||
softWrap: @softWrap
|
||||
cursorScreenPosition: @getCursorScreenPosition().serialize()
|
||||
@state
|
||||
serialize: -> @state.clone()
|
||||
getState: -> @state
|
||||
|
||||
getState: -> @serialize()
|
||||
|
||||
# Creates a copy of the current {EditSession}.Returns an identical `EditSession`.
|
||||
# Creates an {EditSession} with the same initial state
|
||||
copy: ->
|
||||
EditSession.deserialize(@serialize())
|
||||
tabLength = @getTabLength()
|
||||
displayBuffer = @displayBuffer.copy()
|
||||
softTabs = @getSoftTabs()
|
||||
softWrap = @getSoftWrap()
|
||||
newEditSession = new EditSession({@buffer, displayBuffer, tabLength, softTabs, softWrap, suppressCursorCreation: true})
|
||||
newEditSession.setScrollTop(@getScrollTop())
|
||||
newEditSession.setScrollLeft(@getScrollLeft())
|
||||
for marker in @findMarkers(editSessionId: @id)
|
||||
marker.copy(editSessionId: newEditSession.id, preserveFolds: true)
|
||||
newEditSession
|
||||
|
||||
### Public ###
|
||||
|
||||
@@ -186,20 +201,26 @@ class EditSession
|
||||
# softWrapColumn - A {Number} defining the soft wrap limit
|
||||
setSoftWrapColumn: (@softWrapColumn) -> @displayBuffer.setSoftWrapColumn(@softWrapColumn)
|
||||
|
||||
getSoftTabs: ->
|
||||
@state.get('softTabs')
|
||||
|
||||
# Defines whether to use soft tabs.
|
||||
#
|
||||
# softTabs - A {Boolean} which, if `true`, indicates that you want soft tabs.
|
||||
setSoftTabs: (@softTabs) ->
|
||||
setSoftTabs: (softTabs) ->
|
||||
@state.set('softTabs', softTabs)
|
||||
|
||||
# Retrieves whether soft tabs are enabled.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
getSoftWrap: -> @softWrap
|
||||
getSoftWrap: ->
|
||||
@state.get('softWrap')
|
||||
|
||||
# Defines whether to use soft wrapping of text.
|
||||
#
|
||||
# softTabs - A {Boolean} which, if `true`, indicates that you want soft wraps.
|
||||
setSoftWrap: (@softWrap) ->
|
||||
setSoftWrap: (softWrap) ->
|
||||
@state.set('softWrap', softWrap)
|
||||
|
||||
# Retrieves that character used to indicate a tab.
|
||||
#
|
||||
@@ -275,7 +296,7 @@ class EditSession
|
||||
|
||||
# Constructs the string used for tabs.
|
||||
buildIndentString: (number) ->
|
||||
if @softTabs
|
||||
if @getSoftTabs()
|
||||
_.multiplyString(" ", number * @getTabLength())
|
||||
else
|
||||
_.multiplyString("\t", Math.floor(number))
|
||||
@@ -306,7 +327,7 @@ class EditSession
|
||||
# Retrieves the current buffer's URI.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getUri: -> @getPath()
|
||||
getUri: -> @buffer.getUri()
|
||||
|
||||
# {Delegates to: Buffer.isRowBlank}
|
||||
isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)
|
||||
@@ -483,7 +504,7 @@ class EditSession
|
||||
#
|
||||
# bufferRange - The {Range} to perform the replace in
|
||||
normalizeTabsInBufferRange: (bufferRange) ->
|
||||
return unless @softTabs
|
||||
return unless @getSoftTabs()
|
||||
@scanInBufferRange /\t/, bufferRange, ({replace}) => replace(@getTabText())
|
||||
|
||||
# Performs a cut to the end of the current line.
|
||||
@@ -596,8 +617,7 @@ class EditSession
|
||||
#
|
||||
# Returns `true` if the buffer row is folded, `false` otherwise.
|
||||
isFoldedAtBufferRow: (bufferRow) ->
|
||||
screenRow = @screenPositionForBufferPosition([bufferRow]).row
|
||||
@isFoldedAtScreenRow(screenRow)
|
||||
@displayBuffer.isFoldedAtBufferRow(bufferRow)
|
||||
|
||||
# Determines if the given screen row is folded.
|
||||
#
|
||||
@@ -605,7 +625,7 @@ class EditSession
|
||||
#
|
||||
# Returns `true` if the screen row is folded, `false` otherwise.
|
||||
isFoldedAtScreenRow: (screenRow) ->
|
||||
@lineForScreenRow(screenRow)?.fold?
|
||||
@displayBuffer.isFoldedAtScreenRow(screenRow)
|
||||
|
||||
# {Delegates to: DisplayBuffer.largestFoldContainingBufferRow}
|
||||
largestFoldContainingBufferRow: (bufferRow) ->
|
||||
@@ -769,15 +789,18 @@ class EditSession
|
||||
selection.insertText(fn(text))
|
||||
selection.setBufferRange(range)
|
||||
|
||||
pushOperation: (operation) ->
|
||||
@buffer.pushOperation(operation, this)
|
||||
|
||||
### Public ###
|
||||
|
||||
# Returns a valid {DisplayBufferMarker} object for the given id if one exists.
|
||||
getMarker: (id) ->
|
||||
@displayBuffer.getMarker(id)
|
||||
|
||||
getMarkers: ->
|
||||
@displayBuffer.getMarkers()
|
||||
|
||||
findMarkers: (attributes) ->
|
||||
@displayBuffer.findMarkers(attributes)
|
||||
|
||||
# {Delegates to: DisplayBuffer.markScreenRange}
|
||||
markScreenRange: (args...) ->
|
||||
@displayBuffer.markScreenRange(args...)
|
||||
@@ -808,6 +831,9 @@ class EditSession
|
||||
hasMultipleCursors: ->
|
||||
@getCursors().length > 1
|
||||
|
||||
getAllCursors: ->
|
||||
@getCursors().concat(@getRemoteCursors())
|
||||
|
||||
# Retrieves all the cursors.
|
||||
#
|
||||
# Returns an {Array} of {Cursor}s.
|
||||
@@ -819,14 +845,16 @@ class EditSession
|
||||
getCursor: ->
|
||||
_.last(@cursors)
|
||||
|
||||
getRemoteCursors: -> new Array(@remoteCursors...)
|
||||
|
||||
# Adds a cursor at the provided `screenPosition`.
|
||||
#
|
||||
# screenPosition - An {Array} of two numbers: the screen row, and the screen column.
|
||||
#
|
||||
# Returns the new {Cursor}.
|
||||
addCursorAtScreenPosition: (screenPosition) ->
|
||||
marker = @markScreenPosition(screenPosition, invalidationStrategy: 'never')
|
||||
@addSelection(marker).cursor
|
||||
@markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
|
||||
@getLastSelection().cursor
|
||||
|
||||
# Adds a cursor at the provided `bufferPosition`.
|
||||
#
|
||||
@@ -834,8 +862,8 @@ class EditSession
|
||||
#
|
||||
# Returns the new {Cursor}.
|
||||
addCursorAtBufferPosition: (bufferPosition) ->
|
||||
marker = @markBufferPosition(bufferPosition, invalidationStrategy: 'never')
|
||||
@addSelection(marker).cursor
|
||||
@markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
|
||||
@getLastSelection().cursor
|
||||
|
||||
# Adds a cursor to the `EditSession`.
|
||||
#
|
||||
@@ -844,7 +872,10 @@ class EditSession
|
||||
# Returns the new {Cursor}.
|
||||
addCursor: (marker) ->
|
||||
cursor = new Cursor(editSession: this, marker: marker)
|
||||
@cursors.push(cursor)
|
||||
if marker.isLocal()
|
||||
@cursors.push(cursor)
|
||||
else
|
||||
@remoteCursors.push(cursor)
|
||||
@trigger 'cursor-added', cursor
|
||||
cursor
|
||||
|
||||
@@ -863,13 +894,18 @@ class EditSession
|
||||
#
|
||||
# Returns the new {Selection}.
|
||||
addSelection: (marker, options={}) ->
|
||||
unless options.preserveFolds
|
||||
unless marker.getAttributes().preserveFolds
|
||||
@destroyFoldsIntersectingBufferRange(marker.getBufferRange())
|
||||
cursor = @addCursor(marker)
|
||||
selection = new Selection(_.extend({editSession: this, marker, cursor}, options))
|
||||
@selections.push(selection)
|
||||
|
||||
if marker.isLocal()
|
||||
@selections.push(selection)
|
||||
else
|
||||
@remoteSelections.push(selection)
|
||||
|
||||
selectionBufferRange = selection.getBufferRange()
|
||||
@mergeIntersectingSelections() unless options.suppressMerge
|
||||
@mergeIntersectingSelections()
|
||||
if selection.destroyed
|
||||
for selection in @getSelections()
|
||||
if selection.intersectsBufferRange(selectionBufferRange)
|
||||
@@ -885,9 +921,8 @@ class EditSession
|
||||
#
|
||||
# Returns the new {Selection}.
|
||||
addSelectionForBufferRange: (bufferRange, options={}) ->
|
||||
options = _.defaults({invalidationStrategy: 'never'}, options)
|
||||
marker = @markBufferRange(bufferRange, options)
|
||||
@addSelection(marker, options)
|
||||
@markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@getLastSelection()
|
||||
|
||||
# Given a buffer range, this removes all previous selections and creates a new selection for it.
|
||||
#
|
||||
@@ -906,19 +941,22 @@ class EditSession
|
||||
selections = @getSelections()
|
||||
selection.destroy() for selection in selections[bufferRanges.length...]
|
||||
|
||||
for bufferRange, i in bufferRanges
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
if selections[i]
|
||||
selections[i].setBufferRange(bufferRange, options)
|
||||
else
|
||||
@addSelectionForBufferRange(bufferRange, options)
|
||||
@mergeIntersectingSelections(options)
|
||||
@mergeIntersectingSelections options, =>
|
||||
for bufferRange, i in bufferRanges
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
if selections[i]
|
||||
selections[i].setBufferRange(bufferRange, options)
|
||||
else
|
||||
@addSelectionForBufferRange(bufferRange, options)
|
||||
|
||||
# Unselects a given selection.
|
||||
#
|
||||
# selection - The {Selection} to remove.
|
||||
removeSelection: (selection) ->
|
||||
_.remove(@selections, selection)
|
||||
if selection.isLocal()
|
||||
_.remove(@selections, selection)
|
||||
else
|
||||
_.remove(@remoteSelections, selection)
|
||||
|
||||
# Clears every selection. TODO
|
||||
clearSelections: ->
|
||||
@@ -933,6 +971,9 @@ class EditSession
|
||||
else
|
||||
false
|
||||
|
||||
getAllSelections: ->
|
||||
@getSelections().concat(@getRemoteSelections())
|
||||
|
||||
# Gets all the selections.
|
||||
#
|
||||
# Returns an {Array} of {Selection}s.
|
||||
@@ -953,14 +994,16 @@ class EditSession
|
||||
getLastSelection: ->
|
||||
_.last(@selections)
|
||||
|
||||
getRemoteSelections: -> new Array(@remoteSelections...)
|
||||
|
||||
# Gets all selections, ordered by their position in the buffer.
|
||||
#
|
||||
# Returns an {Array} of {Selection}s.
|
||||
getSelectionsOrderedByBufferPosition: ->
|
||||
@getSelections().sort (a, b) ->
|
||||
aRange = a.getBufferRange()
|
||||
bRange = b.getBufferRange()
|
||||
aRange.end.compare(bRange.end)
|
||||
@getSelections().sort (a, b) -> a.compare(b)
|
||||
|
||||
getRemoteSelectionsOrderedByBufferPosition: ->
|
||||
@getRemoteSelections().sort (a, b) -> a.compare(b)
|
||||
|
||||
# Gets the very last selection, as it's ordered in the buffer.
|
||||
#
|
||||
@@ -1031,6 +1074,9 @@ class EditSession
|
||||
getSelectedBufferRanges: ->
|
||||
selection.getBufferRange() for selection in @getSelectionsOrderedByBufferPosition()
|
||||
|
||||
getRemoteSelectedBufferRanges: ->
|
||||
selection.getBufferRange() for selection in @getRemoteSelectionsOrderedByBufferPosition()
|
||||
|
||||
# Gets the selected text of the most recently added {Selection}.
|
||||
#
|
||||
# Returns a {String}.
|
||||
@@ -1128,7 +1174,7 @@ class EditSession
|
||||
selectToScreenPosition: (position) ->
|
||||
lastSelection = @getLastSelection()
|
||||
lastSelection.selectToScreenPosition(position)
|
||||
@mergeIntersectingSelections(reverse: lastSelection.isReversed())
|
||||
@mergeIntersectingSelections(isReversed: lastSelection.isReversed())
|
||||
|
||||
# Selects the text one position right of the cursor.
|
||||
selectRight: ->
|
||||
@@ -1250,14 +1296,6 @@ class EditSession
|
||||
@setSelectedBufferRange(range)
|
||||
range
|
||||
|
||||
# Given a buffer position, this finds all markers that contain the position.
|
||||
#
|
||||
# bufferPosition - A {Point} to check
|
||||
#
|
||||
# Returns an {Array} of {Numbers}, representing marker IDs containing `bufferPosition`.
|
||||
markersForBufferPosition: (bufferPosition) ->
|
||||
@buffer.markersForPosition(bufferPosition)
|
||||
|
||||
mergeCursors: ->
|
||||
positions = []
|
||||
for cursor in @getCursors()
|
||||
@@ -1268,25 +1306,38 @@ class EditSession
|
||||
positions.push(position)
|
||||
|
||||
expandSelectionsForward: (fn) ->
|
||||
fn(selection) for selection in @getSelections()
|
||||
@mergeIntersectingSelections()
|
||||
@mergeIntersectingSelections =>
|
||||
fn(selection) for selection in @getSelections()
|
||||
|
||||
expandSelectionsBackward: (fn) ->
|
||||
fn(selection) for selection in @getSelections()
|
||||
@mergeIntersectingSelections(reverse: true)
|
||||
@mergeIntersectingSelections isReversed: true, =>
|
||||
fn(selection) for selection in @getSelections()
|
||||
|
||||
finalizeSelections: ->
|
||||
selection.finalize() for selection in @getSelections()
|
||||
|
||||
mergeIntersectingSelections: (options) ->
|
||||
for selection in @getSelections()
|
||||
otherSelections = @getSelections()
|
||||
_.remove(otherSelections, selection)
|
||||
for otherSelection in otherSelections
|
||||
if selection.intersectsWith(otherSelection)
|
||||
selection.merge(otherSelection, options)
|
||||
@mergeIntersectingSelections(options)
|
||||
return
|
||||
# Merges intersecting selections. If passed a function, it executes the function
|
||||
# with merging suppressed, then merges intersecting selections afterward.
|
||||
mergeIntersectingSelections: (args...) ->
|
||||
fn = args.pop() if _.isFunction(_.last(args))
|
||||
options = args.pop() ? {}
|
||||
|
||||
return fn?() if @suppressSelectionMerging
|
||||
|
||||
if fn?
|
||||
@suppressSelectionMerging = true
|
||||
result = fn()
|
||||
@suppressSelectionMerging = false
|
||||
|
||||
reducer = (disjointSelections, selection) ->
|
||||
intersectingSelection = _.find(disjointSelections, (s) -> s.intersectsWith(selection))
|
||||
if intersectingSelection?
|
||||
intersectingSelection.merge(selection, options)
|
||||
disjointSelections
|
||||
else
|
||||
disjointSelections.concat([selection])
|
||||
|
||||
_.reduce(@getSelections(), reducer, [])
|
||||
|
||||
preserveCursorPositionOnBufferReload: ->
|
||||
cursorPosition = null
|
||||
@@ -1315,9 +1366,11 @@ class EditSession
|
||||
|
||||
transact: (fn) -> @buffer.transact(fn)
|
||||
|
||||
commit: -> @buffer.commit()
|
||||
beginTransaction: -> @buffer.beginTransaction()
|
||||
|
||||
abort: -> @buffer.abort()
|
||||
commitTransaction: -> @buffer.commitTransaction()
|
||||
|
||||
abortTransaction: -> @buffer.abortTransaction()
|
||||
|
||||
inspect: ->
|
||||
JSON.stringify @state.toObject()
|
||||
@@ -1328,6 +1381,13 @@ class EditSession
|
||||
@unfoldAll()
|
||||
@trigger 'grammar-changed'
|
||||
|
||||
handleMarkerCreated: (marker) =>
|
||||
if marker.matchesAttributes(@getSelectionMarkerAttributes())
|
||||
@addSelection(marker)
|
||||
|
||||
getSelectionMarkerAttributes: ->
|
||||
type: 'selection', editSessionId: @id, invalidation: 'never'
|
||||
|
||||
getDebugSnapshot: ->
|
||||
[
|
||||
@displayBuffer.getDebugSnapshot()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{View, $$} = require 'space-pen'
|
||||
Buffer = require 'text-buffer'
|
||||
TextBuffer = require 'text-buffer'
|
||||
Gutter = require 'gutter'
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
{Point, Range} = require 'telepath'
|
||||
EditSession = require 'edit-session'
|
||||
CursorView = require 'cursor-view'
|
||||
SelectionView = require 'selection-view'
|
||||
@@ -97,7 +96,7 @@ class Editor extends View
|
||||
@edit(editSession)
|
||||
else if @mini
|
||||
@edit(new EditSession
|
||||
buffer: new Buffer()
|
||||
buffer: new TextBuffer
|
||||
softWrap: false
|
||||
tabLength: 2
|
||||
softTabs: true
|
||||
@@ -586,6 +585,9 @@ class Editor extends View
|
||||
# {Delegates to: EditSession.getPath}
|
||||
getPath: -> @activeEditSession?.getPath()
|
||||
|
||||
# {Delegates to: EditSession.getRelativePath}
|
||||
getRelativePath: -> @activeEditSession?.getRelativePath()
|
||||
|
||||
# {Delegates to: Buffer.getLineCount}
|
||||
getLineCount: -> @getBuffer().getLineCount()
|
||||
|
||||
@@ -704,8 +706,7 @@ class Editor extends View
|
||||
$(document).one "mouseup.editor-#{@id}", =>
|
||||
clearInterval(interval)
|
||||
$(document).off 'mousemove', moveHandler
|
||||
reverse = @activeEditSession.getLastSelection().isReversed()
|
||||
@activeEditSession.mergeIntersectingSelections({reverse})
|
||||
@activeEditSession.mergeIntersectingSelections(isReversed: @activeEditSession.getLastSelection().isReversed())
|
||||
@activeEditSession.finalizeSelections()
|
||||
@syncCursorAnimations()
|
||||
|
||||
@@ -1133,8 +1134,8 @@ class Editor extends View
|
||||
@updateLayerDimensions()
|
||||
@scrollTop(editSessionScrollTop)
|
||||
@scrollLeft(editSessionScrollLeft)
|
||||
@newCursors = @activeEditSession.getCursors()
|
||||
@newSelections = @activeEditSession.getSelections()
|
||||
@newCursors = @activeEditSession.getAllCursors()
|
||||
@newSelections = @activeEditSession.getAllSelections()
|
||||
@updateDisplay(suppressAutoScroll: true)
|
||||
|
||||
requestDisplayUpdate: ->
|
||||
@@ -1278,6 +1279,7 @@ class Editor extends View
|
||||
)
|
||||
intactRanges = newIntactRanges
|
||||
@pendingChanges = []
|
||||
|
||||
intactRanges
|
||||
|
||||
truncateIntactRanges: (intactRanges, renderFrom, renderTo) ->
|
||||
@@ -1670,8 +1672,9 @@ class Editor extends View
|
||||
console.log @activeEditSession.getCursorScopes()
|
||||
|
||||
transact: (fn) -> @activeEditSession.transact(fn)
|
||||
commit: -> @activeEditSession.commit()
|
||||
abort: -> @activeEditSession.abort()
|
||||
beginTransaction: -> @activeEditSession.beginTransaction()
|
||||
commitTransaction: -> @activeEditSession.commitTransaction()
|
||||
abortTransaction: -> @activeEditSession.abortTransaction()
|
||||
|
||||
saveDebugSnapshot: ->
|
||||
atom.showSaveDialog (path) =>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Range = require 'range'
|
||||
Point = require 'point'
|
||||
{Point, Range} = require 'telepath'
|
||||
|
||||
# Public: Represents a fold that collapses multiple buffer lines into a single
|
||||
# line on the screen.
|
||||
@@ -18,10 +17,14 @@ class Fold
|
||||
@displayBuffer.foldsByMarkerId[@marker.id] = this
|
||||
@updateDisplayBuffer()
|
||||
@marker.on 'destroyed', => @destroyed()
|
||||
@marker.on 'changed', ({isValid}) => @destroy() unless isValid
|
||||
|
||||
# Returns whether this fold is contained within another fold
|
||||
isInsideLargerFold: ->
|
||||
@displayBuffer.findMarker(class: 'fold', containsBufferRange: @getBufferRange())?
|
||||
if largestContainingFoldMarker = @displayBuffer.findMarker(class: 'fold', containsBufferRange: @getBufferRange())
|
||||
not largestContainingFoldMarker.getBufferRange().isEqual(@getBufferRange())
|
||||
else
|
||||
false
|
||||
|
||||
# Destroys this fold
|
||||
destroy: ->
|
||||
|
||||
@@ -10,6 +10,28 @@ GitUtils = require 'git-utils'
|
||||
# Ultimately, this is an overlay to the native [git-utils](https://github.com/atom/node-git) module.
|
||||
module.exports =
|
||||
class Git
|
||||
### Public ###
|
||||
# Creates a new `Git` instance.
|
||||
#
|
||||
# path - The git repository to open
|
||||
# options - A hash with one key:
|
||||
# refreshOnWindowFocus: A {Boolean} that identifies if the windows should refresh
|
||||
#
|
||||
# Returns a new {Git} object.
|
||||
@open: (path, options) ->
|
||||
return null unless path
|
||||
try
|
||||
new Git(path, options)
|
||||
catch e
|
||||
null
|
||||
|
||||
@exists: (path) ->
|
||||
if git = @open(path)
|
||||
git.destroy()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
path: null
|
||||
statuses: null
|
||||
upstream: null
|
||||
@@ -57,20 +79,6 @@ class Git
|
||||
|
||||
### Public ###
|
||||
|
||||
# Creates a new `Git` instance.
|
||||
#
|
||||
# path - The git repository to open
|
||||
# options - A hash with one key:
|
||||
# refreshOnWindowFocus: A {Boolean} that identifies if the windows should refresh
|
||||
#
|
||||
# Returns a new {Git} object.
|
||||
@open: (path, options) ->
|
||||
return null unless path
|
||||
try
|
||||
new Git(path, options)
|
||||
catch e
|
||||
null
|
||||
|
||||
# Retrieves the git repository.
|
||||
#
|
||||
# Returns a new `Repository`.
|
||||
@@ -213,6 +221,16 @@ class Git
|
||||
# Returns an object with two keys, `ahead` and `behind`. These will always be greater than zero.
|
||||
getLineDiffs: (path, text) -> @getRepo().getLineDiffs(@relativize(path), text)
|
||||
|
||||
getConfigValue: (key) -> @getRepo().getConfigValue(key)
|
||||
|
||||
getOriginUrl: -> @getConfigValue('remote.origin.url')
|
||||
|
||||
getReferenceTarget: (reference) -> @getRepo().getReferenceTarget(reference)
|
||||
|
||||
getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference)
|
||||
|
||||
hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")?
|
||||
|
||||
### Internal ###
|
||||
|
||||
refreshStatus: ->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{View, $$, $$$} = require 'space-pen'
|
||||
Range = require 'range'
|
||||
{Range} = require 'telepath'
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Range = require 'range'
|
||||
{Range} = require 'telepath'
|
||||
_ = require 'underscore'
|
||||
require 'underscore-extensions'
|
||||
{OnigRegExp} = require 'oniguruma'
|
||||
|
||||
@@ -15,10 +15,10 @@ class PaneAxis extends View
|
||||
@state = args[0]
|
||||
@state.get('children').each (child, index) => @addChild(deserialize(child), index, updateState: false)
|
||||
else
|
||||
@state = telepath.Document.create(deserializer: @className(), children: [])
|
||||
@state = site.createDocument(deserializer: @className(), children: [])
|
||||
@addChild(child) for child in args
|
||||
|
||||
@state.get('children').observe ({index, inserted, removed, site}) =>
|
||||
@state.get('children').on 'changed', ({index, inserted, removed, site}) =>
|
||||
return if site is @state.site.id
|
||||
for childState in removed
|
||||
@removeChild(@children(":eq(#{index})").view(), updateState: false)
|
||||
@@ -27,7 +27,7 @@ class PaneAxis extends View
|
||||
|
||||
addChild: (child, index=@children().length, options={}) ->
|
||||
@insertAt(index, child)
|
||||
@state.get('children').insert(index, child.serialize()) if options.updateState ? true
|
||||
@state.get('children').insert(index, child.getState()) if options.updateState ? true
|
||||
@getContainer()?.adjustPaneDimensions()
|
||||
|
||||
removeChild: (child, options={}) ->
|
||||
@@ -83,8 +83,9 @@ class PaneAxis extends View
|
||||
children.insert(childIndex + 1, newChild.getState())
|
||||
|
||||
serialize: ->
|
||||
child.serialize() for child in @children().views()
|
||||
@state
|
||||
state = @state.clone()
|
||||
state.set('children', child.serialize() for child in @children().views())
|
||||
state
|
||||
|
||||
getState: -> @state
|
||||
|
||||
|
||||
@@ -18,22 +18,27 @@ class PaneContainer extends View
|
||||
@content: ->
|
||||
@div id: 'panes'
|
||||
|
||||
initialize: (@state) ->
|
||||
if @state?
|
||||
@setRoot(deserialize(@state.get('root')), updateState: false)
|
||||
initialize: (state) ->
|
||||
if state instanceof telepath.Document
|
||||
@state = state
|
||||
@setRoot(deserialize(@state.get('root')))
|
||||
else
|
||||
@state = telepath.Document.create(deserializer: 'PaneContainer')
|
||||
@state = site.createDocument(deserializer: 'PaneContainer')
|
||||
|
||||
@state.observe ({key, newValue, site}) =>
|
||||
@state.on 'changed', ({key, newValue, site}) =>
|
||||
return if site is @state.site.id
|
||||
if key is 'root'
|
||||
@setRoot(deserialize(newValue), updateState: false)
|
||||
if newValue?
|
||||
@setRoot(deserialize(newValue))
|
||||
else
|
||||
@setRoot(null)
|
||||
|
||||
@destroyedItemStates = []
|
||||
|
||||
serialize: ->
|
||||
@getRoot()?.serialize()
|
||||
@state
|
||||
state = @state.clone()
|
||||
state.set('root', @getRoot()?.serialize())
|
||||
state
|
||||
|
||||
getState: -> @state
|
||||
|
||||
@@ -89,10 +94,10 @@ class PaneContainer extends View
|
||||
getRoot: ->
|
||||
@children().first().view()
|
||||
|
||||
setRoot: (root, options={}) ->
|
||||
setRoot: (root) ->
|
||||
@empty()
|
||||
@append(root) if root?
|
||||
@state.set(root: root?.getState()) if options.updateState ? true
|
||||
@state.set(root: root?.getState())
|
||||
|
||||
removeChild: (child) ->
|
||||
throw new Error("Removing non-existant child") unless @getRoot() is child
|
||||
|
||||
@@ -29,29 +29,28 @@ class Pane extends View
|
||||
initialize: (args...) ->
|
||||
if args[0] instanceof telepath.Document
|
||||
@state = args[0]
|
||||
@items = @state.get('items').map (item) -> deserialize(item)
|
||||
@items = _.compact(@state.get('items').map (item) -> deserialize(item))
|
||||
else
|
||||
@items = args
|
||||
@state = telepath.Document.create
|
||||
@state = site.createDocument
|
||||
deserializer: 'Pane'
|
||||
items: @items.map (item) -> item.getState?() ? item.serialize()
|
||||
|
||||
@state.get('items').observe ({index, removed, inserted, site}) =>
|
||||
@state.get('items').on 'changed', ({index, removed, inserted, site}) =>
|
||||
return if site is @state.site.id
|
||||
for itemState in removed
|
||||
@removeItemAtIndex(index, updateState: false)
|
||||
for itemState, i in inserted
|
||||
@addItem(deserialize(itemState), index + i, updateState: false)
|
||||
|
||||
@state.observe ({key, newValue, site}) =>
|
||||
@state.on 'changed', ({key, newValue, site}) =>
|
||||
return if site is @state.site.id
|
||||
@showItemForUri(newValue) if key is 'activeItemUri'
|
||||
|
||||
@viewsByClassName = {}
|
||||
@viewsByItem = new WeakMap()
|
||||
if activeItemUri = @state.get('activeItemUri')
|
||||
@showItemForUri(activeItemUri)
|
||||
else
|
||||
activeItemUri = @state.get('activeItemUri')
|
||||
unless activeItemUri? and @showItemForUri(activeItemUri)
|
||||
@showItem(@items[0]) if @items.length > 0
|
||||
|
||||
@command 'core:close', @destroyActiveItem
|
||||
@@ -226,7 +225,7 @@ class Pane extends View
|
||||
saveItemAs: (item, nextAction) ->
|
||||
return unless item.saveAs?
|
||||
|
||||
itemPath = item.getUri?()
|
||||
itemPath = item.getPath?()
|
||||
itemPath = dirname(itemPath) if itemPath
|
||||
path = atom.showSaveDialogSync(itemPath)
|
||||
if path
|
||||
@@ -272,7 +271,11 @@ class Pane extends View
|
||||
_.detect @items, (item) -> item.getUri?() is uri
|
||||
|
||||
showItemForUri: (uri) ->
|
||||
@showItem(@itemForUri(uri))
|
||||
if item = @itemForUri(uri)
|
||||
@showItem(item)
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
cleanupItemView: (item) ->
|
||||
if item instanceof $
|
||||
@@ -318,9 +321,10 @@ class Pane extends View
|
||||
@viewForItem(@activeItem)
|
||||
|
||||
serialize: ->
|
||||
@state.get('items').set(index, item.serialize()) for item, index in @items
|
||||
@state.set focused: @is(':has(:focus)')
|
||||
@state
|
||||
state = @state.clone()
|
||||
state.set('items', item.serialize() for item, index in @items)
|
||||
state.set('focused', @is(':has(:focus)'))
|
||||
state
|
||||
|
||||
getState: -> @state
|
||||
|
||||
@@ -377,7 +381,7 @@ class Pane extends View
|
||||
@closest('#panes').view()
|
||||
|
||||
copyActiveItem: ->
|
||||
deserialize(@activeItem.serialize())
|
||||
@activeItem.copy?() ? deserialize(@activeItem.serialize())
|
||||
|
||||
remove: (selector, keepData) ->
|
||||
return super if keepData
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
# Public: Represents a coordinate in the editor.
|
||||
#
|
||||
# Each `Point` is actually an object with two properties: `row` and `column`.
|
||||
module.exports =
|
||||
class Point
|
||||
|
||||
# Constructs a `Point` from a given object.
|
||||
#
|
||||
# object - This can be an {Array} (`[startRow, startColumn, endRow, endColumn]`) or an object `{row, column}`
|
||||
#
|
||||
# Returns the new {Point}.
|
||||
@fromObject: (object) ->
|
||||
if object instanceof Point
|
||||
object
|
||||
else
|
||||
if _.isArray(object)
|
||||
[row, column] = object
|
||||
else
|
||||
{ row, column } = object
|
||||
|
||||
new Point(row, column)
|
||||
|
||||
# Identifies which `Point` is smaller.
|
||||
#
|
||||
# "Smaller" means that both the `row` and `column` values of one `Point` are less than or equal
|
||||
# to the other.
|
||||
#
|
||||
# point1 - The first {Point} to check
|
||||
# point2 - The second {Point} to check
|
||||
#
|
||||
# Returns the smaller {Point}.
|
||||
@min: (point1, point2) ->
|
||||
point1 = @fromObject(point1)
|
||||
point2 = @fromObject(point2)
|
||||
if point1.isLessThanOrEqual(point2)
|
||||
point1
|
||||
else
|
||||
point2
|
||||
|
||||
# Creates a new `Point` object.
|
||||
#
|
||||
# row - A {Number} indicating the row (default: 0)
|
||||
# column - A {Number} indicating the column (default: 0)
|
||||
#
|
||||
# Returns a {Point},
|
||||
constructor: (@row=0, @column=0) ->
|
||||
|
||||
# Creates an identical copy of the `Point`.
|
||||
#
|
||||
# Returns a duplicate {Point}.
|
||||
copy: ->
|
||||
new Point(@row, @column)
|
||||
|
||||
# Adds the `column`s of two `Point`s together.
|
||||
#
|
||||
# other - The {Point} to add with
|
||||
#
|
||||
# Returns the new {Point}.
|
||||
add: (other) ->
|
||||
other = Point.fromObject(other)
|
||||
row = @row + other.row
|
||||
if other.row == 0
|
||||
column = @column + other.column
|
||||
else
|
||||
column = other.column
|
||||
|
||||
new Point(row, column)
|
||||
|
||||
# Moves a `Point`.
|
||||
#
|
||||
# In other words, the `row` values and `column` values are added to each other.
|
||||
#
|
||||
# other - The {Point} to add with
|
||||
#
|
||||
# Returns the new {Point}.
|
||||
translate: (other) ->
|
||||
other = Point.fromObject(other)
|
||||
new Point(@row + other.row, @column + other.column)
|
||||
|
||||
# Creates two new `Point`s, split down a `column` value.
|
||||
#
|
||||
# In other words, given a point, this creates `Point(0, column)` and `Point(row, column)`.
|
||||
#
|
||||
# column - The {Number} to split at
|
||||
#
|
||||
# Returns an {Array} of two {Point}s.
|
||||
splitAt: (column) ->
|
||||
if @row == 0
|
||||
rightColumn = @column - column
|
||||
else
|
||||
rightColumn = @column
|
||||
|
||||
[new Point(0, column), new Point(@row, rightColumn)]
|
||||
|
||||
# Compares two `Point`s.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
# Returns a {Number} matching the following rules:
|
||||
# * If the first `row` is greater than `other.row`, returns `1`.
|
||||
# * If the first `row` is less than `other.row`, returns `-1`.
|
||||
# * If the first `column` is greater than `other.column`, returns `1`.
|
||||
# * If the first `column` is less than `other.column`, returns `-1`.
|
||||
#
|
||||
# Otherwise, returns `0`.
|
||||
compare: (other) ->
|
||||
if @row > other.row
|
||||
1
|
||||
else if @row < other.row
|
||||
-1
|
||||
else
|
||||
if @column > other.column
|
||||
1
|
||||
else if @column < other.column
|
||||
-1
|
||||
else
|
||||
0
|
||||
|
||||
# Identifies if two `Point`s are equal.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isEqual: (other) ->
|
||||
return false unless other
|
||||
other = Point.fromObject(other)
|
||||
@row == other.row and @column == other.column
|
||||
|
||||
# Identifies if one `Point` is less than another.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isLessThan: (other) ->
|
||||
@compare(other) < 0
|
||||
|
||||
# Identifies if one `Point` is less than or equal to another.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isLessThanOrEqual: (other) ->
|
||||
@compare(other) <= 0
|
||||
|
||||
# Identifies if one `Point` is greater than another.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isGreaterThan: (other) ->
|
||||
@compare(other) > 0
|
||||
|
||||
# Identifies if one `Point` is greater than or equal to another.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isGreaterThanOrEqual: (other) ->
|
||||
@compare(other) >= 0
|
||||
|
||||
# Converts the {Point} to a String.
|
||||
#
|
||||
# Returns a {String}.
|
||||
toString: ->
|
||||
"#{@row},#{@column}"
|
||||
|
||||
# Converts the {Point} to an Array.
|
||||
#
|
||||
# Returns an {Array}.
|
||||
toArray: ->
|
||||
[@row, @column]
|
||||
|
||||
### Internal ###
|
||||
|
||||
inspect: ->
|
||||
"(#{@row}, #{@column})"
|
||||
|
||||
# Internal:
|
||||
serialize: ->
|
||||
@toArray()
|
||||
@@ -1,13 +1,17 @@
|
||||
fsUtils = require 'fs-utils'
|
||||
path = require 'path'
|
||||
url = require 'url'
|
||||
|
||||
_ = require 'underscore'
|
||||
$ = require 'jquery'
|
||||
Range = require 'range'
|
||||
Buffer = require 'text-buffer'
|
||||
telepath = require 'telepath'
|
||||
{Range} = telepath
|
||||
TextBuffer = require 'text-buffer'
|
||||
EditSession = require 'edit-session'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Directory = require 'directory'
|
||||
BufferedNodeProcess = require 'buffered-node-process'
|
||||
Git = require 'git'
|
||||
|
||||
# Public: Represents a project that's opened in Atom.
|
||||
#
|
||||
@@ -15,9 +19,12 @@ BufferedNodeProcess = require 'buffered-node-process'
|
||||
# of directories and files that you can operate on.
|
||||
module.exports =
|
||||
class Project
|
||||
@acceptsDocuments: true
|
||||
@version: 1
|
||||
|
||||
registerDeserializer(this)
|
||||
|
||||
@deserialize: (state) -> new Project(state.path)
|
||||
@deserialize: (state) -> new Project(state)
|
||||
|
||||
@openers: []
|
||||
|
||||
@@ -27,6 +34,11 @@ class Project
|
||||
@unregisterOpener: (opener) ->
|
||||
_.remove(@openers, opener)
|
||||
|
||||
@pathForRepositoryUrl: (repoUrl) ->
|
||||
[repoName] = url.parse(repoUrl).path.split('/')[-1..]
|
||||
repoName = repoName.replace(/\.git$/, '')
|
||||
path.join(config.get('core.projectHome'), repoName)
|
||||
|
||||
tabLength: 2
|
||||
softTabs: true
|
||||
softWrap: false
|
||||
@@ -39,20 +51,50 @@ class Project
|
||||
destroy: ->
|
||||
editSession.destroy() for editSession in @getEditSessions()
|
||||
buffer.release() for buffer in @getBuffers()
|
||||
if @repo?
|
||||
@repo.destroy()
|
||||
@repo = null
|
||||
|
||||
### Public ###
|
||||
|
||||
# Establishes a new project at a given path.
|
||||
#
|
||||
# path - The {String} name of the path
|
||||
constructor: (path) ->
|
||||
@setPath(path)
|
||||
constructor: (pathOrState) ->
|
||||
@editSessions = []
|
||||
@buffers = []
|
||||
|
||||
if pathOrState instanceof telepath.Document
|
||||
@state = pathOrState
|
||||
if projectPath = @state.remove('path')
|
||||
@setPath(projectPath)
|
||||
else
|
||||
@setPath(@constructor.pathForRepositoryUrl(@state.get('repoUrl')))
|
||||
|
||||
@state.get('buffers').each (bufferState) =>
|
||||
if buffer = deserialize(bufferState, project: this)
|
||||
@addBuffer(buffer, updateState: false)
|
||||
else
|
||||
@state = site.createDocument(deserializer: @constructor.name, version: @constructor.version, buffers: [])
|
||||
@setPath(pathOrState)
|
||||
|
||||
@state.get('buffers').on 'changed', ({inserted, removed, index, site}) =>
|
||||
return if site is @state.site.id
|
||||
|
||||
for removedBuffer in removed
|
||||
@removeBufferAtIndex(index, updateState: false)
|
||||
for insertedBuffer, i in inserted
|
||||
@addBufferAtIndex(deserialize(insertedBuffer, project: this), index + i, updateState: false)
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'Project'
|
||||
path: @getPath()
|
||||
state = @state.clone()
|
||||
state.set('path', @getPath())
|
||||
state.set('buffers', buffer.serialize() for buffer in @getBuffers())
|
||||
state
|
||||
|
||||
getState: -> @state
|
||||
|
||||
getRepo: -> @repo
|
||||
|
||||
# Retrieves the project path.
|
||||
#
|
||||
@@ -69,8 +111,15 @@ class Project
|
||||
if projectPath?
|
||||
directory = if fsUtils.isDirectorySync(projectPath) then projectPath else path.dirname(projectPath)
|
||||
@rootDirectory = new Directory(directory)
|
||||
@repo = Git.open(projectPath)
|
||||
else
|
||||
@rootDirectory = null
|
||||
if @repo?
|
||||
@repo.destroy()
|
||||
@repo = null
|
||||
|
||||
if originUrl = @repo?.getOriginUrl()
|
||||
@state.set('repoUrl', originUrl)
|
||||
|
||||
@trigger "path-changed"
|
||||
|
||||
@@ -110,7 +159,7 @@ class Project
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
ignoreRepositoryPath: (repositoryPath) ->
|
||||
config.get("core.hideGitIgnoredFiles") and git?.isPathIgnored(path.join(@getPath(), repositoryPath))
|
||||
config.get("core.hideGitIgnoredFiles") and @repo?.isPathIgnored(path.join(@getPath(), repositoryPath))
|
||||
|
||||
# Given a uri, this resolves it relative to the project directory. If the path
|
||||
# is already absolute or if it is prefixed with a scheme, it is returned unchanged.
|
||||
@@ -168,6 +217,7 @@ class Project
|
||||
#
|
||||
# Returns either an {EditSession} (for text) or {ImageEditSession} (for images).
|
||||
open: (filePath, options={}) ->
|
||||
filePath = @resolve(filePath) if filePath?
|
||||
for opener in @constructor.openers
|
||||
return resource if resource = opener(filePath, options)
|
||||
|
||||
@@ -215,23 +265,40 @@ class Project
|
||||
else
|
||||
@buildBuffer(null, text)
|
||||
|
||||
bufferForId: (id) ->
|
||||
_.find @buffers, (buffer) -> buffer.id is id
|
||||
|
||||
# Given a file path, this sets its {Buffer}.
|
||||
#
|
||||
# filePath - A {String} representing a path
|
||||
# text - The {String} text to use as a buffer
|
||||
#
|
||||
# Returns the {Buffer}.
|
||||
buildBuffer: (filePath, text) ->
|
||||
buffer = new Buffer(filePath, text)
|
||||
@buffers.push buffer
|
||||
buildBuffer: (filePath, initialText) ->
|
||||
filePath = @resolve(filePath) if filePath?
|
||||
buffer = new TextBuffer({project: this, filePath, initialText})
|
||||
@addBuffer(buffer)
|
||||
@trigger 'buffer-created', buffer
|
||||
buffer
|
||||
|
||||
addBuffer: (buffer, options={}) ->
|
||||
@addBufferAtIndex(buffer, @buffers.length, options)
|
||||
|
||||
addBufferAtIndex: (buffer, index, options={}) ->
|
||||
@buffers[index] = buffer
|
||||
@state.get('buffers').insert(index, buffer.getState()) if options.updateState ? true
|
||||
|
||||
# Removes a {Buffer} association from the project.
|
||||
#
|
||||
# Returns the removed {Buffer}.
|
||||
removeBuffer: (buffer) ->
|
||||
_.remove(@buffers, buffer)
|
||||
index = @buffers.indexOf(buffer)
|
||||
@removeBufferAtIndex(index) unless index is -1
|
||||
|
||||
removeBufferAtIndex: (index, options={}) ->
|
||||
[buffer] = @buffers.splice(index, 1)
|
||||
@state.get('buffers').remove(index) if options.updateState ? true
|
||||
buffer?.destroy()
|
||||
|
||||
# Performs a search across all the files in the project.
|
||||
#
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
Point = require 'point'
|
||||
_ = require 'underscore'
|
||||
|
||||
# Public: Indicates a region within the editor.
|
||||
#
|
||||
# To better visualize how this works, imagine a rectangle.
|
||||
# Each quadrant of the rectangle is analogus to a range, as ranges contain a
|
||||
# starting row and a starting column, as well as an ending row, and an ending column.
|
||||
#
|
||||
# Each `Range` is actually constructed of two `Point` objects, labelled `start` and `end`.
|
||||
module.exports =
|
||||
class Range
|
||||
|
||||
# Constructs a `Range` from a given object.
|
||||
#
|
||||
# object - This can be an {Array} (`[startRow, startColumn, endRow, endColumn]`) or an object `{start: Point, end: Point}`
|
||||
#
|
||||
# Returns the new {Range}.
|
||||
@fromObject: (object) ->
|
||||
if _.isArray(object)
|
||||
new Range(object...)
|
||||
else if object instanceof Range
|
||||
object
|
||||
else
|
||||
new Range(object.start, object.end)
|
||||
|
||||
# Constructs a `Range` from a {Point}, and the delta values beyond that point.
|
||||
#
|
||||
# point - A {Point} to start with
|
||||
# rowDelta - A {Number} indicating how far from the starting {Point} the range's row should be
|
||||
# columnDelta - A {Number} indicating how far from the starting {Point} the range's column should be
|
||||
#
|
||||
# Returns the new {Range}.
|
||||
@fromPointWithDelta: (pointA, rowDelta, columnDelta) ->
|
||||
pointA = Point.fromObject(pointA)
|
||||
pointB = new Point(pointA.row + rowDelta, pointA.column + columnDelta)
|
||||
new Range(pointA, pointB)
|
||||
|
||||
# Creates a new `Range` object based on two {Point}s.
|
||||
#
|
||||
# pointA - The first {Point} (default: `0, 0`)
|
||||
# pointB - The second {Point} (default: `0, 0`)
|
||||
constructor: (pointA = new Point(0, 0), pointB = new Point(0, 0)) ->
|
||||
pointA = Point.fromObject(pointA)
|
||||
pointB = Point.fromObject(pointB)
|
||||
|
||||
if pointA.compare(pointB) <= 0
|
||||
@start = pointA
|
||||
@end = pointB
|
||||
else
|
||||
@start = pointB
|
||||
@end = pointA
|
||||
|
||||
# Creates an identical copy of the `Range`.
|
||||
#
|
||||
# Returns a duplicate {Range}.
|
||||
copy: ->
|
||||
new Range(@start.copy(), @end.copy())
|
||||
|
||||
# Identifies if two `Range`s are equal.
|
||||
#
|
||||
# All four points (`start.row`, `start.column`, `end.row`, `end.column`) must be
|
||||
# equal for this method to return `true`.
|
||||
#
|
||||
# other - A different {Range} to check against
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isEqual: (other) ->
|
||||
if _.isArray(other) and other.length == 2
|
||||
other = new Range(other...)
|
||||
|
||||
other.start.isEqual(@start) and other.end.isEqual(@end)
|
||||
|
||||
# Returns an integer (-1, 0, 1) indicating whether this range is less than, equal,
|
||||
# or greater than the given range when sorting.
|
||||
#
|
||||
# Ranges that start earlier are considered "less than" ranges that start later.
|
||||
# If ranges start at the same location, the larger range sorts before the smaller
|
||||
# range.
|
||||
#
|
||||
# other - A {Range} to compare against.
|
||||
#
|
||||
# Returns a {Number}, either -1, 0, or 1.
|
||||
compare: (other) ->
|
||||
other = Range.fromObject(other)
|
||||
if value = @start.compare(other.start)
|
||||
value
|
||||
else
|
||||
other.end.compare(@end)
|
||||
|
||||
# Identifies if the `Range` is on the same line.
|
||||
#
|
||||
# In other words, if `start.row` is equal to `end.row`.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isSingleLine: ->
|
||||
@start.row == @end.row
|
||||
|
||||
# Identifies if two `Range`s are on the same line.
|
||||
#
|
||||
# other - A different {Range} to check against
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
coversSameRows: (other) ->
|
||||
@start.row == other.start.row && @end.row == other.end.row
|
||||
|
||||
# Adds a new point to the `Range`s `start` and `end`.
|
||||
#
|
||||
# point - A new {Point} to add
|
||||
#
|
||||
# Returns the new {Range}.
|
||||
add: (point) ->
|
||||
new Range(@start.add(point), @end.add(point))
|
||||
|
||||
# Moves a `Range`.
|
||||
#
|
||||
# In other words, the starting and ending `row` values, and the starting and ending
|
||||
# `column` values, are added to each other.
|
||||
#
|
||||
# startPoint - The {Point} to move the `Range`s `start` by
|
||||
# endPoint - The {Point} to move the `Range`s `end` by
|
||||
#
|
||||
# Returns the new {Range}.
|
||||
translate: (startPoint, endPoint=startPoint) ->
|
||||
new Range(@start.translate(startPoint), @end.translate(endPoint))
|
||||
|
||||
# Identifies if two `Range`s intersect each other.
|
||||
#
|
||||
# otherRange - A different {Range} to check against
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
intersectsWith: (otherRange) ->
|
||||
if @start.isLessThanOrEqual(otherRange.start)
|
||||
@end.isGreaterThanOrEqual(otherRange.start)
|
||||
else
|
||||
otherRange.intersectsWith(this)
|
||||
|
||||
# Identifies if a second `Range` is contained within a first.
|
||||
#
|
||||
# otherRange - A different {Range} to check against
|
||||
# options - A hash with a single option:
|
||||
# exclusive: A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
containsRange: (otherRange, {exclusive} = {}) ->
|
||||
{ start, end } = Range.fromObject(otherRange)
|
||||
@containsPoint(start, {exclusive}) and @containsPoint(end, {exclusive})
|
||||
|
||||
# Identifies if a `Range` contains a {Point}.
|
||||
#
|
||||
# point - A {Point} to check against
|
||||
# options - A hash with a single option:
|
||||
# exclusive: A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
containsPoint: (point, {exclusive} = {}) ->
|
||||
point = Point.fromObject(point)
|
||||
if exclusive
|
||||
point.isGreaterThan(@start) and point.isLessThan(@end)
|
||||
else
|
||||
point.isGreaterThanOrEqual(@start) and point.isLessThanOrEqual(@end)
|
||||
|
||||
# Identifies if a `Range` contains a row.
|
||||
#
|
||||
# row - A row {Number} to check against
|
||||
# options - A hash with a single option:
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
containsRow: (row) ->
|
||||
@start.row <= row <= @end.row
|
||||
|
||||
# Constructs a union between two `Range`s.
|
||||
#
|
||||
# otherRange - A different {Range} to unionize with
|
||||
#
|
||||
# Returns the new {Range}.
|
||||
union: (otherRange) ->
|
||||
start = if @start.isLessThan(otherRange.start) then @start else otherRange.start
|
||||
end = if @end.isGreaterThan(otherRange.end) then @end else otherRange.end
|
||||
new Range(start, end)
|
||||
|
||||
# Identifies if a `Range` is empty.
|
||||
#
|
||||
# A `Range` is empty if its start {Point} matches its end.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isEmpty: ->
|
||||
@start.isEqual(@end)
|
||||
|
||||
# Calculates the difference between a `Range`s `start` and `end` points.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
toDelta: ->
|
||||
rows = @end.row - @start.row
|
||||
if rows == 0
|
||||
columns = @end.column - @start.column
|
||||
else
|
||||
columns = @end.column
|
||||
new Point(rows, columns)
|
||||
|
||||
# Calculates the number of rows a `Range`s contains.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getRowCount: ->
|
||||
@end.row - @start.row + 1
|
||||
|
||||
# Returns an array of all rows in a `Range`
|
||||
#
|
||||
# Returns an {Array}
|
||||
getRows: ->
|
||||
[@start.row..@end.row]
|
||||
|
||||
### Internal ###
|
||||
|
||||
inspect: ->
|
||||
"[#{@start.inspect()} - #{@end.inspect()}]"
|
||||
@@ -1,3 +1,4 @@
|
||||
path = require 'path'
|
||||
$ = require 'jquery'
|
||||
{$$} = require 'space-pen'
|
||||
fsUtils = require 'fs-utils'
|
||||
@@ -26,6 +27,7 @@ class RootView extends View
|
||||
excludeVcsIgnoredPaths: false
|
||||
disabledPackages: []
|
||||
themes: ['atom-dark-ui', 'atom-dark-syntax']
|
||||
projectHome: path.join(atom.getHomeDirPath(), 'github')
|
||||
|
||||
### Internal ###
|
||||
@acceptsDocuments: true
|
||||
@@ -34,7 +36,7 @@ class RootView extends View
|
||||
@div id: 'root-view', =>
|
||||
@div id: 'horizontal', outlet: 'horizontal', =>
|
||||
@div id: 'vertical', outlet: 'vertical', =>
|
||||
@subview 'panes', deserialize(state?.get?('panes')) ? new PaneContainer
|
||||
@div outlet: 'panes'
|
||||
|
||||
@deserialize: (state) ->
|
||||
new RootView(state)
|
||||
@@ -42,8 +44,16 @@ class RootView extends View
|
||||
initialize: (state={}) ->
|
||||
if state instanceof telepath.Document
|
||||
@state = state
|
||||
panes = deserialize(state.get('panes'))
|
||||
else
|
||||
@state = telepath.Document.create(_.extend({version: RootView.version, deserializer: 'RootView', panes: @panes.serialize()}, state))
|
||||
panes = new PaneContainer
|
||||
@state = site.createDocument
|
||||
deserializer: @constructor.name
|
||||
version: @constructor.version
|
||||
panes: panes.getState()
|
||||
|
||||
@panes.replaceWith(panes)
|
||||
@panes = panes
|
||||
|
||||
@on 'focus', (e) => @handleFocus(e)
|
||||
@subscribe $(window), 'focus', (e) =>
|
||||
@@ -82,9 +92,12 @@ class RootView extends View
|
||||
_.nextTick => atom.setFullScreen(@state.get('fullScreen'))
|
||||
|
||||
serialize: ->
|
||||
@panes.serialize()
|
||||
@state.set('fullScreen', atom.isFullScreen())
|
||||
@state
|
||||
state = @state.clone()
|
||||
state.set('panes', @panes.serialize())
|
||||
state.set('fullScreen', atom.isFullScreen())
|
||||
state
|
||||
|
||||
getState: -> @state
|
||||
|
||||
handleFocus: (e) ->
|
||||
if @getActivePane()
|
||||
@@ -113,7 +126,7 @@ class RootView extends View
|
||||
# Returns the `EditSession` for the file URI.
|
||||
open: (path, options = {}) ->
|
||||
changeFocus = options.changeFocus ? true
|
||||
path = project.resolve(path) if path?
|
||||
path = project.relativize(path)
|
||||
if activePane = @getActivePane()
|
||||
editSession = activePane.itemForUri(path) ? project.open(path)
|
||||
activePane.showItem(editSession)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
{Point, Range} = require 'telepath'
|
||||
{View, $$} = require 'space-pen'
|
||||
|
||||
# Internal:
|
||||
@@ -19,6 +18,9 @@ class SelectionView extends View
|
||||
@needsRemoval = true
|
||||
@editor.requestDisplayUpdate()
|
||||
|
||||
if @selection.marker.isRemote()
|
||||
@addClass("site-#{@selection.marker.getOriginSiteId()}")
|
||||
|
||||
updateDisplay: ->
|
||||
@clearRegions()
|
||||
range = @getScreenRange()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Range = require 'range'
|
||||
{Range} = require 'telepath'
|
||||
EventEmitter = require 'event-emitter'
|
||||
_ = require 'underscore'
|
||||
|
||||
@@ -9,25 +9,21 @@ class Selection
|
||||
marker: null
|
||||
editSession: null
|
||||
initialScreenRange: null
|
||||
goalBufferRange: null
|
||||
wordwise: false
|
||||
needsAutoscroll: null
|
||||
|
||||
### Internal ###
|
||||
|
||||
constructor: ({@cursor, @marker, @editSession, @goalBufferRange}) ->
|
||||
constructor: ({@cursor, @marker, @editSession}) ->
|
||||
@cursor.selection = this
|
||||
@marker.on 'changed', => @screenRangeChanged()
|
||||
@cursor.on 'destroyed.selection', =>
|
||||
@cursor = null
|
||||
@destroy()
|
||||
@marker.on 'destroyed', =>
|
||||
@destroyed = true
|
||||
@editSession.removeSelection(this)
|
||||
@trigger 'destroyed' unless @editSession.destroyed
|
||||
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@destroyed = true
|
||||
@editSession.removeSelection(this)
|
||||
@trigger 'destroyed' unless @editSession.destroyed
|
||||
@cursor?.destroy()
|
||||
@marker.destroy()
|
||||
|
||||
finalize: ->
|
||||
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
|
||||
@@ -88,7 +84,7 @@ class Selection
|
||||
setBufferRange: (bufferRange, options={}) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
@needsAutoscroll = options.autoscroll
|
||||
options.reverse ?= @isReversed()
|
||||
options.isReversed ?= @isReversed()
|
||||
@editSession.destroyFoldsIntersectingBufferRange(bufferRange) unless options.preserveFolds
|
||||
@modifySelection =>
|
||||
@cursor.needsAutoscroll = false if options.autoscroll?
|
||||
@@ -112,7 +108,8 @@ class Selection
|
||||
|
||||
# Clears the selection, moving the marker to move to the head.
|
||||
clear: ->
|
||||
@marker.clearTail()
|
||||
@marker.setAttributes(goalBufferRange: null)
|
||||
@marker.clearTail() unless @retainSelection
|
||||
|
||||
# Modifies the selection to mark the current word.
|
||||
#
|
||||
@@ -149,7 +146,7 @@ class Selection
|
||||
@modifySelection =>
|
||||
if @initialScreenRange
|
||||
if position.isLessThan(@initialScreenRange.start)
|
||||
@marker.setScreenRange([position, @initialScreenRange.end], reverse: true)
|
||||
@marker.setScreenRange([position, @initialScreenRange.end], isReversed: true)
|
||||
else
|
||||
@marker.setScreenRange([@initialScreenRange.start, position])
|
||||
else
|
||||
@@ -228,7 +225,7 @@ class Selection
|
||||
|
||||
# Moves the selection down one row.
|
||||
addSelectionBelow: ->
|
||||
range = (@goalBufferRange ? @getBufferRange()).copy()
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
nextRow = range.end.row + 1
|
||||
|
||||
for row in [nextRow..@editSession.getLastBufferRow()]
|
||||
@@ -241,12 +238,15 @@ class Selection
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true)
|
||||
@editSession.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
|
||||
getGoalBufferRange: ->
|
||||
@marker.getAttributes().goalBufferRange
|
||||
|
||||
# Moves the selection up one row.
|
||||
addSelectionAbove: ->
|
||||
range = (@goalBufferRange ? @getBufferRange()).copy()
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
previousRow = range.end.row - 1
|
||||
|
||||
for row in [previousRow..0]
|
||||
@@ -259,7 +259,7 @@ class Selection
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true)
|
||||
@editSession.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
|
||||
# Replaces text at the current selection.
|
||||
@@ -283,7 +283,7 @@ class Selection
|
||||
|
||||
newBufferRange = @editSession.buffer.change(oldBufferRange, text)
|
||||
if options.select
|
||||
@setBufferRange(newBufferRange, reverse: wasReversed)
|
||||
@setBufferRange(newBufferRange, isReversed: wasReversed)
|
||||
else
|
||||
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
|
||||
|
||||
@@ -490,7 +490,7 @@ class Selection
|
||||
|
||||
modifySelection: (fn) ->
|
||||
@retainSelection = true
|
||||
@placeTail()
|
||||
@plantTail()
|
||||
fn()
|
||||
@retainSelection = false
|
||||
|
||||
@@ -499,8 +499,8 @@ class Selection
|
||||
# This only works if there isn't already a tail position.
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
placeTail: ->
|
||||
@marker.placeTail()
|
||||
plantTail: ->
|
||||
@marker.plantTail()
|
||||
|
||||
# Identifies if a selection intersects with a given buffer range.
|
||||
#
|
||||
@@ -523,13 +523,24 @@ class Selection
|
||||
# otherSelection - A `Selection` to merge with
|
||||
# options - A hash of options matching those found in {.setBufferRange}
|
||||
merge: (otherSelection, options) ->
|
||||
myGoalBufferRange = @getGoalBufferRange()
|
||||
otherGoalBufferRange = otherSelection.getGoalBufferRange()
|
||||
if myGoalBufferRange? and otherGoalBufferRange?
|
||||
options.goalBufferRange = myGoalBufferRange.union(otherGoalBufferRange)
|
||||
else
|
||||
options.goalBufferRange = myGoalBufferRange ? otherGoalBufferRange
|
||||
@setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options)
|
||||
if @goalBufferRange and otherSelection.goalBufferRange
|
||||
@goalBufferRange = @goalBufferRange.union(otherSelection.goalBufferRange)
|
||||
else if otherSelection.goalBufferRange
|
||||
@goalBufferRange = otherSelection.goalBufferRange
|
||||
otherSelection.destroy()
|
||||
|
||||
compare: (other) ->
|
||||
@getBufferRange().compare(other.getBufferRange())
|
||||
|
||||
isLocal: ->
|
||||
@marker.isLocal()
|
||||
|
||||
isRemote: ->
|
||||
@marker.isRemote()
|
||||
|
||||
### Internal ###
|
||||
|
||||
screenRangeChanged: ->
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
_ = require 'underscore'
|
||||
telepath = require 'telepath'
|
||||
{Point, Range} = telepath
|
||||
fsUtils = require 'fs-utils'
|
||||
File = require 'file'
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
EventEmitter = require 'event-emitter'
|
||||
UndoManager = require 'undo-manager'
|
||||
BufferChangeOperation = require 'buffer-change-operation'
|
||||
BufferMarker = require 'buffer-marker'
|
||||
guid = require 'guid'
|
||||
|
||||
# Public: Represents the contents of a file.
|
||||
#
|
||||
@@ -14,54 +12,69 @@ BufferMarker = require 'buffer-marker'
|
||||
# the case, as a `Buffer` could be an unsaved chunk of text.
|
||||
module.exports =
|
||||
class TextBuffer
|
||||
@idCounter = 1
|
||||
@acceptsDocuments: true
|
||||
@version: 2
|
||||
registerDeserializer(this)
|
||||
|
||||
@deserialize: (state, params) ->
|
||||
new this(state, params)
|
||||
|
||||
stoppedChangingDelay: 300
|
||||
stoppedChangingTimeout: null
|
||||
undoManager: null
|
||||
cachedDiskContents: null
|
||||
cachedMemoryContents: null
|
||||
conflict: false
|
||||
lines: null
|
||||
lineEndings: null
|
||||
file: null
|
||||
validMarkers: null
|
||||
invalidMarkers: null
|
||||
refcount: 0
|
||||
|
||||
# Creates a new buffer.
|
||||
#
|
||||
# path - A {String} representing the file path
|
||||
# initialText - A {String} setting the starting text
|
||||
constructor: (path, initialText) ->
|
||||
@id = @constructor.idCounter++
|
||||
@nextMarkerId = 1
|
||||
@validMarkers = {}
|
||||
@invalidMarkers = {}
|
||||
@lines = ['']
|
||||
@lineEndings = []
|
||||
|
||||
if path
|
||||
@setPath(path)
|
||||
if initialText?
|
||||
@setText(initialText)
|
||||
@updateCachedDiskContents()
|
||||
else if fsUtils.exists(path)
|
||||
@reload()
|
||||
else
|
||||
@setText('')
|
||||
constructor: (optionsOrState={}, params={}) ->
|
||||
if optionsOrState instanceof telepath.Document
|
||||
{@project} = params
|
||||
@state = optionsOrState
|
||||
@id = @state.get('id')
|
||||
filePath = @state.get('relativePath')
|
||||
@text = @state.get('text')
|
||||
reloadFromDisk = @state.get('isModified') is false
|
||||
else
|
||||
@setText(initialText ? '')
|
||||
{@project, filePath, initialText} = optionsOrState
|
||||
@text = site.createDocument(initialText ? '', shareStrings: true)
|
||||
reloadFromDisk = true
|
||||
@id = guid.create().toString()
|
||||
@state = site.createDocument
|
||||
id: @id
|
||||
deserializer: @constructor.name
|
||||
version: @constructor.version
|
||||
text: @text
|
||||
|
||||
@undoManager = new UndoManager(this)
|
||||
@text.on 'changed', @handleTextChange
|
||||
@text.on 'marker-created', (marker) => @trigger 'marker-created', marker
|
||||
@text.on 'markers-updated', => @trigger 'markers-updated'
|
||||
|
||||
if filePath
|
||||
@setPath(@project.resolve(filePath))
|
||||
if fsUtils.exists(@getPath())
|
||||
@updateCachedDiskContents()
|
||||
@reload() if reloadFromDisk and @isModified()
|
||||
@text.clearUndoStack()
|
||||
|
||||
### Internal ###
|
||||
|
||||
handleTextChange: (event) =>
|
||||
@cachedMemoryContents = null
|
||||
@conflict = false if @conflict and !@isModified()
|
||||
bufferChangeEvent = _.pick(event, 'oldRange', 'newRange', 'oldText', 'newText')
|
||||
@trigger 'changed', bufferChangeEvent
|
||||
@scheduleModifiedEvents()
|
||||
|
||||
destroy: ->
|
||||
throw new Error("Destroying buffer twice with path '#{@getPath()}'") if @destroyed
|
||||
@file?.off()
|
||||
@destroyed = true
|
||||
project?.removeBuffer(this)
|
||||
unless @destroyed
|
||||
@file?.off()
|
||||
@destroyed = true
|
||||
@project?.removeBuffer(this)
|
||||
|
||||
retain: ->
|
||||
@refcount++
|
||||
@@ -73,12 +86,13 @@ class TextBuffer
|
||||
this
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'TextBuffer'
|
||||
path: @getPath()
|
||||
text: @getText() if @isModified()
|
||||
state = @state.clone()
|
||||
state.set('isModified', @isModified())
|
||||
for marker in state.get('text').getMarkers() when marker.isRemote()
|
||||
marker.destroy()
|
||||
state
|
||||
|
||||
@deserialize: ({path, text}) ->
|
||||
project.bufferForPath(path, text)
|
||||
getState: -> @state
|
||||
|
||||
subscribeToFile: ->
|
||||
@file.on "contents-changed", =>
|
||||
@@ -133,6 +147,15 @@ class TextBuffer
|
||||
getPath: ->
|
||||
@file?.getPath()
|
||||
|
||||
getUri: ->
|
||||
@getRelativePath()
|
||||
|
||||
getRelativePath: ->
|
||||
@state.get('relativePath')
|
||||
|
||||
setRelativePath: (relativePath) ->
|
||||
@setPath(@project.resolve(relativePath))
|
||||
|
||||
# Sets the path for the file.
|
||||
#
|
||||
# path - A {String} representing the new file path
|
||||
@@ -143,7 +166,7 @@ class TextBuffer
|
||||
@file = new File(path)
|
||||
@file.read() if @file.exists()
|
||||
@subscribeToFile()
|
||||
|
||||
@state.set('relativePath', @project.relativize(path))
|
||||
@trigger "path-changed", this
|
||||
|
||||
# Retrieves the current buffer's file extension.
|
||||
@@ -171,7 +194,8 @@ class TextBuffer
|
||||
#
|
||||
# Returns a new {Range}, from `[0, 0]` to the end of the buffer.
|
||||
getRange: ->
|
||||
new Range([0, 0], [@getLastRow(), @getLastLine().length])
|
||||
lastRow = @getLastRow()
|
||||
new Range([0, 0], [lastRow, @lineLengthForRow(lastRow)])
|
||||
|
||||
# Given a range, returns the lines of text within it.
|
||||
#
|
||||
@@ -179,25 +203,13 @@ class TextBuffer
|
||||
#
|
||||
# Returns a {String} of the combined lines.
|
||||
getTextInRange: (range) ->
|
||||
range = @clipRange(range)
|
||||
if range.start.row == range.end.row
|
||||
return @lineForRow(range.start.row)[range.start.column...range.end.column]
|
||||
|
||||
multipleLines = []
|
||||
multipleLines.push @lineForRow(range.start.row)[range.start.column..] # first line
|
||||
multipleLines.push @lineEndingForRow(range.start.row)
|
||||
for row in [range.start.row + 1...range.end.row]
|
||||
multipleLines.push @lineForRow(row) # middle lines
|
||||
multipleLines.push @lineEndingForRow(row)
|
||||
multipleLines.push @lineForRow(range.end.row)[0...range.end.column] # last line
|
||||
|
||||
return multipleLines.join ''
|
||||
@text.getTextInRange(@clipRange(range))
|
||||
|
||||
# Gets all the lines in a file.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getLines: ->
|
||||
@lines
|
||||
@text.getLines()
|
||||
|
||||
# Given a row, returns the line of text.
|
||||
#
|
||||
@@ -205,7 +217,7 @@ class TextBuffer
|
||||
#
|
||||
# Returns a {String}.
|
||||
lineForRow: (row) ->
|
||||
@lines[row]
|
||||
@text.lineForRow(row)
|
||||
|
||||
# Given a row, returns its line ending.
|
||||
#
|
||||
@@ -213,10 +225,13 @@ class TextBuffer
|
||||
#
|
||||
# Returns a {String}, or `undefined` if `row` is the final row.
|
||||
lineEndingForRow: (row) ->
|
||||
@lineEndings[row] unless row is @getLastRow()
|
||||
@text.lineEndingForRow(row)
|
||||
|
||||
suggestedLineEndingForRow: (row) ->
|
||||
@lineEndingForRow(row) ? @lineEndingForRow(row - 1)
|
||||
if row is @getLastRow()
|
||||
@lineEndingForRow(row - 1)
|
||||
else
|
||||
@lineEndingForRow(row)
|
||||
|
||||
# Given a row, returns the length of the line of text.
|
||||
#
|
||||
@@ -224,7 +239,7 @@ class TextBuffer
|
||||
#
|
||||
# Returns a {Number}.
|
||||
lineLengthForRow: (row) ->
|
||||
@lines[row].length
|
||||
@text.lineLengthForRow(row)
|
||||
|
||||
# Given a row, returns the length of the line ending
|
||||
#
|
||||
@@ -251,13 +266,13 @@ class TextBuffer
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getLineCount: ->
|
||||
@getLines().length
|
||||
@text.getLineCount()
|
||||
|
||||
# Gets the row number of the last line.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getLastRow: ->
|
||||
@getLines().length - 1
|
||||
@getLineCount() - 1
|
||||
|
||||
# Finds the last line in the current buffer.
|
||||
#
|
||||
@@ -273,20 +288,10 @@ class TextBuffer
|
||||
new Point(lastRow, @lineLengthForRow(lastRow))
|
||||
|
||||
characterIndexForPosition: (position) ->
|
||||
position = @clipPosition(position)
|
||||
|
||||
index = 0
|
||||
for row in [0...position.row]
|
||||
index += @lineLengthForRow(row) + Math.max(@lineEndingLengthForRow(row), 1)
|
||||
index + position.column
|
||||
@text.indexForPoint(@clipPosition(position))
|
||||
|
||||
positionForCharacterIndex: (index) ->
|
||||
row = 0
|
||||
while index >= (lineLength = @lineLengthForRow(row) + Math.max(@lineEndingLengthForRow(row), 1))
|
||||
index -= lineLength
|
||||
row++
|
||||
|
||||
new Point(row, index)
|
||||
@text.pointForIndex(index)
|
||||
|
||||
# Given a row, this deletes it from the buffer.
|
||||
#
|
||||
@@ -310,7 +315,6 @@ class TextBuffer
|
||||
else
|
||||
startPoint = [start, 0]
|
||||
endPoint = [end + 1, 0]
|
||||
|
||||
@delete(new Range(startPoint, endPoint))
|
||||
|
||||
# Adds text to the end of the buffer.
|
||||
@@ -321,10 +325,10 @@ class TextBuffer
|
||||
|
||||
# Adds text to a specific point in the buffer
|
||||
#
|
||||
# point - A {Point} in the buffer to insert into
|
||||
# position - A {Point} in the buffer to insert into
|
||||
# text - A {String} of text to add
|
||||
insert: (point, text) ->
|
||||
@change(new Range(point, point), text)
|
||||
insert: (position, text) ->
|
||||
@change(new Range(position, position), text)
|
||||
|
||||
# Deletes text from the buffer
|
||||
#
|
||||
@@ -340,15 +344,7 @@ class TextBuffer
|
||||
#
|
||||
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
|
||||
clipPosition: (position) ->
|
||||
position = Point.fromObject(position)
|
||||
eofPosition = @getEofPosition()
|
||||
if position.isGreaterThan(eofPosition)
|
||||
eofPosition
|
||||
else
|
||||
row = Math.max(position.row, 0)
|
||||
column = Math.max(position.column, 0)
|
||||
column = Math.min(@lineLengthForRow(row), column)
|
||||
new Point(row, column)
|
||||
@text.clipPosition(position)
|
||||
|
||||
# Given a range, this clips it to a real range.
|
||||
#
|
||||
@@ -363,19 +359,11 @@ class TextBuffer
|
||||
range = Range.fromObject(range)
|
||||
new Range(@clipPosition(range.start), @clipPosition(range.end))
|
||||
|
||||
prefixAndSuffixForRange: (range) ->
|
||||
prefix: @lines[range.start.row][0...range.start.column]
|
||||
suffix: @lines[range.end.row][range.end.column..]
|
||||
undo: ->
|
||||
@text.undo()
|
||||
|
||||
# Undos the last operation.
|
||||
#
|
||||
# editSession - The {EditSession} associated with the buffer.
|
||||
undo: (editSession) -> @undoManager.undo(editSession)
|
||||
|
||||
# Redos the last operation.
|
||||
#
|
||||
# editSession - The {EditSession} associated with the buffer.
|
||||
redo: (editSession) -> @undoManager.redo(editSession)
|
||||
redo: ->
|
||||
@text.redo()
|
||||
|
||||
# Saves the buffer.
|
||||
save: ->
|
||||
@@ -411,25 +399,24 @@ class TextBuffer
|
||||
# Identifies if a buffer is empty.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isEmpty: -> @lines.length is 1 and @lines[0].length is 0
|
||||
isEmpty: -> @text.isEmpty()
|
||||
|
||||
# Returns all valid {BufferMarker}s on the buffer.
|
||||
getMarkers: ({includeInvalid} = {}) ->
|
||||
markers = _.values(@validMarkers)
|
||||
if includeInvalid
|
||||
markers.concat(_.values(@invalidMarkers))
|
||||
else
|
||||
markers
|
||||
getMarkers: ->
|
||||
@text.getMarkers()
|
||||
|
||||
# Returns the {BufferMarker} with the given id.
|
||||
getMarker: (id) ->
|
||||
@validMarkers[id]
|
||||
@text.getMarker(id)
|
||||
|
||||
destroyMarker: (id) ->
|
||||
@getMarker(id)?.destroy()
|
||||
|
||||
# Public: Finds the first marker satisfying the given attributes
|
||||
#
|
||||
# Returns a {String} marker-identifier
|
||||
findMarker: (attributes) ->
|
||||
@findMarkers(attributes)[0]
|
||||
@text.findMarker(attributes)
|
||||
|
||||
# Public: Finds all markers satisfying the given attributes
|
||||
#
|
||||
@@ -440,14 +427,13 @@ class TextBuffer
|
||||
#
|
||||
# Returns an {Array} of {BufferMarker}s
|
||||
findMarkers: (attributes) ->
|
||||
markers = @getMarkers().filter (marker) -> marker.matchesAttributes(attributes)
|
||||
markers.sort (a, b) -> a.getRange().compare(b.getRange())
|
||||
@text.findMarkers(attributes)
|
||||
|
||||
# Retrieves the quantity of markers in a buffer.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getMarkerCount: ->
|
||||
_.size(@validMarkers)
|
||||
@text.getMarkers().length
|
||||
|
||||
# Constructs a new marker at a given range.
|
||||
#
|
||||
@@ -456,23 +442,12 @@ class TextBuffer
|
||||
# Any attributes you pass will be associated with the marker and can be retrieved
|
||||
# or used in marker queries.
|
||||
# The following attribute keys reserved, and control the marker's initial range
|
||||
# reverse - if `true`, the marker is reversed; that is, its head precedes the tail
|
||||
# noTail - if `true`, the marker is created without a tail
|
||||
# isReversed - if `true`, the marker is reversed; that is, its head precedes the tail
|
||||
# hasTail - if `false`, the marker is created without a tail
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markRange: (range, attributes={}) ->
|
||||
optionKeys = ['invalidationStrategy', 'noTail', 'reverse']
|
||||
options = _.pick(attributes, optionKeys)
|
||||
attributes = _.omit(attributes, optionKeys)
|
||||
marker = new BufferMarker(_.defaults({
|
||||
id: (@nextMarkerId++).toString()
|
||||
buffer: this
|
||||
range
|
||||
attributes
|
||||
}, options))
|
||||
@validMarkers[marker.id] = marker
|
||||
@trigger 'marker-created', marker
|
||||
marker
|
||||
markRange: (range, options={}) ->
|
||||
@text.markRange(range, options)
|
||||
|
||||
# Constructs a new marker at a given position.
|
||||
#
|
||||
@@ -481,16 +456,7 @@ class TextBuffer
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markPosition: (position, options) ->
|
||||
@markRange([position, position], _.defaults({noTail: true}, options))
|
||||
|
||||
# Given a buffer position, this finds all markers that contain the position.
|
||||
#
|
||||
# bufferPosition - A {Point} to check
|
||||
#
|
||||
# Returns an {Array} of {Numbers}, representing marker IDs containing `bufferPosition`.
|
||||
markersForPosition: (position) ->
|
||||
position = Point.fromObject(position)
|
||||
@getMarkers().filter (marker) -> marker.containsPoint(position)
|
||||
@text.markPosition(position, options)
|
||||
|
||||
# Identifies if a character sequence is within a certain range.
|
||||
#
|
||||
@@ -628,7 +594,7 @@ class TextBuffer
|
||||
checkoutHead: ->
|
||||
path = @getPath()
|
||||
return unless path
|
||||
git?.checkoutHead(path)
|
||||
@project.getRepo()?.checkoutHead(path)
|
||||
|
||||
# Checks to see if a file exists.
|
||||
#
|
||||
@@ -638,37 +604,24 @@ class TextBuffer
|
||||
|
||||
### Internal ###
|
||||
|
||||
pushOperation: (operation, editSession) ->
|
||||
if @undoManager
|
||||
@undoManager.pushOperation(operation, editSession)
|
||||
transact: (fn) -> @text.transact fn
|
||||
|
||||
beginTransaction: -> @text.beginTransaction()
|
||||
|
||||
commitTransaction: -> @text.commitTransaction()
|
||||
|
||||
abortTransaction: -> @text.abortTransaction()
|
||||
|
||||
change: (oldRange, newText, options={}) ->
|
||||
oldRange = @clipRange(oldRange)
|
||||
newText = @normalizeLineEndings(oldRange.start.row, newText) if options.normalizeLineEndings ? true
|
||||
@text.change(oldRange, newText, options)
|
||||
|
||||
normalizeLineEndings: (startRow, text) ->
|
||||
if lineEnding = @suggestedLineEndingForRow(startRow)
|
||||
text.replace(/\r?\n/g, lineEnding)
|
||||
else
|
||||
operation.do()
|
||||
|
||||
transact: (fn) ->
|
||||
if isNewTransaction = @undoManager.transact()
|
||||
@pushOperation(new BufferChangeOperation(buffer: this)) # restores markers on undo
|
||||
if fn
|
||||
try
|
||||
fn()
|
||||
finally
|
||||
@commit() if isNewTransaction
|
||||
|
||||
commit: ->
|
||||
@pushOperation(new BufferChangeOperation(buffer: this)) # restores markers on redo
|
||||
@undoManager.commit()
|
||||
|
||||
abort: -> @undoManager.abort()
|
||||
|
||||
change: (oldRange, newText, options) ->
|
||||
oldRange = Range.fromObject(oldRange)
|
||||
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
|
||||
range = @pushOperation(operation)
|
||||
range
|
||||
|
||||
destroyMarker: (id) ->
|
||||
if marker = @validMarkers[id] ? @invalidMarkers[id]
|
||||
delete @validMarkers[id]
|
||||
delete @invalidMarkers[id]
|
||||
text
|
||||
|
||||
scheduleModifiedEvents: ->
|
||||
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
|
||||
|
||||
@@ -3,18 +3,15 @@ TokenizedLine = require 'tokenized-line'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Subscriber = require 'subscriber'
|
||||
Token = require 'token'
|
||||
Range = require 'range'
|
||||
Point = require 'point'
|
||||
telepath = require 'telepath'
|
||||
{Point, Range} = telepath
|
||||
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class TokenizedBuffer
|
||||
@idCounter: 1
|
||||
|
||||
grammar: null
|
||||
currentGrammarScore: null
|
||||
tabLength: null
|
||||
buffer: null
|
||||
aceAdaptor: null
|
||||
tokenizedLines: null
|
||||
@@ -22,9 +19,22 @@ class TokenizedBuffer
|
||||
invalidRows: null
|
||||
visible: false
|
||||
|
||||
constructor: (@buffer, { @tabLength } = {}) ->
|
||||
@tabLength ?= 2
|
||||
@id = @constructor.idCounter++
|
||||
@acceptsDocuments: true
|
||||
registerDeserializer(this)
|
||||
|
||||
@deserialize: (state) ->
|
||||
new this(state)
|
||||
|
||||
constructor: (optionsOrState) ->
|
||||
if optionsOrState instanceof telepath.Document
|
||||
@state = optionsOrState
|
||||
@buffer = project.bufferForId(optionsOrState.get('bufferId'))
|
||||
else
|
||||
{ @buffer, tabLength } = optionsOrState
|
||||
@state = site.createDocument
|
||||
deserializer: @constructor.name
|
||||
bufferId: @buffer.id
|
||||
tabLength: tabLength ? 2
|
||||
|
||||
@subscribe syntax, 'grammar-added grammar-updated', (grammar) =>
|
||||
if grammar.injectionSelector?
|
||||
@@ -34,10 +44,13 @@ class TokenizedBuffer
|
||||
@setGrammar(grammar, newScore) if newScore > @currentGrammarScore
|
||||
|
||||
@on 'grammar-changed grammar-updated', => @resetTokenizedLines()
|
||||
@subscribe @buffer, "changed.tokenized-buffer#{@id}", (e) => @handleBufferChange(e)
|
||||
@subscribe @buffer, "changed", (e) => @handleBufferChange(e)
|
||||
|
||||
@reloadGrammar()
|
||||
|
||||
serialize: -> @state.clone()
|
||||
getState: -> @state
|
||||
|
||||
setGrammar: (grammar, score) ->
|
||||
return if grammar is @grammar
|
||||
@unsubscribe(@grammar) if @grammar
|
||||
@@ -71,12 +84,13 @@ class TokenizedBuffer
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getTabLength: ->
|
||||
@tabLength
|
||||
@state.get('tabLength')
|
||||
|
||||
# Specifies the tab length.
|
||||
#
|
||||
# tabLength - A {Number} that defines the new tab length.
|
||||
setTabLength: (@tabLength) ->
|
||||
setTabLength: (tabLength) ->
|
||||
@state.set('tabLength', tabLength)
|
||||
lastRow = @buffer.getLastRow()
|
||||
@tokenizedLines = @buildPlaceholderTokenizedLinesForRows(0, lastRow)
|
||||
@invalidateRow(0)
|
||||
@@ -175,13 +189,15 @@ class TokenizedBuffer
|
||||
buildPlaceholderTokenizedLineForRow: (row) ->
|
||||
line = @buffer.lineForRow(row)
|
||||
tokens = [new Token(value: line, scopes: [@grammar.scopeName])]
|
||||
new TokenizedLine({tokens, @tabLength})
|
||||
tabLength = @getTabLength()
|
||||
new TokenizedLine({tokens, tabLength})
|
||||
|
||||
buildTokenizedTokenizedLineForRow: (row, ruleStack) ->
|
||||
line = @buffer.lineForRow(row)
|
||||
lineEnding = @buffer.lineEndingForRow(row)
|
||||
tabLength = @getTabLength()
|
||||
{ tokens, ruleStack } = @grammar.tokenizeLine(line, ruleStack, row is 0)
|
||||
new TokenizedLine({tokens, ruleStack, @tabLength, lineEnding})
|
||||
new TokenizedLine({tokens, ruleStack, tabLength, lineEnding})
|
||||
|
||||
# FIXME: benogle says: These are actually buffer rows as all buffer rows are
|
||||
# accounted for in @tokenizedLines
|
||||
@@ -231,7 +247,6 @@ class TokenizedBuffer
|
||||
|
||||
destroy: ->
|
||||
@unsubscribe()
|
||||
@buffer.off ".tokenized-buffer#{@id}"
|
||||
|
||||
iterateTokensInBufferRange: (bufferRange, iterator) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
# Internal: The object in charge of managing redo and undo operations.
|
||||
module.exports =
|
||||
class UndoManager
|
||||
undoHistory: null
|
||||
redoHistory: null
|
||||
currentTransaction: null
|
||||
|
||||
constructor: ->
|
||||
@clear()
|
||||
|
||||
clear: ->
|
||||
@currentTransaction = [] if @currentTransaction?
|
||||
@undoHistory = []
|
||||
@redoHistory = []
|
||||
|
||||
pushOperation: (operation, editSession) ->
|
||||
if @currentTransaction
|
||||
@currentTransaction.push(operation)
|
||||
else
|
||||
@undoHistory.push([operation])
|
||||
@redoHistory = []
|
||||
|
||||
try
|
||||
operation.do?(editSession)
|
||||
catch e
|
||||
@clear()
|
||||
throw e
|
||||
|
||||
transact: ->
|
||||
isNewTransaction = not @currentTransaction?
|
||||
@currentTransaction ?= []
|
||||
isNewTransaction
|
||||
|
||||
commit: ->
|
||||
unless @currentTransaction?
|
||||
throw new Error("Trying to commit when there is no current transaction")
|
||||
|
||||
empty = @currentTransaction.length is 0
|
||||
@undoHistory.push(@currentTransaction) unless empty
|
||||
@currentTransaction = null
|
||||
not empty
|
||||
|
||||
abort: ->
|
||||
unless @currentTransaction?
|
||||
throw new Error("Trying to abort when there is no current transaction")
|
||||
|
||||
if @commit()
|
||||
@undo()
|
||||
@redoHistory.pop()
|
||||
|
||||
undo: (editSession) ->
|
||||
try
|
||||
if batch = @undoHistory.pop()
|
||||
opsInReverse = new Array(batch...)
|
||||
opsInReverse.reverse()
|
||||
op.undo?(editSession) for op in opsInReverse
|
||||
@redoHistory.push batch
|
||||
batch.oldSelectionRanges
|
||||
catch e
|
||||
@clear()
|
||||
throw e
|
||||
|
||||
redo: (editSession) ->
|
||||
try
|
||||
if batch = @redoHistory.pop()
|
||||
for op in batch
|
||||
op.do?(editSession)
|
||||
op.redo?(editSession)
|
||||
|
||||
@undoHistory.push(batch)
|
||||
batch.newSelectionRanges
|
||||
catch e
|
||||
@clear()
|
||||
throw e
|
||||
@@ -20,6 +20,7 @@ windowEventHandler = null
|
||||
|
||||
# This method is called in any window needing a general environment, including specs
|
||||
window.setUpEnvironment = (windowMode) ->
|
||||
window.site = new telepath.Site(1)
|
||||
atom.windowMode = windowMode
|
||||
window.resourcePath = remote.getCurrentWindow().loadSettings.resourcePath
|
||||
|
||||
@@ -63,6 +64,7 @@ window.startEditorWindow = ->
|
||||
window.unloadEditorWindow = ->
|
||||
return if not project and not rootView
|
||||
windowState = atom.getWindowState()
|
||||
windowState.set('project', project.serialize())
|
||||
windowState.set('syntax', syntax.serialize())
|
||||
windowState.set('rootView', rootView.serialize())
|
||||
atom.deactivatePackages()
|
||||
@@ -70,11 +72,9 @@ window.unloadEditorWindow = ->
|
||||
atom.saveWindowState()
|
||||
rootView.remove()
|
||||
project.destroy()
|
||||
git?.destroy()
|
||||
windowEventHandler?.unsubscribe()
|
||||
window.rootView = null
|
||||
window.project = null
|
||||
window.git = null
|
||||
|
||||
window.installAtomCommand = (callback) ->
|
||||
commandPath = path.join(window.resourcePath, 'atom.sh')
|
||||
@@ -93,32 +93,28 @@ window.onDrop = (e) ->
|
||||
window.deserializeEditorWindow = ->
|
||||
RootView = require 'root-view'
|
||||
Project = require 'project'
|
||||
Git = require 'git'
|
||||
|
||||
windowState = atom.getWindowState()
|
||||
|
||||
atom.packageStates = windowState.getObject('packageStates') ? {}
|
||||
windowState.remove('packageStates')
|
||||
|
||||
window.project = deserialize(windowState.get('project'))
|
||||
unless window.project?
|
||||
window.project = new Project(atom.getLoadSettings().initialPath)
|
||||
windowState.set('project', window.project.serialize())
|
||||
windowState.set('project', window.project.getState())
|
||||
|
||||
window.rootView = deserialize(windowState.get('rootView'))
|
||||
unless window.rootView?
|
||||
window.rootView = new RootView()
|
||||
windowState.set('rootView', window.rootView.serialize())
|
||||
windowState.set('rootView', window.rootView.getState())
|
||||
|
||||
$(rootViewParentSelector).append(rootView)
|
||||
|
||||
window.git = Git.open(project.getPath())
|
||||
project.on 'path-changed', ->
|
||||
projectPath = project.getPath()
|
||||
atom.getLoadSettings().initialPath = projectPath
|
||||
|
||||
window.git?.destroy()
|
||||
window.git = Git.open(projectPath)
|
||||
|
||||
window.stylesheetElementForId = (id) ->
|
||||
$("""head style[id="#{id}"]""")
|
||||
|
||||
@@ -206,14 +202,14 @@ window.registerDeferredDeserializer = (name, fn) ->
|
||||
window.unregisterDeserializer = (klass) ->
|
||||
delete deserializers[klass.name]
|
||||
|
||||
window.deserialize = (state) ->
|
||||
window.deserialize = (state, params) ->
|
||||
return unless state?
|
||||
if deserializer = getDeserializer(state)
|
||||
stateVersion = state.get?('version') ? state.version
|
||||
return if deserializer.version? and deserializer.version isnt stateVersion
|
||||
if (state instanceof telepath.Document) and not deserializer.acceptsDocuments
|
||||
state = state.toObject()
|
||||
deserializer.deserialize(state)
|
||||
deserializer.deserialize(state, params)
|
||||
else
|
||||
console.warn "No deserializer found for", state
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ dialog = require 'dialog'
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
net = require 'net'
|
||||
url = require 'url'
|
||||
|
||||
socketPath = '/tmp/atom.sock'
|
||||
|
||||
@@ -37,7 +38,7 @@ class AtomApplication
|
||||
installUpdate: null
|
||||
version: null
|
||||
|
||||
constructor: ({@resourcePath, pathsToOpen, @version, test, pidToKillWhenClosed, @devMode, newWindow}) ->
|
||||
constructor: ({@resourcePath, pathsToOpen, urlsToOpen, @version, test, pidToKillWhenClosed, @devMode, newWindow}) ->
|
||||
global.atomApplication = this
|
||||
|
||||
@pidsToOpenWindows = {}
|
||||
@@ -55,6 +56,8 @@ class AtomApplication
|
||||
@runSpecs({exitWhenDone: true, @resourcePath})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, @devMode})
|
||||
else if urlsToOpen.length > 0
|
||||
@openUrl(urlToOpen) for urlToOpen in urlsToOpen
|
||||
else
|
||||
# Always open a editor window if this is the first instance of Atom.
|
||||
@openPath({pidToKillWhenClosed, newWindow, @devMode})
|
||||
@@ -175,6 +178,10 @@ class AtomApplication
|
||||
event.preventDefault()
|
||||
@openPath({pathToOpen})
|
||||
|
||||
app.on 'open-url', (event, urlToOpen) =>
|
||||
event.preventDefault()
|
||||
@openUrl(urlToOpen)
|
||||
|
||||
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdate) =>
|
||||
event.preventDefault()
|
||||
@installUpdate = quitAndUpdate
|
||||
@@ -241,6 +248,17 @@ class AtomApplication
|
||||
console.log("Killing process #{pid} failed: #{error.code}")
|
||||
delete @pidsToOpenWindows[pid]
|
||||
|
||||
openUrl: (urlToOpen) ->
|
||||
parsedUrl = url.parse(urlToOpen)
|
||||
if parsedUrl.host is 'session'
|
||||
sessionId = parsedUrl.path.split('/')[1]
|
||||
console.log "Joining session #{sessionId}"
|
||||
if sessionId
|
||||
bootstrapScript = 'collaboration/lib/bootstrap'
|
||||
new AtomWindow({bootstrapScript, @resourcePath, sessionId, @devMode})
|
||||
else
|
||||
console.log "Opening unknown url #{urlToOpen}"
|
||||
|
||||
runSpecs: ({exitWhenDone, resourcePath}) ->
|
||||
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
|
||||
resourcePath = @resourcePath
|
||||
|
||||
@@ -18,18 +18,21 @@ require 'coffee-script'
|
||||
delegate.browserMainParts.preMainMessageLoopRun = ->
|
||||
args = parseCommandLine()
|
||||
|
||||
addPathToOpen = (event, filePath) ->
|
||||
addPathToOpen = (event, pathToOpen) ->
|
||||
event.preventDefault()
|
||||
args.pathsToOpen.push(filePath)
|
||||
args.pathsToOpen.push(pathToOpen)
|
||||
|
||||
app.on 'open-url', (event, url) =>
|
||||
args.urlsToOpen = []
|
||||
addUrlToOpen = (event, urlToOpen) ->
|
||||
event.preventDefault()
|
||||
dialog.showMessageBox
|
||||
message: 'Atom opened with URL'
|
||||
detail: url
|
||||
buttons: ['OK']
|
||||
args.urlsToOpen.push(urlToOpen)
|
||||
|
||||
app.on 'open-url', (event, urlToOpen) ->
|
||||
event.preventDefault()
|
||||
args.urlsToOpen.push(urlToOpen)
|
||||
|
||||
app.on 'open-file', addPathToOpen
|
||||
app.on 'open-url', addUrlToOpen
|
||||
|
||||
app.on 'will-finish-launching', ->
|
||||
setupCrashReporter()
|
||||
@@ -37,6 +40,7 @@ delegate.browserMainParts.preMainMessageLoopRun = ->
|
||||
|
||||
app.on 'finish-launching', ->
|
||||
app.removeListener 'open-file', addPathToOpen
|
||||
app.removeListener 'open-url', addUrlToOpen
|
||||
|
||||
args.pathsToOpen = args.pathsToOpen.map (pathToOpen) ->
|
||||
path.resolve(args.executedFrom ? process.cwd(), pathToOpen)
|
||||
|
||||
Reference in New Issue
Block a user