mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Define file stuff
This commit is contained in:
@@ -5,17 +5,35 @@ pathWatcher = require 'pathwatcher'
|
||||
File = require 'file'
|
||||
EventEmitter = require 'event-emitter'
|
||||
|
||||
# Public: Represents a directory in the project.
|
||||
#
|
||||
# Directories contain an array of {File}s.
|
||||
module.exports =
|
||||
class Directory
|
||||
path: null
|
||||
|
||||
# Public: Creates a new directory.
|
||||
#
|
||||
# path - A {String} representing the file directory
|
||||
# symlink - A {Boolean} indicating if the path is a symlink (default: false)
|
||||
constructor: (@path, @symlink=false) ->
|
||||
|
||||
# Public: Retrieves the basename of the directory.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getBaseName: ->
|
||||
fsUtils.base(@path)
|
||||
|
||||
# Public: Retrieves the directory's path.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: -> @path
|
||||
|
||||
# Public: Retrieves the file entries in the directory.
|
||||
#
|
||||
# This does follow symlinks.
|
||||
#
|
||||
# Returns an {Array} of {Files}.
|
||||
getEntries: ->
|
||||
directories = []
|
||||
files = []
|
||||
@@ -33,16 +51,20 @@ class Directory
|
||||
|
||||
directories.concat(files)
|
||||
|
||||
# Internal:
|
||||
afterSubscribe: ->
|
||||
@subscribeToNativeChangeEvents() if @subscriptionCount() == 1
|
||||
|
||||
# Internal:
|
||||
afterUnsubscribe: ->
|
||||
@unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0
|
||||
|
||||
# Internal:
|
||||
subscribeToNativeChangeEvents: ->
|
||||
@watchSubscription = pathWatcher.watch @path, (eventType) =>
|
||||
@trigger "contents-changed" if eventType is "change"
|
||||
|
||||
|
||||
# Internal:
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
if @watchSubscription?
|
||||
@watchSubscription.close()
|
||||
|
||||
@@ -5,29 +5,55 @@ fsUtils = require 'fs-utils'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
_ = require 'underscore'
|
||||
|
||||
# Public: Represents an individual file in the editor.
|
||||
#
|
||||
# The entry point for this class is in two locations:
|
||||
# * {Buffer}, which associates text contents with a file
|
||||
# * {Directory}, which associcates the children of a directory as files
|
||||
module.exports =
|
||||
class File
|
||||
path: null
|
||||
cachedContents: null
|
||||
|
||||
# Public: Creates a new file.
|
||||
#
|
||||
# path - A {String} representing the file path
|
||||
# symlink - A {Boolean} indicating if the path is a symlink (default: false)
|
||||
constructor: (@path, @symlink=false) ->
|
||||
try
|
||||
if fs.statSync(@path).isDirectory()
|
||||
throw new Error("#{@path} is a directory")
|
||||
|
||||
# Public: Sets the path for the file.
|
||||
#
|
||||
# path - A {String} representing the new file path
|
||||
setPath: (@path) ->
|
||||
|
||||
# Public: Retrieves the path for the file.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: -> @path
|
||||
|
||||
# Public: Gets the file's basename--that is, the file without any directory information.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getBaseName: ->
|
||||
fsUtils.base(@path)
|
||||
|
||||
# Public: Writes (and saves) new contents to the file.
|
||||
#
|
||||
# text - A {String} representing the new contents.
|
||||
write: (text) ->
|
||||
previouslyExisted = @exists()
|
||||
@cachedContents = text
|
||||
fsUtils.write(@getPath(), text)
|
||||
@subscribeToNativeChangeEvents() if not previouslyExisted and @subscriptionCount() > 0
|
||||
|
||||
# Public: Reads the file.
|
||||
#
|
||||
# flushCache - A {Boolean} indicating if the cache should be erased--_i.e._, a force read is performed
|
||||
#
|
||||
# Returns a {String}.
|
||||
read: (flushCache)->
|
||||
if not @exists()
|
||||
@cachedContents = null
|
||||
@@ -36,15 +62,21 @@ class File
|
||||
else
|
||||
@cachedContents
|
||||
|
||||
# Public: Checks to see if a file exists.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
exists: ->
|
||||
fsUtils.exists(@getPath())
|
||||
|
||||
# Internal:
|
||||
afterSubscribe: ->
|
||||
@subscribeToNativeChangeEvents() if @exists() and @subscriptionCount() == 1
|
||||
|
||||
|
||||
# Internal:
|
||||
afterUnsubscribe: ->
|
||||
@unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0
|
||||
|
||||
# Internal:
|
||||
handleNativeChangeEvent: (eventType, path) ->
|
||||
if eventType is "delete"
|
||||
@unsubscribeFromNativeChangeEvents()
|
||||
@@ -58,9 +90,11 @@ class File
|
||||
return if oldContents == newContents
|
||||
@trigger 'contents-changed'
|
||||
|
||||
# Internal:
|
||||
detectResurrectionAfterDelay: ->
|
||||
_.delay (=> @detectResurrection()), 50
|
||||
|
||||
# Internal:
|
||||
detectResurrection: ->
|
||||
if @exists()
|
||||
@subscribeToNativeChangeEvents()
|
||||
@@ -69,10 +103,12 @@ class File
|
||||
@cachedContents = null
|
||||
@trigger "removed"
|
||||
|
||||
# Internal:
|
||||
subscribeToNativeChangeEvents: ->
|
||||
@watchSubscription = pathWatcher.watch @path, (eventType, path) =>
|
||||
@handleNativeChangeEvent(eventType, path)
|
||||
|
||||
# Internal:
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
if @watchSubscription
|
||||
@watchSubscription.close()
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
crypto = require 'crypto'
|
||||
|
||||
# Public: Represents the clipboard used for copying and pasting in Atom.
|
||||
module.exports =
|
||||
class Pasteboard
|
||||
signatureForMetadata: null
|
||||
|
||||
# Internal: Creates and `md5` hash of some text.
|
||||
#
|
||||
# text - A {String} to encrypt.
|
||||
#
|
||||
# Returns an encrypted {String}.
|
||||
md5: (text) ->
|
||||
crypto.createHash('md5').update(text, 'utf8').digest('hex')
|
||||
|
||||
# Public: Saves from the clipboard.
|
||||
#
|
||||
# text - A {String} to store
|
||||
# metadata - An object of additional info to associate with the text
|
||||
write: (text, metadata) ->
|
||||
@signatureForMetadata = @md5(text)
|
||||
@metadata = metadata
|
||||
$native.writeToPasteboard(text)
|
||||
|
||||
# Public: Loads from the clipboard.
|
||||
#
|
||||
# Returns an {Array}. The first index is the saved text, and the second is any metadata associated with the text.
|
||||
read: ->
|
||||
text = $native.readFromPasteboard()
|
||||
value = [text]
|
||||
|
||||
@@ -8,6 +8,10 @@ UndoManager = require 'undo-manager'
|
||||
BufferChangeOperation = require 'buffer-change-operation'
|
||||
BufferMarker = require 'buffer-marker'
|
||||
|
||||
# Public: Represents the contents of a file.
|
||||
#
|
||||
# The `Buffer` is often associated with a {File}. However, this is not always
|
||||
# the case, as a `Buffer` could be an unsaved chunk of text.
|
||||
module.exports =
|
||||
class Buffer
|
||||
@idCounter = 1
|
||||
@@ -28,6 +32,10 @@ class Buffer
|
||||
@deserialize: ({path, text}) ->
|
||||
project.bufferForPath(path, text)
|
||||
|
||||
# Public: 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
|
||||
@@ -72,6 +80,7 @@ class Buffer
|
||||
|
||||
hasMultipleEditors: -> @refcount > 1
|
||||
|
||||
# Internal:
|
||||
subscribeToFile: ->
|
||||
@file.on "contents-changed", =>
|
||||
if @isModified()
|
||||
@@ -88,6 +97,9 @@ class Buffer
|
||||
@file.on "moved", =>
|
||||
@trigger "path-changed", this
|
||||
|
||||
# Public: Reloads a file in the {EditSession}.
|
||||
#
|
||||
# Essentially, this performs a force read of the file.
|
||||
reload: ->
|
||||
@trigger 'will-reload'
|
||||
@updateCachedDiskContents()
|
||||
@@ -95,15 +107,27 @@ class Buffer
|
||||
@triggerModifiedStatusChanged(false)
|
||||
@trigger 'reloaded'
|
||||
|
||||
# Public: Rereads the contents of the file, and stores them in the cache.
|
||||
#
|
||||
# Essentially, this performs a force read of the file on disk.
|
||||
updateCachedDiskContents: ->
|
||||
@cachedDiskContents = @file.read()
|
||||
|
||||
# Public: Gets the file's basename--that is, the file without any directory information.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getBaseName: ->
|
||||
@file?.getBaseName()
|
||||
|
||||
# Public: Retrieves the path for the file.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: ->
|
||||
@file?.getPath()
|
||||
|
||||
# Public: Sets the path for the file.
|
||||
#
|
||||
# path - A {String} representing the new file path
|
||||
setPath: (path) ->
|
||||
return if path == @getPath()
|
||||
|
||||
@@ -135,6 +159,9 @@ class Buffer
|
||||
setText: (text) ->
|
||||
@change(@getRange(), text, normalizeLineEndings: false)
|
||||
|
||||
# Public: Gets the range of the buffer contents.
|
||||
#
|
||||
# Returns a new {Range}, from `[0, 0]` to the end of the buffer.
|
||||
getRange: ->
|
||||
new Range([0, 0], [@getLastRow(), @getLastLine().length])
|
||||
|
||||
@@ -243,9 +270,16 @@ class Buffer
|
||||
|
||||
new Point(row, index)
|
||||
|
||||
# Public: Given a row, this deletes it from the buffer.
|
||||
#
|
||||
# row - A {Number} representing the row to delete
|
||||
deleteRow: (row) ->
|
||||
@deleteRows(row, row)
|
||||
|
||||
# Public: Deletes a range of rows from the buffer.
|
||||
#
|
||||
# start - A {Number} representing the starting row
|
||||
# end - A {Number} representing the ending row
|
||||
deleteRows: (start, end) ->
|
||||
startPoint = null
|
||||
endPoint = null
|
||||
@@ -261,15 +295,26 @@ class Buffer
|
||||
|
||||
@delete(new Range(startPoint, endPoint))
|
||||
|
||||
# Public: Adds text to the end of the buffer.
|
||||
#
|
||||
# text - A {String} of text to add
|
||||
append: (text) ->
|
||||
@insert(@getEofPosition(), text)
|
||||
|
||||
# Public: Adds text to a specific point in the buffer
|
||||
#
|
||||
# point - A {Point} in the buffer to insert into
|
||||
# text - A {String} of text to add
|
||||
insert: (point, text) ->
|
||||
@change(new Range(point, point), text)
|
||||
|
||||
# Public: Deletes text from the buffer
|
||||
#
|
||||
# range - A {Range} whose text to delete
|
||||
delete: (range) ->
|
||||
@change(range, '')
|
||||
|
||||
# Internal:
|
||||
change: (oldRange, newText, options) ->
|
||||
oldRange = Range.fromObject(oldRange)
|
||||
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
|
||||
@@ -295,14 +340,22 @@ class Buffer
|
||||
prefix: @lines[range.start.row][0...range.start.column]
|
||||
suffix: @lines[range.end.row][range.end.column..]
|
||||
|
||||
# Internal:
|
||||
pushOperation: (operation, editSession) ->
|
||||
if @undoManager
|
||||
@undoManager.pushOperation(operation, editSession)
|
||||
else
|
||||
operation.do()
|
||||
|
||||
# Internal:
|
||||
transact: (fn) -> @undoManager.transact(fn)
|
||||
# Public: Undos the last operation.
|
||||
#
|
||||
# editSession - The {EditSession} associated with the buffer.
|
||||
undo: (editSession) -> @undoManager.undo(editSession)
|
||||
# Public: Redos the last operation.
|
||||
#
|
||||
# editSession - The {EditSession} associated with the buffer.
|
||||
redo: (editSession) -> @undoManager.redo(editSession)
|
||||
commit: -> @undoManager.commit()
|
||||
abort: -> @undoManager.abort()
|
||||
@@ -333,13 +386,22 @@ class Buffer
|
||||
else
|
||||
not @isEmpty()
|
||||
|
||||
# Public: Identifies if a buffer is in a git conflict with `HEAD`.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isInConflict: -> @conflict
|
||||
|
||||
# Public: Identifies if a buffer is empty.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isEmpty: -> @lines.length is 1 and @lines[0].length is 0
|
||||
|
||||
getMarkers: ->
|
||||
_.values(@validMarkers)
|
||||
|
||||
# Public: Retrieves the quantity of markers in a buffer.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getMarkerCount: ->
|
||||
_.size(@validMarkers)
|
||||
|
||||
@@ -479,6 +541,12 @@ class Buffer
|
||||
isRowBlank: (row) ->
|
||||
not /\S/.test @lineForRow(row)
|
||||
|
||||
# Public: Given a row, this finds the next row above it that's empty.
|
||||
#
|
||||
# startRow - A {Number} identifying the row to start checking at
|
||||
#
|
||||
# Returns the row {Number} of the first blank row.
|
||||
# Returns `null` if there's no other blank row.
|
||||
previousNonBlankRow: (startRow) ->
|
||||
return null if startRow == 0
|
||||
|
||||
@@ -491,7 +559,8 @@ class Buffer
|
||||
#
|
||||
# startRow - A row {Number} to check
|
||||
#
|
||||
# Returns a {Number}, or `null` if there's no other blank row.
|
||||
# Returns the row {Number} of the next blank row.
|
||||
# Returns `null` if there's no other blank row.
|
||||
nextNonBlankRow: (startRow) ->
|
||||
lastRow = @getLastRow()
|
||||
if startRow < lastRow
|
||||
@@ -499,18 +568,22 @@ class Buffer
|
||||
return row unless @isRowBlank(row)
|
||||
null
|
||||
|
||||
# Public: Identifies if the buffer has soft tabs anywhere.
|
||||
#
|
||||
# Returns a {Boolean},
|
||||
usesSoftTabs: ->
|
||||
for line in @getLines()
|
||||
if match = line.match(/^\s/)
|
||||
return match[0][0] != '\t'
|
||||
undefined
|
||||
|
||||
# Public: Checks out the current HEAD revision of the file.
|
||||
# Public: Checks out the current `HEAD` revision of the file.
|
||||
checkoutHead: ->
|
||||
path = @getPath()
|
||||
return unless path
|
||||
git?.checkoutHead(path)
|
||||
|
||||
# Internal:
|
||||
scheduleModifiedEvents: ->
|
||||
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
|
||||
stoppedChangingCallback = =>
|
||||
@@ -520,6 +593,7 @@ class Buffer
|
||||
@triggerModifiedStatusChanged(modifiedStatus)
|
||||
@stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay)
|
||||
|
||||
# Internal:
|
||||
triggerModifiedStatusChanged: (modifiedStatus) ->
|
||||
return if modifiedStatus is @previousModifiedStatus
|
||||
@previousModifiedStatus = modifiedStatus
|
||||
@@ -531,11 +605,13 @@ class Buffer
|
||||
fileExists: ->
|
||||
@file? && @file.exists()
|
||||
|
||||
# Internal:
|
||||
logLines: (start=0, end=@getLastRow())->
|
||||
for row in [start..end]
|
||||
line = @lineForRow(row)
|
||||
console.log row, line, line.length
|
||||
|
||||
# Internal:
|
||||
getDebugSnapshot: ->
|
||||
lines = ['Buffer:']
|
||||
for row in [0..@getLastRow()]
|
||||
|
||||
Reference in New Issue
Block a user