diff --git a/.gitignore b/.gitignore index af9371e4d..32be5e65a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ tags /cef/ /sources.gypi /node/ +docs/api diff --git a/Rakefile b/Rakefile index bd8e4c7b7..10e00f439 100644 --- a/Rakefile +++ b/Rakefile @@ -116,6 +116,25 @@ task :tags do system %{find src native cef vendor -not -name "*spec.coffee" -type f -print0 | xargs -0 ctags} end +namespace :docs do + namespace :app do + desc "Builds the API docs in src/app" + task :build do + system %{./node_modules/biscotto/bin/biscotto -o docs/api src/app/} + end + + desc "Lists the stats for API doc coverage in src/app" + task :stats do + system %{./node_modules/biscotto/bin/biscotto --statsOnly src/app/} + end + + desc "Show which docs are missing" + task :missing do + system %{./node_modules/biscotto/bin/biscotto --listMissing src/app/} + end + end +end + def application_path applications = FileList["#{BUILD_DIR}/**/Atom.app"] if applications.size == 0 diff --git a/package.json b/package.json index 2bfa3c87f..086b99e0b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,10 @@ "less": "git://github.com/nathansobo/less.js.git" }, + "devDependencies" : { + "biscotto" : "0.0.8" + }, + "private": true, "scripts": { diff --git a/spec/app/atom-spec.coffee b/spec/app/atom-spec.coffee index 82a3d7672..0f24d9c71 100644 --- a/spec/app/atom-spec.coffee +++ b/spec/app/atom-spec.coffee @@ -2,6 +2,9 @@ $ = require 'jquery' RootView = require 'root-view' {$$} = require 'space-pen' fsUtils = require 'fs-utils' +Exec = require('child_process').exec +_ = require 'underscore' +Project = require 'project' describe "the `atom` global", -> beforeEach -> @@ -321,3 +324,26 @@ describe "the `atom` global", -> expect(atom.sendMessageToBrowserProcess.callCount).toBe 1 expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "A2" atom.sendMessageToBrowserProcess.simulateConfirmation('Next') + + describe "API documentation", -> + it "meets a minimum threshold for /app (with no errors)", -> + docRunner = jasmine.createSpy("docRunner") + Exec "cd #{project.resolve('../..')} && rake docs:app:stats", docRunner + waitsFor -> + docRunner.callCount > 0 + + runs -> + # error + expect(docRunner.argsForCall[0][0]).toBeNull() + + results = docRunner.argsForCall[0][1].split("\n") + results.pop() + + errors = parseInt results.pop().match(/\d+/) + expect(errors).toBe 0 + + coverage = parseFloat results.pop().match(/.+?%/) + expect(coverage).toBeGreaterThan 80 + + # stderr + expect(docRunner.argsForCall[0][2]).toBe '' \ No newline at end of file diff --git a/src/app/atom-package.coffee b/src/app/atom-package.coffee index 9013d461a..5aa89e281 100644 --- a/src/app/atom-package.coffee +++ b/src/app/atom-package.coffee @@ -5,6 +5,11 @@ _ = require 'underscore' $ = require 'jquery' CSON = require 'cson' + +### +# Internal: Loads and resolves packages. # +### + module.exports = class AtomPackage extends Package metadata: null diff --git a/src/app/atom-theme.coffee b/src/app/atom-theme.coffee index c679298a1..d0de2eaa0 100644 --- a/src/app/atom-theme.coffee +++ b/src/app/atom-theme.coffee @@ -2,12 +2,17 @@ fsUtils = require 'fs-utils' Theme = require 'theme' CSON = require 'cson' +# Internal: Represents a theme that Atom can use. module.exports = class AtomTheme extends Theme + # Internal: Given a path, this loads it as a stylesheet. + # + # stylesheetPath - A {String} to a stylesheet loadStylesheet: (stylesheetPath)-> @stylesheets[stylesheetPath] = window.loadStylesheet(stylesheetPath) + # Internal: Loads the stylesheets found in a `package.cson` file. load: -> if fsUtils.extension(@path) in ['.css', '.less'] @loadStylesheet(@path) diff --git a/src/app/binding-set.coffee b/src/app/binding-set.coffee index 9fadc8756..a13c564c1 100644 --- a/src/app/binding-set.coffee +++ b/src/app/binding-set.coffee @@ -5,6 +5,10 @@ fsUtils = require 'fs-utils' Specificity = require 'specificity' PEG = require 'pegjs' +### +# Internal # +### + module.exports = class BindingSet diff --git a/src/app/buffer-change-operation.coffee b/src/app/buffer-change-operation.coffee index cbbf68b0a..2dc6ccb08 100644 --- a/src/app/buffer-change-operation.coffee +++ b/src/app/buffer-change-operation.coffee @@ -1,6 +1,10 @@ Range = require 'range' _ = require 'underscore' +### +# Internal # +### + module.exports = class BufferChangeOperation buffer: null diff --git a/src/app/buffer-marker.coffee b/src/app/buffer-marker.coffee index 87b305155..4a6e35af1 100644 --- a/src/app/buffer-marker.coffee +++ b/src/app/buffer-marker.coffee @@ -10,10 +10,23 @@ class BufferMarker suppressObserverNotification: false invalidationStrategy: null + ### + # Internal # + ### constructor: ({@id, @buffer, range, @invalidationStrategy, noTail, reverse}) -> @invalidationStrategy ?= 'contains' @setRange(range, {noTail, reverse}) + ### + # Public # + ### + + # 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) @@ -24,22 +37,47 @@ class BufferMarker @setTailPosition(range.start) unless options.noTail @setHeadPosition(range.end) + # Public: 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) + # Public: Identifies if the marker's head position is equal to its tail. + # + # Returns a {Boolean}. isRangeEmpty: -> @getHeadPosition().isEqual(@getTailPosition()) + # Public: Retrieves the {Range} between a marker's head and its tail. + # + # Returns a {Range}. getRange: -> if @tailPosition new Range(@tailPosition, @headPosition) else new Range(@headPosition, @headPosition) + # Public: Retrieves the position of the marker's head. + # + # Returns a {Point}. getHeadPosition: -> @headPosition + # Public: Retrieves the position of the marker's tail. + # + # Returns a {Point}. getTailPosition: -> @tailPosition ? @getHeadPosition() + # Public: 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) @@ -50,6 +88,14 @@ class BufferMarker @notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged}) @headPosition + # Public: 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) @@ -60,21 +106,52 @@ class BufferMarker @notifyObservers({oldTailPosition, newTailPosition, bufferChanged}) @tailPosition + # Public: Retrieves the starting position of the marker. + # + # Returns a {Point}. getStartPosition: -> @getRange().start + # Public: Retrieves the ending position of the marker. + # + # Returns a {Point}. getEndPosition: -> @getRange().end + # Public: 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 + # Public: Removes the tail from the marker. clearTail: -> oldTailPosition = @getTailPosition() @tailPosition = null newTailPosition = @getTailPosition() @notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false}) + # Public: Identifies if a {Point} is within the marker. + # + # Returns a {Boolean}. + containsPoint: (point) -> + @getRange().containsPoint(point) + + # Public: Sets a callback to be fired whenever a marker is changed. + observe: (callback) -> + @on 'changed', callback + cancel: => @unobserve(callback) + + # Public: Removes the fired callback whenever a marker changes. + unobserve: (callback) -> + @off 'changed', callback + + ### + # Internal # + ### + tryToInvalidate: (changedRange) -> betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false) containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true) @@ -122,16 +199,6 @@ class BufferMarker [newRow, newColumn] - observe: (callback) -> - @on 'changed', callback - cancel: => @unobserve(callback) - - unobserve: (callback) -> - @off 'changed', callback - - containsPoint: (point) -> - @getRange().containsPoint(point) - notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged} = {}) -> return if @suppressObserverNotification diff --git a/src/app/config.coffee b/src/app/config.coffee index c57d357e5..5e2633fd0 100644 --- a/src/app/config.coffee +++ b/src/app/config.coffee @@ -11,6 +11,10 @@ vendoredThemesDirPath = fsUtils.join(resourcePath, "vendor/themes") userThemesDirPath = fsUtils.join(configDirPath, "themes") userPackagesDirPath = fsUtils.join(configDirPath, "packages") +# Public: Handles all of Atom's configuration details. +# +# This includes loading and setting default options, as well as reading from the +# user's configuration file. module.exports = class Config configDirPath: configDirPath @@ -22,6 +26,10 @@ class Config settings: null configFileHasErrors: null + ### + # Internal # + ### + constructor: -> @defaultSettings = core: _.clone(require('root-view').configDefaults) @@ -63,10 +71,24 @@ class Config console.error "Failed to load user config '#{@configFilePath}'", e.message console.error e.stack + # Public: Retrieves the setting for the given key. + # + # keyPath - The {String} name of the key to retrieve + # + # Returns the value from Atom's default settings, the user's configuration file, + # or `null` if the key doesn't exist in either. get: (keyPath) -> _.valueForKeyPath(@settings, keyPath) ? _.valueForKeyPath(@defaultSettings, keyPath) + # Public: Sets the value for a configuration setting. + # + # This value is stored in Atom's internal configuration file. + # + # keyPath - The {String} name of the key + # value - The value of the setting + # + # Returns the `value`. set: (keyPath, value) -> _.setValueForKeyPath(@settings, keyPath, value) @update() @@ -82,6 +104,13 @@ class Config _.extend hash, defaults @update() + # Public: Establishes an event listener for a given key. + # + # Whenever the value of the key is changed, a callback is fired. + # + # keyPath - The {String} name of the key to watch + # callback - The {Function} that fires when the. It is given a single argument, `value`, + # which is the new value of `keyPath`. observe: (keyPath, callback) -> value = @get(keyPath) previousValue = _.clone(value) diff --git a/src/app/cursor-view.coffee b/src/app/cursor-view.coffee index a2bead1f8..6e4c96686 100644 --- a/src/app/cursor-view.coffee +++ b/src/app/cursor-view.coffee @@ -3,6 +3,7 @@ Point = require 'point' Range = require 'range' _ = require 'underscore' +# Internal: module.exports = class CursorView extends View @content: -> diff --git a/src/app/cursor.coffee b/src/app/cursor.coffee index 696cbfb4d..e2905fdb7 100644 --- a/src/app/cursor.coffee +++ b/src/app/cursor.coffee @@ -3,6 +3,9 @@ Range = require 'range' EventEmitter = require 'event-emitter' _ = require 'underscore' +# Public: The `Cursor` class represents the little blinking line identifying where text can be inserted. +# +# Cursors have some metadata attached in the form of a {BufferMarker}. module.exports = class Cursor screenPosition: null @@ -11,6 +14,9 @@ class Cursor visible: true needsAutoscroll: null + ### + # Internal # + ### constructor: ({@editSession, @marker}) -> @updateVisibility() @editSession.observeMarker @marker, (e) => @@ -39,20 +45,6 @@ class Cursor @editSession.removeCursor(this) @trigger 'destroyed' - setScreenPosition: (screenPosition, options={}) -> - @changePosition options, => - @editSession.setMarkerHeadScreenPosition(@marker, screenPosition, options) - - getScreenPosition: -> - @editSession.getMarkerHeadScreenPosition(@marker) - - setBufferPosition: (bufferPosition, options={}) -> - @changePosition options, => - @editSession.setMarkerHeadBufferPosition(@marker, bufferPosition, options) - - getBufferPosition: -> - @editSession.getMarkerHeadBufferPosition(@marker) - changePosition: (options, fn) -> @goalColumn = null @clearSelection() @@ -60,82 +52,161 @@ class Cursor unless fn() @trigger 'autoscrolled' if @needsAutoscroll + ### + # Public # + ### + + # Public: Moves a cursor to a given screen position. + # + # screenPosition - An {Array} of two numbers: the screen row, and the screen column. + # options - An object with the following keys: + # :autoscroll - A {Boolean} which, if `true`, scrolls the {EditSession} to wherever the cursor moves to + # + setScreenPosition: (screenPosition, options={}) -> + @changePosition options, => + @editSession.setMarkerHeadScreenPosition(@marker, screenPosition, options) + + # Public: Gets the screen position of the cursor. + # + # Returns an {Array} of two numbers: the screen row, and the screen column. + getScreenPosition: -> + @editSession.getMarkerHeadScreenPosition(@marker) + + # Public: Moves a cursor to a given buffer position. + # + # bufferPosition - An {Array} of two numbers: the buffer row, and the buffer column. + # options - An object with the following keys: + # :autoscroll - A {Boolean} which, if `true`, scrolls the {EditSession} to wherever the cursor moves to + # + setBufferPosition: (bufferPosition, options={}) -> + @changePosition options, => + @editSession.setMarkerHeadBufferPosition(@marker, bufferPosition, options) + + # Public: Gets the current buffer position. + # + # Returns an {Array} of two numbers: the buffer row, and the buffer column. + getBufferPosition: -> + @editSession.getMarkerHeadBufferPosition(@marker) + + # Public: If the marker range is empty, the cursor is marked as being visible. updateVisibility: -> @setVisible(@editSession.isMarkerRangeEmpty(@marker)) + # Public: Sets the visibility of the cursor. + # + # visible - A {Boolean} indicating whether the cursor should be visible setVisible: (visible) -> if @visible != visible @visible = visible @needsAutoscroll ?= true if @visible and @isLastCursor() @trigger 'visibility-changed', @visible + # Public: Retrieves the visibility of the cursor. + # + # Returns a {Boolean}. isVisible: -> @visible + # Public: Identifies what the cursor considers a "word" RegExp. + # + # Returns a {RegExp}. wordRegExp: -> nonWordCharacters = config.get("editor.nonWordCharacters") new RegExp("^[\t ]*$|[^\\s#{_.escapeRegExp(nonWordCharacters)}]+|[#{_.escapeRegExp(nonWordCharacters)}]+", "g") + # Public: Identifies if this cursor is the last in the {EditSession}. + # + # Returns a {Boolean}. isLastCursor: -> this == @editSession.getCursor() + # Public: Identifies if the cursor is surrounded by whitespace. + # + # "Surrounded" here means that all characters before and after the cursor is whitespace. + # + # Returns a {Boolean}. isSurroundedByWhitespace: -> {row, column} = @getBufferPosition() range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]] /^\s+$/.test @editSession.getTextInBufferRange(range) + # Public: Removes the setting for auto-scroll. clearAutoscroll: -> @needsAutoscroll = null + # Public: Deselects whatever the cursor is selecting. clearSelection: -> if @selection @selection.goalBufferRange = null @selection.clear() unless @selection.retainSelection + # Public: Retrieves the cursor's screen row. + # + # Returns a {Number}. getScreenRow: -> @getScreenPosition().row + # Public: Retrieves the cursor's screen column. + # + # Returns a {Number}. getScreenColumn: -> @getScreenPosition().column + # Public: Retrieves the cursor's buffer row. + # + # Returns a {Number}. getBufferRow: -> @getBufferPosition().row + # Public: Retrieves the cursor's buffer column. + # + # Returns a {Number}. getBufferColumn: -> @getBufferPosition().column + # Public: Retrieves the cursor's buffer row text. + # + # Returns a {String}. getCurrentBufferLine: -> @editSession.lineForBufferRow(@getBufferRow()) + # Public: Moves the cursor up one screen row. moveUp: (rowCount = 1) -> { row, column } = @getScreenPosition() column = @goalColumn if @goalColumn? @setScreenPosition({row: row - rowCount, column: column}) @goalColumn = column + # Public: Moves the cursor down one screen row. moveDown: (rowCount = 1) -> { row, column } = @getScreenPosition() column = @goalColumn if @goalColumn? @setScreenPosition({row: row + rowCount, column: column}) @goalColumn = column + # Public: Moves the cursor left one screen column. moveLeft: -> { row, column } = @getScreenPosition() [row, column] = if column > 0 then [row, column - 1] else [row - 1, Infinity] @setScreenPosition({row, column}) + # Public: Moves the cursor right one screen column. moveRight: -> { row, column } = @getScreenPosition() @setScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true) + # Public: Moves the cursor to the top of the buffer. moveToTop: -> @setBufferPosition([0,0]) + # Public: Moves the cursor to the bottom of the buffer. moveToBottom: -> @setBufferPosition(@editSession.getEofBufferPosition()) + # Public: Moves the cursor to the beginning of the buffer line. moveToBeginningOfLine: -> @setBufferPosition([@getBufferRow(), 0]) + # Public: Moves the cursor to the beginning of the first character in the line. moveToFirstCharacterOfLine: -> position = @getBufferPosition() scanRange = @getCurrentLineBufferRange() @@ -146,6 +217,7 @@ class Cursor newPosition = [position.row, 0] if newPosition.isEqual(position) @setBufferPosition(newPosition) + # Public: Moves the cursor to the beginning of the buffer line, skipping all whitespace. skipLeadingWhitespace: -> position = @getBufferPosition() scanRange = @getCurrentLineBufferRange() @@ -155,20 +227,30 @@ class Cursor @setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position) + # Public: Moves the cursor to the end of the buffer line. moveToEndOfLine: -> @setBufferPosition([@getBufferRow(), Infinity]) + # Public: Moves the cursor to the beginning of the word. moveToBeginningOfWord: -> @setBufferPosition(@getBeginningOfCurrentWordBufferPosition()) + # Public: Moves the cursor to the end of the word. moveToEndOfWord: -> if position = @getEndOfCurrentWordBufferPosition() @setBufferPosition(position) + # Public: Moves the cursor to the beginning of the next word. moveToBeginningOfNextWord: -> if position = @getBeginningOfNextWordBufferPosition() @setBufferPosition(position) + # Public: Retrieves the buffer position of where the current word starts. + # + # options - A hash with one option: + # :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp}) + # + # Returns a {Range}. getBeginningOfCurrentWordBufferPosition: (options = {}) -> allowPrevious = options.allowPrevious ? true currentBufferPosition = @getBufferPosition() @@ -184,6 +266,12 @@ class Cursor beginningOfWordPosition or currentBufferPosition + # Public: Retrieves the buffer position of where the current word ends. + # + # options - A hash with one option: + # :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp}) + # + # Returns a {Range}. getEndOfCurrentWordBufferPosition: (options = {}) -> allowNext = options.allowNext ? true currentBufferPosition = @getBufferPosition() @@ -198,6 +286,12 @@ class Cursor endOfWordPosition ? currentBufferPosition + # Public: Retrieves the buffer position of where the next word starts. + # + # options - A hash with one option: + # :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp}) + # + # Returns a {Range}. getBeginningOfNextWordBufferPosition: (options = {}) -> currentBufferPosition = @getBufferPosition() start = if @isSurroundedByWhitespace() then currentBufferPosition else @getEndOfCurrentWordBufferPosition() @@ -210,14 +304,29 @@ class Cursor beginningOfNextWordPosition or currentBufferPosition + # Public: Gets the word located under the cursor. + # + # options - An object with properties based on {.getBeginningOfCurrentWordBufferPosition}. + # + # Returns a {String}. getCurrentWordBufferRange: (options={}) -> startOptions = _.extend(_.clone(options), allowPrevious: false) endOptions = _.extend(_.clone(options), allowNext: false) new Range(@getBeginningOfCurrentWordBufferPosition(startOptions), @getEndOfCurrentWordBufferPosition(endOptions)) + # Public: Retrieves the range for the current line. + # + # options - A hash with the same keys as {EditSession.bufferRangeForBufferRow} + # + # Returns a {Range}. getCurrentLineBufferRange: (options) -> @editSession.bufferRangeForBufferRow(@getBufferRow(), options) + # Public: Retrieves the range for the current paragraph. + # + # A paragraph is defined as a block of text surrounded by empty lines. + # + # Returns a {Range}. getCurrentParagraphBufferRange: -> row = @getBufferRow() return unless /\w/.test(@editSession.lineForBufferRow(row)) @@ -235,21 +344,36 @@ class Cursor new Range([startRow, 0], [endRow, @editSession.lineLengthForBufferRow(endRow)]) + # Public: Retrieves the characters that constitute a word preceeding the current cursor position. + # + # Returns a {String}. getCurrentWordPrefix: -> @editSession.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()]) + # Public: Identifies if the cursor is at the start of a line. + # + # Returns a {Boolean}. isAtBeginningOfLine: -> @getBufferPosition().column == 0 + # Public: Retrieves the indentation level of the current line. + # + # Returns a {Number}. getIndentLevel: -> if @editSession.softTabs @getBufferColumn() / @editSession.getTabLength() else @getBufferColumn() + # Public: Identifies if the cursor is at the end of a line. + # + # Returns a {Boolean}. isAtEndOfLine: -> @getBufferPosition().isEqual(@getCurrentLineBufferRange().end) + # Public: Retrieves the grammar's token scopes for the line. + # + # Returns an {Array} of {String}s. getScopes: -> @editSession.scopesForBufferPosition(@getBufferPosition()) diff --git a/src/app/directory.coffee b/src/app/directory.coffee index aadc75a94..1b480de9b 100644 --- a/src/app/directory.coffee +++ b/src/app/directory.coffee @@ -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,6 +51,10 @@ class Directory directories.concat(files) + ### + # Internal # + ### + afterSubscribe: -> @subscribeToNativeChangeEvents() if @subscriptionCount() == 1 @@ -42,7 +64,7 @@ class Directory subscribeToNativeChangeEvents: -> @watchSubscription = pathWatcher.watch @path, (eventType) => @trigger "contents-changed" if eventType is "change" - + unsubscribeFromNativeChangeEvents: -> if @watchSubscription? @watchSubscription.close() diff --git a/src/app/display-buffer-marker.coffee b/src/app/display-buffer-marker.coffee index c8ed1648a..d88f67bdc 100644 --- a/src/app/display-buffer-marker.coffee +++ b/src/app/display-buffer-marker.coffee @@ -9,62 +9,128 @@ class DisplayBufferMarker tailScreenPosition: null valid: true + ### + # Internal # + ### + constructor: ({@id, @displayBuffer}) -> @buffer = @displayBuffer.buffer + ### + # Public # + ### + + # Public: Gets the screen range of the display marker. + # + # Returns a {Range}. getScreenRange: -> @displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true) + # Public: Modifies the screen range of the display marker. + # + # screenRange - The new {Range} to use + # options - A hash of options matching those found in {BufferMarker.setRange} setScreenRange: (screenRange, options) -> - @setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange, options), options) + @setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options) + # Public: Gets the buffer range of the display marker. + # + # Returns a {Range}. getBufferRange: -> @buffer.getMarkerRange(@id) + # Public: Modifies the buffer range of the display marker. + # + # screenRange - The new {Range} to use + # options - A hash of options matching those found in {BufferMarker.setRange} setBufferRange: (bufferRange, options) -> @buffer.setMarkerRange(@id, bufferRange, options) + # Public: Retrieves the screen position of the marker's head. + # + # Returns a {Point}. getHeadScreenPosition: -> @headScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true) + # Public: Sets the screen position of the marker's head. + # + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setHeadScreenPosition: (screenPosition, options) -> screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options) @setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options)) + # Public: Retrieves the buffer position of the marker's head. + # + # Returns a {Point}. getHeadBufferPosition: -> @buffer.getMarkerHeadPosition(@id) + # Public: Sets the buffer position of the marker's head. + # + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setHeadBufferPosition: (bufferPosition) -> @buffer.setMarkerHeadPosition(@id, bufferPosition) + # Public: Retrieves the screen position of the marker's tail. + # + # Returns a {Point}. getTailScreenPosition: -> @tailScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true) - + + # Public: Sets the screen position of the marker's tail. + # + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setTailScreenPosition: (screenPosition, options) -> screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options) @setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options)) + # Public: Retrieves the buffer position of the marker's tail. + # + # Returns a {Point}. getTailBufferPosition: -> @buffer.getMarkerTailPosition(@id) - + + # Public: Sets the buffer position of the marker's tail. + # + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setTailBufferPosition: (bufferPosition) -> @buffer.setMarkerTailPosition(@id, bufferPosition) + # Public: 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: -> @buffer.placeMarkerTail(@id) + # Public: Removes the tail from the marker. clearTail: -> @buffer.clearMarkerTail(@id) + # Public: Sets a callback to be fired whenever the marker is changed. + # + # callback - A {Function} to execute observe: (callback) -> @observeBufferMarkerIfNeeded() @on 'changed', callback cancel: => @unobserve(callback) + # Public: Removes the callback that's fired whenever the marker changes. + # + # callback - A {Function} to remove unobserve: (callback) -> @off 'changed', callback @unobserveBufferMarkerIfNeeded() + ### + # Internal # + ### + observeBufferMarkerIfNeeded: -> return if @subscriptionCount() @getHeadScreenPosition() # memoize current value diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee index 37aba8bff..ee7daac27 100644 --- a/src/app/display-buffer.coffee +++ b/src/app/display-buffer.coffee @@ -19,6 +19,10 @@ class DisplayBuffer foldsById: null markers: null + ### + # Internal # + ### + constructor: (@buffer, options={}) -> @id = @constructor.idCounter++ @languageMode = options.languageMode @@ -31,8 +35,6 @@ class DisplayBuffer @tokenizedBuffer.on 'changed', @handleTokenizedBufferChange @buffer.on 'markers-updated', @handleMarkersUpdated - setVisible: (visible) -> @tokenizedBuffer.setVisible(visible) - buildLineMap: -> @lineMap = new LineMap @lineMap.insertAtScreenRow 0, @buildLinesForBufferRows(0, @buffer.getLastRow()) @@ -44,6 +46,15 @@ class DisplayBuffer @trigger 'changed', eventProperties @resumeMarkerObservers() + ### + # Public # + ### + + setVisible: (visible) -> @tokenizedBuffer.setVisible(visible) + + # Public: Defines the limit at which the buffer begins to soft wrap text. + # + # softWrapColumn - A {Number} defining the soft wrap limit. setSoftWrapColumn: (@softWrapColumn) -> start = 0 end = @getLastRow() @@ -51,19 +62,40 @@ class DisplayBuffer screenDelta = @getLastRow() - end bufferDelta = 0 @triggerChanged({ start, end, screenDelta, bufferDelta }) - + + # Public: Gets the line for the given screen row. + # + # screenRow - A {Number} indicating the screen row. + # + # Returns a {String}. lineForRow: (row) -> @lineMap.lineForScreenRow(row) + # Public: Gets the lines for the given screen row boundaries. + # + # startRow - A {Number} indicating the beginning screen row. + # endRow - A {Number} indicating the ending screen row. + # + # Returns an {Array} of {String}s. linesForRows: (startRow, endRow) -> @lineMap.linesForScreenRows(startRow, endRow) + # Public: Gets the lines in the buffer. + # + # Returns an {Array} of {String}s. getLines: -> @lineMap.linesForScreenRows(0, @lineMap.lastScreenRow()) + # Public: Given a starting and ending row, this converts every row into a buffer position. + # + # startRow - The row {Number} to start at + # endRow - The row {Number} to end at (default: {.getLastRow}) + # + # Returns an {Array} of {Range}s. bufferRowsForScreenRows: (startRow, endRow) -> @lineMap.bufferRowsForScreenRows(startRow, endRow) + # Public: Folds all the foldable lines in the buffer. foldAll: -> for currentRow in [0..@buffer.getLastRow()] [startRow, endRow] = @languageMode.rowRangeForFoldAtBufferRow(currentRow) ? [] @@ -71,6 +103,7 @@ class DisplayBuffer @createFold(startRow, endRow) + # Public: Unfolds all the foldable lines in the buffer. unfoldAll: -> for row in [@buffer.getLastRow()..0] @activeFolds[row]?.forEach (fold) => @destroyFold(fold) @@ -90,6 +123,9 @@ class DisplayBuffer endRow = currentRow return [startRow, endRow] if startRow isnt endRow + # Public: Given a buffer row, this folds it. + # + # bufferRow - A {Number} indicating the buffer row foldBufferRow: (bufferRow) -> for currentRow in [bufferRow..0] rowRange = @rowRangeForCommentAtBufferRow(currentRow) @@ -103,9 +139,18 @@ class DisplayBuffer return + # Public: Given a buffer row, this unfolds it. + # + # bufferRow - A {Number} indicating the buffer row unfoldBufferRow: (bufferRow) -> @largestFoldContainingBufferRow(bufferRow)?.destroy() + # Public: Creates a new fold between two row numbers. + # + # startRow - The row {Number} to start folding at + # endRow - The row {Number} to end the fold + # + # Returns the new {Fold}. createFold: (startRow, endRow) -> return fold if fold = @foldFor(startRow, endRow) fold = new Fold(this, startRow, endRow) @@ -127,15 +172,210 @@ class DisplayBuffer fold + # Public: Given a {Fold}, determines if it is contained within another fold. + # + # fold - The {Fold} to check + # + # Returns the contaiing {Fold} (if it exists), `null` otherwise. isFoldContainedByActiveFold: (fold) -> for row, folds of @activeFolds for otherFold in folds return otherFold if fold != otherFold and fold.isContainedByFold(otherFold) + # Public: Given a starting and ending row, tries to find an existing fold. + # + # startRow - A {Number} representing a fold's starting row + # endRow - A {Number} representing a fold's ending row + # + # Returns a {Fold} (if it exists). foldFor: (startRow, endRow) -> _.find @activeFolds[startRow] ? [], (fold) -> fold.startRow == startRow and fold.endRow == endRow + # Public: Removes any folds found that contain the given buffer row. + # + # bufferRow - The buffer row {Number} to check against + destroyFoldsContainingBufferRow: (bufferRow) -> + for row, folds of @activeFolds + for fold in new Array(folds...) + fold.destroy() if fold.getBufferRange().containsRow(bufferRow) + + # Public: Given a buffer row, this returns the largest fold that starts there. + # + # Largest is defined as the fold whose difference between its start and end points + # are the greatest. + # + # bufferRow - A {Number} indicating the buffer row + # + # Returns a {Fold}. + largestFoldStartingAtBufferRow: (bufferRow) -> + return unless folds = @activeFolds[bufferRow] + (folds.sort (a, b) -> b.endRow - a.endRow)[0] + + # Public: Given a screen row, this returns the largest fold that starts there. + # + # Largest is defined as the fold whose difference between its start and end points + # are the greatest. + # + # screenRow - A {Number} indicating the screen row + # + # Returns a {Fold}. + largestFoldStartingAtScreenRow: (screenRow) -> + @largestFoldStartingAtBufferRow(@bufferRowForScreenRow(screenRow)) + + # Public: Given a buffer row, this returns the largest fold that includes it. + # + # Largest is defined as the fold whose difference between its start and end points + # are the greatest. + # + # bufferRow - A {Number} indicating the buffer row + # + # Returns a {Fold}. + largestFoldContainingBufferRow: (bufferRow) -> + largestFold = null + for currentBufferRow in [bufferRow..0] + if fold = @largestFoldStartingAtBufferRow(currentBufferRow) + largestFold = fold if fold.endRow >= bufferRow + largestFold + + # Public: Given a buffer range, this converts it into a screen range. + # + # bufferRange - A {Range} consisting of buffer positions + # + # Returns a {Range}. + screenLineRangeForBufferRange: (bufferRange) -> + @expandScreenRangeToLineEnds( + @lineMap.screenRangeForBufferRange( + @expandBufferRangeToLineEnds(bufferRange))) + + # Public: Given a buffer row, this converts it into a screen row. + # + # bufferRow - A {Number} representing a buffer row + # + # Returns a {Number}. + screenRowForBufferRow: (bufferRow) -> + @lineMap.screenPositionForBufferPosition([bufferRow, 0]).row + + lastScreenRowForBufferRow: (bufferRow) -> + @lineMap.screenPositionForBufferPosition([bufferRow, Infinity]).row + + # Public: Given a screen row, this converts it into a buffer row. + # + # screenRow - A {Number} representing a screen row + # + # Returns a {Number}. + bufferRowForScreenRow: (screenRow) -> + @lineMap.bufferPositionForScreenPosition([screenRow, 0]).row + + # Public: Given a buffer range, this converts it into a screen position. + # + # bufferRange - The {Range} to convert + # + # Returns a {Range}. + screenRangeForBufferRange: (bufferRange) -> + @lineMap.screenRangeForBufferRange(bufferRange) + + # Public: Given a screen range, this converts it into a buffer position. + # + # screenRange - The {Range} to convert + # + # Returns a {Range}. + bufferRangeForScreenRange: (screenRange) -> + @lineMap.bufferRangeForScreenRange(screenRange) + + # Public: Gets the number of lines in the buffer. + # + # Returns a {Number}. + lineCount: -> + @lineMap.screenLineCount() + + # Public: Gets the number of the last row in the buffer. + # + # Returns a {Number}. + getLastRow: -> + @lineCount() - 1 + + # Public: Gets the length of the longest screen line. + # + # Returns a {Number}. + maxLineLength: -> + @lineMap.maxScreenLineLength + + # Public: Given a buffer position, this converts it into a screen position. + # + # bufferPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - A hash of options with the following keys: + # :wrapBeyondNewlines - + # :wrapAtSoftNewlines - + # + # Returns a {Point}. + screenPositionForBufferPosition: (position, options) -> + @lineMap.screenPositionForBufferPosition(position, options) + + # Public: Given a buffer range, this converts it into a screen position. + # + # screenPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - A hash of options with the following keys: + # :wrapBeyondNewlines - + # :wrapAtSoftNewlines - + # + # Returns a {Point}. + bufferPositionForScreenPosition: (position, options) -> + @lineMap.bufferPositionForScreenPosition(position, options) + + # Public: Retrieves the grammar's token scopes for a buffer position. + # + # bufferPosition - A {Point} in the {Buffer} + # + # Returns an {Array} of {String}s. + scopesForBufferPosition: (bufferPosition) -> + @tokenizedBuffer.scopesForPosition(bufferPosition) + + # Public: Retrieves the current tab length. + # + # Returns a {Number}. + getTabLength: -> + @tokenizedBuffer.getTabLength() + + # Public: Specifies the tab length. + # + # tabLength - A {Number} that defines the new tab length. + setTabLength: (tabLength) -> + @tokenizedBuffer.setTabLength(tabLength) + + # Public: Given a position, this clips it to a real position. + # + # For example, if `position`'s row exceeds the row count of the buffer, + # or if its column goes beyond a line's length, this "sanitizes" the value + # to a real position. + # + # position - The {Point} to clip + # options - A hash with the following values: + # :wrapBeyondNewlines - if `true`, continues wrapping past newlines + # :wrapAtSoftNewlines - if `true`, continues wrapping past soft newlines + # :screenLine - if `true`, indicates that you're using a line number, not a row number + # + # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. + clipScreenPosition: (position, options) -> + @lineMap.clipScreenPosition(position, options) + + ### + # Internal # + ### + + registerFold: (fold) -> + @activeFolds[fold.startRow] ?= [] + @activeFolds[fold.startRow].push(fold) + @foldsById[fold.id] = fold + + unregisterFold: (bufferRow, fold) -> + folds = @activeFolds[bufferRow] + _.remove(folds, fold) + delete @foldsById[fold.id] + delete @activeFolds[bufferRow] if folds.length == 0 + destroyFold: (fold) -> @unregisterFold(fold.startRow, fold) @@ -154,83 +394,6 @@ class DisplayBuffer @triggerChanged({ start, end, screenDelta, bufferDelta }) - destroyFoldsContainingBufferRow: (bufferRow) -> - for row, folds of @activeFolds - for fold in new Array(folds...) - fold.destroy() if fold.getBufferRange().containsRow(bufferRow) - - registerFold: (fold) -> - @activeFolds[fold.startRow] ?= [] - @activeFolds[fold.startRow].push(fold) - @foldsById[fold.id] = fold - - unregisterFold: (bufferRow, fold) -> - folds = @activeFolds[bufferRow] - _.remove(folds, fold) - delete @foldsById[fold.id] - delete @activeFolds[bufferRow] if folds.length == 0 - - largestFoldStartingAtBufferRow: (bufferRow) -> - return unless folds = @activeFolds[bufferRow] - (folds.sort (a, b) -> b.endRow - a.endRow)[0] - - largestFoldStartingAtScreenRow: (screenRow) -> - @largestFoldStartingAtBufferRow(@bufferRowForScreenRow(screenRow)) - - largestFoldContainingBufferRow: (bufferRow) -> - largestFold = null - for currentBufferRow in [bufferRow..0] - if fold = @largestFoldStartingAtBufferRow(currentBufferRow) - largestFold = fold if fold.endRow >= bufferRow - largestFold - - screenLineRangeForBufferRange: (bufferRange) -> - @expandScreenRangeToLineEnds( - @lineMap.screenRangeForBufferRange( - @expandBufferRangeToLineEnds(bufferRange))) - - screenRowForBufferRow: (bufferRow) -> - @lineMap.screenPositionForBufferPosition([bufferRow, 0]).row - - lastScreenRowForBufferRow: (bufferRow) -> - @lineMap.screenPositionForBufferPosition([bufferRow, Infinity]).row - - bufferRowForScreenRow: (screenRow) -> - @lineMap.bufferPositionForScreenPosition([screenRow, 0]).row - - screenRangeForBufferRange: (bufferRange) -> - @lineMap.screenRangeForBufferRange(bufferRange) - - bufferRangeForScreenRange: (screenRange) -> - @lineMap.bufferRangeForScreenRange(screenRange) - - lineCount: -> - @lineMap.screenLineCount() - - getLastRow: -> - @lineCount() - 1 - - maxLineLength: -> - @lineMap.maxScreenLineLength - - screenPositionForBufferPosition: (position, options) -> - @lineMap.screenPositionForBufferPosition(position, options) - - bufferPositionForScreenPosition: (position, options) -> - @lineMap.bufferPositionForScreenPosition(position, options) - - scopesForBufferPosition: (bufferPosition) -> - @tokenizedBuffer.scopesForPosition(bufferPosition) - - getTabLength: -> - @tokenizedBuffer.getTabLength() - - setTabLength: (tabLength) -> - @tokenizedBuffer.setTabLength(tabLength) - - clipScreenPosition: (position, options) -> - @lineMap.clipScreenPosition(position, options) - handleBufferChange: (e) -> allFolds = [] # Folds can modify @activeFolds, so first make sure we have a stable array of folds allFolds.push(folds...) for row, folds of @activeFolds @@ -299,6 +462,17 @@ class DisplayBuffer lineFragments + ### + # Public # + ### + + # Public: Given a line, finds the point where it would wrap. + # + # line - The {String} to check + # softWrapColumn - The {Number} where you want soft wrapping to occur + # + # Returns a {Number} representing the `line` position where the wrap would take place. + # Returns `null` if a wrap wouldn't occur. findWrapColumn: (line, softWrapColumn) -> return unless line.length > softWrapColumn @@ -313,99 +487,247 @@ class DisplayBuffer return column + 1 if /\s/.test(line[column]) return softWrapColumn + # Public: Given a range in screen coordinates, this expands it to the start and end of a line + # + # screenRange - The {Range} to expand + # + # Returns a new {Range}. expandScreenRangeToLineEnds: (screenRange) -> screenRange = Range.fromObject(screenRange) { start, end } = screenRange new Range([start.row, 0], [end.row, @lineMap.lineForScreenRow(end.row).text.length]) + # Public: Given a range in buffer coordinates, this expands it to the start and end of a line + # + # screenRange - The {Range} to expand + # + # Returns a new {Range}. expandBufferRangeToLineEnds: (bufferRange) -> bufferRange = Range.fromObject(bufferRange) { start, end } = bufferRange new Range([start.row, 0], [end.row, Infinity]) + # Public: Calculates a {Range} representing the start of the {Buffer} until the end. + # + # Returns a {Range}. rangeForAllLines: -> new Range([0, 0], @clipScreenPosition([Infinity, Infinity])) + # Public: Retrieves a {DisplayBufferMarker} based on its id. + # + # id - A {Number} representing a marker id + # + # Returns the {DisplayBufferMarker} (if it exists). getMarker: (id) -> @markers[id] ? new DisplayBufferMarker({id, displayBuffer: this}) + # Public: Retrieves the active markers in the buffer. + # + # Returns an {Array} of existing {DisplayBufferMarker}s. getMarkers: -> _.values(@markers) + # Public: Constructs a new marker at the given screen range. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markScreenRange: (args...) -> bufferRange = @bufferRangeForScreenRange(args.shift()) @markBufferRange(bufferRange, args...) + # Public: Constructs a new marker at the given buffer range. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markBufferRange: (args...) -> @buffer.markRange(args...) + # Public: Constructs a new marker at the given screen position. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markScreenPosition: (screenPosition, options) -> @markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options) + # Public: Constructs a new marker at the given buffer position. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markBufferPosition: (bufferPosition, options) -> @buffer.markPosition(bufferPosition, options) + # Public: Removes the marker with the given id. + # + # id - The {Number} of the ID to remove destroyMarker: (id) -> @buffer.destroyMarker(id) delete @markers[id] + # Public: Gets the screen range of the display marker. + # + # id - The {Number} of the ID to check + # + # Returns a {Range}. getMarkerScreenRange: (id) -> @getMarker(id).getScreenRange() + # Public: Modifies the screen range of the display marker. + # + # id - The {Number} of the ID to change + # screenRange - The new {Range} to use + # options - A hash of options matching those found in {BufferMarker.setRange} setMarkerScreenRange: (id, screenRange, options) -> @getMarker(id).setScreenRange(screenRange, options) + # Public: Gets the buffer range of the display marker. + # + # id - The {Number} of the ID to check + # + # Returns a {Range}. getMarkerBufferRange: (id) -> @getMarker(id).getBufferRange() + # Public: Modifies the buffer range of the display marker. + # + # id - The {Number} of the ID to change + # screenRange - The new {Range} to use + # options - A hash of options matching those found in {BufferMarker.setRange} setMarkerBufferRange: (id, bufferRange, options) -> @getMarker(id).setBufferRange(bufferRange, options) + # Public: Retrieves the screen position of the marker's head. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerScreenPosition: (id) -> @getMarkerHeadScreenPosition(id) + # Public: Retrieves the buffer position of the marker's head. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerBufferPosition: (id) -> @getMarkerHeadBufferPosition(id) + # Public: Retrieves the screen position of the marker's head. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerHeadScreenPosition: (id) -> @getMarker(id).getHeadScreenPosition() + # Public: Sets the screen position of the marker's head. + # + # id - The {Number} of the ID to change + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setMarkerHeadScreenPosition: (id, screenPosition, options) -> @getMarker(id).setHeadScreenPosition(screenPosition, options) + # Public: Retrieves the buffer position of the marker's head. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerHeadBufferPosition: (id) -> @getMarker(id).getHeadBufferPosition() + # Public: Sets the buffer position of the marker's head. + # + # id - The {Number} of the ID to check + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setMarkerHeadBufferPosition: (id, bufferPosition) -> @getMarker(id).setHeadBufferPosition(bufferPosition) - + + # Public: Retrieves the screen position of the marker's tail. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerTailScreenPosition: (id) -> @getMarker(id).getTailScreenPosition() + # Public: Sets the screen position of the marker's tail. + # + # id - The {Number} of the ID to change + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setMarkerTailScreenPosition: (id, screenPosition, options) -> @getMarker(id).setTailScreenPosition(screenPosition, options) + # Public: Retrieves the buffer position of the marker's tail. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerTailBufferPosition: (id) -> @getMarker(id).getTailBufferPosition() + # Public: Sets the buffer position of the marker's tail. + # + # id - The {Number} of the ID to check + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setMarkerTailBufferPosition: (id, bufferPosition) -> @getMarker(id).setTailBufferPosition(bufferPosition) + # Public: 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. + # + # id - A {Number} representing the marker to change + # + # Returns a {Point} representing the new tail position. placeMarkerTail: (id) -> @getMarker(id).placeTail() + # Public: Removes the tail from the marker. + # + # id - A {Number} representing the marker to change clearMarkerTail: (id) -> @getMarker(id).clearTail() + # Public: 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}. + # + # id - A {Number} representing the marker to check + # + # Returns a {Boolean}. isMarkerReversed: (id) -> @buffer.isMarkerReversed(id) + # Public: Identifies if the marker's head position is equal to its tail. + # + # id - A {Number} representing the marker to check + # + # Returns a {Boolean}. isMarkerRangeEmpty: (id) -> @buffer.isMarkerRangeEmpty(id) + # Public: Sets a callback to be fired whenever a marker is changed. + # + # id - A {Number} representing the marker to watch + # callback - A {Function} to execute observeMarker: (id, callback) -> @getMarker(id).observe(callback) + ### + # Internal # + ### + pauseMarkerObservers: -> marker.pauseEvents() for marker in @getMarkers() diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index e394fddc7..7100c5429 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -10,10 +10,15 @@ Range = require 'range' _ = require 'underscore' fsUtils = require 'fs-utils' +# Public: An `EditSession` manages the states between {Editor}s, {Buffer}s, and the project as a whole. module.exports = class EditSession registerDeserializer(this) + ### + # Internal # + ### + @version: 1 @deserialize: (state) -> @@ -62,20 +67,6 @@ class EditSession getViewClass: -> require 'editor' - getTitle: -> - if path = @getPath() - fsUtils.base(path) - else - 'untitled' - - getLongTitle: -> - if path = @getPath() - fileName = fsUtils.base(path) - directory = fsUtils.base(fsUtils.directory(path)) - "#{fileName} - #{directory}" - else - 'untitled' - destroy: -> return if @destroyed @destroyed = true @@ -96,9 +87,49 @@ class EditSession scrollLeft: @getScrollLeft() cursorScreenPosition: @getCursorScreenPosition().serialize() + # Internal: Creates a copy of the current {EditSession}. + # + # Returns an identical `EditSession`. copy: -> EditSession.deserialize(@serialize(), @project) + ### + # Public # + ### + + # Public: Retrieves the filename of the open file. + # + # This is `'untitled'` if the file is new and not saved to the disk. + # + # Returns a {String}. + getTitle: -> + if path = @getPath() + fsUtils.base(path) + else + 'untitled' + + # Public: Retrieves the filename of the open file, followed by a dash, then the file's directory. + # + # If the file is brand new, the title is `untitled`. + # + # Returns a {String}. + getLongTitle: -> + if path = @getPath() + fileName = fsUtils.base(path) + directory = fsUtils.base(fsUtils.directory(path)) + "#{fileName} - #{directory}" + else + 'untitled' + + # Public: Compares two `EditSession`s to determine equality. + # + # Equality is based on the condition that: + # + # * the two {Buffer}s are the same + # * the two `scrollTop` and `scrollLeft` property are the same + # * the two {Cursor} screen positions are the same + # + # Returns a {Boolean}. isEqual: (other) -> return false unless other instanceof EditSession @buffer == other.buffer and @@ -106,37 +137,107 @@ class EditSession @scrollLeft == other.getScrollLeft() and @getCursorScreenPosition().isEqual(other.getCursorScreenPosition()) + setVisible: (visible) -> @displayBuffer.setVisible(visible) + # Public: Defines the value of the `EditSession`'s `scrollTop` property. + # + # scrollTop - A {Number} defining the `scrollTop`, in pixels. setScrollTop: (@scrollTop) -> + # Public: Gets the value of the `EditSession`'s `scrollTop` property. + # + # Returns a {Number} defining the `scrollTop`, in pixels. getScrollTop: -> @scrollTop + # Public: Defines the value of the `EditSession`'s `scrollLeft` property. + # + # scrollLeft - A {Number} defining the `scrollLeft`, in pixels. setScrollLeft: (@scrollLeft) -> + # Public: Gets the value of the `EditSession`'s `scrollLeft` property. + # + # Returns a {Number} defining the `scrollLeft`, in pixels. getScrollLeft: -> @scrollLeft + # Public: Defines the limit at which the buffer begins to soft wrap text. + # + # softWrapColumn - A {Number} defining the soft wrap limit setSoftWrapColumn: (@softWrapColumn) -> @displayBuffer.setSoftWrapColumn(@softWrapColumn) + # Public: Defines whether to use soft tabs. + # + # softTabs - A {Boolean} which, if `true`, indicates that you want soft tabs. setSoftTabs: (@softTabs) -> - + # Public: Retrieves whether soft tabs are enabled. + # + # Returns a {Boolean}. getSoftWrap: -> @softWrap + # Public: Defines whether to use soft wrapping of text. + # + # softTabs - A {Boolean} which, if `true`, indicates that you want soft wraps. setSoftWrap: (@softWrap) -> + # Public: Retrieves that character used to indicate a tab. + # + # If soft tabs are enabled, this is a space (`" "`) times the {.getTabLength} value. + # Otherwise, it's a tab (`\t`). + # + # Returns a {String}. getTabText: -> @buildIndentString(1) + # Public: Retrieves the current tab length. + # + # Returns a {Number}. getTabLength: -> @displayBuffer.getTabLength() - setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength) + # Public: Specifies the tab length. + # + # tabLength - A {Number} that defines the new tab length. + setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength) + + # Public: Given a position, this clips it to a real position. + # + # For example, if `position`'s row exceeds the row count of the buffer, + # or if its column goes beyond a line's length, this "sanitizes" the value + # to a real position. + # + # position - The {Point} to clip + # + # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition) + + # Public: Given a range, this clips it to a real range. + # + # For example, if `range`'s row exceeds the row count of the buffer, + # or if its column goes beyond a line's length, this "sanitizes" the value + # to a real range. + # + # range - The {Point} to clip + # + # Returns the new, clipped {Point}. Note that this could be the same as `range` if no clipping was performed. clipBufferRange: (range) -> @buffer.clipRange(range) + # Public: Given a buffer row, this retrieves the indentation level. + # + # bufferRow - A {Number} indicating the buffer row. + # + # Returns the indentation level as a {Number}. indentationForBufferRow: (bufferRow) -> @indentLevelForLine(@lineForBufferRow(bufferRow)) + # Public: This specifies the new indentation level for a buffer row. + # + # bufferRow - A {Number} indicating the buffer row. + # newLevel - A {Number} indicating the new indentation level. setIndentationForBufferRow: (bufferRow, newLevel) -> currentLevel = @indentationForBufferRow(bufferRow) currentIndentString = @buildIndentString(currentLevel) newIndentString = @buildIndentString(newLevel) @buffer.change([[bufferRow, 0], [bufferRow, currentIndentString.length]], newIndentString) + # Internal: Given a line, this gets the indentation level. + # + # line - A {String} in the current {Buffer}. + # + # Returns a {Number}. indentLevelForLine: (line) -> if match = line.match(/^[\t ]+/) leadingWhitespace = match[0] @@ -146,63 +247,218 @@ class EditSession else 0 + # Internal: Constructs the string used for tabs. buildIndentString: (number) -> if @softTabs _.multiplyString(" ", number * @getTabLength()) else _.multiplyString("\t", Math.floor(number)) + # Public: Saves the buffer. save: -> @buffer.save() + # Public: Saves the buffer at a specific path. + # + # path - The path to save at. saveAs: (path) -> @buffer.saveAs(path) + # Public: Retrieves the current buffer's file extension. + # + # Returns a {String}. getFileExtension: -> @buffer.getExtension() + # Public: Retrieves the current buffer's file path. + # + # Returns a {String}. getPath: -> @buffer.getPath() + # Public: Retrieves the current buffer. + # + # Returns a {String}. getBuffer: -> @buffer + # Public: Retrieves the current buffer's URI. + # + # Returns a {String}. getUri: -> @getPath() + # Public: Given a buffer row, identifies if it is blank. + # + # bufferRow - A buffer row {Number} to check + # + # Returns a {Boolean}. isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow) + # Public: Given a buffer row, this finds the next row that's blank. + # + # bufferRow - A buffer row {Number} to check + # + # Returns a {Number}, or `null` if there's no other blank row. nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow) + # Public: Finds the last point in the current buffer. + # + # Returns a {Point} representing the last position. getEofBufferPosition: -> @buffer.getEofPosition() + # Public: Finds the last line in the current buffer. + # + # Returns a {Number}. getLastBufferRow: -> @buffer.getLastRow() + # Public: Given a buffer row, this retrieves the range for that line. + # + # row - A {Number} identifying the row + # options - A hash with one key, `includeNewline`, which specifies whether you + # want to include the trailing newline + # + # Returns a {Range}. bufferRangeForBufferRow: (row, options) -> @buffer.rangeForRow(row, options) + # Public: Given a buffer row, this retrieves that line. + # + # row - A {Number} identifying the row + # + # Returns a {String}. lineForBufferRow: (row) -> @buffer.lineForRow(row) + # Public: Given a buffer row, this retrieves that line's length. + # + # row - A {Number} identifying the row + # + # Returns a {Number}. lineLengthForBufferRow: (row) -> @buffer.lineLengthForRow(row) + # Public: Scans for text in the buffer, 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 scanInBufferRange: (args...) -> @buffer.scanInRange(args...) + # Public: Scans for text in the buffer _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 backwardsScanInBufferRange: (args...) -> @buffer.backwardsScanInRange(args...) + # Public: Identifies if the {Buffer} is modified (and not saved). + # + # Returns a {Boolean}. isModified: -> @buffer.isModified() + # Public: Identifies if the modified buffer should let you know if it's closing + # without being saved. + # + # Returns a {Boolean}. shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors() + # Public: Given a buffer position, this converts it into a screen position. + # + # bufferPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - The same options available to {DisplayBuffer.screenPositionForBufferPosition}. + # + # Returns a {Point}. screenPositionForBufferPosition: (bufferPosition, options) -> @displayBuffer.screenPositionForBufferPosition(bufferPosition, options) + + # Public: Given a buffer range, this converts it into a screen position. + # + # screenPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - The same options available to {DisplayBuffer.bufferPositionForScreenPosition}. + # + # Returns a {Point}. bufferPositionForScreenPosition: (screenPosition, options) -> @displayBuffer.bufferPositionForScreenPosition(screenPosition, options) - screenRangeForBufferRange: (range) -> @displayBuffer.screenRangeForBufferRange(range) - bufferRangeForScreenRange: (range) -> @displayBuffer.bufferRangeForScreenRange(range) + + # Public: Given a buffer range, this converts it into a screen position. + # + # bufferRange - The {Range} to convert + # + # Returns a {Range}. + screenRangeForBufferRange: (bufferRange) -> @displayBuffer.screenRangeForBufferRange(bufferRange) + + # Public: Given a screen range, this converts it into a buffer position. + # + # screenRange - The {Range} to convert + # + # Returns a {Range}. + bufferRangeForScreenRange: (screenRange) -> @displayBuffer.bufferRangeForScreenRange(screenRange) + # Public: Given a position, this clips it to a real position. + # + # For example, if `position`'s row exceeds the row count of the buffer, + # or if its column goes beyond a line's length, this "sanitizes" the value + # to a real position. + # + # position - The {Point} to clip + # options - A hash with the following values: + # :wrapBeyondNewlines - if `true`, continues wrapping past newlines + # :wrapAtSoftNewlines - if `true`, continues wrapping past soft newlines + # :screenLine - if `true`, indicates that you're using a line number, not a row number + # + # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options) + # Public: Gets the line for the given screen row. + # + # screenRow - A {Number} indicating the screen row. + # + # Returns a {String}. lineForScreenRow: (row) -> @displayBuffer.lineForRow(row) + # Public: Gets the lines for the given screen row boundaries. + # + # start - A {Number} indicating the beginning screen row. + # end - A {Number} indicating the ending screen row. + # + # Returns an {Array} of {String}s. linesForScreenRows: (start, end) -> @displayBuffer.linesForRows(start, end) + # Public: Gets the number of screen rows. + # + # Returns a {Number}. screenLineCount: -> @displayBuffer.lineCount() + # Public: Gets the length of the longest screen line. + # + # Returns a {Number}. maxScreenLineLength: -> @displayBuffer.maxLineLength() + # Public: Gets the number of the last row in the buffer. + # + # Returns a {Number}. getLastScreenRow: -> @displayBuffer.getLastRow() + # Public: Given a starting and ending row, this converts every row into a buffer position. + # + # startRow - The row {Number} to start at + # endRow - The row {Number} to end at (default: {.getLastScreenRow}) + # + # Returns an {Array} of {Range}s. bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow) + # Public: Retrieves the grammar's token scopes for a buffer position. + # + # bufferPosition - A {Point} in the {Buffer} + # + # Returns an {Array} of {String}s. scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition) + # Public: Retrieves the grammar's token scopes for the line with the most recently added cursor. + # + # Returns an {Array} of {String}s. getCursorScopes: -> @getCursor().getScopes() + # Internal: logScreenLines: (start, end) -> @displayBuffer.logLines(start, end) + # Public: Determines whether the {Editor} will auto indent rows. + # + # Returns a {Boolean}. shouldAutoIndent: -> config.get("editor.autoIndent") + # Public: Determines whether the {Editor} will auto indent pasted text. + # + # Returns a {Boolean}. shouldAutoIndentPastedText: -> config.get("editor.autoIndentOnPaste") + # Public: Inserts text at the current cursor positions + # + # text - A {String} representing the text to insert. + # options - A set of options equivalent to {Selection.insertText} insertText: (text, options={}) -> options.autoIndent ?= @shouldAutoIndent() @mutateSelectedText (selection) -> selection.insertText(text, options) + # Public: Inserts a new line at the current cursor positions. insertNewline: -> @insertText('\n') + # Public: Inserts a new line below the current cursor positions. insertNewlineBelow: -> @transact => @moveCursorToEndOfLine() @insertNewline() + # Public: Inserts a new line above the current cursor positions. insertNewlineAbove: -> @transact => onFirstLine = @getCursorBufferPosition().row is 0 @@ -211,62 +467,91 @@ class EditSession @insertNewline() @moveCursorUp() if onFirstLine + # Public: Indents the current line. + # + # options - A set of options equivalent to {Selection.indent}. indent: (options={})-> options.autoIndent ?= @shouldAutoIndent() @mutateSelectedText (selection) -> selection.indent(options) + # Public: Performs a backspace, removing the character found behind the cursor position. backspace: -> @mutateSelectedText (selection) -> selection.backspace() + # Public: Performs a backspace to the beginning of the current word, removing characters found there. backspaceToBeginningOfWord: -> @mutateSelectedText (selection) -> selection.backspaceToBeginningOfWord() + # Public: Performs a backspace to the beginning of the current line, removing characters found there. backspaceToBeginningOfLine: -> @mutateSelectedText (selection) -> selection.backspaceToBeginningOfLine() + # Public: Performs a delete, removing the character found ahead of the cursor position. delete: -> @mutateSelectedText (selection) -> selection.delete() + # Public: Performs a delete to the end of the current word, removing characters found there. deleteToEndOfWord: -> @mutateSelectedText (selection) -> selection.deleteToEndOfWord() + # Public: Deletes the entire line. deleteLine: -> @mutateSelectedText (selection) -> selection.deleteLine() + # Public: Indents the selected rows. indentSelectedRows: -> @mutateSelectedText (selection) -> selection.indentSelectedRows() + # Public: Outdents the selected rows. outdentSelectedRows: -> @mutateSelectedText (selection) -> selection.outdentSelectedRows() + # Public: Wraps the lines within a selection in comments. + # + # If the language doesn't have comments, nothing happens. + # + # selection - The {Selection} to comment + # + # Returns an {Array} of the commented {Ranges}. toggleLineCommentsInSelection: -> @mutateSelectedText (selection) -> selection.toggleLineComments() autoIndentSelectedRows: -> @mutateSelectedText (selection) -> selection.autoIndentSelectedRows() + # Given a buffer range, this converts all `\t` characters to the appopriate {.getTabText} value. + # + # bufferRange - The {Range} to perform the replace in normalizeTabsInBufferRange: (bufferRange) -> return unless @softTabs @scanInBufferRange /\t/, bufferRange, ({replace}) => replace(@getTabText()) + # Public: Performs a cut to the end of the current line. + # + # Characters are removed, but the text remains in the clipboard. cutToEndOfLine: -> maintainPasteboard = false @mutateSelectedText (selection) -> selection.cutToEndOfLine(maintainPasteboard) maintainPasteboard = true + # Public: Cuts the selected text. cutSelectedText: -> maintainPasteboard = false @mutateSelectedText (selection) -> selection.cut(maintainPasteboard) maintainPasteboard = true + # Public: Copies the selected text. copySelectedText: -> maintainPasteboard = false for selection in @getSelections() selection.copy(maintainPasteboard) maintainPasteboard = true + # Public: Pastes the text in the clipboard. + # + # options - A set of options equivalent to {Selection.insertText}. pasteText: (options={}) -> options.normalizeIndent ?= true options.autoIndent ?= @shouldAutoIndentPastedText() @@ -276,12 +561,18 @@ class EditSession @insertText(text, options) + # Public: Undos the last {Buffer} change. undo: -> @buffer.undo(this) + # Public: Redos the last {Buffer} change. redo: -> @buffer.redo(this) + ### + # Internal # + ### + transact: (fn) -> isNewTransaction = @buffer.transact() oldSelectedRanges = @getSelectedBufferRanges() @@ -303,78 +594,166 @@ class EditSession abort: -> @buffer.abort() + ### + # Public # + ### + + # Public: Folds all the rows. foldAll: -> @displayBuffer.foldAll() + # Public: Unfolds all the rows. unfoldAll: -> @displayBuffer.unfoldAll() + # Public: Folds the current row. foldCurrentRow: -> bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row @foldBufferRow(bufferRow) + # Public: Given a buffer row, this folds it. + # + # bufferRow - A {Number} indicating the buffer row foldBufferRow: (bufferRow) -> @displayBuffer.foldBufferRow(bufferRow) + # Public: Unfolds the current row. unfoldCurrentRow: -> bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row @unfoldBufferRow(bufferRow) + # Public: Given a buffer row, this unfolds it. + # + # bufferRow - A {Number} indicating the buffer row unfoldBufferRow: (bufferRow) -> @displayBuffer.unfoldBufferRow(bufferRow) + # Public: Folds all selections. foldSelection: -> selection.fold() for selection in @getSelections() + # Public: Creates a new fold between two row numbers. + # + # startRow - The row {Number} to start folding at + # endRow - The row {Number} to end the fold + # + # Returns the new {Fold}. createFold: (startRow, endRow) -> @displayBuffer.createFold(startRow, endRow) + # Public: Removes any {Fold}s found that contain the given buffer row. + # + # bufferRow - The buffer row {Number} to check against destroyFoldsContainingBufferRow: (bufferRow) -> @displayBuffer.destroyFoldsContainingBufferRow(bufferRow) + # Public: Removes any {Fold}s found that intersect the given buffer row. + # + # bufferRow - The buffer row {Number} to check against destroyFoldsIntersectingBufferRange: (bufferRange) -> for row in [bufferRange.start.row..bufferRange.end.row] @destroyFoldsContainingBufferRow(row) + # Public: Given the id of a {Fold}, this removes it. + # + # foldId - The fold id {Number} to remove destroyFold: (foldId) -> fold = @displayBuffer.foldsById[foldId] fold.destroy() @setCursorBufferPosition([fold.startRow, 0]) + # Public: Determines if the given row that the cursor is at is folded. + # + # Returns `true` if the row is folded, `false` otherwise. isFoldedAtCursorRow: -> @isFoldedAtScreenRow(@getCursorScreenRow()) + # Public: Determines if the given buffer row is folded. + # + # bufferRow - A {Number} indicating the buffer row. + # + # Returns `true` if the buffer row is folded, `false` otherwise. isFoldedAtBufferRow: (bufferRow) -> screenRow = @screenPositionForBufferPosition([bufferRow]).row @isFoldedAtScreenRow(screenRow) + # Public: Determines if the given screen row is folded. + # + # screenRow - A {Number} indicating the screen row. + # + # Returns `true` if the screen row is folded, `false` otherwise. isFoldedAtScreenRow: (screenRow) -> @lineForScreenRow(screenRow)?.fold? + # Public: Given a buffer row, this returns the largest fold that includes it. + # + # Largest is defined as the fold whose difference between its start and end points + # are the greatest. + # + # bufferRow - A {Number} indicating the buffer row + # + # Returns a {Fold}. largestFoldContainingBufferRow: (bufferRow) -> @displayBuffer.largestFoldContainingBufferRow(bufferRow) + # Public: Given a screen row, this returns the largest fold that starts there. + # + # Largest is defined as the fold whose difference between its start and end points + # are the greatest. + # + # screenRow - A {Number} indicating the screen row + # + # Returns a {Fold}. largestFoldStartingAtScreenRow: (screenRow) -> @displayBuffer.largestFoldStartingAtScreenRow(screenRow) + # Public: Given a buffer row, this returns a suggested indentation level. + # + # The indentation level provided is based on the current language. + # + # bufferRow - A {Number} indicating the buffer row + # + # Returns a {Number}. suggestedIndentForBufferRow: (bufferRow) -> @languageMode.suggestedIndentForBufferRow(bufferRow) + # Public: Indents all the rows between two buffer rows. + # + # startRow - The row {Number} to start at + # endRow - The row {Number} to end at autoIndentBufferRows: (startRow, endRow) -> @languageMode.autoIndentBufferRows(startRow, endRow) + # Public: Given a buffer row, this indents it. + # + # bufferRow - The row {Number} autoIndentBufferRow: (bufferRow) -> @languageMode.autoIndentBufferRow(bufferRow) + # Public: Given a buffer row, this increases the indentation. + # + # bufferRow - The row {Number} autoIncreaseIndentForBufferRow: (bufferRow) -> @languageMode.autoIncreaseIndentForBufferRow(bufferRow) + # Public: Given a buffer row, this decreases the indentation. + # + # bufferRow - The row {Number} autoDecreaseIndentForRow: (bufferRow) -> @languageMode.autoDecreaseIndentForBufferRow(bufferRow) + # Public: Wraps the lines between two rows in comments. + # + # If the language doesn't have comments, nothing happens. + # + # startRow - The row {Number} to start at + # endRow - The row {Number} to end at + # + # Returns an {Array} of the commented {Ranges}. toggleLineCommentsForBufferRows: (start, end) -> @languageMode.toggleLineCommentsForBufferRows(start, end) + # Public: Moves the selected line up one row. moveLineUp: -> selection = @getSelectedBufferRange() return if selection.start.row is 0 @@ -408,6 +787,7 @@ class EditSession @setSelectedBufferRange(selection.translate([-1]), preserveFolds: true) + # Public: Moves the selected line down one row. moveLineDown: -> selection = @getSelectedBufferRange() lastRow = @buffer.getLastRow() @@ -445,6 +825,9 @@ class EditSession @setSelectedBufferRange(selection.translate([1]), preserveFolds: true) + # Public: Duplicates the current line. + # + # If more than one cursor is present, only the most recently added one is considered. duplicateLine: -> return unless @getSelection().isEmpty() @@ -468,6 +851,11 @@ class EditSession @setCursorScreenPosition(@getCursorScreenPosition().translate([1])) @foldCurrentRow() if cursorRowFolded + + ### + # Internal # + ### + mutateSelectedText: (fn) -> @transact => fn(selection) for selection in @getSelections() @@ -485,106 +873,271 @@ class EditSession pushOperation: (operation) -> @buffer.pushOperation(operation, this) + ### + # Public # + ### + + # Public: Constructs a new marker at the given screen range. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markScreenRange: (args...) -> @displayBuffer.markScreenRange(args...) + # Public: Constructs a new marker at the given buffer range. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markBufferRange: (args...) -> @displayBuffer.markBufferRange(args...) + # Public: Constructs a new marker at the given screen position. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markScreenPosition: (args...) -> @displayBuffer.markScreenPosition(args...) + # Public: Constructs a new marker at the given buffer position. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markBufferPosition: (args...) -> @displayBuffer.markBufferPosition(args...) + # Public: Removes the marker with the given id. + # + # id - The {Number} of the ID to remove destroyMarker: (args...) -> @displayBuffer.destroyMarker(args...) + # Public: Gets the number of markers in the buffer. + # + # Returns a {Number}. getMarkerCount: -> @buffer.getMarkerCount() + # Public: Gets the screen range of the display marker. + # + # id - The {Number} of the ID to check + # + # Returns a {Range}. getMarkerScreenRange: (args...) -> @displayBuffer.getMarkerScreenRange(args...) + # Public: Modifies the screen range of the display marker. + # + # id - The {Number} of the ID to change + # screenRange - The new {Range} to use + # options - A hash of options matching those found in {BufferMarker.setRange} setMarkerScreenRange: (args...) -> @displayBuffer.setMarkerScreenRange(args...) + # Public: Gets the buffer range of the display marker. + # + # id - The {Number} of the ID to check + # + # Returns a {Range}. getMarkerBufferRange: (args...) -> @displayBuffer.getMarkerBufferRange(args...) + # Public: Modifies the buffer range of the display marker. + # + # id - The {Number} of the ID to check + # screenRange - The new {Range} to use + # options - A hash of options matching those found in {BufferMarker.setRange} setMarkerBufferRange: (args...) -> @displayBuffer.setMarkerBufferRange(args...) + # Public: Retrieves the screen position of the marker's head. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerScreenPosition: (args...) -> @displayBuffer.getMarkerScreenPosition(args...) + # Public: Retrieves the buffer position of the marker's head. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerBufferPosition: (args...) -> @displayBuffer.getMarkerBufferPosition(args...) + # Public: Retrieves the screen position of the marker's head. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerHeadScreenPosition: (args...) -> @displayBuffer.getMarkerHeadScreenPosition(args...) - + + # Public: Sets the screen position of the marker's head. + # + # id - The {Number} of the ID to change + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setMarkerHeadScreenPosition: (args...) -> @displayBuffer.setMarkerHeadScreenPosition(args...) + # Public: Retrieves the buffer position of the marker's head. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerHeadBufferPosition: (args...) -> @displayBuffer.getMarkerHeadBufferPosition(args...) + # Public: Sets the buffer position of the marker's head. + # + # id - The {Number} of the ID to check + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setMarkerHeadBufferPosition: (args...) -> @displayBuffer.setMarkerHeadBufferPosition(args...) + # Public: Retrieves the screen position of the marker's tail. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerTailScreenPosition: (args...) -> @displayBuffer.getMarkerTailScreenPosition(args...) + # Public: Sets the screen position of the marker's tail. + # + # id - The {Number} of the ID to change + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setMarkerTailScreenPosition: (args...) -> @displayBuffer.setMarkerTailScreenPosition(args...) - + + # Public: Retrieves the buffer position of the marker's tail. + # + # id - The {Number} of the ID to check + # + # Returns a {Point}. getMarkerTailBufferPosition: (args...) -> @displayBuffer.getMarkerTailBufferPosition(args...) + # Public: Sets the buffer position of the marker's tail. + # + # id - The {Number} of the ID to change + # screenRange - The new {Point} to use + # options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition} setMarkerTailBufferPosition: (args...) -> @displayBuffer.setMarkerTailBufferPosition(args...) + # Public: Sets a callback to be fired whenever a marker is changed. + # + # id - A {Number} representing the marker to watch + # callback - A {Function} to execute observeMarker: (args...) -> @displayBuffer.observeMarker(args...) + # Public: 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. + # + # id - A {Number} representing the marker to change + # + # Returns a {Point} representing the new tail position. placeMarkerTail: (args...) -> @displayBuffer.placeMarkerTail(args...) + # Public: Removes the tail from the marker. + # + # id - A {Number} representing the marker to change clearMarkerTail: (args...) -> @displayBuffer.clearMarkerTail(args...) + # Public: 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}. + # + # id - A {Number} representing the marker to check + # + # Returns a {Boolean}. isMarkerReversed: (args...) -> @displayBuffer.isMarkerReversed(args...) + # Public: Identifies if the marker's head position is equal to its tail. + # + # id - A {Number} representing the marker to check + # + # Returns a {Boolean}. isMarkerRangeEmpty: (args...) -> @displayBuffer.isMarkerRangeEmpty(args...) + # Public: Returns `true` if there are multiple cursors in the edit session. + # + # Returns a {Boolean}. hasMultipleCursors: -> @getCursors().length > 1 + # Public: Retrieves all the cursors. + # + # Returns an {Array} of {Cursor}s. getCursors: -> new Array(@cursors...) + # Public: Retrieves a single cursor + # + # Returns a {Cursor}. getCursor: -> _.last(@cursors) + # Public: 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 + # Public: Adds a cursor at the provided `bufferPosition`. + # + # bufferPosition - An {Array} of two numbers: the buffer row, and the buffer column. + # + # Returns the new {Cursor}. addCursorAtBufferPosition: (bufferPosition) -> marker = @markBufferPosition(bufferPosition, invalidationStrategy: 'never') @addSelection(marker).cursor + # Public: Adds a cursor to the `EditSession`. + # + # marker - The marker where the cursor should be added + # + # Returns the new {Cursor}. addCursor: (marker) -> cursor = new Cursor(editSession: this, marker: marker) @cursors.push(cursor) @trigger 'cursor-added', cursor cursor + # Public: Removes a cursor from the `EditSession`. + # + # cursor - The cursor to remove + # + # Returns the removed {Cursor}. removeCursor: (cursor) -> _.remove(@cursors, cursor) + # Public: Creates a new selection at the given marker. + # + # marker - The marker to highlight + # options - A hash of options that pertain to the {Selection} constructor. + # + # Returns the new {Selection}. addSelection: (marker, options={}) -> unless options.preserveFolds @destroyFoldsIntersectingBufferRange(@getMarkerBufferRange(marker)) @@ -601,14 +1154,28 @@ class EditSession @trigger 'selection-added', selection selection + # Public: Given a buffer range, this adds a new selection for it. + # + # bufferRange - A {Range} in the buffer + # options - A hash of options + # + # Returns the new {Selection}. addSelectionForBufferRange: (bufferRange, options={}) -> options = _.defaults({invalidationStrategy: 'never'}, options) marker = @markBufferRange(bufferRange, options) @addSelection(marker, options) + # Public: Given a buffer range, this removes all previous selections and creates a new selection for it. + # + # bufferRange - A {Range} in the buffer + # options - A hash of options setSelectedBufferRange: (bufferRange, options) -> @setSelectedBufferRanges([bufferRange], options) + # Public: Given an array of buffer ranges, this removes all previous selections and creates new selections for them. + # + # bufferRanges - An {Array} of {Range}s in the buffer + # options - A hash of options setSelectedBufferRanges: (bufferRanges, options={}) -> throw new Error("Passed an empty array to setSelectedBufferRanges") unless bufferRanges.length @@ -623,9 +1190,13 @@ class EditSession @addSelectionForBufferRange(bufferRange, options) @mergeIntersectingSelections(options) + # Public: Unselects a given selection. + # + # selection - The {Selection} to remove. removeSelection: (selection) -> _.remove(@selections, selection) + # Public: Clears every selection. TODO clearSelections: -> @consolidateSelections() @getSelection().clear() @@ -638,145 +1209,248 @@ class EditSession else false + # Public: Gets all the selections. + # + # Returns an {Array} of {Selection}s. getSelections: -> new Array(@selections...) - + + # Public: Gets the selection at the specified index. + # + # index - The id {Number} of the selection + # + # Returns a {Selection}. getSelection: (index) -> index ?= @selections.length - 1 @selections[index] + # Public: Gets the last selection, _i.e._ the most recently added. + # + # Returns a {Selection}. getLastSelection: -> _.last(@selections) + # Public: 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) + # Public: Gets the very last selection, as it's ordered in the buffer. + # + # Returns a {Selection}. getLastSelectionInBuffer: -> _.last(@getSelectionsOrderedByBufferPosition()) + # Public: Determines if a given buffer range is included in a selection. + # + # bufferRange - The {Range} you're checking against + # + # Returns a {Boolean}. selectionIntersectsBufferRange: (bufferRange) -> _.any @getSelections(), (selection) -> selection.intersectsBufferRange(bufferRange) + # Public: Moves every cursor to a given screen position. + # + # position - An {Array} of two numbers: the screen row, and the screen column. + # options - An object with properties based on {Cursor.setScreenPosition} + # setCursorScreenPosition: (position, options) -> @moveCursors (cursor) -> cursor.setScreenPosition(position, options) + # Public: Gets the current screen position. + # + # Returns an {Array} of two numbers: the screen row, and the screen column. getCursorScreenPosition: -> @getCursor().getScreenPosition() + # Public: Gets the current cursor's screen row. + # + # Returns the screen row. getCursorScreenRow: -> @getCursor().getScreenRow() + # Public: Moves every cursor to a given buffer position. + # + # position - An {Array} of two numbers: the buffer row, and the buffer column. + # options - An object with properties based on {Cursor.setBufferPosition} + # setCursorBufferPosition: (position, options) -> @moveCursors (cursor) -> cursor.setBufferPosition(position, options) + # Public: Gets the current buffer position of the cursor. + # + # Returns an {Array} of two numbers: the buffer row, and the buffer column. getCursorBufferPosition: -> @getCursor().getBufferPosition() + # Public: Gets the screen range of the most recently added {Selection}. + # + # Returns a {Range}. getSelectedScreenRange: -> @getLastSelection().getScreenRange() + # Public: Gets the buffer range of the most recently added {Selection}. + # + # Returns a {Range}. getSelectedBufferRange: -> @getLastSelection().getBufferRange() + # Public: Gets the buffer ranges of all the {Selection}s. + # + # This is ordered by their buffer position. + # + # Returns an {Array} of {Range}s. getSelectedBufferRanges: -> selection.getBufferRange() for selection in @getSelectionsOrderedByBufferPosition() + # Public: Gets the currently selected text. + # + # Returns a {String}. getSelectedText: -> @getLastSelection().getText() + # Public: Given a buffer range, this retrieves the text in that range. + # + # range - The {Range} you're interested in + # + # Returns a {String} of the combined lines. getTextInBufferRange: (range) -> @buffer.getTextInRange(range) + # Public: Retrieves the range for the current paragraph. + # + # A paragraph is defined as a block of text surrounded by empty lines. + # + # Returns a {Range}. getCurrentParagraphBufferRange: -> @getCursor().getCurrentParagraphBufferRange() + # Public: Gets the word located under the cursor. + # + # options - An object with properties based on {Cursor.getBeginningOfCurrentWordBufferPosition}. + # + # Returns a {String}. getWordUnderCursor: (options) -> @getTextInBufferRange(@getCursor().getCurrentWordBufferRange(options)) + # Public: Moves every cursor up one row. moveCursorUp: (lineCount) -> @moveCursors (cursor) -> cursor.moveUp(lineCount) + # Public: Moves every cursor down one row. moveCursorDown: (lineCount) -> @moveCursors (cursor) -> cursor.moveDown(lineCount) + # Public: Moves every cursor left one column. moveCursorLeft: -> @moveCursors (cursor) -> cursor.moveLeft() + # Public: Moves every cursor right one column. moveCursorRight: -> @moveCursors (cursor) -> cursor.moveRight() + # Public: Moves every cursor to the top of the buffer. moveCursorToTop: -> @moveCursors (cursor) -> cursor.moveToTop() + # Public: Moves every cursor to the bottom of the buffer. moveCursorToBottom: -> @moveCursors (cursor) -> cursor.moveToBottom() + # Public: Moves every cursor to the beginning of the line. moveCursorToBeginningOfLine: -> @moveCursors (cursor) -> cursor.moveToBeginningOfLine() + # Public: Moves every cursor to the first non-whitespace character of the line. moveCursorToFirstCharacterOfLine: -> @moveCursors (cursor) -> cursor.moveToFirstCharacterOfLine() + # Public: Moves every cursor to the end of the line. moveCursorToEndOfLine: -> @moveCursors (cursor) -> cursor.moveToEndOfLine() + # Public: Moves every cursor to the beginning of the current word. moveCursorToBeginningOfWord: -> @moveCursors (cursor) -> cursor.moveToBeginningOfWord() + # Public: Moves every cursor to the end of the current word. moveCursorToEndOfWord: -> @moveCursors (cursor) -> cursor.moveToEndOfWord() + # Public: Moves every cursor to the beginning of the next word. moveCursorToBeginningOfNextWord: -> @moveCursors (cursor) -> cursor.moveToBeginningOfNextWord() + # Internal: moveCursors: (fn) -> fn(cursor) for cursor in @getCursors() @mergeCursors() + # Public: Selects the text from the current cursor position to a given screen position. + # + # position - An instance of {Point}, with a given `row` and `column`. selectToScreenPosition: (position) -> lastSelection = @getLastSelection() lastSelection.selectToScreenPosition(position) @mergeIntersectingSelections(reverse: lastSelection.isReversed()) + # Public: Selects the text one position right of the cursor. selectRight: -> @expandSelectionsForward (selection) => selection.selectRight() + # Public: Selects the text one position left of the cursor. selectLeft: -> @expandSelectionsBackward (selection) => selection.selectLeft() + # Public: Selects all the text one position above the cursor. selectUp: -> @expandSelectionsBackward (selection) => selection.selectUp() + # Public: Selects all the text one position below the cursor. selectDown: -> @expandSelectionsForward (selection) => selection.selectDown() + # Public: Selects all the text from the current cursor position to the top of the buffer. selectToTop: -> @expandSelectionsBackward (selection) => selection.selectToTop() + # Public: Selects all the text in the buffer. selectAll: -> @expandSelectionsForward (selection) => selection.selectAll() + # Public: Selects all the text from the current cursor position to the bottom of the buffer. selectToBottom: -> @expandSelectionsForward (selection) => selection.selectToBottom() + # Public: Selects all the text from the current cursor position to the beginning of the line. selectToBeginningOfLine: -> @expandSelectionsBackward (selection) => selection.selectToBeginningOfLine() + # Public: Selects all the text from the current cursor position to the end of the line. selectToEndOfLine: -> @expandSelectionsForward (selection) => selection.selectToEndOfLine() + # Public: Selects the current line. selectLine: -> @expandSelectionsForward (selection) => selection.selectLine() + # Public: Moves the current selection down one row. addSelectionBelow: -> @expandSelectionsForward (selection) => selection.addSelectionBelow() + # Public: Moves the current selection up one row. addSelectionAbove: -> @expandSelectionsBackward (selection) => selection.addSelectionAbove() + # Public: Transposes the current text selections. + # + # This only works if there is more than one selection. Each selection is transferred + # to the position of the selection after it. The last selection is transferred to the + # position of the first. transpose: -> @mutateSelectedText (selection) => if selection.isEmpty() @@ -788,27 +1462,37 @@ class EditSession else selection.insertText selection.getText().split('').reverse().join('') + # Public: Turns the current selection into upper case. upperCase: -> @replaceSelectedText selectWordIfEmpty:true, (text) => text.toUpperCase() + # Public: Turns the current selection into lower case. lowerCase: -> @replaceSelectedText selectWordIfEmpty:true, (text) => text.toLowerCase() + # Public: Joins the current line with the one below it. + # + # Multiple cursors are considered equally. If there's a selection in the editor, + # all the lines are joined together. joinLine: -> @mutateSelectedText (selection) -> selection.joinLine() expandLastSelectionOverLine: -> @getLastSelection().expandOverLine() + # Public: Selects all the text from the current cursor position to the beginning of the word. selectToBeginningOfWord: -> @expandSelectionsBackward (selection) => selection.selectToBeginningOfWord() + # Public: Selects all the text from the current cursor position to the end of the word. selectToEndOfWord: -> @expandSelectionsForward (selection) => selection.selectToEndOfWord() + # Public: Selects all the text from the current cursor position to the beginning of the next word. selectToBeginningOfNextWord: -> @expandSelectionsForward (selection) => selection.selectToBeginningOfNextWord() - + + # Public: Selects the current word. selectWord: -> @expandSelectionsForward (selection) => selection.selectWord() @@ -822,6 +1506,11 @@ class EditSession else false + # Public: 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) @@ -855,6 +1544,7 @@ class EditSession @mergeIntersectingSelections(options) return + # Internal: inspect: -> JSON.stringify @serialize() @@ -866,18 +1556,27 @@ class EditSession @setCursorBufferPosition(cursorPosition) if cursorPosition cursorPosition = null + # Public: Retrieves the current {EditSession}'s grammar. + # + # Returns a {String} indicating the language's grammar rules. getGrammar: -> @languageMode.grammar + # Public: Sets the current {EditSession}'s grammar. + # + # grammar - A {String} indicating the language's grammar rules. setGrammar: (grammar) -> @languageMode.setGrammar(grammar) + # Public: Reloads the current grammar. reloadGrammar: -> @languageMode.reloadGrammar() + # Internal: handleGrammarChange: -> @unfoldAll() @trigger 'grammar-changed' + # Internal: getDebugSnapshot: -> [ @displayBuffer.getDebugSnapshot() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 09c1829d7..202f817cf 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -10,6 +10,9 @@ fsUtils = require 'fs-utils' $ = require 'jquery' _ = require 'underscore' +# Public: Represents the entire visual pane in Atom. +# +# The Editor manages the {EditSession}, which manages the file buffers. module.exports = class Editor extends View @configDefaults: @@ -23,6 +26,10 @@ class Editor extends View @nextEditorId: 1 + ### + # Internal # + ### + @content: (params) -> @div class: @classes(params), tabindex: -1, => @subview 'gutter', new Gutter @@ -56,6 +63,13 @@ class Editor extends View newSelections: null redrawOnReattach: false + # Public: The constructor for setting up an `Editor` instance. + # + # editSessionOrOptions - Either an {EditSession}, or an object with one property, `mini`. + # If `mini` is `true`, a "miniature" `EditSession` is constructed. + # Typically, this is ideal for scenarios where you need an Atom editor, + # but without all the chrome, like scrollbars, gutter, _e.t.c._. + # initialize: (editSessionOrOptions) -> if editSessionOrOptions instanceof EditSession editSession = editSessionOrOptions @@ -87,6 +101,9 @@ class Editor extends View else throw new Error("Must supply an EditSession or mini: true") + # Internal: Sets up the core Atom commands. + # + # Some commands are excluded from mini-editors. bindKeys: -> editorBindings = 'core:move-left': @moveCursorLeft @@ -171,139 +188,418 @@ class Editor extends View do (name, method) => @command name, (e) => method.call(this, e); false - getCursor: -> @activeEditSession.getCursor() - getCursors: -> @activeEditSession.getCursors() - addCursorAtScreenPosition: (screenPosition) -> @activeEditSession.addCursorAtScreenPosition(screenPosition) - addCursorAtBufferPosition: (bufferPosition) -> @activeEditSession.addCursorAtBufferPosition(bufferPosition) - moveCursorUp: -> @activeEditSession.moveCursorUp() - moveCursorDown: -> @activeEditSession.moveCursorDown() - moveCursorLeft: -> @activeEditSession.moveCursorLeft() - moveCursorRight: -> @activeEditSession.moveCursorRight() - moveCursorToBeginningOfWord: -> @activeEditSession.moveCursorToBeginningOfWord() - moveCursorToEndOfWord: -> @activeEditSession.moveCursorToEndOfWord() - moveCursorToBeginningOfNextWord: -> @activeEditSession.moveCursorToBeginningOfNextWord() - moveCursorToTop: -> @activeEditSession.moveCursorToTop() - moveCursorToBottom: -> @activeEditSession.moveCursorToBottom() - moveCursorToBeginningOfLine: -> @activeEditSession.moveCursorToBeginningOfLine() - moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine() - moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine() - moveLineUp: -> @activeEditSession.moveLineUp() - moveLineDown: -> @activeEditSession.moveLineDown() - setCursorScreenPosition: (position, options) -> @activeEditSession.setCursorScreenPosition(position, options) - duplicateLine: -> @activeEditSession.duplicateLine() - joinLine: -> @activeEditSession.joinLine() - getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition() - getCursorScreenRow: -> @activeEditSession.getCursorScreenRow() - setCursorBufferPosition: (position, options) -> @activeEditSession.setCursorBufferPosition(position, options) - getCursorBufferPosition: -> @activeEditSession.getCursorBufferPosition() - getCurrentParagraphBufferRange: -> @activeEditSession.getCurrentParagraphBufferRange() - getWordUnderCursor: (options) -> @activeEditSession.getWordUnderCursor(options) + ### + # Public # + ### + # Public: Retrieves a single cursor + # + # Returns a {Cursor}. + getCursor: -> @activeEditSession.getCursor() + # Public: Retrieves an array of all the cursors. + # + # Returns a {[Cursor]}. + getCursors: -> @activeEditSession.getCursors() + # Public: 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) -> @activeEditSession.addCursorAtScreenPosition(screenPosition) + # Public: Adds a cursor at the provided `bufferPosition`. + # + # bufferPosition - An {Array} of two numbers: the buffer row, and the buffer column. + # + # Returns the new {Cursor}. + addCursorAtBufferPosition: (bufferPosition) -> @activeEditSession.addCursorAtBufferPosition(bufferPosition) + # Public: Moves every cursor up one row. + moveCursorUp: -> @activeEditSession.moveCursorUp() + # Public: Moves every cursor down one row. + moveCursorDown: -> @activeEditSession.moveCursorDown() + # Public: Moves every cursor left one column. + moveCursorLeft: -> @activeEditSession.moveCursorLeft() + # Public: Moves every cursor right one column. + moveCursorRight: -> @activeEditSession.moveCursorRight() + # Public: Moves every cursor to the beginning of the current word. + moveCursorToBeginningOfWord: -> @activeEditSession.moveCursorToBeginningOfWord() + # Public: Moves every cursor to the end of the current word. + moveCursorToEndOfWord: -> @activeEditSession.moveCursorToEndOfWord() + # Public: Moves the cursor to the beginning of the next word. + moveCursorToBeginningOfNextWord: -> @activeEditSession.moveCursorToBeginningOfNextWord() + # Public: Moves every cursor to the top of the buffer. + moveCursorToTop: -> @activeEditSession.moveCursorToTop() + # Public: Moves every cursor to the bottom of the buffer. + moveCursorToBottom: -> @activeEditSession.moveCursorToBottom() + # Public: Moves every cursor to the beginning of the line. + moveCursorToBeginningOfLine: -> @activeEditSession.moveCursorToBeginningOfLine() + # Public: Moves every cursor to the first non-whitespace character of the line. + moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine() + # Public: Moves every cursor to the end of the line. + moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine() + # Public: Moves the selected line up one row. + moveLineUp: -> @activeEditSession.moveLineUp() + # Public: Moves the selected line down one row. + moveLineDown: -> @activeEditSession.moveLineDown() + # Public: Sets the cursor based on a given screen position. + # + # position - An {Array} of two numbers: the screen row, and the screen column. + # options - An object with properties based on {Cursor.setScreenPosition}. + # + setCursorScreenPosition: (position, options) -> @activeEditSession.setCursorScreenPosition(position, options) + # Public: Duplicates the current line. + # + # If more than one cursor is present, only the most recently added one is considered. + duplicateLine: -> @activeEditSession.duplicateLine() + # Public: Joins the current line with the one below it. + # + # Multiple cursors are considered equally. If there's a selection in the editor, + # all the lines are joined together. + joinLine: -> @activeEditSession.joinLine() + # Public: Gets the current screen position. + # + # Returns an {Array} of two numbers: the screen row, and the screen column. + getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition() + # Public: Gets the current screen row. + # + # Returns a {Number}. + getCursorScreenRow: -> @activeEditSession.getCursorScreenRow() + # Public: Sets the cursor based on a given buffer position. + # + # position - An {Array} of two numbers: the buffer row, and the buffer column. + # options - An object with properties based on {Cursor.setBufferPosition}. + # + setCursorBufferPosition: (position, options) -> @activeEditSession.setCursorBufferPosition(position, options) + # Public: Gets the current buffer position of the cursor. + # + # Returns an {Array} of two numbers: the buffer row, and the buffer column. + getCursorBufferPosition: -> @activeEditSession.getCursorBufferPosition() + # Public: Retrieves the range for the current paragraph. + # + # A paragraph is defined as a block of text surrounded by empty lines. + # + # Returns a {Range}. + getCurrentParagraphBufferRange: -> @activeEditSession.getCurrentParagraphBufferRange() + # Public: Gets the word located under the cursor. + # + # options - An object with properties based on {Cursor.getBeginningOfCurrentWordBufferPosition}. + # + # Returns a {String}. + getWordUnderCursor: (options) -> @activeEditSession.getWordUnderCursor(options) + # Public: Gets the selection at the specified index. + # + # index - The id {Number} of the selection + # + # Returns a {Selection}. getSelection: (index) -> @activeEditSession.getSelection(index) + # Public: Gets the last selection, _i.e._ the most recently added. + # + # Returns a {Selection}. getSelections: -> @activeEditSession.getSelections() + # Public: Gets all selections, ordered by their position in the buffer. + # + # Returns an {Array} of {Selection}s. getSelectionsOrderedByBufferPosition: -> @activeEditSession.getSelectionsOrderedByBufferPosition() + # Public: Gets the very last selection, as it's ordered in the buffer. + # + # Returns a {Selection}. getLastSelectionInBuffer: -> @activeEditSession.getLastSelectionInBuffer() + # Public: Gets the currently selected text. + # + # Returns a {String}. getSelectedText: -> @activeEditSession.getSelectedText() + # Public: Gets the buffer ranges of all the {Selection}s. + # + # This is ordered by their buffer position. + # + # Returns an {Array} of {Range}s. getSelectedBufferRanges: -> @activeEditSession.getSelectedBufferRanges() + # Public: Gets the buffer range of the most recently added {Selection}. + # + # Returns a {Range}. getSelectedBufferRange: -> @activeEditSession.getSelectedBufferRange() - setSelectedBufferRange: (bufferRange, options) -> @activeEditSession.setSelectedBufferRange(bufferRange, options) + # Public: Given a buffer range, this removes all previous selections and creates a new selection for it. + # + # bufferRange - A {Range} in the buffer + # options - A hash of options + setSelectedBufferRange: (bufferRange, options) -> @activeEditSession.setSelectedBufferRange(bufferRange, options) + # Public: Given an array of buffer ranges, this removes all previous selections and creates new selections for them. + # + # bufferRanges - An {Array} of {Range}s in the buffer + # options - A hash of options setSelectedBufferRanges: (bufferRanges, options) -> @activeEditSession.setSelectedBufferRanges(bufferRanges, options) + # Public: Given a buffer range, this adds a new selection for it. + # + # bufferRange - A {Range} in the buffer + # options - A hash of options + # + # Returns the new {Selection}. addSelectionForBufferRange: (bufferRange, options) -> @activeEditSession.addSelectionForBufferRange(bufferRange, options) + # Public: Selects the text one position right of the cursor. selectRight: -> @activeEditSession.selectRight() + # Public: Selects the text one position left of the cursor. selectLeft: -> @activeEditSession.selectLeft() + # Public: Selects all the text one position above the cursor. selectUp: -> @activeEditSession.selectUp() + # Public: Selects all the text one position below the cursor. selectDown: -> @activeEditSession.selectDown() + # Public: Selects all the text from the current cursor position to the top of the buffer. selectToTop: -> @activeEditSession.selectToTop() + # Public: Selects all the text from the current cursor position to the bottom of the buffer. selectToBottom: -> @activeEditSession.selectToBottom() + # Public: Selects all the text in the buffer. selectAll: -> @activeEditSession.selectAll() + # Public: Selects all the text from the current cursor position to the beginning of the line. selectToBeginningOfLine: -> @activeEditSession.selectToBeginningOfLine() + # Public: Selects all the text from the current cursor position to the end of the line. selectToEndOfLine: -> @activeEditSession.selectToEndOfLine() + # Public: Moves the current selection down one row. addSelectionBelow: -> @activeEditSession.addSelectionBelow() + # Public: Moves the current selection up one row. addSelectionAbove: -> @activeEditSession.addSelectionAbove() + # Public: Selects all the text from the current cursor position to the beginning of the word. selectToBeginningOfWord: -> @activeEditSession.selectToBeginningOfWord() + # Public: Selects all the text from the current cursor position to the end of the word. selectToEndOfWord: -> @activeEditSession.selectToEndOfWord() + # Public: Selects all the text from the current cursor position to the beginning of the next word. selectToBeginningOfNextWord: -> @activeEditSession.selectToBeginningOfNextWord() + # Public: Selects the current word. selectWord: -> @activeEditSession.selectWord() + # Public: Selects the current line. selectLine: -> @activeEditSession.selectLine() + # Public: Selects the text from the current cursor position to a given position. + # + # position - An instance of {Point}, with a given `row` and `column`. selectToScreenPosition: (position) -> @activeEditSession.selectToScreenPosition(position) + # Public: Transposes the current text selections. + # + # This only works if there is more than one selection. Each selection is transferred + # to the position of the selection after it. The last selection is transferred to the + # position of the first. transpose: -> @activeEditSession.transpose() + # Public: Turns the current selection into upper case. upperCase: -> @activeEditSession.upperCase() + # Public: Turns the current selection into lower case. lowerCase: -> @activeEditSession.lowerCase() + # Public: Clears every selection. TODO clearSelections: -> @activeEditSession.clearSelections() + # Public: Performs a backspace, removing the character found behind the cursor position. backspace: -> @activeEditSession.backspace() + # Public: Performs a backspace to the beginning of the current word, removing characters found there. backspaceToBeginningOfWord: -> @activeEditSession.backspaceToBeginningOfWord() + # Public: Performs a backspace to the beginning of the current line, removing characters found there. backspaceToBeginningOfLine: -> @activeEditSession.backspaceToBeginningOfLine() + # Public: Performs a delete, removing the character found ahead the cursor position. delete: -> @activeEditSession.delete() + # Public: Performs a delete to the end of the current word, removing characters found there. deleteToEndOfWord: -> @activeEditSession.deleteToEndOfWord() + # Public: Performs a delete to the end of the current line, removing characters found there. deleteLine: -> @activeEditSession.deleteLine() + # Public: Performs a cut to the end of the current line. + # + # Characters are removed, but the text remains in the clipboard. cutToEndOfLine: -> @activeEditSession.cutToEndOfLine() + # Public: Inserts text at the current cursor positions. + # + # text - A {String} representing the text to insert. + # options - A set of options equivalent to {Selection.insertText}. insertText: (text, options) -> @activeEditSession.insertText(text, options) + # Public: Inserts a new line at the current cursor positions. insertNewline: -> @activeEditSession.insertNewline() + # Internal: consolidateSelections: (e) -> e.abortKeyBinding() unless @activeEditSession.consolidateSelections() + # Public: Inserts a new line below the current cursor positions. insertNewlineBelow: -> @activeEditSession.insertNewlineBelow() + # Public: Inserts a new line above the current cursor positions. insertNewlineAbove: -> @activeEditSession.insertNewlineAbove() + # Public: Indents the current line. + # + # options - A set of options equivalent to {Selection.indent}. indent: (options) -> @activeEditSession.indent(options) - autoIndent: (options) -> @activeEditSession.autoIndentSelectedRows(options) + # Public: TODO + autoIndent: (options) -> @activeEditSession.autoIndentSelectedRows() + # Public: Indents the selected rows. indentSelectedRows: -> @activeEditSession.indentSelectedRows() + # Public: Outdents the selected rows. outdentSelectedRows: -> @activeEditSession.outdentSelectedRows() + # Public: Cuts the selected text. cutSelection: -> @activeEditSession.cutSelectedText() + # Public: Copies the selected text. copySelection: -> @activeEditSession.copySelectedText() - paste: -> @activeEditSession.pasteText() + # Public: Pastes the text in the clipboard. + # + # options - A set of options equivalent to {Selection.insertText}. + paste: (options) -> @activeEditSession.pasteText(options) + # Public: Undos the last {Buffer} change. undo: -> @activeEditSession.undo() + # Public: Redos the last {Buffer} change. redo: -> @activeEditSession.redo() - transact: (fn) -> @activeEditSession.transact(fn) - commit: -> @activeEditSession.commit() - abort: -> @activeEditSession.abort() + # Public: Creates a new fold between two row numbers. + # + # startRow - The row {Number} to start folding at + # endRow - The row {Number} to end the fold + # + # Returns the new {Fold}. createFold: (startRow, endRow) -> @activeEditSession.createFold(startRow, endRow) + # Public: Folds the current row. foldCurrentRow: -> @activeEditSession.foldCurrentRow() + # Public: Unfolds the current row. unfoldCurrentRow: -> @activeEditSession.unfoldCurrentRow() + # Public: Folds all the rows. foldAll: -> @activeEditSession.foldAll() + # Public: Unfolds all the rows. unfoldAll: -> @activeEditSession.unfoldAll() + # Public: Folds the most recent selection. foldSelection: -> @activeEditSession.foldSelection() + # Public: Given the id of a {Fold}, this removes it. + # + # foldId - The fold id {Number} to remove destroyFold: (foldId) -> @activeEditSession.destroyFold(foldId) + # Public: Removes any {Fold}s found that contain the given buffer row. + # + # bufferRow - The buffer row {Number} to check against destroyFoldsContainingBufferRow: (bufferRow) -> @activeEditSession.destroyFoldsContainingBufferRow(bufferRow) + # Public: Determines if the given screen row is folded. + # + # screenRow - A {Number} indicating the screen row. + # + # Returns `true` if the screen row is folded, `false` otherwise. isFoldedAtScreenRow: (screenRow) -> @activeEditSession.isFoldedAtScreenRow(screenRow) + # Public: Determines if the given buffer row is folded. + # + # screenRow - A {Number} indicating the buffer row. + # + # Returns `true` if the buffer row is folded, `false` otherwise. isFoldedAtBufferRow: (bufferRow) -> @activeEditSession.isFoldedAtBufferRow(bufferRow) + # Public: Determines if the given row that the cursor is at is folded. + # + # Returns `true` if the row is folded, `false` otherwise. isFoldedAtCursorRow: -> @activeEditSession.isFoldedAtCursorRow() + # Public: Gets the line for the given screen row. + # + # screenRow - A {Number} indicating the screen row. + # + # Returns a {String}. lineForScreenRow: (screenRow) -> @activeEditSession.lineForScreenRow(screenRow) + # Public: Gets the lines for the given screen row boundaries. + # + # start - A {Number} indicating the beginning screen row. + # end - A {Number} indicating the ending screen row. + # + # Returns an {Array} of {String}s. linesForScreenRows: (start, end) -> @activeEditSession.linesForScreenRows(start, end) + # Public: Gets the number of screen rows. + # + # Returns a {Number}. screenLineCount: -> @activeEditSession.screenLineCount() + # Public: Defines the limit at which the buffer begins to soft wrap text. + # + # softWrapColumn - A {Number} defining the soft wrap limit setSoftWrapColumn: (softWrapColumn) -> softWrapColumn ?= @calcSoftWrapColumn() @activeEditSession.setSoftWrapColumn(softWrapColumn) if softWrapColumn - + # Public: Gets the length of the longest screen line. + # + # Returns a {Number}. maxScreenLineLength: -> @activeEditSession.maxScreenLineLength() + # Public: Gets the text in the last screen row. + # + # Returns a {String}. getLastScreenRow: -> @activeEditSession.getLastScreenRow() + # Public: Given a position, this clips it to a real position. + # + # For example, if `position`'s row exceeds the row count of the buffer, + # or if its column goes beyond a line's length, this "sanitizes" the value + # to a real position. + # + # position - The {Point} to clip + # options - A hash with the following values: + # :wrapBeyondNewlines - if `true`, continues wrapping past newlines + # :wrapAtSoftNewlines - if `true`, continues wrapping past soft newlines + # :screenLine - if `true`, indicates that you're using a line number, not a row number + # + # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. clipScreenPosition: (screenPosition, options={}) -> @activeEditSession.clipScreenPosition(screenPosition, options) - screenPositionForBufferPosition: (position, options) -> @activeEditSession.screenPositionForBufferPosition(position, options) - bufferPositionForScreenPosition: (position, options) -> @activeEditSession.bufferPositionForScreenPosition(position, options) - screenRangeForBufferRange: (range) -> @activeEditSession.screenRangeForBufferRange(range) - bufferRangeForScreenRange: (range) -> @activeEditSession.bufferRangeForScreenRange(range) - bufferRowsForScreenRows: (startRow, endRow) -> @activeEditSession.bufferRowsForScreenRows(startRow, endRow) - getLastScreenRow: -> @activeEditSession.getLastScreenRow() + # Public: Given a buffer position, this converts it into a screen position. + # + # bufferPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - The same options available to {DisplayBuffer.screenPositionForBufferPosition}. + # + # Returns a {Point}. + screenPositionForBufferPosition: (position, options) -> @activeEditSession.screenPositionForBufferPosition(position, options) + + # Public: Given a buffer range, this converts it into a screen position. + # + # screenPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - The same options available to {DisplayBuffer.bufferPositionForScreenPosition}. + # + # Returns a {Point}. + bufferPositionForScreenPosition: (position, options) -> @activeEditSession.bufferPositionForScreenPosition(position, options) + + # Public: Given a buffer range, this converts it into a screen position. + # + # bufferRange - The {Range} to convert + # + # Returns a {Range}. + screenRangeForBufferRange: (range) -> @activeEditSession.screenRangeForBufferRange(range) + + # Public: Given a screen range, this converts it into a buffer position. + # + # screenRange - The {Range} to convert + # + # Returns a {Range}. + bufferRangeForScreenRange: (range) -> @activeEditSession.bufferRangeForScreenRange(range) + # Public: Given a starting and ending row, this converts every row into a buffer position. + # + # startRow - The row {Number} to start at + # endRow - The row {Number} to end at (default: {.getLastScreenRow}) + # + # Returns an {Array} of {Range}s. + bufferRowsForScreenRows: (startRow, endRow) -> @activeEditSession.bufferRowsForScreenRows(startRow, endRow) + # Public: Gets the number of the last row in the buffer. + # + # Returns a {Number}. + getLastScreenRow: -> @activeEditSession.getLastScreenRow() + # Internal: logCursorScope: -> console.log @activeEditSession.getCursorScopes() - + # Public: Emulates the "page down" key, where the last row of a buffer scrolls to become the first. pageDown: -> newScrollTop = @scrollTop() + @scrollView[0].clientHeight @activeEditSession.moveCursorDown(@getPageRows()) @scrollTop(newScrollTop, adjustVerticalScrollbar: true) + + # Public: Emulates the "page up" key, where the frst row of a buffer scrolls to become the last. pageUp: -> newScrollTop = @scrollTop() - @scrollView[0].clientHeight @activeEditSession.moveCursorUp(@getPageRows()) @scrollTop(newScrollTop, adjustVerticalScrollbar: true) + # Public: Gets the number of actual page rows existing in an editor. + # + # Returns a {Number}. getPageRows: -> Math.max(1, Math.ceil(@scrollView[0].clientHeight / @lineHeight)) + # Public: Set whether invisible characters are shown. + # + # showInvisibles - A {Boolean} which, if `true`, show invisible characters setShowInvisibles: (showInvisibles) -> return if showInvisibles == @showInvisibles @showInvisibles = showInvisibles @resetDisplay() + # Public: Defines which characters are invisible. + # + # invisibles - A hash defining the invisible characters: The defaults are: + # :eol - `\u00ac` + # :space - `\u00b7` + # :tab - `\u00bb` + # :cr - `\u00a4` setInvisibles: (@invisibles={}) -> _.defaults @invisibles, eol: '\u00ac' @@ -312,25 +608,80 @@ class Editor extends View cr: '\u00a4' @resetDisplay() + # Public: Sets whether you want to show the indentation guides. + # + # showIndentGuide - A {Boolean} you can set to `true` if you want to see the indentation guides. setShowIndentGuide: (showIndentGuide) -> return if showIndentGuide == @showIndentGuide @showIndentGuide = showIndentGuide @resetDisplay() + # Public: Checks out the current HEAD revision of the file. checkoutHead: -> @getBuffer().checkoutHead() + # Public: Replaces the current buffer contents. + # + # text - A {String} containing the new buffer contents. setText: (text) -> @getBuffer().setText(text) + # Public: Retrieves the current buffer contents. + # + # Returns a {String}. getText: -> @getBuffer().getText() + # Public: Retrieves the current buffer's file path. + # + # Returns a {String}. getPath: -> @activeEditSession?.getPath() + # Public: Gets the number of lines in a file. + # + # Returns a {Number}. getLineCount: -> @getBuffer().getLineCount() + # Public: Gets the row number of the last line. + # + # Returns a {Number}. getLastBufferRow: -> @getBuffer().getLastRow() + # Public: Given a range, returns the lines of text within it. + # + # range - A {Range} object specifying your points of interest + # + # Returns a {String} of the combined lines. getTextInRange: (range) -> @getBuffer().getTextInRange(range) + # Public: Finds the last point in the current buffer. + # + # Returns a {Point} representing the last position. getEofPosition: -> @getBuffer().getEofPosition() + # Public: Given a row, returns the line of text. + # + # row - A {Number} indicating the row. + # + # Returns a {String}. lineForBufferRow: (row) -> @getBuffer().lineForRow(row) + # Public: Given a row, returns the length of the line of text. + # + # row - A {Number} indicating the row + # + # Returns a {Number}. lineLengthForBufferRow: (row) -> @getBuffer().lineLengthForRow(row) + # Public: Given a buffer row, this retrieves the range for that line. + # + # row - A {Number} identifying the row + # options - A hash with one key, `includeNewline`, which specifies whether you + # want to include the trailing newline + # + # Returns a {Range}. rangeForBufferRow: (row) -> @getBuffer().rangeForRow(row) + # Public: Scans for text in the buffer, 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 scanInBufferRange: (args...) -> @getBuffer().scanInRange(args...) + # Public: Scans for text in the buffer _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 backwardsScanInBufferRange: (args...) -> @getBuffer().backwardsScanInRange(args...) + # Internal: configure: -> @observeConfig 'editor.showLineNumbers', (showLineNumbers) => @gutter.setShowLineNumbers(showLineNumbers) @observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles) @@ -339,6 +690,7 @@ class Editor extends View @observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize) @observeConfig 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily) + # Internal: Responsible for handling events made to the editor. handleEvents: -> @on 'focus', => @hiddenInput.focus() @@ -410,6 +762,7 @@ class Editor extends View else @gutter.addClass('drop-shadow') + # Internal: selectOnMousemoveUntilMouseup: -> lastMoveEvent = null moveHandler = (event = lastMoveEvent) => @@ -428,6 +781,7 @@ class Editor extends View @activeEditSession.finalizeSelections() @syncCursorAnimations() + # Internal: afterAttach: (onDom) -> return unless onDom @redraw() if @redrawOnReattach @@ -449,6 +803,7 @@ class Editor extends View @trigger 'editor:attached', [this] + # Internal: edit: (editSession) -> return if editSession is @activeEditSession @@ -478,14 +833,24 @@ class Editor extends View if @attached and @activeEditSession.buffer.isInConflict() _.defer => @showBufferConflictAlert(@activeEditSession) # Display after editSession has a chance to display + # Internal: Retrieves the currently active session. + # + # Returns an {EditSession}. getModel: -> @activeEditSession + # Internal: Set the new active session. + # + # editSession - The new {EditSession} to use. setModel: (editSession) -> @edit(editSession) + # Public: Retrieves the {EditSession}'s buffer. + # + # Returns the current {Buffer}. getBuffer: -> @activeEditSession.buffer + # Internal: showBufferConflictAlert: (editSession) -> atom.confirm( editSession.getPath(), @@ -494,6 +859,7 @@ class Editor extends View "Cancel" ) + # Internal: scrollTop: (scrollTop, options={}) -> return @cachedScrollTop or 0 unless scrollTop? maxScrollTop = @verticalScrollbar.prop('scrollHeight') - @verticalScrollbar.height() @@ -510,29 +876,51 @@ class Editor extends View if options?.adjustVerticalScrollbar ? true @verticalScrollbar.scrollTop(scrollTop) + # Internal: scrollBottom: (scrollBottom) -> if scrollBottom? @scrollTop(scrollBottom - @scrollView.height()) else @scrollTop() + @scrollView.height() + # Public: Scrolls the editor to the bottom. scrollToBottom: -> @scrollBottom(@screenLineCount() * @lineHeight) + # Public: Scrolls the editor to the position of the most recently added cursor. + # + # The editor is also centered. scrollToCursorPosition: -> @scrollToBufferPosition(@getCursorBufferPosition(), center: true) + # Public: Scrolls the editor to the given buffer position. + # + # bufferPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - A hash matching the options available to {.scrollToPixelPosition} scrollToBufferPosition: (bufferPosition, options) -> @scrollToPixelPosition(@pixelPositionForBufferPosition(bufferPosition), options) + # Public: Scrolls the editor to the given screen position. + # + # screenPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - A hash matching the options available to {.scrollToPixelPosition} scrollToScreenPosition: (screenPosition, options) -> @scrollToPixelPosition(@pixelPositionForScreenPosition(screenPosition), options) + # Public: Scrolls the editor to the given pixel position. + # + # pixelPosition - An object that represents a pixel position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - A hash with the following keys: + # :center - if `true`, the position is scrolled such that it's in the center of the editor scrollToPixelPosition: (pixelPosition, options) -> return unless @attached @scrollVertically(pixelPosition, options) @scrollHorizontally(pixelPosition) + # Internal: Scrolls the editor vertically to a given position. scrollVertically: (pixelPosition, {center}={}) -> scrollViewHeight = @scrollView.height() scrollTop = @scrollTop() @@ -553,6 +941,7 @@ class Editor extends View else if desiredTop < scrollTop @scrollTop(desiredTop) + # Internal: Scrolls the editor horizontally to a given position. scrollHorizontally: (pixelPosition) -> return if @activeEditSession.getSoftWrap() @@ -568,6 +957,11 @@ class Editor extends View else if desiredLeft < @scrollView.scrollLeft() @scrollView.scrollLeft(desiredLeft) + # Public: Given a buffer range, this highlights all the folds within that range + # + # "Highlighting" essentially just adds the `selected` class to the line + # + # bufferRange - The {Range} to check highlightFoldsContainingBufferRange: (bufferRange) -> screenLines = @linesForScreenRows(@firstRenderedScreenRow, @lastRenderedScreenRow) for screenLine, i in screenLines @@ -588,9 +982,11 @@ class Editor extends View @activeEditSession.setScrollTop(@scrollTop()) @activeEditSession.setScrollLeft(@scrollView.scrollLeft()) + # Public: Activates soft tabs in the editor. toggleSoftTabs: -> @activeEditSession.setSoftTabs(not @activeEditSession.softTabs) + # Public: Activates soft wraps in the editor. toggleSoftWrap: -> @setSoftWrap(not @activeEditSession.getSoftWrap()) @@ -600,6 +996,11 @@ class Editor extends View else Infinity + # Public: Sets the soft wrap column for the editor. + # + # softWrap - A {Boolean} which, if `true`, sets soft wraps + # softWrapColumn - A {Number} indicating the length of a line in the editor when soft + # wrapping turns on setSoftWrap: (softWrap, softWrapColumn=undefined) -> @activeEditSession.setSoftWrap(softWrap) @setSoftWrapColumn(softWrapColumn) if @attached @@ -612,6 +1013,9 @@ class Editor extends View @removeClass 'soft-wrap' $(window).off 'resize', @_setSoftWrapColumn + # Public: Sets the font size for the editor. + # + # fontSize - A {Number} indicating the font size in pixels. setFontSize: (fontSize) -> headTag = $("head") styleTag = headTag.find("style.font-size") @@ -626,9 +1030,15 @@ class Editor extends View else @redrawOnReattach = @attached + # Public: Retrieves the font size for the editor. + # + # Returns a {Number} indicating the font size in pixels. getFontSize: -> parseInt(@css("font-size")) + # Public: Sets the font family for the editor. + # + # fontFamily - A {String} identifying the CSS `font-family`, setFontFamily: (fontFamily) -> return if fontFamily == undefined headTag = $("head") @@ -640,11 +1050,16 @@ class Editor extends View styleTag.text(".editor {font-family: #{fontFamily}}") @redraw() + # Public: Gets the font family for the editor. + # + # Returns a {String} identifying the CSS `font-family`, getFontFamily: -> @css("font-family") + # Public: Clears the CSS `font-family` property from the editor. clearFontFamily: -> $('head style.editor-font-family').remove() + # Public: Clears the CSS `font-family` property from the editor. redraw: -> return unless @hasParent() return unless @attached @@ -666,6 +1081,9 @@ class Editor extends View splitDown: (items...) -> @getPane()?.splitDown(items...).activeView + # Public: Retrieve's the `Editor`'s pane. + # + # Returns a {Pane}. getPane: -> @parent('.item-views').parent('.pane').view() @@ -720,6 +1138,10 @@ class Editor extends View appendToLinesView: (view) -> @overlayer.append(view) + ### + # Internal # + ### + calculateDimensions: -> fragment = $('') @renderedLines.append(fragment) @@ -969,15 +1391,34 @@ class Editor extends View @renderedLines.css('padding-bottom', paddingBottom) @gutter.lineNumbers.css('padding-bottom', paddingBottom) + ### + # Public # + ### + + # Public: Retrieves the number of the row that is visible and currently at the top of the editor. + # + # Returns a {Number}. getFirstVisibleScreenRow: -> Math.floor(@scrollTop() / @lineHeight) + # Public: Retrieves the number of the row that is visible and currently at the top of the editor. + # + # Returns a {Number}. getLastVisibleScreenRow: -> Math.max(0, Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1) + # Public: Given a row number, identifies if it is currently visible. + # + # row - A row {Number} to check + # + # Returns a {Boolean}. isScreenRowVisible: (row) -> @getFirstVisibleScreenRow() <= row <= @getLastVisibleScreenRow() + ### + # Internal # + ### + handleScreenLinesChange: (change) -> @pendingChanges.push(change) @requestDisplayUpdate() @@ -1084,9 +1525,25 @@ class Editor extends View toggleLineCommentsInSelection: -> @activeEditSession.toggleLineCommentsInSelection() + ### + # Public # + ### + + # Public: Converts a buffer position to a pixel position. + # + # position - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # + # Returns an object with two values: `top` and `left`, representing the pixel positions. pixelPositionForBufferPosition: (position) -> @pixelPositionForScreenPosition(@screenPositionForBufferPosition(position)) + # Public: Converts a screen position to a pixel position. + # + # position - An object that represents a screen position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # + # Returns an object with two values: `top` and `left`, representing the pixel positions. pixelPositionForScreenPosition: (position) -> return { top: 0, left: 0 } unless @isOnDom() and @isVisible() {row, column} = Point.fromObject(position) @@ -1152,6 +1609,7 @@ class Editor extends View new Point(row, column) + # Public: Highlights the current line the cursor is on. highlightCursorLine: -> return if @mini @@ -1162,13 +1620,20 @@ class Editor extends View else @highlightedLine = null + # Public: Retrieves the current {EditSession}'s grammar. + # + # Returns a {String} indicating the language's grammar rules. getGrammar: -> @activeEditSession.getGrammar() + # Public: Sets the current {EditSession}'s grammar. This only works for mini-editors. + # + # grammar - A {String} indicating the language's grammar rules. setGrammar: (grammar) -> throw new Error("Only mini-editors can explicity set their grammar") unless @mini @activeEditSession.setGrammar(grammar) + # Public: Reloads the current grammar. reloadGrammar: -> @activeEditSession.reloadGrammar() @@ -1179,6 +1644,7 @@ class Editor extends View @on event, => callback(this, event) + # Internal: Replaces all the currently selected text. replaceSelectedText: (replaceFn) -> selection = @getSelection() return false if selection.isEmpty() @@ -1189,10 +1655,19 @@ class Editor extends View @insertText(text, select: true) true + # Public: Copies the current file path to the native clipboard. copyPathToPasteboard: -> path = @getPath() pasteboard.write(path) if path? + ### + # Internal # + ### + + transact: (fn) -> @activeEditSession.transact(fn) + commit: -> @activeEditSession.commit() + abort: -> @activeEditSession.abort() + saveDebugSnapshot: -> atom.showSaveDialog (path) => fsUtils.write(path, @getDebugSnapshot()) if path diff --git a/src/app/event-emitter.coffee b/src/app/event-emitter.coffee index 3abcb74ac..cd7bbccda 100644 --- a/src/app/event-emitter.coffee +++ b/src/app/event-emitter.coffee @@ -1,6 +1,16 @@ _ = require 'underscore' +# Public: Provides a list of functions that can be used in Atom for event management. +# +# Each event can have more than one handler; that is, an event can trigger multiple functions. module.exports = + # Public: Associates an event name with a function to perform. + # + # This is called endlessly, until the event is turned {.off}. The {.on} method + # calls an `eventName` only once. + # + # eventName - A {String} name identifying an event + # handler - A {Function} that's executed when the event is triggered on: (eventName, handler) -> [eventName, namespace] = eventName.split('.') @@ -17,6 +27,10 @@ module.exports = @afterSubscribe?() + # Public: Associates an event name with a function to perform only once. + # + # eventName - A {String} name identifying an event + # handler - A {Function} that's executed when the event is triggered one: (eventName, handler) -> oneShotHandler = (args...) => @off(eventName, oneShotHandler) @@ -24,6 +38,10 @@ module.exports = @on eventName, oneShotHandler + # Public: Triggers a registered event. + # + # eventName - A {String} name identifying an event + # args - Any additional arguments to pass over to the event `handler` trigger: (eventName, args...) -> if @queuedEvents @queuedEvents.push [eventName, args...] @@ -37,6 +55,10 @@ module.exports = if handlers = @eventHandlersByEventName?[eventName] handlers.forEach (handler) -> handler(args...) + # Public: Stops executing handlers for a registered event. + # + # eventName - A {String} name identifying an event + # handler - The {Function} to remove from the event. If not provided, all handlers are removed. off: (eventName='', handler) -> [eventName, namespace] = eventName.split('.') eventName = undefined if eventName == '' @@ -67,17 +89,22 @@ module.exports = @afterUnsubscribe?() if @subscriptionCount() < subscriptionCountBefore + # Public: When called, stops triggering any event. pauseEvents: -> @pauseCount ?= 0 if @pauseCount++ == 0 @queuedEvents ?= [] + # Public: When called, resumes triggering events. resumeEvents: -> if --@pauseCount == 0 queuedEvents = @queuedEvents @queuedEvents = null @trigger(event...) for event in queuedEvents + # Public: Identifies how many events are registered. + # + # Returns a `number`. subscriptionCount: -> count = 0 for name, handlers of @eventHandlersByEventName diff --git a/src/app/file.coffee b/src/app/file.coffee index ec06ef189..4f1782ff2 100644 --- a/src/app/file.coffee +++ b/src/app/file.coffee @@ -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,12 +62,19 @@ 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 - + afterUnsubscribe: -> @unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0 diff --git a/src/app/fold.coffee b/src/app/fold.coffee index 50e5d34ba..e53824924 100644 --- a/src/app/fold.coffee +++ b/src/app/fold.coffee @@ -1,6 +1,10 @@ Range = require 'range' Point = require 'point' +# Public: Represents a fold that's hiding text from the screen. +# +# Folds are the primary reason that screen ranges and buffer ranges vary. Their +# creation is managed by the {DisplayBuffer}. module.exports = class Fold @idCounter: 1 @@ -9,6 +13,10 @@ class Fold startRow: null endRow: null + ### + # Internal # + ### + constructor: (@displayBuffer, @startRow, @endRow) -> @id = @constructor.idCounter++ @@ -18,6 +26,11 @@ class Fold inspect: -> "Fold(#{@startRow}, #{@endRow})" + # Public: Retrieves the buffer row range that a fold occupies. + # + # includeNewline - A {Boolean} which, if `true`, includes the trailing newline + # + # Returns a {Range}. getBufferRange: ({includeNewline}={}) -> if includeNewline end = [@endRow + 1, 0] @@ -26,6 +39,9 @@ class Fold new Range([@startRow, 0], end) + # Public: Retrieves the number of buffer rows a fold occupies. + # + # Returns a {Number}. getBufferRowCount: -> @endRow - @startRow + 1 @@ -43,9 +59,19 @@ class Fold @displayBuffer.unregisterFold(oldStartRow, this) @displayBuffer.registerFold(this) + # Public: Identifies if a {Range} occurs within a fold. + # + # range - A {Range} to check + # + # Returns a {Boolean}. isContainedByRange: (range) -> range.start.row <= @startRow and @endRow <= range.end.row + # Public: Identifies if a fold is nested within a fold. + # + # fold - A {Fold} to check + # + # Returns a {Boolean}. isContainedByFold: (fold) -> @isContainedByRange(fold.getBufferRange()) diff --git a/src/app/git.coffee b/src/app/git.coffee index ee9c953ac..7c15a55ac 100644 --- a/src/app/git.coffee +++ b/src/app/git.coffee @@ -5,8 +5,18 @@ EventEmitter = require 'event-emitter' RepositoryStatusTask = require 'repository-status-task' GitUtils = require 'git-utils' +# Public: Represents the underlying git operations performed by Atom. +# +# Ultimately, this is an overlay to the native [git-utils](https://github.com/atom/node-git) model. 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 @@ -18,6 +28,15 @@ class Git upstream: null statusTask: null + ### + # Internal # + ### + + # Internal: Creates a new `Git` object. + # + # path - The {String} representing the path to your git working directory + # options - A hash with the following keys: + # :refreshOnWindowFocus - If `true`, {#refreshIndex} and {#refreshStatus} are called on focus constructor: (path, options={}) -> @repo = GitUtils.open(path) unless @repo? @@ -40,16 +59,6 @@ class Git @subscribe buffer, 'saved', bufferStatusHandler @subscribe buffer, 'reloaded', bufferStatusHandler - getRepo: -> - unless @repo? - throw new Error("Repository has been destroyed") - @repo - - refreshIndex: -> @getRepo().refreshIndex() - - getPath: -> - @path ?= fsUtils.absolute(@getRepo().getPath()) - destroy: -> if @statusTask? @statusTask.abort() @@ -62,12 +71,46 @@ class Git @unsubscribe() + ### + # Public # + ### + + # Public: Retrieves the git repository. + # + # Returns a new `Repository`. + getRepo: -> + unless @repo? + throw new Error("Repository has been destroyed") + @repo + + # Public: Reread the index to update any values that have changed since the last time the index was read. + refreshIndex: -> @getRepo().refreshIndex() + + # Public: Retrieves the path of the repository. + # + # Returns a {String}. + getPath: -> + @path ?= fsUtils.absolute(@getRepo().getPath()) + + # Public: Retrieves the working directory of the repository. + # + # Returns a {String}. getWorkingDirectory: -> @getRepo().getWorkingDirectory() + # Public: Retrieves the reference or SHA-1 that `HEAD` points to. + # + # This can be `refs/heads/master`, or a full SHA-1 if the repository is in a detached `HEAD` state. + # + # Returns a {String}. getHead: -> @getRepo().getHead() ? '' + # Public: Retrieves the status of a single path in the repository. + # + # path - An {String} defining a relative path + # + # Returns a {Number}. getPathStatus: (path) -> currentPathStatus = @statuses[path] ? 0 pathStatus = @getRepo().getStatus(@relativize(path)) ? 0 @@ -79,38 +122,132 @@ class Git @trigger 'status-changed', path, pathStatus pathStatus + # Public: Identifies if a path is ignored. + # + # path - The {String} path to check + # + # Returns a {Boolean}. isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path)) + # Public: Identifies if a value represents a status code. + # + # status - The code {Number} to check + # + # Returns a {Boolean}. isStatusModified: (status) -> @getRepo().isStatusModified(status) + # Public: Identifies if a path was modified. + # + # path - The {String} path to check + # + # Returns a {Boolean}. isPathModified: (path) -> @isStatusModified(@getPathStatus(path)) + # Public: Identifies if a status code represents a new path. + # + # status - The code {Number} to check + # + # Returns a {Boolean}. isStatusNew: (status) -> @getRepo().isStatusNew(status) + # Public: Identifies if a path is new. + # + # path - The {String} path to check + # + # Returns a {Boolean}. isPathNew: (path) -> @isStatusNew(@getPathStatus(path)) + # Public: Makes a path relative to the repository's working directory. + # + # path - The {String} path to convert + # + # Returns a {String}. relativize: (path) -> @getRepo().relativize(path) + # Public: Retrieves a shortened version of {.getHead}. + # + # This removes the leading segments of `refs/heads`, `refs/tags`, or `refs/remotes`. + # It also shortenes the SHA-1 of a detached `HEAD` to 7 characters. + # + # Returns a {String}. getShortHead: -> @getRepo().getShortHead() + # Public: Restore the contents of a path in the working directory and index to the version at `HEAD`. + # + # This is essentially the same as running: + # ``` + # git reset HEAD -- + # git checkout HEAD -- + # ``` + # + # path - The {String} path to checkout + # + # Returns a {Boolean} that's `true` if the method was successful. checkoutHead: (path) -> headCheckedOut = @getRepo().checkoutHead(@relativize(path)) @getPathStatus(path) if headCheckedOut headCheckedOut + # Public: Retrieves the number of lines added and removed to a path. + # + # This compares the working directory contents of the path to the `HEAD` version. + # + # path - The {String} path to check + # + # Returns an object with two keys, `added` and `deleted`. These will always be greater than 0. getDiffStats: (path) -> @getRepo().getDiffStats(@relativize(path)) + # Public: Identifies if a path is a submodule. + # + # path - The {String} path to check + # + # Returns a {Boolean}. isSubmodule: (path) -> @getRepo().isSubmodule(@relativize(path)) + # Public: Retrieves the status of a directory. + # + # path - The {String} path to check + # + # Returns a {Number} representing the status. + getDirectoryStatus: (directoryPath) -> + directoryPath = "#{directoryPath}/" + directoryStatus = 0 + for path, status of @statuses + directoryStatus |= status if path.indexOf(directoryPath) is 0 + directoryStatus + + # Public: Retrieves the number of commits the `HEAD` branch is ahead/behind the remote branch it is tracking. + # + # This is similar to the commit numbers reported by `git status` when a remote tracking branch exists. + # + # Returns an object with two keys, `ahead` and `behind`. These will always be greater than zero. + getAheadBehindCounts: -> + @getRepo().getAheadBehindCount() + + # Public: Retrieves the line diffs comparing the `HEAD` version of the given path and the given text. + # + # This is similar to the commit numbers reported by `git status` when a remote tracking branch exists. + # + # path - The {String} path (relative to the repository) + # text - The {String} to compare against the `HEAD` contents + # + # Returns an object with two keys, `ahead` and `behind`. These will always be greater than zero. + getLineDiffs: (path, text) -> + @getRepo().getLineDiffs(@relativize(path), text) + + ### + # Internal # + ### + refreshStatus: -> if @statusTask? @statusTask.off() @@ -122,19 +259,6 @@ class Git @statusTask.one 'task-completed', => @statusTask = null @statusTask.start() - - getDirectoryStatus: (directoryPath) -> - directoryPath = "#{directoryPath}/" - directoryStatus = 0 - for path, status of @statuses - directoryStatus |= status if path.indexOf(directoryPath) is 0 - directoryStatus - - getAheadBehindCounts: -> - @getRepo().getAheadBehindCount() - - getLineDiffs: (path, text) -> - @getRepo().getLineDiffs(@relativize(path), text) - + _.extend Git.prototype, Subscriber _.extend Git.prototype, EventEmitter diff --git a/src/app/gutter.coffee b/src/app/gutter.coffee index 2715905c3..c5c33b279 100644 --- a/src/app/gutter.coffee +++ b/src/app/gutter.coffee @@ -3,8 +3,16 @@ Range = require 'range' $ = require 'jquery' _ = require 'underscore' +# Public: Represents the portion of the {Editor} containing row numbers. +# +# The gutter also indicates if rows are folded. module.exports = class Gutter extends View + + ### + # Internal # + ### + @content: -> @div class: 'gutter', => @div outlet: 'lineNumbers', class: 'line-numbers' @@ -21,9 +29,6 @@ class Gutter extends View @getEditor().on 'selection:changed', highlightLines @on 'mousedown', (e) => @handleMouseEvents(e) - getEditor: -> - @parentView - beforeRemove: -> $(document).off(".gutter-#{@getEditor().id}") @@ -45,9 +50,26 @@ class Gutter extends View $(document).on "mousemove.gutter-#{@getEditor().id}", moveHandler $(document).one "mouseup.gutter-#{@getEditor().id}", => $(document).off 'mousemove', moveHandler + ### + # Public # + ### + + # Public: Retrieves the containing {Editor}. + # + # Returns an {Editor}. + getEditor: -> + @parentView + + # Public: Defines whether to show the gutter or not. + # + # showLineNumbers - A {Boolean} which, if `false`, hides the gutter setShowLineNumbers: (showLineNumbers) -> if showLineNumbers then @lineNumbers.show() else @lineNumbers.hide() + ### + # Internal # + ### + updateLineNumbers: (changes, renderFrom, renderTo) -> if renderFrom < @firstScreenRow or renderTo > @lastScreenRow performUpdate = true @@ -60,7 +82,7 @@ class Gutter extends View break @renderLineNumbers(renderFrom, renderTo) if performUpdate - + renderLineNumbers: (startScreenRow, endScreenRow) -> editor = @getEditor() maxDigits = editor.getLineCount().toString().length diff --git a/src/app/image-edit-session.coffee b/src/app/image-edit-session.coffee index f0e25318a..6a8ecb856 100644 --- a/src/app/image-edit-session.coffee +++ b/src/app/image-edit-session.coffee @@ -1,10 +1,18 @@ fsUtils = require 'fs-utils' _ = require 'underscore' +# Public: Manages the states between {Editor}s, images, and the project as a whole. +# +# Essentially, the graphical version of a {EditSession}. module.exports= class ImageEditSession registerDeserializer(this) + # Public: Identifies if a path can be opened by the image viewer. + # + # path - The {String} name of the path to check + # + # Returns a {Boolean}. @canOpen: (path) -> _.indexOf([ '.gif' @@ -13,6 +21,10 @@ class ImageEditSession '.png' ], fsUtils.extension(path), true) >= 0 + ### + # Internal # + ### + @deserialize: (state) -> if fsUtils.exists(state.path) project.buildEditSession(state.path) @@ -28,15 +40,31 @@ class ImageEditSession getViewClass: -> require 'image-view' + # Public: Retrieves the filename of the open file. + # + # This is `'untitled'` if the file is new and not saved to the disk. + # + # Returns a {String}. getTitle: -> if path = @getPath() fsUtils.base(path) else 'untitled' + # Public: Retrieves the URI of the current image. + # + # Returns a {String}. getUri: -> @path + # Public: Retrieves the path of the current image. + # + # Returns a {String}. getPath: -> @path + # Public: Compares two `ImageEditSession`s to determine equality. + # + # Equality is based on the condition that the two URIs are the same. + # + # Returns a {Boolean}. isEqual: (other) -> other instanceof ImageEditSession and @getUri() is other.getUri() diff --git a/src/app/image-view.coffee b/src/app/image-view.coffee index 0db56b20a..2a339576b 100644 --- a/src/app/image-view.coffee +++ b/src/app/image-view.coffee @@ -2,12 +2,16 @@ ScrollView = require 'scroll-view' _ = require 'underscore' $ = require 'jquery' +# Public: Renders images in the {Editor}. module.exports = class ImageView extends ScrollView + + # Internal: @content: -> @div class: 'image-view', tabindex: -1, => @img outlet: 'image' + # Internal: initialize: (imageEditSession) -> super @@ -25,6 +29,7 @@ class ImageView extends ScrollView @command 'image-view:zoom-out', => @zoomOut() @command 'image-view:reset-zoom', => @resetZoom() + # Internal: afterAttach: (onDom) -> return unless onDom @@ -35,6 +40,7 @@ class ImageView extends ScrollView @active = @is(pane.activeView) @centerImage() if @active and not wasActive + # Public: Places the image in the center of the {Editor}. centerImage: -> return unless @loaded and @isVisible() @@ -43,6 +49,9 @@ class ImageView extends ScrollView 'left': Math.max((@width() - @image.outerWidth()) / 2, 0) @image.show() + # Public: Indicates the path of the image. + # + # path - A {String} for the new image path. setPath: (path) -> if path? if @image.attr('src') isnt path @@ -51,12 +60,36 @@ class ImageView extends ScrollView else @image.hide() - setModel: (imageEditSession) -> - @setPath(imageEditSession?.getPath()) - + # Public: Retrieve's the {Editor}'s pane. + # + # Returns a {Pane}. getPane: -> @parent('.item-views').parent('.pane').view() + # Public: Zooms the image out. + # + # This is done by a factor of `0.9`. + zoomOut: -> + @adjustSize(0.9) + + # Public: Zooms the image in. + # + # This is done by a factor of `1.1`. + zoomIn: -> + @adjustSize(1.1) + + # Public: Zooms the image to its normal width and height. + resetZoom: -> + return unless @loaded and @isVisible() + + @image.width(@originalWidth) + @image.height(@originalHeight) + @centerImage() + + ### + # Internal # + ### + adjustSize: (factor) -> return unless @loaded and @isVisible() @@ -66,15 +99,5 @@ class ImageView extends ScrollView @image.height(newHeight) @centerImage() - zoomOut: -> - @adjustSize(0.9) - - zoomIn: -> - @adjustSize(1.1) - - resetZoom: -> - return unless @loaded and @isVisible() - - @image.width(@originalWidth) - @image.height(@originalHeight) - @centerImage() + setModel: (imageEditSession) -> + @setPath(imageEditSession?.getPath()) \ No newline at end of file diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 0cc615726..e05e423d6 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -5,6 +5,19 @@ CSON = require 'cson' BindingSet = require 'binding-set' +# Internal: Associates keymaps with actions. +# +# Keymaps are defined in a CSON format. A typical keymap looks something like this: +# +# ```cson +# 'body': +# 'ctrl-l': 'package:do-something' +#'.someClass': +# 'enter': 'package:confirm' +# ``` +# +# As a key, you define the DOM element you want to work on, using CSS notation. For that +# key, you define one or more key:value pairs, associating keystrokes with a command to execute. module.exports = class Keymap bindingSets: null @@ -39,23 +52,23 @@ class Keymap loadDirectory: (directoryPath) -> @load(filePath) for filePath in fsUtils.list(directoryPath, ['.cson', '.json']) - + load: (path) -> @add(path, CSON.readObject(path)) - + add: (args...) -> name = args.shift() if args.length > 1 keymap = args.shift() for selector, bindings of keymap @bindKeys(name, selector, bindings) - + remove: (name) -> for bindingSet in @bindingSets.filter((bindingSet) -> bindingSet.name is name) _.remove(@bindingSets, bindingSet) for keystrokes of bindingSet.commandsByKeystrokes keystroke = keystrokes.split(' ')[0] _.remove(@bindingSetsByFirstKeystroke[keystroke], bindingSet) - + bindKeys: (args...) -> name = args.shift() if args.length > 2 [selector, bindings] = args @@ -65,7 +78,7 @@ class Keymap keystroke = keystrokes.split(' ')[0] # only index by first keystroke @bindingSetsByFirstKeystroke[keystroke] ?= [] @bindingSetsByFirstKeystroke[keystroke].push(bindingSet) - + unbindKeys: (selector, bindings) -> bindingSet = _.detect @bindingSets, (bindingSet) -> bindingSet.selector is selector and bindingSet.bindings is bindings @@ -73,7 +86,7 @@ class Keymap if bindingSet console.log "binding set", bindingSet _.remove(@bindingSets, bindingSet) - + bindingsForElement: (element) -> keystrokeMap = {} currentNode = $(element) diff --git a/src/app/language-mode.coffee b/src/app/language-mode.coffee index 447cf576a..2ad48459d 100644 --- a/src/app/language-mode.coffee +++ b/src/app/language-mode.coffee @@ -5,6 +5,10 @@ require 'underscore-extensions' EventEmitter = require 'event-emitter' Subscriber = require 'subscriber' +### +# Internal # +### + module.exports = class LanguageMode buffer = null @@ -12,13 +16,17 @@ class LanguageMode editSession = null currentGrammarScore: null + # Public: Sets up a `LanguageMode` for the given {EditSession}. + # + # editSession - The {EditSession} to associate with constructor: (@editSession) -> @buffer = @editSession.buffer @reloadGrammar() @subscribe syntax, 'grammar-added', (grammar) => newScore = grammar.getScore(@buffer.getPath(), @buffer.getText()) @setGrammar(grammar, newScore) if newScore > @currentGrammarScore - + + # Internal: destroy: -> @unsubscribe() @@ -34,6 +42,14 @@ class LanguageMode else throw new Error("No grammar found for path: #{path}") + # Public: Wraps the lines between two rows in comments. + # + # If the language doesn't have comment, nothing happens. + # + # startRow - The row {Number} to start at + # endRow - The row {Number} to end at + # + # Returns an {Array} of the commented {Ranges}. toggleLineCommentsForBufferRows: (start, end) -> scopes = @editSession.scopesForBufferPosition([start, 0]) return unless commentStartString = syntax.getProperty(scopes, "editor.commentStart") @@ -96,6 +112,13 @@ class LanguageMode [bufferRow, foldEndRow] + # Public: Given a buffer row, this returns a suggested indentation level. + # + # The indentation level provided is based on the current {LanguageMode}. + # + # bufferRow - A {Number} indicating the buffer row + # + # Returns a {Number}. suggestedIndentForBufferRow: (bufferRow) -> currentIndentLevel = @editSession.indentationForBufferRow(bufferRow) scopes = @editSession.scopesForBufferPosition([bufferRow, 0]) @@ -115,13 +138,23 @@ class LanguageMode Math.max(desiredIndentLevel, currentIndentLevel) + # Public: Indents all the rows between two buffer row numbers. + # + # startRow - The row {Number} to start at + # endRow - The row {Number} to end at autoIndentBufferRows: (startRow, endRow) -> @autoIndentBufferRow(row) for row in [startRow..endRow] + # Public: Given a buffer row, this indents it. + # + # bufferRow - The row {Number} autoIndentBufferRow: (bufferRow) -> @autoIncreaseIndentForBufferRow(bufferRow) @autoDecreaseIndentForBufferRow(bufferRow) + # Public: Given a buffer row, this increases the indentation. + # + # bufferRow - The row {Number} autoIncreaseIndentForBufferRow: (bufferRow) -> precedingRow = @buffer.previousNonBlankRow(bufferRow) return unless precedingRow? @@ -137,6 +170,9 @@ class LanguageMode if desiredIndentLevel > currentIndentLevel @editSession.setIndentationForBufferRow(bufferRow, desiredIndentLevel) + # Public: Given a buffer row, this decreases the indentation. + # + # bufferRow - The row {Number} autoDecreaseIndentForBufferRow: (bufferRow) -> scopes = @editSession.scopesForBufferPosition([bufferRow, 0]) increaseIndentRegex = @increaseIndentRegexForScopes(scopes) diff --git a/src/app/line-map.coffee b/src/app/line-map.coffee index f58333835..d55b1656c 100644 --- a/src/app/line-map.coffee +++ b/src/app/line-map.coffee @@ -1,6 +1,7 @@ Point = require 'point' Range = require 'range' +# Internal: Responsible for doing the translations between screen positions and buffer positions. module.exports = class LineMap maxScreenLineLength: 0 @@ -25,13 +26,30 @@ class LineMap for screenLine in maxLengthCandidates @maxScreenLineLength = Math.max(@maxScreenLineLength, screenLine.text.length) - + + # Public: Gets the line for the given screen row. + # + # screenRow - A {Number} indicating the screen row. + # + # Returns a {String}. lineForScreenRow: (row) -> @screenLines[row] + # Public: Gets the lines for the given screen row boundaries. + # + # start - A {Number} indicating the beginning screen row. + # end - A {Number} indicating the ending screen row. + # + # Returns an {Array} of {String}s. linesForScreenRows: (startRow, endRow) -> @screenLines[startRow..endRow] - + + # Public: Given a starting and ending row, this converts every row into a buffer position. + # + # startRow - The row {Number} to start at + # endRow - The row {Number} to end at (default: {.lastScreenRow}) + # + # Returns an {Array} of {Range}s. bufferRowsForScreenRows: (startRow, endRow=@lastScreenRow()) -> bufferRows = [] bufferRow = 0 @@ -44,7 +62,10 @@ class LineMap screenLineCount: -> @screenLines.length - + + # Retrieves the last row number in the buffer. + # + # Returns an {Integer}. lastScreenRow: -> @screenLineCount() - 1 @@ -76,7 +97,14 @@ class LineMap else column = screenLine.clipScreenColumn(column, options) new Point(row, column) - + + # Public: Given a buffer position, this converts it into a screen position. + # + # bufferPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - The same options available to {.clipScreenPosition}. + # + # Returns a {Point}. screenPositionForBufferPosition: (bufferPosition, options={}) -> { row, column } = Point.fromObject(bufferPosition) [screenRow, screenLines] = @screenRowAndScreenLinesForBufferRow(row) @@ -109,9 +137,15 @@ class LineMap currentBufferRow = nextBufferRow [screenRow, screenLines] - + # Public: Given a buffer range, this converts it into a screen position. + # + # screenPosition - An object that represents a buffer position. It can be either + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # options - The same options available to {.clipScreenPosition}. + # + # Returns a {Point}. bufferPositionForScreenPosition: (screenPosition, options) -> - { row, column } = @clipScreenPosition(Point.fromObject(screenPosition)) + { row, column } = @clipScreenPosition(Point.fromObject(screenPosition), options) [bufferRow, screenLine] = @bufferRowAndScreenLineForScreenRow(row) bufferColumn = screenLine.bufferColumnForScreenColumn(column) new Point(bufferRow, bufferColumn) @@ -125,19 +159,29 @@ class LineMap bufferRow += screenLine.bufferRows [bufferRow, screenLine] - + + # Public: Given a buffer range, this converts it into a screen position. + # + # bufferRange - The {Range} to convert + # + # Returns a {Range}. screenRangeForBufferRange: (bufferRange) -> bufferRange = Range.fromObject(bufferRange) start = @screenPositionForBufferPosition(bufferRange.start) end = @screenPositionForBufferPosition(bufferRange.end) new Range(start, end) - + # Public: Given a screen range, this converts it into a buffer position. + # + # screenRange - The {Range} to convert + # + # Returns a {Range}. bufferRangeForScreenRange: (screenRange) -> screenRange = Range.fromObject(screenRange) start = @bufferPositionForScreenPosition(screenRange.start) end = @bufferPositionForScreenPosition(screenRange.end) new Range(start, end) + # Internal: logLines: (start=0, end=@screenLineCount() - 1)-> for row in [start..end] line = @lineForScreenRow(row).text diff --git a/src/app/null-grammar.coffee b/src/app/null-grammar.coffee index 2f2a37108..8b5e7b3e4 100644 --- a/src/app/null-grammar.coffee +++ b/src/app/null-grammar.coffee @@ -1,5 +1,8 @@ Token = require 'token' +### +# Internal # +### module.exports = class NullGrammar name: 'Null Grammar' diff --git a/src/app/package.coffee b/src/app/package.coffee index b3f3333e0..d4b72a446 100644 --- a/src/app/package.coffee +++ b/src/app/package.coffee @@ -1,5 +1,8 @@ fsUtils = require 'fs-utils' +### +# Internal # +### module.exports = class Package @build: (path) -> diff --git a/src/app/pane-axis.coffee b/src/app/pane-axis.coffee index 9aa4fda81..f7b8cf7ff 100644 --- a/src/app/pane-axis.coffee +++ b/src/app/pane-axis.coffee @@ -1,8 +1,10 @@ $ = require 'jquery' {View} = require 'space-pen' +# Internal: module.exports = class PaneAxis extends View + @deserialize: ({children}) -> childViews = children.map (child) -> deserialize(child) new this(childViews) diff --git a/src/app/pane-column.coffee b/src/app/pane-column.coffee index 43ba40cbb..9df7016a4 100644 --- a/src/app/pane-column.coffee +++ b/src/app/pane-column.coffee @@ -2,8 +2,10 @@ $ = require 'jquery' _ = require 'underscore' PaneAxis = require 'pane-axis' +# Internal: module.exports = class PaneColumn extends PaneAxis + @content: -> @div class: 'column' diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index 8bcaa45fb..48e506877 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -6,6 +6,10 @@ module.exports = class PaneContainer extends View registerDeserializer(this) + ### + # Internal # + ### + @deserialize: ({root}) -> container = new PaneContainer container.append(deserialize(root)) if root @@ -21,6 +25,10 @@ class PaneContainer extends View serialize: -> deserializer: 'PaneContainer' root: @getRoot()?.serialize() + + ### + # Public # + ### focusNextPane: -> panes = @getPanes() diff --git a/src/app/pane-row.coffee b/src/app/pane-row.coffee index ce7a09f82..d1030cd68 100644 --- a/src/app/pane-row.coffee +++ b/src/app/pane-row.coffee @@ -2,6 +2,10 @@ $ = require 'jquery' _ = require 'underscore' PaneAxis = require 'pane-axis' +### +# Internal # +### + module.exports = class PaneRow extends PaneAxis @content: -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 371bd6095..b042a66fb 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -6,6 +6,11 @@ PaneColumn = require 'pane-column' module.exports = class Pane extends View + + ### + # Internal # + ### + @content: (wrappedView) -> @div class: 'pane', => @div class: 'item-views', outlet: 'itemViews' @@ -60,6 +65,10 @@ class Pane extends View @attached = true @trigger 'pane:attached', [this] + ### + # Public # + ### + makeActive: -> for pane in @getContainer().getPanes() when pane isnt this pane.makeInactive() diff --git a/src/app/pasteboard.coffee b/src/app/pasteboard.coffee index 404b48216..7a3384905 100644 --- a/src/app/pasteboard.coffee +++ b/src/app/pasteboard.coffee @@ -1,17 +1,30 @@ crypto = require 'crypto' +# Internal: Represents the clipboard used for copying and pasting in Atom. module.exports = class Pasteboard signatureForMetadata: null + # Internal: Creates an `md5` hash of some text. + # + # text - A {String} to encrypt. + # + # Returns an encrypted {String}. md5: (text) -> crypto.createHash('md5').update(text, 'utf8').digest('hex') + # 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) + # 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] diff --git a/src/app/point.coffee b/src/app/point.coffee index 6dd3eca27..b49a3520c 100644 --- a/src/app/point.coffee +++ b/src/app/point.coffee @@ -1,7 +1,16 @@ _ = 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 + + # Public: 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 @@ -13,6 +22,15 @@ class Point new Point(row, column) + # Public: 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) @@ -21,11 +39,25 @@ class Point else point2 + # Public: 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) -> + # Public: Creates an identical copy of the `Point`. + # + # Returns a duplicate {Point}. copy: -> new Point(@row, @column) + # Public: 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 @@ -36,10 +68,24 @@ class Point new Point(row, column) + # Public: 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) + # Public: 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 @@ -48,6 +94,17 @@ class Point [new Point(0, column), new Point(@row, rightColumn)] + # Internal: 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 @@ -61,31 +118,67 @@ class Point else 0 + # Public: 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 + # Public: Identifies if one `Point` is less than another. + # + # other - The {Point} to compare against + # + # Returns a {Boolean}. isLessThan: (other) -> @compare(other) < 0 + # Public: 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 + # Public: Identifies if one `Point` is greater than another. + # + # other - The {Point} to compare against + # + # Returns a {Boolean}. isGreaterThan: (other) -> @compare(other) > 0 + # Public: 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 - inspect: -> - "(#{@row}, #{@column})" - + # Public: Converts the {Point} to a String. + # + # Returns a {String}. toString: -> "#{@row},#{@column}" + # Public: Converts the {Point} to an Array. + # + # Returns an {Array}. toArray: -> [@row, @column] + ### + # Internal # + ### + + inspect: -> + "(#{@row}, #{@column})" + + # Internal: serialize: -> @toArray() diff --git a/src/app/project.coffee b/src/app/project.coffee index d0b5a2ce6..f47a8894e 100644 --- a/src/app/project.coffee +++ b/src/app/project.coffee @@ -9,13 +9,14 @@ EventEmitter = require 'event-emitter' Directory = require 'directory' BufferedProcess = require 'buffered-process' +# Public: Represents a project that's opened in Atom. +# +# Ultimately, a project is a git directory that's been opened. It's a collection +# of directories and files that you can operate on. module.exports = class Project registerDeserializer(this) - @deserialize: (state) -> - new Project(state.path) - tabLength: 2 softTabs: true softWrap: false @@ -23,21 +24,41 @@ class Project editSessions: null ignoredPathRegexes: null + # Public: Establishes a new project at a given path. + # + # path - The {String} name of the path constructor: (path) -> @setPath(path) @editSessions = [] @buffers = [] + ### + # Internal # + ### + serialize: -> deserializer: 'Project' path: @getPath() + @deserialize: (state) -> + new Project(state.path) + destroy: -> editSession.destroy() for editSession in @getEditSessions() + ### + # Public # + ### + + # Public: Retrieves the project path. + # + # Returns a {String}. getPath: -> @rootDirectory?.path + # Public: Sets the project path. + # + # path - A {String} representing the new path setPath: (path) -> @rootDirectory?.off() @@ -49,9 +70,15 @@ class Project @trigger "path-changed" + # Public: Retrieves the name of the root directory. + # + # Returns a {String}. getRootDirectory: -> @rootDirectory + # Public: Retrieves the names of every file (that's not `git ignore`d) in the project. + # + # Returns an {Array} of {String}s. getFilePaths: -> deferred = $.Deferred() paths = [] @@ -61,6 +88,11 @@ class Project deferred.resolve(paths) deferred.promise() + # Public: Identifies if a path is ignored. + # + # path - The {String} name of the path to check + # + # Returns a {Boolean}. isPathIgnored: (path) -> for segment in path.split("/") ignoredNames = config.get("core.ignoredNames") or [] @@ -68,29 +100,74 @@ class Project @ignoreRepositoryPath(path) + # Public: Identifies if a path is ignored. + # + # path - The {String} name of the path to check + # + # Returns a {Boolean}. ignoreRepositoryPath: (path) -> config.get("core.hideGitIgnoredFiles") and git?.isPathIgnored(fsUtils.join(@getPath(), path)) + # Public: Given a path, this resolves it relative to the project directory. + # + # filePath - The {String} name of the path to convert + # + # Returns a {String}. resolve: (filePath) -> filePath = fsUtils.join(@getPath(), filePath) unless filePath[0] == '/' fsUtils.absolute filePath + # Public: Given a path, this makes it relative to the project directory. + # + # filePath - The {String} name of the path to convert + # + # Returns a {String}. relativize: (fullPath) -> return fullPath unless fullPath.lastIndexOf(@getPath()) is 0 fullPath.replace(@getPath(), "").replace(/^\//, '') + # Public: Identifies if the project is using soft tabs. + # + # Returns a {Boolean}. getSoftTabs: -> @softTabs + + # Public: Sets the project to use soft tabs. + # + # softTabs - A {Boolean} which, if `true`, sets soft tabs setSoftTabs: (@softTabs) -> + # Public: Identifies if the project is using soft wrapping. + # + # Returns a {Boolean}. getSoftWrap: -> @softWrap + + # Public: Sets the project to use soft wrapping. + # + # softTabs - A {Boolean} which, if `true`, sets soft wrapping setSoftWrap: (@softWrap) -> + # Public: Given a path to a file, this constructs and associates a new `EditSession`, showing the file. + # + # filePath - The {String} path of the file to associate with + # editSessionOptions - Options that you can pass to the `EditSession` constructor + # + # Returns either an {EditSession} (for text) or {ImageEditSession} (for images). buildEditSession: (filePath, editSessionOptions={}) -> if ImageEditSession.canOpen(filePath) new ImageEditSession(filePath) else @buildEditSessionForBuffer(@bufferForPath(filePath), editSessionOptions) + # Public: Retrieves all the {EditSession}s in the project; that is, the `EditSession`s for all open files. + # + # Returns an {Array} of {EditSession}s. + getEditSessions: -> + new Array(@editSessions...) + + ### + # Internal # + ### + buildEditSessionForBuffer: (buffer, editSessionOptions) -> options = _.extend(@defaultEditSessionOptions(), editSessionOptions) options.project = this @@ -105,22 +182,10 @@ class Project softTabs: @getSoftTabs() softWrap: @getSoftWrap() - getEditSessions: -> - new Array(@editSessions...) - eachEditSession: (callback) -> callback(editSession) for editSession in @getEditSessions() @on 'edit-session-created', (editSession) -> callback(editSession) - - removeEditSession: (editSession) -> - _.remove(@editSessions, editSession) - - getBuffers: -> - buffers = [] - for editSession in @editSessions when not _.include(buffers, editSession.buffer) - buffers.push editSession.buffer - buffers - + eachBuffer: (args...) -> subscriber = args.shift() if args.length > 1 callback = args.shift() @@ -131,6 +196,34 @@ class Project else @on 'buffer-created', (buffer) -> callback(buffer) + ### + # Public # + ### + + # Public: Removes an {EditSession} association from the project. + # + # Returns the removed {EditSession}. + removeEditSession: (editSession) -> + _.remove(@editSessions, editSession) + + # Public: Retrieves all the {Buffer}s in the project; that is, the buffers for all open files. + # + # Returns an {Array} of {Buffer}s. + getBuffers: -> + buffers = [] + for editSession in @editSessions when not _.include(buffers, editSession.buffer) + buffers.push editSession.buffer + buffers + + # Public: Given a file path, this retrieves or creates a new {Buffer}. + # + # If the `filePath` already has a `buffer`, that value is used instead. Otherwise, + # `text` is used as the contents of the new buffer. + # + # filePath - A {String} representing a path. If `null`, an "Untitled" buffer is created. + # text - The {String} text to use as a buffer, if the file doesn't have any contents + # + # Returns the {Buffer}. bufferForPath: (filePath, text) -> if filePath? filePath = @resolve(filePath) @@ -140,15 +233,28 @@ class Project else @buildBuffer(null, text) + # Public: 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 @trigger 'buffer-created', buffer buffer + # Public: Removes a {Buffer} association from the project. + # + # Returns the removed {Buffer}. removeBuffer: (buffer) -> _.remove(@buffers, buffer) + # Public: Performs a search across all the files in the project. + # + # regex - A {RegExp} to search with + # iterator - A {Function} callback on each file found scan: (regex, iterator) -> bufferedData = "" state = 'readingPath' diff --git a/src/app/range.coffee b/src/app/range.coffee index d72ef6514..2b82cc3bd 100644 --- a/src/app/range.coffee +++ b/src/app/range.coffee @@ -1,8 +1,20 @@ 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 + # Public: 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...) @@ -11,11 +23,22 @@ class Range else new Range(object.start, object.end) + # Public: 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: (point, rowDelta, columnDelta) -> pointA = Point.fromObject(point) pointB = new Point(point.row + rowDelta, point.column + columnDelta) new Range(pointA, pointB) + # Public: 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) @@ -27,40 +50,95 @@ class Range @start = pointB @end = pointA + # Public: Creates an identical copy of the `Range`. + # + # Returns a duplicate {Range}. copy: -> new Range(@start.copy(), @end.copy()) + # Public: 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) + # Public: 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 + # Public: 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 + # Internal: inspect: -> "[#{@start.inspect()} - #{@end.inspect()}]" + # Public: 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)) + # Public: 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)) + # Public: 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) + # Public: 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}) + # Public: 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 @@ -68,17 +146,36 @@ class Range else point.isGreaterThanOrEqual(@start) and point.isLessThanOrEqual(@end) + # Public: 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 + # Public: 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) + # Public: Identifies if a `Range` is empty. + # + # A `Range` is empty if its start {Point} matches its end. + # + # Returns a {Boolean}. isEmpty: -> @start.isEqual(@end) + # Public: Calculates the difference between a `Range`s `start` and `end` points. + # + # Returns a {Point}. toDelta: -> rows = @end.row - @start.row if rows == 0 @@ -87,5 +184,8 @@ class Range columns = @end.column new Point(rows, columns) + # Public: Calculates the number of rows a `Range`s contains. + # + # Returns a {Number}. getRowCount: -> @end.row - @start.row + 1 diff --git a/src/app/repository-status-task.coffee b/src/app/repository-status-task.coffee index 0e5f746a5..358d9e9fb 100644 --- a/src/app/repository-status-task.coffee +++ b/src/app/repository-status-task.coffee @@ -1,8 +1,10 @@ Task = require 'task' _ = require 'underscore' +# Internal: module.exports = class RepositoryStatusTask extends Task + constructor: (@repo) -> super('repository-status-handler') diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 6b4bf73bb..a6be35e1e 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -13,6 +13,7 @@ PaneRow = require 'pane-row' PaneContainer = require 'pane-container' EditSession = require 'edit-session' +# Public: The container for the entire Atom application. module.exports = class RootView extends View registerDeserializers(this, Pane, PaneRow, PaneColumn, Editor) @@ -23,12 +24,16 @@ class RootView extends View ignoredNames: [".git", ".svn", ".DS_Store"] disabledPackages: [] + ### + # Internal: + ### + @content: ({panes}={}) -> @div id: 'root-view', => @div id: 'horizontal', outlet: 'horizontal', => @div id: 'vertical', outlet: 'vertical', => @subview 'panes', panes ? new PaneContainer - + @deserialize: ({ panes }) -> panes = deserialize(panes) if panes?.deserializer is 'PaneContainer' new RootView({panes}) @@ -74,9 +79,6 @@ class RootView extends View deserializer: 'RootView' panes: @panes.serialize() - confirmClose: -> - @panes.confirmClose() - handleFocus: (e) -> if @getActivePane() @getActivePane().focus() @@ -93,6 +95,17 @@ class RootView extends View afterAttach: (onDom) -> @focus() if onDom + ### + # Public # + ### + + # Public: Shows a dialog asking if the pane was _really_ meant to be closed. + confirmClose: -> + @panes.confirmClose() + + # Public: Given a filepath, this opens it in Atom. + # + # Returns the `EditSession` for the file URI. open: (path, options = {}) -> changeFocus = options.changeFocus ? true path = project.resolve(path) if path? @@ -107,6 +120,7 @@ class RootView extends View activePane.focus() if changeFocus editSession + # Public: Updates the application's title, based on whichever file is open. updateTitle: -> if projectPath = project.getPath() if item = @getActivePaneItem() @@ -116,12 +130,21 @@ class RootView extends View else @setTitle('untitled') + # Public: Sets the application's title. + # + # Returns a {String}. setTitle: (title) -> document.title = title + # Public: Retrieves all of the application's {Editor}s. + # + # Returns an {Array} of {Editor}s. getEditors: -> @panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray() + # Public: Retrieves all of the modified buffers that are open and unsaved. + # + # Returns an {Array} of {Buffer}s. getModifiedBuffers: -> modifiedBuffers = [] for pane in @getPanes() @@ -129,9 +152,15 @@ class RootView extends View modifiedBuffers.push item.buffer if item.buffer.isModified() modifiedBuffers + # Public: Retrieves all of the paths to open files. + # + # Returns an {Array} of {String}s. getOpenBufferPaths: -> _.uniq(_.flatten(@getEditors().map (editor) -> editor.getOpenBufferPaths())) + # Public: Retrieves the pane that's currently open. + # + # Returns an {Pane}. getActivePane: -> @panes.getActivePane() @@ -145,29 +174,51 @@ class RootView extends View focusNextPane: -> @panes.focusNextPane() getFocusedPane: -> @panes.getFocusedPane() + # Internal: Destroys everything. remove: -> editor.remove() for editor in @getEditors() project.destroy() super + # Public: Saves all of the open buffers. saveAll: -> @panes.saveAll() + # Public: Fires a callback on each open {Pane}. + # + # callback - A {Function} to call eachPane: (callback) -> @panes.eachPane(callback) + # Public: Retrieves all of the open {Pane}s. + # + # Returns an {Array} of {Pane}. getPanes: -> @panes.getPanes() + # Public: Given a {Pane}, this fetches its ID. + # + # pane - An open {Pane} + # + # Returns a {Number}. indexOfPane: (pane) -> @panes.indexOfPane(pane) + # Public: Fires a callback on each open {Editor}. + # + # callback - A {Function} to call eachEditor: (callback) -> callback(editor) for editor in @getEditors() @on 'editor:attached', (e, editor) -> callback(editor) + # Public: Fires a callback on each open {EditSession}. + # + # callback - A {Function} to call eachEditSession: (callback) -> project.eachEditSession(callback) + # Public: Fires a callback on each open {Buffer}. + # + # callback - A {Function} to call eachBuffer: (callback) -> project.eachBuffer(callback) diff --git a/src/app/screen-line.coffee b/src/app/screen-line.coffee index 739d23eff..99a0424fb 100644 --- a/src/app/screen-line.coffee +++ b/src/app/screen-line.coffee @@ -1,5 +1,9 @@ _ = require 'underscore' +### +# Internal # +### + module.exports = class ScreenLine constructor: ({tokens, @lineEnding, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) -> @@ -104,6 +108,9 @@ class ScreenLine breakOutLeadingWhitespace = token.isOnlyWhitespace() if breakOutLeadingWhitespace outputTokens + # Public: Determins if the current line is commented. + # + # Returns a {Boolean}. isComment: -> for token in @tokens continue if token.scopes.length is 1 diff --git a/src/app/scroll-view.coffee b/src/app/scroll-view.coffee index f89a5c5da..398dbca25 100644 --- a/src/app/scroll-view.coffee +++ b/src/app/scroll-view.coffee @@ -1,6 +1,13 @@ {View} = require 'space-pen' + +# Public: Represents a view that scrolls. +# +# This `View` subclass listens to events such as `page-up`, `page-down`, +# `move-to-top`, and `move-to-bottom`. module.exports = class ScrollView extends View + + # Internal: The constructor. initialize: -> @on 'core:page-up', => @pageUp() @on 'core:page-down', => @pageDown() diff --git a/src/app/select-list.coffee b/src/app/select-list.coffee index 4051a4fff..e82c07598 100644 --- a/src/app/select-list.coffee +++ b/src/app/select-list.coffee @@ -5,6 +5,11 @@ fuzzyFilter = require 'fuzzy-filter' module.exports = class SelectList extends View + + ### + # Internal # + ### + @content: -> @div class: @viewClass(), => @subview 'miniEditor', new Editor(mini: true) diff --git a/src/app/selection-view.coffee b/src/app/selection-view.coffee index 4b66e3d97..2adcf7627 100644 --- a/src/app/selection-view.coffee +++ b/src/app/selection-view.coffee @@ -4,6 +4,8 @@ Range = require 'range' module.exports = class SelectionView extends View + + # Internal: Establishes the DOM for the selection view. @content: -> @div class: 'selection' diff --git a/src/app/selection.coffee b/src/app/selection.coffee index c3f91eb69..e7b69a2a3 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -2,6 +2,7 @@ Range = require 'range' EventEmitter = require 'event-emitter' _ = require 'underscore' +# Public: Represents a selection in the {EditSession}. module.exports = class Selection cursor: null @@ -12,6 +13,10 @@ class Selection wordwise: false needsAutoscroll: null + ### + # Internal # + ### + constructor: ({@cursor, @marker, @editSession, @goalBufferRange}) -> @cursor.selection = this @editSession.observeMarker @marker, => @screenRangeChanged() @@ -32,27 +37,56 @@ class Selection @wordwise = false @linewise = false + clearAutoscroll: -> + @needsAutoscroll = null + + ### + # Public # + ### + + # Public: Identifies if the selection is highlighting anything. + # + # Returns a {Boolean}. isEmpty: -> @getBufferRange().isEmpty() + # Public: Identifies if the selection is reversed, that is, it is highlighting "up." + # + # Returns a {Boolean}. isReversed: -> @editSession.isMarkerReversed(@marker) + # Public: Identifies if the selection is a single line. + # + # Returns a {Boolean}. isSingleScreenLine: -> @getScreenRange().isSingleLine() - clearAutoscroll: -> - @needsAutoscroll = null - + # Public: Retrieves the screen range for the selection. + # + # Returns a {Range}. getScreenRange: -> @editSession.getMarkerScreenRange(@marker) + # Public: Modifies the screen range for the selection. + # + # screenRange - The new {Range} to use + # options - A hash of options matching those found in {.setBufferRange} setScreenRange: (screenRange, options) -> @setBufferRange(@editSession.bufferRangeForScreenRange(screenRange), options) + # Public: Retrieves the buffer range for the selection. + # + # Returns a {Range}. getBufferRange: -> @editSession.getMarkerBufferRange(@marker) + # Public: Modifies the buffer range for the selection. + # + # screenRange - The new {Range} to select + # options - A hash of options with the following keys: + # :preserveFolds - if `true`, the fold settings are preserved after the selection moves + # :autoscroll - if `true`, the {EditSession} scrolls to the new selection setBufferRange: (bufferRange, options={}) -> bufferRange = Range.fromObject(bufferRange) @needsAutoscroll = options.autoscroll @@ -62,6 +96,9 @@ class Selection @cursor.needsAutoscroll = false if options.autoscroll? @editSession.setMarkerBufferRange(@marker, bufferRange, options) + # Public: Retrieves the starting and ending buffer rows the selection is highlighting. + # + # Returns an {Array} of two {Number}s: the starting row, and the ending row. getBufferRowRange: -> range = @getBufferRange() start = range.start.row @@ -69,16 +106,24 @@ class Selection end = Math.max(start, end - 1) if range.end.column == 0 [start, end] + # Internal: screenRangeChanged: -> screenRange = @getScreenRange() @trigger 'screen-range-changed', screenRange + # Public: Retrieves the text in the selection. + # + # Returns a {String}. getText: -> @editSession.buffer.getTextInRange(@getBufferRange()) + # Public: Clears the selection, moving the marker to move to the head. clear: -> @editSession.clearMarkerTail(@marker) + # Public: Modifies the selection to mark the current word. + # + # Returns a {Range}. selectWord: -> options = {} options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace() @@ -90,6 +135,9 @@ class Selection expandOverWord: -> @setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange())) + # Public: Selects an entire line in the {Buffer}. + # + # row - The line {Number} to select (default: the row of the cursor) selectLine: (row=@cursor.getBufferPosition().row) -> range = @editSession.bufferRangeForBufferRow(row, includeNewline: true) @setBufferRange(range) @@ -101,6 +149,9 @@ class Selection range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true)) @setBufferRange(range) + # Public: Selects the text from the current cursor position to a given screen position. + # + # position - An instance of {Point}, with a given `row` and `column`. selectToScreenPosition: (position) -> @modifySelection => if @initialScreenRange @@ -116,45 +167,61 @@ class Selection else if @wordwise @expandOverWord() + # Public: Selects the text from the current cursor position to a given buffer position. + # + # position - An instance of {Point}, with a given `row` and `column`. selectToBufferPosition: (position) -> @modifySelection => @cursor.setBufferPosition(position) + # Public: Selects the text one position right of the cursor. selectRight: -> @modifySelection => @cursor.moveRight() + # Public: Selects the text one position left of the cursor. selectLeft: -> @modifySelection => @cursor.moveLeft() + # Public: Selects all the text one position above the cursor. selectUp: -> @modifySelection => @cursor.moveUp() + # Public: Selects all the text one position below the cursor. selectDown: -> @modifySelection => @cursor.moveDown() + # Public: Selects all the text from the current cursor position to the top of the buffer. selectToTop: -> @modifySelection => @cursor.moveToTop() + # Public: Selects all the text from the current cursor position to the bottom of the buffer. selectToBottom: -> @modifySelection => @cursor.moveToBottom() + # Public: Selects all the text in the buffer. selectAll: -> @setBufferRange(@editSession.buffer.getRange(), autoscroll: false) + # Public: Selects all the text from the current cursor position to the beginning of the line. selectToBeginningOfLine: -> @modifySelection => @cursor.moveToBeginningOfLine() + # Public: Selects all the text from the current cursor position to the end of the line. selectToEndOfLine: -> @modifySelection => @cursor.moveToEndOfLine() + # Public: Selects all the text from the current cursor position to the beginning of the word. selectToBeginningOfWord: -> @modifySelection => @cursor.moveToBeginningOfWord() + # Public: Selects all the text from the current cursor position to the end of the word. selectToEndOfWord: -> @modifySelection => @cursor.moveToEndOfWord() + # Public: Selects all the text from the current cursor position to the beginning of the next word. selectToBeginningOfNextWord: -> @modifySelection => @cursor.moveToBeginningOfNextWord() + # Public: Moves the selection down one row. addSelectionBelow: -> range = (@goalBufferRange ? @getBufferRange()).copy() nextRow = range.end.row + 1 @@ -172,6 +239,7 @@ class Selection @editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true) break + # Public: Moves the selection up one row. addSelectionAbove: -> range = (@goalBufferRange ? @getBufferRange()).copy() previousRow = range.end.row - 1 @@ -189,6 +257,13 @@ class Selection @editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true) break + # Public: Replaces text at the current selection. + # + # text - A {String} representing the text to add + # options - A hash containing the following options: + # :normalizeIndent - TODO + # :select - if `true`, selects the newly added text + # :autoIndent - if `true`, indents the newly added text appropriately insertText: (text, options={}) -> oldBufferRange = @getBufferRange() @editSession.destroyFoldsContainingBufferRow(oldBufferRange.end.row) @@ -210,6 +285,10 @@ class Selection newBufferRange + # Public: Indents the selection. + # + # options - A hash with one key, `autoIndent`. If `true`, the indentation is + # performed appropriately. Otherwise, {EditSession#getTabText} is used indent: ({ autoIndent }={})-> { row, column } = @cursor.getBufferPosition() @@ -225,6 +304,7 @@ class Selection else @indentSelectedRows() + # Public: If the selection spans multiple rows, indents all of them. indentSelectedRows: -> [start, end] = @getBufferRowRange() for row in [start..end] @@ -270,6 +350,7 @@ class Selection desiredIndentString = @editSession.buildIndentString(desiredIndentLevel) line.replace(/^[\t ]*/, desiredIndentString) + # Public: Performs a backspace, removing the character found behind the selection. backspace: -> if @isEmpty() and not @editSession.isFoldedAtScreenRow(@cursor.getScreenRow()) if @cursor.isAtBeginningOfLine() and @editSession.isFoldedAtScreenRow(@cursor.getScreenRow() - 1) @@ -279,10 +360,12 @@ class Selection @deleteSelectedText() + # Public: Performs a backspace to the beginning of the current word, removing characters found there. backspaceToBeginningOfWord: -> @selectToBeginningOfWord() if @isEmpty() @deleteSelectedText() + # Public: Performs a backspace to the beginning of the current line, removing characters found there. backspaceToBeginningOfLine: -> if @isEmpty() and @cursor.isAtBeginningOfLine() @selectLeft() @@ -290,6 +373,7 @@ class Selection @selectToBeginningOfLine() @deleteSelectedText() + # Public: Performs a delete, removing the character found ahead of the cursor position. delete: -> if @isEmpty() if @cursor.isAtEndOfLine() and fold = @editSession.largestFoldStartingAtScreenRow(@cursor.getScreenRow() + 1) @@ -298,10 +382,12 @@ class Selection @selectRight() @deleteSelectedText() + # Public: Performs a delete to the end of the current word, removing characters found there. deleteToEndOfWord: -> @selectToEndOfWord() if @isEmpty() @deleteSelectedText() + # Public: Deletes the selected text. deleteSelectedText: -> bufferRange = @getBufferRange() if fold = @editSession.largestFoldContainingBufferRow(bufferRange.end.row) @@ -311,6 +397,7 @@ class Selection @editSession.buffer.delete(bufferRange) unless bufferRange.isEmpty() @cursor?.setBufferPosition(bufferRange.start) + # Public: Deletes the line. deleteLine: -> if @isEmpty() start = @cursor.getScreenRow() @@ -327,6 +414,9 @@ class Selection end-- @editSession.buffer.deleteRows(start, end) + # Public: Joins the current line with the one below it. + # + # If there selection spans more than one line, all the lines are joined together. joinLine: -> selectedRange = @getBufferRange() if selectedRange.isEmpty() @@ -364,17 +454,29 @@ class Selection [start, end] = @getBufferRowRange() @editSession.autoIndentBufferRows(start, end) + # Public: Wraps the selected lines in comments. + # + # Returns an {Array} of the commented {Ranges}. toggleLineComments: -> @editSession.toggleLineCommentsForBufferRows(@getBufferRowRange()...) + # Public: Performs a cut operation on the selection, until the end of the line. + # + # maintainPasteboard - A {Boolean} indicating TODO cutToEndOfLine: (maintainPasteboard) -> @selectToEndOfLine() if @isEmpty() @cut(maintainPasteboard) + # Public: Performs a cut operation on the selection. + # + # maintainPasteboard - A {Boolean} indicating TODO cut: (maintainPasteboard=false) -> @copy(maintainPasteboard) @delete() + # Public: Performs a copy operation on the selection. + # + # maintainPasteboard - A {Boolean} indicating TODO copy: (maintainPasteboard=false) -> return if @isEmpty() text = @editSession.buffer.getTextInRange(@getBufferRange()) @@ -386,6 +488,7 @@ class Selection pasteboard.write(text, metadata) + # Public: Folds the selection. fold: -> range = @getBufferRange() @editSession.createFold(range.start.row, range.end.row) @@ -406,12 +509,26 @@ class Selection placeTail: -> @editSession.placeMarkerTail(@marker) + # Public: Identifies if a selection intersects with a given buffer range. + # + # bufferRange - A {Range} to check against + # + # Returns a {Boolean}. intersectsBufferRange: (bufferRange) -> @getBufferRange().intersectsWith(bufferRange) + # Public: Identifies if a selection intersects with another selection. + # + # otherSelection - A `Selection` to check against + # + # Returns a {Boolean}. intersectsWith: (otherSelection) -> @getBufferRange().intersectsWith(otherSelection.getBufferRange()) + # Public: Merges two selections together. + # + # otherSelection - A `Selection` to merge with + # options - A hash of options matching those found in {.setBufferRange} merge: (otherSelection, options) -> @setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options) if @goalBufferRange and otherSelection.goalBufferRange diff --git a/src/app/syntax.coffee b/src/app/syntax.coffee index 3aebccbb8..c7076fbd2 100644 --- a/src/app/syntax.coffee +++ b/src/app/syntax.coffee @@ -6,6 +6,10 @@ fsUtils = require 'fs-utils' EventEmitter = require 'event-emitter' NullGrammar = require 'null-grammar' +### +# Internal # +### + module.exports = class Syntax registerDeserializer(this) diff --git a/src/app/text-buffer.coffee b/src/app/text-buffer.coffee index 471af8fa5..cc1cc1901 100644 --- a/src/app/text-buffer.coffee +++ b/src/app/text-buffer.coffee @@ -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 @@ -25,9 +29,10 @@ class Buffer invalidMarkers: null refcount: 0 - @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 @@ -50,6 +55,10 @@ class Buffer @undoManager = new UndoManager(this) + ### + # Internal # + ### + destroy: -> throw new Error("Destroying buffer twice with path '#{@getPath()}'") if @destroyed @file?.off() @@ -70,7 +79,8 @@ class Buffer path: @getPath() text: @getText() if @isModified() - hasMultipleEditors: -> @refcount > 1 + @deserialize: ({path, text}) -> + project.bufferForPath(path, text) subscribeToFile: -> @file.on "contents-changed", => @@ -87,7 +97,21 @@ class Buffer @file.on "moved", => @trigger "path-changed", this + + ### + # Public # + ### + # Public: Identifies if the buffer belongs to multiple editors. + # + # For example, if the {Editor} was split. + # + # Returns a {Boolean}. + hasMultipleEditors: -> @refcount > 1 + + # Public: Reloads a file in the {EditSession}. + # + # Essentially, this performs a force read of the file. reload: -> @trigger 'will-reload' @updateCachedDiskContents() @@ -95,15 +119,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() @@ -114,21 +150,38 @@ class Buffer @trigger "path-changed", this + # Public: Retrieves the current buffer's file extension. + # + # Returns a {String}. getExtension: -> if @getPath() @getPath().split('/').pop().split('.').pop() else null + # Public: Retrieves the cached buffer contents. + # + # Returns a {String}. getText: -> @cachedMemoryContents ?= @getTextInRange(@getRange()) + # Public: Replaces the current buffer contents. + # + # text - A {String} containing the new buffer contents. 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]) + # Public: Given a range, returns the lines of text within it. + # + # range - A {Range} object specifying your points of interest + # + # Returns a {String} of the combined lines. getTextInRange: (range) -> range = @clipRange(range) if range.start.row == range.end.row @@ -144,9 +197,17 @@ class Buffer return multipleLines.join '' + # Public: Gets all the lines in a file. + # + # Returns an {Array} of {String}s. getLines: -> @lines + # Public: Given a row, returns the line of text. + # + # row - A {Number} indicating the row. + # + # Returns a {String}. lineForRow: (row) -> @lines[row] @@ -156,27 +217,51 @@ class Buffer suggestedLineEndingForRow: (row) -> @lineEndingForRow(row) ? @lineEndingForRow(row - 1) + # Public: Given a row, returns the length of the line of text. + # + # row - A {Number} indicating the row. + # + # Returns a {Number}. lineLengthForRow: (row) -> @lines[row].length lineEndingLengthForRow: (row) -> (@lineEndingForRow(row) ? '').length + # Public: Given a buffer row, this retrieves the range for that line. + # + # row - A {Number} identifying the row + # options - A hash with one key, `includeNewline`, which specifies whether you + # want to include the trailing newline + # + # Returns a {Range}. rangeForRow: (row, { includeNewline } = {}) -> if includeNewline and row < @getLastRow() new Range([row, 0], [row + 1, 0]) else new Range([row, 0], [row, @lineLengthForRow(row)]) + # Public: Gets the number of lines in a file. + # + # Returns a {Number}. getLineCount: -> @getLines().length + # Public: Gets the row number of the last line. + # + # Returns a {Number}. getLastRow: -> @getLines().length - 1 + # Public: Finds the last line in the current buffer. + # + # Returns a {String}. getLastLine: -> @lineForRow(@getLastRow()) + # Public: Finds the last point in the current buffer. + # + # Returns a {Point} representing the last position. getEofPosition: -> lastRow = @getLastRow() new Point(lastRow, @lineLengthForRow(lastRow)) @@ -197,9 +282,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 @@ -215,21 +307,39 @@ 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}) range = @pushOperation(operation) range + # Public: Given a position, this clips it to a real position. + # + # For example, if `position`'s row exceeds the row count of the buffer, + # or if its column goes beyond a line's length, this "sanitizes" the value + # to a real position. + # + # 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() @@ -240,7 +350,16 @@ class Buffer column = Math.max(position.column, 0) column = Math.min(@lineLengthForRow(row), column) new Point(row, column) - + + # Public: Given a range, this clips it to a real range. + # + # For example, if `range`'s row exceeds the row count of the buffer, + # or if its column goes beyond a line's length, this "sanitizes" the value + # to a real range. + # + # range - The {Point} to clip + # + # Returns the new, clipped {Point}. Note that this could be the same as `range` if no clipping was performed. clipRange: (range) -> range = Range.fromObject(range) new Range(@clipPosition(range.start), @clipPosition(range.end)) @@ -249,21 +368,33 @@ 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() + # Public: Saves the buffer. save: -> @saveAs(@getPath()) if @isModified() + # Public: Saves the buffer at a specific path. + # + # path - The path to save at. saveAs: (path) -> unless path then throw new Error("Can't save buffer with no file path") @@ -274,22 +405,40 @@ class Buffer @triggerModifiedStatusChanged(false) @trigger 'saved' + # Public: Identifies if the buffer was modified. + # + # Returns a {Boolean}. isModified: -> if @file @getText() != @cachedDiskContents 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) + # Public: Constructs a new marker at a given range. + # + # range - The marker {Range} (representing the distance between the head and tail) + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markRange: (range, options={}) -> marker = new BufferMarker(_.defaults({ id: (@nextMarkerId++).toString() @@ -299,9 +448,18 @@ class Buffer @validMarkers[marker.id] = marker marker.id + # Public: Constructs a new marker at a given position. + # + # position - The marker {Point}; there won't be a tail + # options - Options to pass to the {BufferMarker} constructor + # + # Returns a {Number} representing the new marker's ID. markPosition: (position, options) -> @markRange([position, position], _.defaults({noTail: true}, options)) + # Public: Removes the marker with the given id. + # + # id - The {Number} of the ID to remove destroyMarker: (id) -> delete @validMarkers[id] delete @invalidMarkers[id] @@ -312,39 +470,110 @@ class Buffer setMarkerPosition: (args...) -> @setMarkerHeadPosition(args...) + # Public: Retrieves the position of the marker's head. + # + # id - A {Number} representing the marker to check + # + # Returns a {Point}, or `null` if the marker does not exist. getMarkerHeadPosition: (id) -> @validMarkers[id]?.getHeadPosition() + # Public: Sets the position of the marker's head. + # + # id - A {Number} representing the marker to change + # position - 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. setMarkerHeadPosition: (id, position, options) -> @validMarkers[id]?.setHeadPosition(position) + # Public: Retrieves the position of the marker's tail. + # + # id - A {Number} representing the marker to check + # + # Returns a {Point}, or `null` if the marker does not exist. getMarkerTailPosition: (id) -> @validMarkers[id]?.getTailPosition() + # Public: Sets the position of the marker's tail. + # + # id - A {Number} representing the marker to change + # position - 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. setMarkerTailPosition: (id, position, options) -> @validMarkers[id]?.setTailPosition(position) + # Public: Retrieves the {Range} between a marker's head and its tail. + # + # id - A {Number} representing the marker to check + # + # Returns a {Range}. getMarkerRange: (id) -> @validMarkers[id]?.getRange() + # Public: Sets the marker's range, potentialy modifying both its head and tail. + # + # id - A {Number} representing the marker to change + # 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 setMarkerRange: (id, range, options) -> @validMarkers[id]?.setRange(range, options) + # Public: 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. + # + # id - A {Number} representing the marker to change + # + # Returns a {Point} representing the new tail position. placeMarkerTail: (id) -> @validMarkers[id]?.placeTail() + # Public: Removes the tail from the marker. + # + # id - A {Number} representing the marker to change clearMarkerTail: (id) -> @validMarkers[id]?.clearTail() + # Public: 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}. + # + # id - A {Number} representing the marker to check + # + # Returns a {Boolean}. isMarkerReversed: (id) -> @validMarkers[id]?.isReversed() + # Public: Identifies if the marker's head position is equal to its tail. + # + # id - A {Number} representing the marker to check + # + # Returns a {Boolean}. isMarkerRangeEmpty: (id) -> @validMarkers[id]?.isRangeEmpty() + # Public: Sets a callback to be fired whenever a marker is changed. + # + # id - A {Number} representing the marker to watch + # callback - A {Function} to execute observeMarker: (id, callback) -> @validMarkers[id]?.observe(callback) + # Public: 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: (bufferPosition) -> bufferPosition = Point.fromObject(bufferPosition) ids = [] @@ -352,6 +581,13 @@ class Buffer ids.push(id) if marker.containsPoint(bufferPosition) ids + # Public: 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 = [] @@ -375,9 +611,19 @@ class Buffer matches + # Public: 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(), iterator) + # Public: 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 @@ -415,12 +661,28 @@ class Buffer break unless global and keepLooping + # Public: 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 + # Public: Given a row, identifies if it is blank. + # + # row - A row {Number} to check + # + # Returns a {Boolean}. 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 @@ -429,6 +691,12 @@ class Buffer return row unless @isRowBlank(row) null + # Public: 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 @@ -436,17 +704,32 @@ 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. checkoutHead: -> path = @getPath() return unless path git?.checkoutHead(path) + # Public: Checks to see if a file exists. + # + # Returns a {Boolean}. + fileExists: -> + @file? && @file.exists() + + + ### + # Internal # + ### + scheduleModifiedEvents: -> clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout stoppedChangingCallback = => @@ -461,9 +744,6 @@ class Buffer @previousModifiedStatus = modifiedStatus @trigger 'modified-status-changed', modifiedStatus - fileExists: -> - @file? && @file.exists() - logLines: (start=0, end=@getLastRow())-> for row in [start..end] line = @lineForRow(row) diff --git a/src/app/text-mate-grammar.coffee b/src/app/text-mate-grammar.coffee index 95d52db1c..62caed59d 100644 --- a/src/app/text-mate-grammar.coffee +++ b/src/app/text-mate-grammar.coffee @@ -6,6 +6,10 @@ Token = require 'token' nodePath = require 'path' pathSplitRegex = new RegExp("[#{nodePath.sep}.]") +### +# Internal # +### + module.exports = class TextMateGrammar @readFromPath: (path) -> @@ -346,6 +350,10 @@ class Pattern tokens +### +# Internal # +### + shiftCapture = (captureIndices) -> [captureIndices.shift(), captureIndices.shift(), captureIndices.shift()] diff --git a/src/app/text-mate-package.coffee b/src/app/text-mate-package.coffee index 10156f4ba..80961754c 100644 --- a/src/app/text-mate-package.coffee +++ b/src/app/text-mate-package.coffee @@ -5,6 +5,10 @@ _ = require 'underscore' TextMateGrammar = require 'text-mate-grammar' async = require 'async' +### +# Internal # +### + module.exports = class TextMatePackage extends Package @testName: (packageName) -> diff --git a/src/app/text-mate-theme.coffee b/src/app/text-mate-theme.coffee index 3054b0633..c032ec620 100644 --- a/src/app/text-mate-theme.coffee +++ b/src/app/text-mate-theme.coffee @@ -3,6 +3,10 @@ fsUtils = require 'fs-utils' plist = require 'plist' Theme = require 'theme' +### +# Internal # +### + module.exports = class TextMateTheme extends Theme @testPath: (path) -> diff --git a/src/app/theme.coffee b/src/app/theme.coffee index da038e391..d6ebdff6b 100644 --- a/src/app/theme.coffee +++ b/src/app/theme.coffee @@ -1,5 +1,9 @@ fsUtils = require 'fs-utils' +### +# Internal # +### + module.exports = class Theme @stylesheets: null diff --git a/src/app/tokenized-buffer.coffee b/src/app/tokenized-buffer.coffee index f35c94680..35d38bff4 100644 --- a/src/app/tokenized-buffer.coffee +++ b/src/app/tokenized-buffer.coffee @@ -5,6 +5,10 @@ Token = require 'token' Range = require 'range' Point = require 'point' +### +# Internal # +### + module.exports = class TokenizedBuffer @idCounter: 1 @@ -33,9 +37,15 @@ class TokenizedBuffer setVisible: (@visible) -> @tokenizeInBackground() if @visible + # Public: Retrieves the current tab length. + # + # Returns a {Number}. getTabLength: -> @tabLength + # Public: Specifies the tab length. + # + # tabLength - A {Number} that defines the new tab length. setTabLength: (@tabLength) -> lastRow = @buffer.getLastRow() @screenLines = @buildPlaceholderScreenLinesForRows(0, lastRow) @@ -219,6 +229,9 @@ class TokenizedBuffer stop() position + # Public: Gets the row number of the last line. + # + # Returns a {Number}. getLastRow: -> @buffer.getLastRow() diff --git a/src/app/undo-manager.coffee b/src/app/undo-manager.coffee index fc5b73e00..d5f914ea8 100644 --- a/src/app/undo-manager.coffee +++ b/src/app/undo-manager.coffee @@ -1,7 +1,7 @@ _ = require 'underscore' +# Internal: The object in charge of managing redo and undo operations. module.exports = - class UndoManager undoHistory: null redoHistory: null @@ -79,4 +79,4 @@ class UndoManager batch.newSelectionRanges catch e @clear() - throw e + throw e \ No newline at end of file diff --git a/src/app/window.coffee b/src/app/window.coffee index 05e18f98f..508cf0dda 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -10,6 +10,10 @@ require 'space-pen-extensions' deserializers = {} deferredDeserializers = {} +### +# Internal # +### + # This method is called in any window needing a general environment, including specs window.setUpEnvironment = -> Config = require 'config' @@ -221,5 +225,6 @@ window.profile = (description, fn) -> console.profileEnd(description) value +# Public: Shows a dialog asking if the window was _really_ meant to be closed. confirmClose = -> rootView.confirmClose().done -> window.close()