mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 07:28:08 +03:00
Merge pull request #1553 from atom/ks-extract-text-buffer
Extract TextBuffer
This commit is contained in:
commit
d87393f21e
@ -1,11 +1,12 @@
|
||||
{Point, Range} = require 'text-buffer'
|
||||
{File, Directory} = require 'pathwatcher'
|
||||
|
||||
module.exports =
|
||||
_: require 'underscore-plus'
|
||||
BufferedNodeProcess: require '../src/buffered-node-process'
|
||||
BufferedProcess: require '../src/buffered-process'
|
||||
Directory: require '../src/directory'
|
||||
File: require '../src/file'
|
||||
Directory: Directory
|
||||
File: File
|
||||
fs: require 'fs-plus'
|
||||
Git: require '../src/git'
|
||||
Point: Point
|
||||
|
@ -39,7 +39,7 @@
|
||||
"nslog": "0.5.0",
|
||||
"oniguruma": "1.x",
|
||||
"optimist": "0.4.0",
|
||||
"pathwatcher": "0.14.2",
|
||||
"pathwatcher": "0.16.0",
|
||||
"pegjs": "0.8.0",
|
||||
"property-accessors": "1.x",
|
||||
"q": "1.0.x",
|
||||
@ -51,7 +51,7 @@
|
||||
"serializable": "1.x",
|
||||
"space-pen": "3.1.1",
|
||||
"temp": "0.5.0",
|
||||
"text-buffer": "1.x",
|
||||
"text-buffer": ">=1.1.1 <2.0",
|
||||
"theorist": "1.x",
|
||||
"underscore-plus": "1.x",
|
||||
"vm-compatibility-layer": "0.1.0"
|
||||
@ -97,7 +97,7 @@
|
||||
"spell-check": "0.25.0",
|
||||
"status-bar": "0.32.0",
|
||||
"styleguide": "0.24.0",
|
||||
"symbols-view": "0.35.0",
|
||||
"symbols-view": "0.36.0",
|
||||
"tabs": "0.19.0",
|
||||
"terminal": "0.27.0",
|
||||
"timecop": "0.13.0",
|
||||
|
@ -1,138 +0,0 @@
|
||||
Directory = require '../src/directory'
|
||||
{fs} = require 'atom'
|
||||
path = require 'path'
|
||||
|
||||
describe "Directory", ->
|
||||
directory = null
|
||||
|
||||
beforeEach ->
|
||||
directory = new Directory(path.join(__dirname, 'fixtures'))
|
||||
|
||||
afterEach ->
|
||||
directory.off()
|
||||
|
||||
describe "when the contents of the directory change on disk", ->
|
||||
temporaryFilePath = null
|
||||
|
||||
beforeEach ->
|
||||
temporaryFilePath = path.join(__dirname, 'fixtures', 'temporary')
|
||||
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
|
||||
|
||||
afterEach ->
|
||||
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
|
||||
|
||||
it "triggers 'contents-changed' event handlers", ->
|
||||
changeHandler = null
|
||||
|
||||
runs ->
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
directory.on 'contents-changed', changeHandler
|
||||
fs.writeFileSync(temporaryFilePath, '')
|
||||
|
||||
waitsFor "first change", -> changeHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
changeHandler.reset()
|
||||
fs.removeSync(temporaryFilePath)
|
||||
|
||||
waitsFor "second change", -> changeHandler.callCount > 0
|
||||
|
||||
describe "when the directory unsubscribes from events", ->
|
||||
temporaryFilePath = null
|
||||
|
||||
beforeEach ->
|
||||
temporaryFilePath = path.join(directory.path, 'temporary')
|
||||
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
|
||||
|
||||
afterEach ->
|
||||
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
|
||||
|
||||
it "no longer triggers events", ->
|
||||
changeHandler = null
|
||||
|
||||
runs ->
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
directory.on 'contents-changed', changeHandler
|
||||
fs.writeFileSync(temporaryFilePath, '')
|
||||
|
||||
waitsFor "change event", -> changeHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
changeHandler.reset()
|
||||
directory.off()
|
||||
waits 20
|
||||
|
||||
runs -> fs.removeSync(temporaryFilePath)
|
||||
waits 20
|
||||
runs -> expect(changeHandler.callCount).toBe 0
|
||||
|
||||
describe "on #darwin or #linux", ->
|
||||
it "includes symlink information about entries", ->
|
||||
entries = directory.getEntriesSync()
|
||||
for entry in entries
|
||||
name = entry.getBaseName()
|
||||
if name is 'symlink-to-dir' or name is 'symlink-to-file'
|
||||
expect(entry.symlink).toBeTruthy()
|
||||
else
|
||||
expect(entry.symlink).toBeFalsy()
|
||||
|
||||
callback = jasmine.createSpy('getEntries')
|
||||
directory.getEntries(callback)
|
||||
|
||||
waitsFor -> callback.callCount is 1
|
||||
|
||||
runs ->
|
||||
entries = callback.mostRecentCall.args[1]
|
||||
for entry in entries
|
||||
name = entry.getBaseName()
|
||||
if name is 'symlink-to-dir' or name is 'symlink-to-file'
|
||||
expect(entry.symlink).toBeTruthy()
|
||||
else
|
||||
expect(entry.symlink).toBeFalsy()
|
||||
|
||||
describe ".relativize(path)", ->
|
||||
describe "on #darwin or #linux", ->
|
||||
it "returns a relative path based on the directory's path", ->
|
||||
absolutePath = directory.getPath()
|
||||
expect(directory.relativize(absolutePath)).toBe ''
|
||||
expect(directory.relativize(path.join(absolutePath, "b"))).toBe "b"
|
||||
expect(directory.relativize(path.join(absolutePath, "b/file.coffee"))).toBe "b/file.coffee"
|
||||
expect(directory.relativize(path.join(absolutePath, "file.coffee"))).toBe "file.coffee"
|
||||
|
||||
it "returns a relative path based on the directory's symlinked source path", ->
|
||||
symlinkPath = path.join(__dirname, 'fixtures', 'symlink-to-dir')
|
||||
symlinkDirectory = new Directory(symlinkPath)
|
||||
realFilePath = require.resolve('./fixtures/dir/a')
|
||||
expect(symlinkDirectory.relativize(symlinkPath)).toBe ''
|
||||
expect(symlinkDirectory.relativize(realFilePath)).toBe 'a'
|
||||
|
||||
it "returns the full path if the directory's path is not a prefix of the path", ->
|
||||
expect(directory.relativize('/not/relative')).toBe '/not/relative'
|
||||
|
||||
describe "on #win32", ->
|
||||
it "returns a relative path based on the directory's path", ->
|
||||
absolutePath = directory.getPath()
|
||||
expect(directory.relativize(absolutePath)).toBe ''
|
||||
expect(directory.relativize(path.join(absolutePath, "b"))).toBe "b"
|
||||
expect(directory.relativize(path.join(absolutePath, "b/file.coffee"))).toBe "b\\file.coffee"
|
||||
expect(directory.relativize(path.join(absolutePath, "file.coffee"))).toBe "file.coffee"
|
||||
|
||||
it "returns the full path if the directory's path is not a prefix of the path", ->
|
||||
expect(directory.relativize('/not/relative')).toBe "\\not\\relative"
|
||||
|
||||
describe ".contains(path)", ->
|
||||
it "returns true if the path is a child of the directory's path", ->
|
||||
absolutePath = directory.getPath()
|
||||
expect(directory.contains(path.join(absolutePath, "b"))).toBe true
|
||||
expect(directory.contains(path.join(absolutePath, "b", "file.coffee"))).toBe true
|
||||
expect(directory.contains(path.join(absolutePath, "file.coffee"))).toBe true
|
||||
|
||||
it "returns false if the directory's path is not a prefix of the path", ->
|
||||
expect(directory.contains('/not/relative')).toBe false
|
||||
|
||||
describe "on #darwin or #linux", ->
|
||||
it "returns true if the path is a child of the directory's symlinked source path", ->
|
||||
symlinkPath = path.join(__dirname, 'fixtures', 'symlink-to-dir')
|
||||
symlinkDirectory = new Directory(symlinkPath)
|
||||
realFilePath = require.resolve('./fixtures/dir/a')
|
||||
expect(symlinkDirectory.contains(realFilePath)).toBe true
|
@ -1,132 +0,0 @@
|
||||
{File, fs} = require 'atom'
|
||||
path = require 'path'
|
||||
|
||||
describe 'File', ->
|
||||
[filePath, file] = []
|
||||
|
||||
beforeEach ->
|
||||
filePath = path.join(__dirname, 'fixtures', 'atom-file-test.txt') # Don't put in /tmp because /tmp symlinks to /private/tmp and screws up the rename test
|
||||
fs.removeSync(filePath) if fs.existsSync(filePath)
|
||||
fs.writeFileSync(filePath, "this is old!")
|
||||
file = new File(filePath)
|
||||
|
||||
afterEach ->
|
||||
file.off()
|
||||
fs.removeSync(filePath) if fs.existsSync(filePath)
|
||||
|
||||
describe "when the file has not been read", ->
|
||||
describe "when the contents of the file change", ->
|
||||
it "triggers 'contents-changed' event handlers", ->
|
||||
file.on 'contents-changed', changeHandler = jasmine.createSpy('changeHandler')
|
||||
fs.writeFileSync(file.getPath(), "this is new!")
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
describe "when the file has already been read", ->
|
||||
beforeEach ->
|
||||
file.readSync()
|
||||
|
||||
describe "when the contents of the file change", ->
|
||||
it "triggers 'contents-changed' event handlers", ->
|
||||
changeHandler = null
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
file.on 'contents-changed', changeHandler
|
||||
fs.writeFileSync(file.getPath(), "this is new!")
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
changeHandler.reset()
|
||||
fs.writeFileSync(file.getPath(), "this is newer!")
|
||||
|
||||
waitsFor "second change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
describe "when the file is removed", ->
|
||||
it "triggers 'remove' event handlers", ->
|
||||
removeHandler = null
|
||||
removeHandler = jasmine.createSpy('removeHandler')
|
||||
file.on 'removed', removeHandler
|
||||
fs.removeSync(file.getPath())
|
||||
|
||||
waitsFor "remove event", ->
|
||||
removeHandler.callCount > 0
|
||||
|
||||
describe "when a file is moved (via the filesystem)", ->
|
||||
newPath = null
|
||||
|
||||
beforeEach ->
|
||||
newPath = path.join(path.dirname(filePath), "atom-file-was-moved-test.txt")
|
||||
|
||||
afterEach ->
|
||||
if fs.existsSync(newPath)
|
||||
fs.removeSync(newPath)
|
||||
waitsFor "remove event", 30000, (done) -> file.on 'removed', done
|
||||
|
||||
it "it updates its path", ->
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
moveHandler = null
|
||||
moveHandler = jasmine.createSpy('moveHandler')
|
||||
file.on 'moved', moveHandler
|
||||
|
||||
fs.moveSync(filePath, newPath)
|
||||
|
||||
waitsFor "move event", 30000, ->
|
||||
moveHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(file.getPath()).toBe newPath
|
||||
|
||||
it "maintains 'contents-changed' events set on previous path", ->
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
moveHandler = null
|
||||
moveHandler = jasmine.createSpy('moveHandler')
|
||||
file.on 'moved', moveHandler
|
||||
changeHandler = null
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
file.on 'contents-changed', changeHandler
|
||||
|
||||
fs.moveSync(filePath, newPath)
|
||||
|
||||
waitsFor "move event", ->
|
||||
moveHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
fs.writeFileSync(file.getPath(), "this is new!")
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
describe "when a file is deleted and the recreated within a small amount of time (git sometimes does this)", ->
|
||||
it "triggers a contents change event if the contents change", ->
|
||||
jasmine.unspy(File.prototype, 'detectResurrectionAfterDelay')
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
|
||||
changeHandler = jasmine.createSpy("file changed")
|
||||
removeHandler = jasmine.createSpy("file removed")
|
||||
file.on 'contents-changed', changeHandler
|
||||
file.on 'removed', removeHandler
|
||||
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
|
||||
fs.removeSync(filePath)
|
||||
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
waits 20
|
||||
runs ->
|
||||
fs.writeFileSync(filePath, "HE HAS RISEN!")
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
|
||||
waitsFor "resurrection change event", ->
|
||||
changeHandler.callCount == 1
|
||||
|
||||
runs ->
|
||||
expect(removeHandler).not.toHaveBeenCalled()
|
||||
fs.writeFileSync(filePath, "Hallelujah!")
|
||||
changeHandler.reset()
|
||||
|
||||
waitsFor "post-resurrection change event", ->
|
||||
changeHandler.callCount > 0
|
@ -164,7 +164,7 @@ describe "PaneView", ->
|
||||
expect(pane.items).toHaveLength(5)
|
||||
|
||||
fs.removeSync(filePath)
|
||||
waitsFor ->
|
||||
waitsFor 30000, ->
|
||||
pane.items.length == 4
|
||||
|
||||
describe "when a pane is destroyed", ->
|
||||
|
@ -1,7 +1,7 @@
|
||||
{times, random} = require 'underscore-plus'
|
||||
randomWords = require 'random-words'
|
||||
TextBuffer = require 'text-buffer'
|
||||
Editor = require '../src/editor'
|
||||
TextBuffer = require '../src/text-buffer'
|
||||
|
||||
describe "Editor", ->
|
||||
[editor, tokenizedBuffer, buffer, steps, previousSteps] = []
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,7 @@ describe "Workspace", ->
|
||||
workspace.open('a').then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
expect(editor.getUri()).toBe 'a'
|
||||
expect(editor.getUri()).toBe atom.project.resolve('a')
|
||||
expect(workspace.activePaneItem).toBe editor
|
||||
expect(workspace.activePane.items).toEqual [editor]
|
||||
expect(workspace.activePane.activate).toHaveBeenCalled()
|
||||
@ -199,23 +199,23 @@ describe "Workspace", ->
|
||||
expect(workspace.activePaneItem.getUri()).not.toBeUndefined()
|
||||
|
||||
# destroy all items
|
||||
expect(workspace.activePaneItem.getUri()).toBe 'file1'
|
||||
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('file1')
|
||||
pane.destroyActiveItem()
|
||||
expect(workspace.activePaneItem.getUri()).toBe 'b'
|
||||
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('b')
|
||||
pane.destroyActiveItem()
|
||||
expect(workspace.activePaneItem.getUri()).toBe 'a'
|
||||
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('a')
|
||||
pane.destroyActiveItem()
|
||||
|
||||
# reopens items with uris
|
||||
expect(workspace.activePaneItem).toBeUndefined()
|
||||
workspace.reopenItemSync()
|
||||
expect(workspace.activePaneItem.getUri()).toBe 'a'
|
||||
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('a')
|
||||
|
||||
# does not reopen items that are already open
|
||||
workspace.openSync('b')
|
||||
expect(workspace.activePaneItem.getUri()).toBe 'b'
|
||||
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('b')
|
||||
workspace.reopenItemSync()
|
||||
expect(workspace.activePaneItem.getUri()).toBe 'file1'
|
||||
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('file1')
|
||||
|
||||
describe "::increase/decreaseFontSize()", ->
|
||||
it "increases/decreases the font size without going below 1", ->
|
||||
|
@ -161,7 +161,8 @@ class Atom extends Model
|
||||
@subscribe @packages, 'activated', => @watchThemes()
|
||||
|
||||
Project = require './project'
|
||||
TextBuffer = require './text-buffer'
|
||||
TextBuffer = require 'text-buffer'
|
||||
@deserializers.add(TextBuffer)
|
||||
TokenizedBuffer = require './tokenized-buffer'
|
||||
DisplayBuffer = require './display-buffer'
|
||||
Editor = require './editor'
|
||||
|
@ -1,146 +0,0 @@
|
||||
path = require 'path'
|
||||
|
||||
async = require 'async'
|
||||
{Emitter} = require 'emissary'
|
||||
fs = require 'fs-plus'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
|
||||
File = require './file'
|
||||
|
||||
# Public: Represents a directory on disk.
|
||||
#
|
||||
# ## Requiring in packages
|
||||
#
|
||||
# ```coffee
|
||||
# {Directory} = require 'atom'
|
||||
# ```
|
||||
module.exports =
|
||||
class Directory
|
||||
Emitter.includeInto(this)
|
||||
|
||||
realPath: null
|
||||
|
||||
# Public: Configures a new Directory instance, no files are accessed.
|
||||
#
|
||||
# path - A {String} containing the absolute path to the directory.
|
||||
# symlink - A {Boolean} indicating if the path is a symlink (default: false).
|
||||
constructor: (@path, @symlink=false) ->
|
||||
@on 'first-contents-changed-subscription-will-be-added', =>
|
||||
# Triggered by emissary, when a new contents-changed listener attaches
|
||||
@subscribeToNativeChangeEvents()
|
||||
|
||||
@on 'last-contents-changed-subscription-removed', =>
|
||||
# Triggered by emissary, when the last contents-changed listener detaches
|
||||
@unsubscribeFromNativeChangeEvents()
|
||||
|
||||
# Public: Returns the {String} basename of the directory.
|
||||
getBaseName: ->
|
||||
path.basename(@path)
|
||||
|
||||
# Public: Returns the directory's symbolic path.
|
||||
#
|
||||
# This may include unfollowed symlinks or relative directory entries. Or it
|
||||
# may be fully resolved, it depends on what you give it.
|
||||
getPath: -> @path
|
||||
|
||||
# Public: Returns this directory's completely resolved path.
|
||||
#
|
||||
# All relative directory entries are removed and symlinks are resolved to
|
||||
# their final destination.
|
||||
getRealPathSync: ->
|
||||
unless @realPath?
|
||||
try
|
||||
@realPath = fs.realpathSync(@path)
|
||||
catch e
|
||||
@realPath = @path
|
||||
@realPath
|
||||
|
||||
# Public: Returns whether the given path (real or symbolic) is inside this
|
||||
# directory.
|
||||
contains: (pathToCheck) ->
|
||||
return false unless pathToCheck
|
||||
|
||||
if pathToCheck.indexOf(path.join(@getPath(), path.sep)) is 0
|
||||
true
|
||||
else if pathToCheck.indexOf(path.join(@getRealPathSync(), path.sep)) is 0
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
# Public: Returns the relative path to the given path from this directory.
|
||||
relativize: (fullPath) ->
|
||||
return fullPath unless fullPath
|
||||
|
||||
# Normalize forward slashes to back slashes on windows
|
||||
fullPath = fullPath.replace(/\//g, '\\') if process.platform is 'win32'
|
||||
|
||||
if fullPath is @getPath()
|
||||
''
|
||||
else if @isPathPrefixOf(@getPath(), fullPath)
|
||||
fullPath.substring(@getPath().length + 1)
|
||||
else if fullPath is @getRealPathSync()
|
||||
''
|
||||
else if @isPathPrefixOf(@getRealPathSync(), fullPath)
|
||||
fullPath.substring(@getRealPathSync().length + 1)
|
||||
else
|
||||
fullPath
|
||||
|
||||
# Public: Reads file entries in this directory from disk synchronously.
|
||||
#
|
||||
# Returns an {Array} of {File} and {Directory} objects.
|
||||
getEntriesSync: ->
|
||||
directories = []
|
||||
files = []
|
||||
for entryPath in fs.listSync(@path)
|
||||
if stat = fs.lstatSyncNoException(entryPath)
|
||||
symlink = stat.isSymbolicLink()
|
||||
stat = fs.statSyncNoException(entryPath) if symlink
|
||||
continue unless stat
|
||||
if stat.isDirectory()
|
||||
directories.push(new Directory(entryPath, symlink))
|
||||
else if stat.isFile()
|
||||
files.push(new File(entryPath, symlink))
|
||||
|
||||
directories.concat(files)
|
||||
|
||||
# Public: Reads file entries in this directory from disk asynchronously.
|
||||
#
|
||||
# callback - A {Function} to call with an {Error} as the 1st argument and
|
||||
# an {Array} of {File} and {Directory} objects as the 2nd argument.
|
||||
getEntries: (callback) ->
|
||||
fs.list @path, (error, entries) ->
|
||||
return callback(error) if error?
|
||||
|
||||
directories = []
|
||||
files = []
|
||||
addEntry = (entryPath, stat, symlink, callback) ->
|
||||
if stat?.isDirectory()
|
||||
directories.push(new Directory(entryPath, symlink))
|
||||
else if stat?.isFile()
|
||||
files.push(new File(entryPath, symlink))
|
||||
callback()
|
||||
|
||||
statEntry = (entryPath, callback) ->
|
||||
fs.lstat entryPath, (error, stat) ->
|
||||
if stat?.isSymbolicLink()
|
||||
fs.stat entryPath, (error, stat) ->
|
||||
addEntry(entryPath, stat, true, callback)
|
||||
else
|
||||
addEntry(entryPath, stat, false, callback)
|
||||
|
||||
async.eachLimit entries, 1, statEntry, ->
|
||||
callback(null, directories.concat(files))
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
unless @watchSubscription?
|
||||
@watchSubscription = pathWatcher.watch @path, (eventType) =>
|
||||
@emit "contents-changed" if eventType is "change"
|
||||
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
if @watchSubscription?
|
||||
@watchSubscription.close()
|
||||
@watchSubscription = null
|
||||
|
||||
# Does given full path start with the given prefix?
|
||||
isPathPrefixOf: (prefix, fullPath) ->
|
||||
fullPath.indexOf(prefix) is 0 and fullPath[prefix.length] is path.sep
|
@ -1,5 +1,4 @@
|
||||
{View, $, $$$} = require './space-pen-extensions'
|
||||
TextBuffer = require './text-buffer'
|
||||
GutterView = require './gutter-view'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
Editor = require './editor'
|
||||
@ -7,6 +6,7 @@ CursorView = require './cursor-view'
|
||||
SelectionView = require './selection-view'
|
||||
fs = require 'fs-plus'
|
||||
_ = require 'underscore-plus'
|
||||
TextBuffer = require 'text-buffer'
|
||||
|
||||
MeasureRange = document.createRange()
|
||||
TextNodeFilter = { acceptNode: -> NodeFilter.FILTER_ACCEPT }
|
||||
|
172
src/file.coffee
172
src/file.coffee
@ -1,172 +0,0 @@
|
||||
crypto = require 'crypto'
|
||||
path = require 'path'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
Q = require 'q'
|
||||
{Emitter} = require 'emissary'
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
runas = require 'runas'
|
||||
|
||||
# Public: Represents an individual file.
|
||||
#
|
||||
# You should probably create a {Directory} and access the {File} objects that
|
||||
# it creates, rather than instantiating the {File} class directly.
|
||||
#
|
||||
# ## Requiring in packages
|
||||
#
|
||||
# ```coffee
|
||||
# {File} = require 'atom'
|
||||
# ```
|
||||
module.exports =
|
||||
class File
|
||||
Emitter.includeInto(this)
|
||||
|
||||
path: null
|
||||
cachedContents: null
|
||||
|
||||
# Public: Creates a new file.
|
||||
#
|
||||
# path - A {String} containing the absolute path to the file
|
||||
# symlink - A {Boolean} indicating if the path is a symlink (default: false).
|
||||
constructor: (@path, @symlink=false) ->
|
||||
throw new Error("#{@path} is a directory") if fs.isDirectorySync(@path)
|
||||
|
||||
@handleEventSubscriptions()
|
||||
|
||||
# Subscribes to file system notifications when necessary.
|
||||
handleEventSubscriptions: ->
|
||||
eventNames = ['contents-changed', 'moved', 'removed']
|
||||
|
||||
subscriptionsAdded = eventNames.map (eventName) -> "first-#{eventName}-subscription-will-be-added"
|
||||
@on subscriptionsAdded.join(' '), =>
|
||||
# Only subscribe when a listener of eventName attaches (triggered by emissary)
|
||||
@subscribeToNativeChangeEvents() if @exists()
|
||||
|
||||
subscriptionsRemoved = eventNames.map (eventName) -> "last-#{eventName}-subscription-removed"
|
||||
@on subscriptionsRemoved.join(' '), =>
|
||||
# Detach when the last listener of eventName detaches (triggered by emissary)
|
||||
subscriptionsEmpty = _.every eventNames, (eventName) => @getSubscriptionCount(eventName) is 0
|
||||
@unsubscribeFromNativeChangeEvents() if subscriptionsEmpty
|
||||
|
||||
# Sets the path for the file.
|
||||
setPath: (@path) ->
|
||||
|
||||
# Public: Returns the {String} path for the file.
|
||||
getPath: -> @path
|
||||
|
||||
# Public: Return the {String} filename without any directory information.
|
||||
getBaseName: ->
|
||||
path.basename(@path)
|
||||
|
||||
# Public: Overwrites the file with the given String.
|
||||
write: (text) ->
|
||||
previouslyExisted = @exists()
|
||||
@writeFileWithPrivilegeEscalationSync(@getPath(), text)
|
||||
@cachedContents = text
|
||||
@subscribeToNativeChangeEvents() if not previouslyExisted and @hasSubscriptions()
|
||||
|
||||
# Deprecated
|
||||
readSync: (flushCache) ->
|
||||
if not @exists()
|
||||
@cachedContents = null
|
||||
else if not @cachedContents? or flushCache
|
||||
@cachedContents = fs.readFileSync(@getPath(), 'utf8')
|
||||
else
|
||||
@cachedContents
|
||||
|
||||
@setDigest(@cachedContents)
|
||||
@cachedContents
|
||||
|
||||
# Public: Reads the contents of the file.
|
||||
#
|
||||
# flushCache - A {Boolean} indicating whether to require a direct read or if
|
||||
# a cached copy is acceptable.
|
||||
#
|
||||
# Returns a promise that resovles to a String.
|
||||
read: (flushCache) ->
|
||||
if not @exists()
|
||||
promise = Q(null)
|
||||
else if not @cachedContents? or flushCache
|
||||
if fs.getSizeSync(@getPath()) >= 1048576 # 1MB
|
||||
throw new Error("Atom can only handle files < 1MB, for now.")
|
||||
|
||||
deferred = Q.defer()
|
||||
promise = deferred.promise
|
||||
content = []
|
||||
bytesRead = 0
|
||||
readStream = fs.createReadStream @getPath(), encoding: 'utf8'
|
||||
readStream.on 'data', (chunk) ->
|
||||
content.push(chunk)
|
||||
bytesRead += chunk.length
|
||||
deferred.notify(bytesRead)
|
||||
|
||||
readStream.on 'end', ->
|
||||
deferred.resolve(content.join(''))
|
||||
|
||||
readStream.on 'error', (error) ->
|
||||
deferred.reject(error)
|
||||
else
|
||||
promise = Q(@cachedContents)
|
||||
|
||||
promise.then (contents) =>
|
||||
@setDigest(contents)
|
||||
@cachedContents = contents
|
||||
|
||||
# Public: Returns whether the file exists.
|
||||
exists: ->
|
||||
fs.existsSync(@getPath())
|
||||
|
||||
setDigest: (contents) ->
|
||||
@digest = crypto.createHash('sha1').update(contents ? '').digest('hex')
|
||||
|
||||
# Public: Get the SHA-1 digest of this file
|
||||
getDigest: ->
|
||||
@digest ? @setDigest(@readSync())
|
||||
|
||||
# Writes the text to specified path.
|
||||
#
|
||||
# Privilege escalation would be asked when current user doesn't have
|
||||
# permission to the path.
|
||||
writeFileWithPrivilegeEscalationSync: (path, text) ->
|
||||
try
|
||||
fs.writeFileSync(path, text)
|
||||
catch error
|
||||
if error.code is 'EACCES' and process.platform is 'darwin'
|
||||
authopen = '/usr/libexec/authopen' # man 1 authopen
|
||||
unless runas(authopen, ['-w', '-c', path], stdin: text) is 0
|
||||
throw error
|
||||
else
|
||||
throw error
|
||||
|
||||
handleNativeChangeEvent: (eventType, path) ->
|
||||
if eventType is "delete"
|
||||
@unsubscribeFromNativeChangeEvents()
|
||||
@detectResurrectionAfterDelay()
|
||||
else if eventType is "rename"
|
||||
@setPath(path)
|
||||
@emit "moved"
|
||||
else if eventType is "change"
|
||||
oldContents = @cachedContents
|
||||
@read(true).done (newContents) =>
|
||||
@emit 'contents-changed' unless oldContents == newContents
|
||||
|
||||
detectResurrectionAfterDelay: ->
|
||||
_.delay (=> @detectResurrection()), 50
|
||||
|
||||
detectResurrection: ->
|
||||
if @exists()
|
||||
@subscribeToNativeChangeEvents()
|
||||
@handleNativeChangeEvent("change", @getPath())
|
||||
else
|
||||
@cachedContents = null
|
||||
@emit "removed"
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
unless @watchSubscription?
|
||||
@watchSubscription = pathWatcher.watch @path, (eventType, path) =>
|
||||
@handleNativeChangeEvent(eventType, path)
|
||||
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
if @watchSubscription?
|
||||
@watchSubscription.close()
|
||||
@watchSubscription = null
|
@ -4,7 +4,7 @@ fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
CSON = require 'season'
|
||||
KeyBinding = require './key-binding'
|
||||
File = require './file'
|
||||
{File} = require 'pathwatcher'
|
||||
{Emitter} = require 'emissary'
|
||||
|
||||
Modifiers = ['alt', 'control', 'ctrl', 'shift', 'cmd']
|
||||
|
@ -7,10 +7,10 @@ Q = require 'q'
|
||||
{Model} = require 'theorist'
|
||||
{Emitter, Subscriber} = require 'emissary'
|
||||
Serializable = require 'serializable'
|
||||
TextBuffer = require 'text-buffer'
|
||||
{Directory} = require 'pathwatcher'
|
||||
|
||||
TextBuffer = require './text-buffer'
|
||||
Editor = require './editor'
|
||||
Directory = require './directory'
|
||||
Task = require './task'
|
||||
Git = require './git'
|
||||
|
||||
@ -94,7 +94,7 @@ class Project extends Model
|
||||
#
|
||||
# uri - The {String} name of the path to convert.
|
||||
#
|
||||
# Returns a String.
|
||||
# Returns a {String} or undefined if the uri is not missing or empty.
|
||||
resolve: (uri) ->
|
||||
return unless uri
|
||||
|
||||
@ -102,7 +102,7 @@ class Project extends Model
|
||||
uri
|
||||
else
|
||||
uri = path.join(@getPath(), uri) unless fs.isAbsolute(uri)
|
||||
fs.absolute uri
|
||||
fs.absolute(uri)
|
||||
|
||||
# Public: Make the given path relative to the project directory.
|
||||
relativize: (fullPath) ->
|
||||
@ -189,6 +189,9 @@ class Project extends Model
|
||||
#
|
||||
# Returns a promise that resolves to the {TextBuffer}.
|
||||
buildBuffer: (absoluteFilePath) ->
|
||||
if fs.getSizeSync(absoluteFilePath) >= 1048576 # 1MB
|
||||
throw new Error("Atom can only handle files < 1MB, for now.")
|
||||
|
||||
buffer = new TextBuffer({filePath: absoluteFilePath})
|
||||
@addBuffer(buffer)
|
||||
buffer.load()
|
||||
|
@ -1,405 +0,0 @@
|
||||
_ = require 'underscore-plus'
|
||||
Q = require 'q'
|
||||
{P} = require 'scandal'
|
||||
Serializable = require 'serializable'
|
||||
TextBufferCore = require 'text-buffer'
|
||||
{Point, Range} = TextBufferCore
|
||||
{Subscriber, Emitter} = require 'emissary'
|
||||
|
||||
File = require './file'
|
||||
|
||||
# Represents the contents of a file.
|
||||
#
|
||||
# The `TextBuffer` is often associated with a {File}. However, this is not always
|
||||
# the case, as a `TextBuffer` could be an unsaved chunk of text.
|
||||
module.exports =
|
||||
class TextBuffer extends TextBufferCore
|
||||
atom.deserializers.add(this)
|
||||
|
||||
Serializable.includeInto(this)
|
||||
Subscriber.includeInto(this)
|
||||
Emitter.includeInto(this)
|
||||
|
||||
stoppedChangingDelay: 300
|
||||
stoppedChangingTimeout: null
|
||||
cachedDiskContents: null
|
||||
conflict: false
|
||||
file: null
|
||||
refcount: 0
|
||||
|
||||
constructor: ({filePath, @modifiedWhenLastPersisted, @digestWhenLastPersisted, loadWhenAttached}={}) ->
|
||||
super
|
||||
@loaded = false
|
||||
@modifiedWhenLastPersisted ?= false
|
||||
|
||||
@useSerializedText = @modifiedWhenLastPersisted != false
|
||||
|
||||
@subscribe this, 'changed', @handleTextChange
|
||||
|
||||
@setPath(filePath)
|
||||
|
||||
@load() if loadWhenAttached
|
||||
|
||||
serializeParams: ->
|
||||
params = super
|
||||
_.extend params,
|
||||
filePath: @getPath()
|
||||
modifiedWhenLastPersisted: @isModified()
|
||||
digestWhenLastPersisted: @file?.getDigest()
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params = super(params)
|
||||
params.loadWhenAttached = true
|
||||
params
|
||||
|
||||
loadSync: ->
|
||||
@updateCachedDiskContentsSync()
|
||||
@finishLoading()
|
||||
|
||||
load: ->
|
||||
@updateCachedDiskContents().then => @finishLoading()
|
||||
|
||||
finishLoading: ->
|
||||
if @isAlive()
|
||||
@loaded = true
|
||||
if @useSerializedText and @digestWhenLastPersisted is @file?.getDigest()
|
||||
@emitModifiedStatusChanged(true)
|
||||
else
|
||||
@reload()
|
||||
@clearUndoStack()
|
||||
this
|
||||
|
||||
handleTextChange: (event) =>
|
||||
@conflict = false if @conflict and !@isModified()
|
||||
@scheduleModifiedEvents()
|
||||
|
||||
destroy: ->
|
||||
unless @destroyed
|
||||
@cancelStoppedChangingTimeout()
|
||||
@file?.off()
|
||||
@unsubscribe()
|
||||
@destroyed = true
|
||||
@emit 'destroyed'
|
||||
|
||||
isAlive: -> not @destroyed
|
||||
|
||||
isDestroyed: -> @destroyed
|
||||
|
||||
isRetained: -> @refcount > 0
|
||||
|
||||
retain: ->
|
||||
@refcount++
|
||||
this
|
||||
|
||||
release: ->
|
||||
@refcount--
|
||||
@destroy() unless @isRetained()
|
||||
this
|
||||
|
||||
subscribeToFile: ->
|
||||
@file.on "contents-changed", =>
|
||||
@conflict = true if @isModified()
|
||||
previousContents = @cachedDiskContents
|
||||
|
||||
# Synchrounously update the disk contents because the {File} has already cached them. If the
|
||||
# contents updated asynchrounously multiple `conlict` events could trigger for the same disk
|
||||
# contents.
|
||||
@updateCachedDiskContentsSync()
|
||||
return if previousContents == @cachedDiskContents
|
||||
|
||||
if @conflict
|
||||
@emit "contents-conflicted"
|
||||
else
|
||||
@reload()
|
||||
|
||||
@file.on "removed", =>
|
||||
modified = @getText() != @cachedDiskContents
|
||||
@wasModifiedBeforeRemove = modified
|
||||
if modified
|
||||
@updateCachedDiskContents()
|
||||
else
|
||||
@destroy()
|
||||
|
||||
@file.on "moved", =>
|
||||
@emit "path-changed", this
|
||||
|
||||
# Identifies if the buffer belongs to multiple editors.
|
||||
#
|
||||
# For example, if the {EditorView} was split.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
hasMultipleEditors: -> @refcount > 1
|
||||
|
||||
# Reloads a file in the {Editor}.
|
||||
#
|
||||
# Sets the buffer's content to the cached disk contents
|
||||
reload: ->
|
||||
@emit 'will-reload'
|
||||
@setTextViaDiff(@cachedDiskContents)
|
||||
@emitModifiedStatusChanged(false)
|
||||
@emit 'reloaded'
|
||||
|
||||
# Rereads the contents of the file, and stores them in the cache.
|
||||
updateCachedDiskContentsSync: ->
|
||||
@cachedDiskContents = @file?.readSync() ? ""
|
||||
|
||||
# Rereads the contents of the file, and stores them in the cache.
|
||||
updateCachedDiskContents: ->
|
||||
Q(@file?.read() ? "").then (contents) =>
|
||||
@cachedDiskContents = contents
|
||||
|
||||
# Gets the file's basename--that is, the file without any directory information.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getBaseName: ->
|
||||
@file?.getBaseName()
|
||||
|
||||
# Retrieves the path for the file.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: ->
|
||||
@file?.getPath()
|
||||
|
||||
getUri: ->
|
||||
atom.project.relativize(@getPath())
|
||||
|
||||
# Sets the path for the file.
|
||||
#
|
||||
# filePath - A {String} representing the new file path
|
||||
setPath: (filePath) ->
|
||||
return if filePath == @getPath()
|
||||
|
||||
@file?.off()
|
||||
|
||||
if filePath
|
||||
@file = new File(filePath)
|
||||
@subscribeToFile()
|
||||
else
|
||||
@file = null
|
||||
|
||||
@emit "path-changed", this
|
||||
|
||||
# Deprecated: Use ::getEndPosition instead
|
||||
getEofPosition: -> @getEndPosition()
|
||||
|
||||
# Saves the buffer.
|
||||
save: ->
|
||||
@saveAs(@getPath()) if @isModified()
|
||||
|
||||
# Saves the buffer at a specific path.
|
||||
#
|
||||
# filePath - The path to save at.
|
||||
saveAs: (filePath) ->
|
||||
unless filePath then throw new Error("Can't save buffer with no file path")
|
||||
|
||||
@emit 'will-be-saved', this
|
||||
@setPath(filePath)
|
||||
@file.write(@getText())
|
||||
@cachedDiskContents = @getText()
|
||||
@conflict = false
|
||||
@emitModifiedStatusChanged(false)
|
||||
@emit 'saved', this
|
||||
|
||||
# Identifies if the buffer was modified.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isModified: ->
|
||||
return false unless @loaded
|
||||
if @file
|
||||
if @file.exists()
|
||||
@getText() != @cachedDiskContents
|
||||
else
|
||||
@wasModifiedBeforeRemove ? not @isEmpty()
|
||||
else
|
||||
not @isEmpty()
|
||||
|
||||
# Is the buffer's text in conflict with the text on disk?
|
||||
#
|
||||
# This occurs when the buffer's file changes on disk while the buffer has
|
||||
# unsaved changes.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isInConflict: -> @conflict
|
||||
|
||||
destroyMarker: (id) ->
|
||||
@getMarker(id)?.destroy()
|
||||
|
||||
# Identifies if a character sequence is within a certain range.
|
||||
#
|
||||
# regex - The {RegExp} to check
|
||||
# startIndex - The starting row {Number}
|
||||
# endIndex - The ending row {Number}
|
||||
#
|
||||
# Returns an {Array} of {RegExp}s, representing the matches.
|
||||
matchesInCharacterRange: (regex, startIndex, endIndex) ->
|
||||
text = @getText()
|
||||
matches = []
|
||||
|
||||
regex.lastIndex = startIndex
|
||||
while match = regex.exec(text)
|
||||
matchLength = match[0].length
|
||||
matchStartIndex = match.index
|
||||
matchEndIndex = matchStartIndex + matchLength
|
||||
|
||||
if matchEndIndex > endIndex
|
||||
regex.lastIndex = 0
|
||||
if matchStartIndex < endIndex and submatch = regex.exec(text[matchStartIndex...endIndex])
|
||||
submatch.index = matchStartIndex
|
||||
matches.push submatch
|
||||
break
|
||||
|
||||
matchEndIndex++ if matchLength is 0
|
||||
regex.lastIndex = matchEndIndex
|
||||
matches.push match
|
||||
|
||||
matches
|
||||
|
||||
# Scans for text in the buffer, calling a function on each match.
|
||||
#
|
||||
# regex - A {RegExp} representing the text to find
|
||||
# iterator - A {Function} that's called on each match
|
||||
scan: (regex, iterator) ->
|
||||
@scanInRange regex, @getRange(), (result) =>
|
||||
result.lineText = @lineForRow(result.range.start.row)
|
||||
result.lineTextOffset = 0
|
||||
iterator(result)
|
||||
|
||||
# Replace all matches of regex with replacementText
|
||||
#
|
||||
# regex: A {RegExp} representing the text to find
|
||||
# replacementText: A {String} representing the text to replace
|
||||
#
|
||||
# Returns the number of replacements made
|
||||
replace: (regex, replacementText) ->
|
||||
doSave = !@isModified()
|
||||
replacements = 0
|
||||
|
||||
@transact =>
|
||||
@scan regex, ({matchText, replace}) ->
|
||||
replace(matchText.replace(regex, replacementText))
|
||||
replacements++
|
||||
|
||||
@save() if doSave
|
||||
|
||||
replacements
|
||||
|
||||
# Scans for text in a given range, calling a function on each match.
|
||||
#
|
||||
# regex - A {RegExp} representing the text to find
|
||||
# range - A {Range} in the buffer to search within
|
||||
# iterator - A {Function} that's called on each match
|
||||
# reverse - A {Boolean} indicating if the search should be backwards (default: `false`)
|
||||
scanInRange: (regex, range, iterator, reverse=false) ->
|
||||
range = @clipRange(range)
|
||||
global = regex.global
|
||||
flags = "gm"
|
||||
flags += "i" if regex.ignoreCase
|
||||
regex = new RegExp(regex.source, flags)
|
||||
|
||||
startIndex = @characterIndexForPosition(range.start)
|
||||
endIndex = @characterIndexForPosition(range.end)
|
||||
|
||||
matches = @matchesInCharacterRange(regex, startIndex, endIndex)
|
||||
lengthDelta = 0
|
||||
|
||||
keepLooping = null
|
||||
replacementText = null
|
||||
stop = -> keepLooping = false
|
||||
replace = (text) -> replacementText = text
|
||||
|
||||
matches.reverse() if reverse
|
||||
for match in matches
|
||||
matchLength = match[0].length
|
||||
matchStartIndex = match.index
|
||||
matchEndIndex = matchStartIndex + matchLength
|
||||
|
||||
startPosition = @positionForCharacterIndex(matchStartIndex + lengthDelta)
|
||||
endPosition = @positionForCharacterIndex(matchEndIndex + lengthDelta)
|
||||
range = new Range(startPosition, endPosition)
|
||||
keepLooping = true
|
||||
replacementText = null
|
||||
matchText = match[0]
|
||||
iterator({ match, matchText, range, stop, replace })
|
||||
|
||||
if replacementText?
|
||||
@change(range, replacementText)
|
||||
lengthDelta += replacementText.length - matchLength unless reverse
|
||||
|
||||
break unless global and keepLooping
|
||||
|
||||
# Scans for text in a given range _backwards_, calling a function on each match.
|
||||
#
|
||||
# regex - A {RegExp} representing the text to find
|
||||
# range - A {Range} in the buffer to search within
|
||||
# iterator - A {Function} that's called on each match
|
||||
backwardsScanInRange: (regex, range, iterator) ->
|
||||
@scanInRange regex, range, iterator, true
|
||||
|
||||
# Given a row, identifies if it is blank.
|
||||
#
|
||||
# row - A row {Number} to check
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isRowBlank: (row) ->
|
||||
not /\S/.test @lineForRow(row)
|
||||
|
||||
# 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
|
||||
|
||||
startRow = Math.min(startRow, @getLastRow())
|
||||
for row in [(startRow - 1)..0]
|
||||
return row unless @isRowBlank(row)
|
||||
null
|
||||
|
||||
# Given a row, this finds the next row that's blank.
|
||||
#
|
||||
# startRow - A row {Number} to check
|
||||
#
|
||||
# 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
|
||||
for row in [(startRow + 1)..lastRow]
|
||||
return row unless @isRowBlank(row)
|
||||
null
|
||||
|
||||
# Identifies if the buffer has soft tabs anywhere.
|
||||
#
|
||||
# Returns a {Boolean},
|
||||
usesSoftTabs: ->
|
||||
for row in [0..@getLastRow()]
|
||||
if match = @lineForRow(row).match(/^\s/)
|
||||
return match[0][0] != '\t'
|
||||
undefined
|
||||
|
||||
change: (oldRange, newText, options={}) ->
|
||||
@setTextInRange(oldRange, newText, options.normalizeLineEndings)
|
||||
|
||||
cancelStoppedChangingTimeout: ->
|
||||
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
|
||||
|
||||
scheduleModifiedEvents: ->
|
||||
@cancelStoppedChangingTimeout()
|
||||
stoppedChangingCallback = =>
|
||||
@stoppedChangingTimeout = null
|
||||
modifiedStatus = @isModified()
|
||||
@emit 'contents-modified', modifiedStatus
|
||||
@emitModifiedStatusChanged(modifiedStatus)
|
||||
@stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay)
|
||||
|
||||
emitModifiedStatusChanged: (modifiedStatus) ->
|
||||
return if modifiedStatus is @previousModifiedStatus
|
||||
@previousModifiedStatus = modifiedStatus
|
||||
@emit 'modified-status-changed', modifiedStatus
|
||||
|
||||
logLines: (start=0, end=@getLastRow())->
|
||||
for row in [start..end]
|
||||
line = @lineForRow(row)
|
||||
console.log row, line, line.length
|
@ -7,7 +7,7 @@ Q = require 'q'
|
||||
|
||||
{$} = require './space-pen-extensions'
|
||||
Package = require './package'
|
||||
File = require './file'
|
||||
{File} = require 'pathwatcher'
|
||||
|
||||
# Public: Handles loading and activating available themes.
|
||||
#
|
||||
|
@ -72,10 +72,10 @@ class Workspace extends Model
|
||||
# if the uri is already open (default: false)
|
||||
#
|
||||
# Returns a promise that resolves to the {Editor} for the file URI.
|
||||
open: (uri, options={}) ->
|
||||
open: (uri='', options={}) ->
|
||||
searchAllPanes = options.searchAllPanes
|
||||
split = options.split
|
||||
uri = atom.project.relativize(uri) ? ''
|
||||
uri = atom.project.resolve(uri)
|
||||
|
||||
pane = switch split
|
||||
when 'left'
|
||||
@ -91,15 +91,15 @@ class Workspace extends Model
|
||||
@openUriInPane(uri, pane, options)
|
||||
|
||||
# Only used in specs
|
||||
openSync: (uri, options={}) ->
|
||||
openSync: (uri='', options={}) ->
|
||||
{initialLine} = options
|
||||
# TODO: Remove deprecated changeFocus option
|
||||
activatePane = options.activatePane ? options.changeFocus ? true
|
||||
uri = atom.project.relativize(uri) ? ''
|
||||
uri = atom.project.resolve(uri)
|
||||
|
||||
item = @activePane.itemForUri(uri)
|
||||
if uri
|
||||
item ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item
|
||||
item ?= opener(uri, options) for opener in @getOpeners() when !item
|
||||
item ?= atom.project.openSync(uri, {initialLine})
|
||||
|
||||
@activePane.activateItem(item)
|
||||
|
Loading…
Reference in New Issue
Block a user