mirror of
https://github.com/atom/atom.git
synced 2026-01-22 13:28:01 -05:00
This entailed quite a few changes to dial in scrollbars. The scrollbars are now adjusted in size to account for the width of the opposite scrollbar. If the width or height are not explicitly constrained and we are scrollable in the opposite direction that is constrained, we account for the width of the opposite scrollbar in assigning a natural height or width based on the content.
1908 lines
70 KiB
CoffeeScript
1908 lines
70 KiB
CoffeeScript
_ = require 'underscore-plus'
|
|
path = require 'path'
|
|
Serializable = require 'serializable'
|
|
Delegator = require 'delegato'
|
|
{deprecate} = require 'grim'
|
|
{Model} = require 'theorist'
|
|
{Point, Range} = require 'text-buffer'
|
|
LanguageMode = require './language-mode'
|
|
DisplayBuffer = require './display-buffer'
|
|
Cursor = require './cursor'
|
|
Selection = require './selection'
|
|
TextMateScopeSelector = require('first-mate').ScopeSelector
|
|
|
|
# Public: This class represents all essential editing state for a single
|
|
# {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
|
|
# If you're manipulating the state of an editor, use this class. If you're
|
|
# interested in the visual appearance of editors, use {EditorView} instead.
|
|
#
|
|
# A single {TextBuffer} can belong to multiple editors. For example, if the
|
|
# same file is open in two different panes, Atom creates a separate editor for
|
|
# each pane. If the buffer is manipulated the changes are reflected in both
|
|
# editors, but each maintains its own cursor position, folded lines, etc.
|
|
#
|
|
# ## Accessing Editor Instances
|
|
#
|
|
# The easiest way to get hold of `Editor` objects is by registering a callback
|
|
# with `::eachEditor` on the `atom.workspace` global. Your callback will then
|
|
# be called with all current editor instances and also when any editor is
|
|
# created in the future.
|
|
#
|
|
# ```coffeescript
|
|
# atom.workspace.eachEditor (editor) ->
|
|
# editor.insertText('Hello World')
|
|
# ```
|
|
#
|
|
# ## Buffer vs. Screen Coordinates
|
|
#
|
|
# Because editors support folds and soft-wrapping, the lines on screen don't
|
|
# always match the lines in the buffer. For example, a long line that soft wraps
|
|
# twice renders as three lines on screen, but only represents one line in the
|
|
# buffer. Similarly, if rows 5-10 are folded, then row 6 on screen corresponds
|
|
# to row 11 in the buffer.
|
|
#
|
|
# Your choice of coordinates systems will depend on what you're trying to
|
|
# achieve. For example, if you're writing a command that jumps the cursor up or
|
|
# down by 10 lines, you'll want to use screen coordinates because the user
|
|
# probably wants to skip lines *on screen*. However, if you're writing a package
|
|
# that jumps between method definitions, you'll want to work in buffer
|
|
# coordinates.
|
|
#
|
|
# **When in doubt, just default to buffer coordinates**, then experiment with
|
|
# soft wraps and folds to ensure your code interacts with them correctly.
|
|
#
|
|
# ## Common Tasks
|
|
#
|
|
# This is a subset of methods on this class. Refer to the complete summary for
|
|
# its full capabilities.
|
|
#
|
|
# ### Cursors
|
|
# - {::setCursorBufferPosition}
|
|
# - {::setCursorScreenPosition}
|
|
# - {::moveCursorUp}
|
|
# - {::moveCursorDown}
|
|
# - {::moveCursorLeft}
|
|
# - {::moveCursorRight}
|
|
# - {::moveCursorToBeginningOfWord}
|
|
# - {::moveCursorToEndOfWord}
|
|
# - {::moveCursorToPreviousWordBoundary}
|
|
# - {::moveCursorToNextWordBoundary}
|
|
# - {::moveCursorToBeginningOfNextWord}
|
|
# - {::moveCursorToBeginningOfLine}
|
|
# - {::moveCursorToEndOfLine}
|
|
# - {::moveCursorToFirstCharacterOfLine}
|
|
# - {::moveCursorToTop}
|
|
# - {::moveCursorToBottom}
|
|
#
|
|
# ### Selections
|
|
# - {::getSelectedBufferRange}
|
|
# - {::getSelectedBufferRanges}
|
|
# - {::setSelectedBufferRange}
|
|
# - {::setSelectedBufferRanges}
|
|
# - {::selectUp}
|
|
# - {::selectDown}
|
|
# - {::selectLeft}
|
|
# - {::selectRight}
|
|
# - {::selectToBeginningOfWord}
|
|
# - {::selectToEndOfWord}
|
|
# - {::selectToPreviousWordBoundary}
|
|
# - {::selectToNextWordBoundary}
|
|
# - {::selectWord}
|
|
# - {::selectToBeginningOfLine}
|
|
# - {::selectToEndOfLine}
|
|
# - {::selectToFirstCharacterOfLine}
|
|
# - {::selectToTop}
|
|
# - {::selectToBottom}
|
|
# - {::selectAll}
|
|
# - {::addSelectionForBufferRange}
|
|
# - {::addSelectionAbove}
|
|
# - {::addSelectionBelow}
|
|
# - {::splitSelectionsIntoLines}
|
|
#
|
|
# ### Manipulating Text
|
|
# - {::getText}
|
|
# - {::getSelectedText}
|
|
# - {::setText}
|
|
# - {::setTextInBufferRange}
|
|
# - {::insertText}
|
|
# - {::insertNewline}
|
|
# - {::insertNewlineAbove}
|
|
# - {::insertNewlineBelow}
|
|
# - {::backspace}
|
|
# - {::backspaceToBeginningOfWord}
|
|
# - {::backspaceToBeginningOfLine}
|
|
# - {::delete}
|
|
# - {::deleteToEndOfWord}
|
|
# - {::deleteLine}
|
|
# - {::cutSelectedText}
|
|
# - {::cutToEndOfLine}
|
|
# - {::copySelectedText}
|
|
# - {::pasteText}
|
|
#
|
|
# ### Undo, Redo, and Transactions
|
|
# - {::undo}
|
|
# - {::redo}
|
|
# - {::transact}
|
|
# - {::abortTransaction}
|
|
#
|
|
# ### Markers
|
|
# - {::markBufferRange}
|
|
# - {::markScreenRange}
|
|
# - {::getMarker}
|
|
# - {::findMarkers}
|
|
module.exports =
|
|
class Editor extends Model
|
|
Serializable.includeInto(this)
|
|
atom.deserializers.add(this)
|
|
Delegator.includeInto(this)
|
|
|
|
deserializing: false
|
|
callDisplayBufferCreatedHook: false
|
|
registerEditor: false
|
|
buffer: null
|
|
languageMode: null
|
|
cursors: null
|
|
selections: null
|
|
suppressSelectionMerging: false
|
|
|
|
@delegatesMethods 'suggestedIndentForBufferRow', 'autoIndentBufferRow', 'autoIndentBufferRows',
|
|
'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
|
|
toProperty: 'languageMode'
|
|
|
|
@delegatesProperties '$lineHeight', '$defaultCharWidth', '$height', '$width',
|
|
'$scrollTop', '$scrollLeft', 'manageScrollPosition', toProperty: 'displayBuffer'
|
|
|
|
constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrap, @displayBuffer, buffer, registerEditor, suppressCursorCreation}) ->
|
|
super
|
|
|
|
@cursors = []
|
|
@selections = []
|
|
|
|
@displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrap})
|
|
@buffer = @displayBuffer.buffer
|
|
@softTabs = @usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true
|
|
|
|
for marker in @findMarkers(@getSelectionMarkerAttributes())
|
|
marker.setAttributes(preserveFolds: true)
|
|
@addSelection(marker)
|
|
|
|
@subscribeToBuffer()
|
|
@subscribeToDisplayBuffer()
|
|
|
|
if @getCursors().length is 0 and not suppressCursorCreation
|
|
initialLine = Math.max(parseInt(initialLine) or 0, 0)
|
|
initialColumn = Math.max(parseInt(initialColumn) or 0, 0)
|
|
@addCursorAtBufferPosition([initialLine, initialColumn])
|
|
|
|
@languageMode = new LanguageMode(this)
|
|
|
|
@subscribe @$scrollTop, (scrollTop) => @emit 'scroll-top-changed', scrollTop
|
|
@subscribe @$scrollLeft, (scrollLeft) => @emit 'scroll-left-changed', scrollLeft
|
|
|
|
atom.workspace?.editorAdded(this) if registerEditor
|
|
|
|
serializeParams: ->
|
|
id: @id
|
|
softTabs: @softTabs
|
|
scrollTop: @scrollTop
|
|
scrollLeft: @scrollLeft
|
|
displayBuffer: @displayBuffer.serialize()
|
|
|
|
deserializeParams: (params) ->
|
|
params.displayBuffer = DisplayBuffer.deserialize(params.displayBuffer)
|
|
params.registerEditor = true
|
|
params
|
|
|
|
subscribeToBuffer: ->
|
|
@buffer.retain()
|
|
@subscribe @buffer, "path-changed", =>
|
|
unless atom.project.getPath()?
|
|
atom.project.setPath(path.dirname(@getPath()))
|
|
@emit "title-changed"
|
|
@emit "path-changed"
|
|
@subscribe @buffer, "contents-modified", => @emit "contents-modified"
|
|
@subscribe @buffer, "contents-conflicted", => @emit "contents-conflicted"
|
|
@subscribe @buffer, "modified-status-changed", => @emit "modified-status-changed"
|
|
@subscribe @buffer, "destroyed", => @destroy()
|
|
@preserveCursorPositionOnBufferReload()
|
|
|
|
subscribeToDisplayBuffer: ->
|
|
@subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
|
|
@subscribe @displayBuffer, "changed", (e) => @emit 'screen-lines-changed', e
|
|
@subscribe @displayBuffer, "markers-updated", => @mergeIntersectingSelections()
|
|
@subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
|
|
@subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...
|
|
|
|
getViewClass: ->
|
|
if atom.config.get('core.useReactEditor')
|
|
require './react-editor-view'
|
|
else
|
|
require './editor-view'
|
|
|
|
destroyed: ->
|
|
@unsubscribe()
|
|
selection.destroy() for selection in @getSelections()
|
|
@buffer.release()
|
|
@displayBuffer.destroy()
|
|
@languageMode.destroy()
|
|
|
|
# Create an {Editor} with its initial state based on this object
|
|
copy: ->
|
|
tabLength = @getTabLength()
|
|
displayBuffer = @displayBuffer.copy()
|
|
softTabs = @getSoftTabs()
|
|
newEditor = new Editor({@buffer, displayBuffer, tabLength, softTabs, suppressCursorCreation: true, registerEditor: true})
|
|
for marker in @findMarkers(editorId: @id)
|
|
marker.copy(editorId: newEditor.id, preserveFolds: true)
|
|
newEditor
|
|
|
|
# Public: Get the title the editor's title for display in other parts of the
|
|
# UI such as the tabs.
|
|
#
|
|
# If the editor's buffer is saved, its title is the file name. If it is
|
|
# unsaved, its title is "untitled".
|
|
#
|
|
# Returns a {String}.
|
|
getTitle: ->
|
|
if sessionPath = @getPath()
|
|
path.basename(sessionPath)
|
|
else
|
|
'untitled'
|
|
|
|
# Public: Get the editor's long title for display in other parts of the UI
|
|
# such as the window title.
|
|
#
|
|
# If the editor's buffer is saved, its long title is formatted as
|
|
# "<filename> - <directory>". If it is unsaved, its title is "untitled"
|
|
#
|
|
# Returns a {String}.
|
|
getLongTitle: ->
|
|
if sessionPath = @getPath()
|
|
fileName = path.basename(sessionPath)
|
|
directory = path.basename(path.dirname(sessionPath))
|
|
"#{fileName} - #{directory}"
|
|
else
|
|
'untitled'
|
|
|
|
# Controls visibility based on the given {Boolean}.
|
|
setVisible: (visible) -> @displayBuffer.setVisible(visible)
|
|
|
|
# Set the number of characters that can be displayed horizontally in the
|
|
# editor.
|
|
#
|
|
# editorWidthInChars - A {Number} representing the width of the {EditorView}
|
|
# in characters.
|
|
setEditorWidthInChars: (editorWidthInChars) ->
|
|
@displayBuffer.setEditorWidthInChars(editorWidthInChars)
|
|
|
|
# Public: Sets the column at which column will soft wrap
|
|
getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn()
|
|
|
|
# Public: Returns a {Boolean} indicating whether softTabs are enabled for this
|
|
# editor.
|
|
getSoftTabs: -> @softTabs
|
|
|
|
# Public: Enable or disable soft tabs for this editor.
|
|
#
|
|
# softTabs - A {Boolean}
|
|
setSoftTabs: (@softTabs) -> @softTabs
|
|
|
|
# Public: Toggle soft tabs for this editor
|
|
toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs())
|
|
|
|
# Public: Get whether soft wrap is enabled for this editor.
|
|
getSoftWrap: -> @displayBuffer.getSoftWrap()
|
|
|
|
# Public: Enable or disable soft wrap for this editor.
|
|
#
|
|
# softWrap - A {Boolean}
|
|
setSoftWrap: (softWrap) -> @displayBuffer.setSoftWrap(softWrap)
|
|
|
|
# Public: Toggle soft wrap for this editor
|
|
toggleSoftWrap: -> @setSoftWrap(not @getSoftWrap())
|
|
|
|
# Public: Get the text representing a single level of indent.
|
|
#
|
|
# If soft tabs are enabled, the text is composed of N spaces, where N is the
|
|
# tab length. Otherwise the text is a tab character (`\t`).
|
|
#
|
|
# Returns a {String}.
|
|
getTabText: -> @buildIndentString(1)
|
|
|
|
# Public: Get the on-screen length of tab characters.
|
|
#
|
|
# Returns a {Number}.
|
|
getTabLength: -> @displayBuffer.getTabLength()
|
|
|
|
# Public: Set the on-screen length of tab characters.
|
|
setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength)
|
|
|
|
# Public: Determine if the buffer uses hard or soft tabs.
|
|
#
|
|
# Returns `true` if the first non-comment line with leading whitespace starts
|
|
# with a space character. Returns `false` if it starts with a hard tab (`\t`).
|
|
#
|
|
# Returns a {Boolean},
|
|
usesSoftTabs: ->
|
|
for bufferRow in [0..@buffer.getLastRow()]
|
|
continue if @displayBuffer.tokenizedBuffer.lineForScreenRow(bufferRow).isComment()
|
|
if match = @buffer.lineForRow(bufferRow).match(/^\s/)
|
|
return match[0][0] != '\t'
|
|
undefined
|
|
|
|
# Public: Clip the given {Point} to a valid position in the buffer.
|
|
#
|
|
# If the given {Point} describes a position that is actually reachable by the
|
|
# cursor based on the current contents of the buffer, it is returned
|
|
# unchanged. If the {Point} does not describe a valid position, the closest
|
|
# valid position is returned instead.
|
|
#
|
|
# For example:
|
|
# * `[-1, -1]` is converted to `[0, 0]`.
|
|
# * If the line at row 2 is 10 long, `[2, Infinity]` is converted to
|
|
# `[2, 10]`.
|
|
#
|
|
# bufferPosition - The {Point} representing the position to clip.
|
|
#
|
|
# Returns a {Point}.
|
|
clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition)
|
|
|
|
# Public: Clip the start and end of the given range to valid positions in the
|
|
# buffer. See {::clipBufferPosition} for more information.
|
|
#
|
|
# range - The {Range} to clip.
|
|
#
|
|
# Returns a {Range}.
|
|
clipBufferRange: (range) -> @buffer.clipRange(range)
|
|
|
|
# Public: Get the indentation level of the given a buffer row.
|
|
#
|
|
# Returns how deeply the given row is indented based on the soft tabs and
|
|
# tab length settings of this editor. Note that if soft tabs are enabled and
|
|
# the tab length is 2, a row with 4 leading spaces would have an indentation
|
|
# level of 2.
|
|
#
|
|
# bufferRow - A {Number} indicating the buffer row.
|
|
#
|
|
# Returns a {Number}.
|
|
indentationForBufferRow: (bufferRow) ->
|
|
@indentLevelForLine(@lineForBufferRow(bufferRow))
|
|
|
|
# Public: Set the indentation level for the given buffer row.
|
|
#
|
|
# Inserts or removes hard tabs or spaces based on the soft tabs and tab length
|
|
# settings of this editor in order to bring it to the given indentation level.
|
|
# Note that if soft tabs are enabled and the tab length is 2, a row with 4
|
|
# leading spaces would have an indentation level of 2.
|
|
#
|
|
# bufferRow - A {Number} indicating the buffer row.
|
|
# newLevel - A {Number} indicating the new indentation level.
|
|
# options - An {Object} with the following keys:
|
|
# :preserveLeadingWhitespace - true to preserve any whitespace already at
|
|
# the beginning of the line (default: false).
|
|
setIndentationForBufferRow: (bufferRow, newLevel, {preserveLeadingWhitespace}={}) ->
|
|
if preserveLeadingWhitespace
|
|
endColumn = 0
|
|
else
|
|
endColumn = @lineForBufferRow(bufferRow).match(/^\s*/)[0].length
|
|
newIndentString = @buildIndentString(newLevel)
|
|
@buffer.setTextInRange([[bufferRow, 0], [bufferRow, endColumn]], newIndentString)
|
|
|
|
# Public: Get the indentation level of the given line of text.
|
|
#
|
|
# Returns how deeply the given line is indented based on the soft tabs and
|
|
# tab length settings of this editor. Note that if soft tabs are enabled and
|
|
# the tab length is 2, a row with 4 leading spaces would have an indentation
|
|
# level of 2.
|
|
#
|
|
# line - A {String} representing a line of text.
|
|
#
|
|
# Returns a {Number}.
|
|
indentLevelForLine: (line) ->
|
|
@displayBuffer.indentLevelForLine(line)
|
|
|
|
# Constructs the string used for tabs.
|
|
buildIndentString: (number) ->
|
|
if @getSoftTabs()
|
|
_.multiplyString(" ", Math.floor(number * @getTabLength()))
|
|
else
|
|
_.multiplyString("\t", Math.floor(number))
|
|
|
|
# Public: Saves the editor's text buffer.
|
|
#
|
|
# See {TextBuffer::save} for more details.
|
|
save: -> @buffer.save()
|
|
|
|
# Public: Saves the editor's text buffer as the given path.
|
|
#
|
|
# See {TextBuffer::saveAs} for more details.
|
|
#
|
|
# filePath - A {String} path.
|
|
saveAs: (filePath) -> @buffer.saveAs(filePath)
|
|
|
|
checkoutHead: ->
|
|
if filePath = @getPath()
|
|
atom.project.getRepo()?.checkoutHead(filePath)
|
|
|
|
# Copies the current file path to the native clipboard.
|
|
copyPathToClipboard: ->
|
|
if filePath = @getPath()
|
|
atom.clipboard.write(filePath)
|
|
|
|
# Public: Returns the {String} path of this editor's text buffer.
|
|
getPath: -> @buffer.getPath()
|
|
|
|
# Public: Returns a {String} representing the entire contents of the editor.
|
|
getText: -> @buffer.getText()
|
|
|
|
# Public: Replaces the entire contents of the buffer with the given {String}.
|
|
setText: (text) -> @buffer.setText(text)
|
|
|
|
# Get the text in the given {Range}.
|
|
#
|
|
# Returns a {String}.
|
|
getTextInRange: (range) -> @buffer.getTextInRange(range)
|
|
|
|
# Public: Returns a {Number} representing the number of lines in the editor.
|
|
getLineCount: -> @buffer.getLineCount()
|
|
|
|
# Retrieves the current {TextBuffer}.
|
|
getBuffer: -> @buffer
|
|
|
|
# Public: Retrieves the current buffer's URI.
|
|
getUri: -> @buffer.getUri()
|
|
|
|
# {Delegates to: TextBuffer.isRowBlank}
|
|
isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)
|
|
|
|
# Public: Determine if the given row is entirely a comment
|
|
isBufferRowCommented: (bufferRow) ->
|
|
if match = @lineForBufferRow(bufferRow).match(/\S/)
|
|
scopes = @tokenForBufferPosition([bufferRow, match.index]).scopes
|
|
new TextMateScopeSelector('comment.*').matches(scopes)
|
|
|
|
# {Delegates to: TextBuffer.nextNonBlankRow}
|
|
nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow)
|
|
|
|
# {Delegates to: TextBuffer.getEndPosition}
|
|
getEofBufferPosition: -> @buffer.getEndPosition()
|
|
|
|
# Public: Returns a {Number} representing the last zero-indexed buffer row
|
|
# number of the editor.
|
|
getLastBufferRow: -> @buffer.getLastRow()
|
|
|
|
# Returns the range for the given buffer row.
|
|
#
|
|
# row - A row {Number}.
|
|
# options - An options hash with an `includeNewline` key.
|
|
#
|
|
# Returns a {Range}.
|
|
bufferRangeForBufferRow: (row, {includeNewline}={}) -> @buffer.rangeForRow(row, includeNewline)
|
|
|
|
# Public: Returns a {String} representing the contents of the line at the
|
|
# given buffer row.
|
|
#
|
|
# row - A {Number} representing a zero-indexed buffer row.
|
|
lineForBufferRow: (row) -> @buffer.lineForRow(row)
|
|
|
|
# Public: Returns a {Number} representing the line length for the given
|
|
# buffer row, exclusive of its line-ending character(s).
|
|
#
|
|
# row - A {Number} indicating the buffer row.
|
|
lineLengthForBufferRow: (row) -> @buffer.lineLengthForRow(row)
|
|
|
|
# {Delegates to: TextBuffer.scan}
|
|
scan: (args...) -> @buffer.scan(args...)
|
|
|
|
# {Delegates to: TextBuffer.scanInRange}
|
|
scanInBufferRange: (args...) -> @buffer.scanInRange(args...)
|
|
|
|
# {Delegates to: TextBuffer.backwardsScanInRange}
|
|
backwardsScanInBufferRange: (args...) -> @buffer.backwardsScanInRange(args...)
|
|
|
|
# {Delegates to: TextBuffer.isModified}
|
|
isModified: -> @buffer.isModified()
|
|
|
|
# Public: Determine whether the user should be prompted to save before closing
|
|
# this editor.
|
|
shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors()
|
|
|
|
# Public: Convert a position in buffer-coordinates to screen-coordinates.
|
|
#
|
|
# The position is clipped via {::clipBufferPosition} prior to the conversion.
|
|
# The position is also clipped via {::clipScreenPosition} following the
|
|
# conversion, which only makes a difference when `options` are supplied.
|
|
#
|
|
# bufferPosition - A {Point} or {Array} of [row, column].
|
|
# options - An options hash for {::clipScreenPosition}.
|
|
#
|
|
# Returns a {Point}.
|
|
screenPositionForBufferPosition: (bufferPosition, options) -> @displayBuffer.screenPositionForBufferPosition(bufferPosition, options)
|
|
|
|
# Public: Convert a position in screen-coordinates to buffer-coordinates.
|
|
#
|
|
# The position is clipped via {::clipScreenPosition} prior to the conversion.
|
|
#
|
|
# bufferPosition - A {Point} or {Array} of [row, column].
|
|
# options - An options hash for {::clipScreenPosition}.
|
|
#
|
|
# Returns a {Point}.
|
|
bufferPositionForScreenPosition: (screenPosition, options) -> @displayBuffer.bufferPositionForScreenPosition(screenPosition, options)
|
|
|
|
# Public: Convert a range in buffer-coordinates to screen-coordinates.
|
|
#
|
|
# Returns a {Range}.
|
|
screenRangeForBufferRange: (bufferRange) -> @displayBuffer.screenRangeForBufferRange(bufferRange)
|
|
|
|
# Public: Convert a range in screen-coordinates to buffer-coordinates.
|
|
#
|
|
# Returns a {Range}.
|
|
bufferRangeForScreenRange: (screenRange) -> @displayBuffer.bufferRangeForScreenRange(screenRange)
|
|
|
|
# Public: Clip the given {Point} to a valid position on screen.
|
|
#
|
|
# If the given {Point} describes a position that is actually reachable by the
|
|
# cursor based on the current contents of the screen, it is returned
|
|
# unchanged. If the {Point} does not describe a valid position, the closest
|
|
# valid position is returned instead.
|
|
#
|
|
# For example:
|
|
# * `[-1, -1]` is converted to `[0, 0]`.
|
|
# * If the line at screen row 2 is 10 long, `[2, Infinity]` is converted to
|
|
# `[2, 10]`.
|
|
#
|
|
# bufferPosition - The {Point} representing the position to clip.
|
|
#
|
|
# Returns a {Point}.
|
|
clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options)
|
|
|
|
# {Delegates to: DisplayBuffer.lineForRow}
|
|
lineForScreenRow: (row) -> @displayBuffer.lineForRow(row)
|
|
|
|
# {Delegates to: DisplayBuffer.linesForRows}
|
|
linesForScreenRows: (start, end) -> @displayBuffer.linesForRows(start, end)
|
|
|
|
# {Delegates to: DisplayBuffer.getLineCount}
|
|
getScreenLineCount: -> @displayBuffer.getLineCount()
|
|
|
|
# {Delegates to: DisplayBuffer.getMaxLineLength}
|
|
getMaxScreenLineLength: -> @displayBuffer.getMaxLineLength()
|
|
|
|
# {Delegates to: DisplayBuffer.getLastRow}
|
|
getLastScreenRow: -> @displayBuffer.getLastRow()
|
|
|
|
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
|
|
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
|
|
|
|
bufferRowForScreenRow: (row) -> @displayBuffer.bufferRowForScreenRow(row)
|
|
|
|
# Public: Get the syntactic scopes for the given position in buffer
|
|
# coordinates.
|
|
#
|
|
# For example, if called with a position inside the parameter list of an
|
|
# anonymous CoffeeScript function, the method returns the following array:
|
|
# `["source.coffee", "meta.inline.function.coffee", "variable.parameter.function.coffee"]`
|
|
#
|
|
# bufferPosition - A {Point} or {Array} of [row, column].
|
|
#
|
|
# Returns an {Array} of {String}s.
|
|
scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)
|
|
|
|
# Public: Get the range in buffer coordinates of all tokens surrounding the
|
|
# cursor that match the given scope selector.
|
|
#
|
|
# For example, if you wanted to find the string surrounding the cursor, you
|
|
# could call `editor.bufferRangeForScopeAtCursor(".string.quoted")`.
|
|
#
|
|
# Returns a {Range}.
|
|
bufferRangeForScopeAtCursor: (selector) ->
|
|
@displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition())
|
|
|
|
# {Delegates to: DisplayBuffer.tokenForBufferPosition}
|
|
tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
|
|
|
|
# Public: Get the syntactic scopes for the most recently added cursor's
|
|
# position. See {::scopesForBufferPosition} for more information.
|
|
#
|
|
# Returns an {Array} of {String}s.
|
|
getCursorScopes: -> @getCursor().getScopes()
|
|
|
|
logCursorScope: ->
|
|
console.log @getCursorScopes()
|
|
|
|
# Public: For each selection, replace the selected text with the given text.
|
|
#
|
|
# text - A {String} representing the text to insert.
|
|
# options - See {Selection::insertText}.
|
|
insertText: (text, options={}) ->
|
|
options.autoIndentNewline ?= @shouldAutoIndent()
|
|
options.autoDecreaseIndent ?= @shouldAutoIndent()
|
|
@mutateSelectedText (selection) -> selection.insertText(text, options)
|
|
|
|
# Public: For each selection, replace the selected text with a newline.
|
|
insertNewline: ->
|
|
@insertText('\n')
|
|
|
|
# Public: For each cursor, insert a newline at beginning the following line.
|
|
insertNewlineBelow: ->
|
|
@transact =>
|
|
@moveCursorToEndOfLine()
|
|
@insertNewline()
|
|
|
|
# Public: For each cursor, insert a newline at the end of the preceding line.
|
|
insertNewlineAbove: ->
|
|
@transact =>
|
|
bufferRow = @getCursorBufferPosition().row
|
|
indentLevel = @indentationForBufferRow(bufferRow)
|
|
onFirstLine = bufferRow is 0
|
|
|
|
@moveCursorToBeginningOfLine()
|
|
@moveCursorLeft()
|
|
@insertNewline()
|
|
|
|
if @shouldAutoIndent() and @indentationForBufferRow(bufferRow) < indentLevel
|
|
@setIndentationForBufferRow(bufferRow, indentLevel)
|
|
|
|
if onFirstLine
|
|
@moveCursorUp()
|
|
@moveCursorToEndOfLine()
|
|
|
|
# Indent all lines intersecting selections. See {Selection::indent} for more
|
|
# information.
|
|
indent: (options={})->
|
|
options.autoIndent ?= @shouldAutoIndent()
|
|
@mutateSelectedText (selection) -> selection.indent(options)
|
|
|
|
# Public: For each selection, if the selection is empty, delete the character
|
|
# preceding the cursor. Otherwise delete the selected text.
|
|
backspace: ->
|
|
@mutateSelectedText (selection) -> selection.backspace()
|
|
|
|
# Public: For each selection, if the selection is empty, delete all characters
|
|
# of the containing word that precede the cursor. Otherwise delete the
|
|
# selected text.
|
|
backspaceToBeginningOfWord: ->
|
|
@mutateSelectedText (selection) -> selection.backspaceToBeginningOfWord()
|
|
|
|
# Public: For each selection, if the selection is empty, delete all characters
|
|
# of the containing line that precede the cursor. Otherwise delete the
|
|
# selected text.
|
|
backspaceToBeginningOfLine: ->
|
|
@mutateSelectedText (selection) -> selection.backspaceToBeginningOfLine()
|
|
|
|
# Public: For each selection, if the selection is empty, delete the character
|
|
# preceding the cursor. Otherwise delete the selected text.
|
|
delete: ->
|
|
@mutateSelectedText (selection) -> selection.delete()
|
|
|
|
# Public: For each selection, if the selection is empty, delete all characters
|
|
# of the containing word following the cursor. Otherwise delete the selected
|
|
# text.
|
|
deleteToEndOfWord: ->
|
|
@mutateSelectedText (selection) -> selection.deleteToEndOfWord()
|
|
|
|
# Public: Delete all lines intersecting selections.
|
|
deleteLine: ->
|
|
@mutateSelectedText (selection) -> selection.deleteLine()
|
|
|
|
# Public: Indent rows intersecting selections by one level.
|
|
indentSelectedRows: ->
|
|
@mutateSelectedText (selection) -> selection.indentSelectedRows()
|
|
|
|
# Public: Outdent rows intersecting selections by one level.
|
|
outdentSelectedRows: ->
|
|
@mutateSelectedText (selection) -> selection.outdentSelectedRows()
|
|
|
|
# Public: Toggle line comments for rows intersecting selections.
|
|
#
|
|
# If the current grammar doesn't support comments, does nothing.
|
|
#
|
|
# Returns an {Array} of the commented {Range}s.
|
|
toggleLineCommentsInSelection: ->
|
|
@mutateSelectedText (selection) -> selection.toggleLineComments()
|
|
|
|
# Public: Indent rows intersecting selections based on the grammar's suggested
|
|
# indent level.
|
|
autoIndentSelectedRows: ->
|
|
@mutateSelectedText (selection) -> selection.autoIndentSelectedRows()
|
|
|
|
# If soft tabs are enabled, convert all hard tabs to soft tabs in the given
|
|
# {Range}.
|
|
normalizeTabsInBufferRange: (bufferRange) ->
|
|
return unless @getSoftTabs()
|
|
@scanInBufferRange /\t/g, bufferRange, ({replace}) => replace(@getTabText())
|
|
|
|
# Public: For each selection, if the selection is empty, cut all characters
|
|
# of the containing line following the cursor. Otherwise cut the selected
|
|
# text.
|
|
cutToEndOfLine: ->
|
|
maintainClipboard = false
|
|
@mutateSelectedText (selection) ->
|
|
selection.cutToEndOfLine(maintainClipboard)
|
|
maintainClipboard = true
|
|
|
|
# Public: For each selection, cut the selected text.
|
|
cutSelectedText: ->
|
|
maintainClipboard = false
|
|
@mutateSelectedText (selection) ->
|
|
selection.cut(maintainClipboard)
|
|
maintainClipboard = true
|
|
|
|
# Public: For each selection, copy the selected text.
|
|
copySelectedText: ->
|
|
maintainClipboard = false
|
|
for selection in @getSelections()
|
|
selection.copy(maintainClipboard)
|
|
maintainClipboard = true
|
|
|
|
# Public: For each selection, replace the selected text with the contents of
|
|
# the clipboard.
|
|
#
|
|
# options - See {Selection::insertText}.
|
|
pasteText: (options={}) ->
|
|
{text, metadata} = atom.clipboard.readWithMetadata()
|
|
|
|
containsNewlines = text.indexOf('\n') isnt -1
|
|
|
|
if atom.config.get('editor.normalizeIndentOnPaste') and metadata
|
|
if !@getCursor().hasPrecedingCharactersOnLine() or containsNewlines
|
|
options.indentBasis ?= metadata.indentBasis
|
|
|
|
@insertText(text, options)
|
|
|
|
# Public: Undo the last change.
|
|
undo: ->
|
|
@getCursor().needsAutoscroll = true
|
|
@buffer.undo(this)
|
|
|
|
# Public: Redo the last change.
|
|
redo: ->
|
|
@getCursor().needsAutoscroll = true
|
|
@buffer.redo(this)
|
|
|
|
# Public: Fold the most recent cursor's row based on its indentation level.
|
|
#
|
|
# The fold will extend from the nearest preceding line with a lower
|
|
# indentation level up to the nearest following row with a lower indentation
|
|
# level.
|
|
foldCurrentRow: ->
|
|
bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row
|
|
@foldBufferRow(bufferRow)
|
|
|
|
# Public: Unfold the most recent cursor's row by one level.
|
|
unfoldCurrentRow: ->
|
|
bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row
|
|
@unfoldBufferRow(bufferRow)
|
|
|
|
# Public: For each selection, fold the rows it intersects.
|
|
foldSelectedLines: ->
|
|
selection.fold() for selection in @getSelections()
|
|
|
|
# Public: Fold all foldable lines.
|
|
foldAll: ->
|
|
@languageMode.foldAll()
|
|
|
|
# Public: Unfold all existing folds.
|
|
unfoldAll: ->
|
|
@languageMode.unfoldAll()
|
|
|
|
# Public: Fold all foldable lines at the given indent level.
|
|
#
|
|
# level - A {Number}.
|
|
foldAllAtIndentLevel: (level) ->
|
|
@languageMode.foldAllAtIndentLevel(level)
|
|
|
|
# Public: Fold the given row in buffer coordinates based on its indentation
|
|
# level.
|
|
#
|
|
# If the given row is foldable, the fold will begin there. Otherwise, it will
|
|
# begin at the first foldable row preceding the given row.
|
|
#
|
|
# bufferRow - A {Number}.
|
|
foldBufferRow: (bufferRow) ->
|
|
@languageMode.foldBufferRow(bufferRow)
|
|
|
|
# Public: Unfold all folds containing the given row in buffer coordinates.
|
|
#
|
|
# bufferRow - A {Number}
|
|
unfoldBufferRow: (bufferRow) ->
|
|
@displayBuffer.unfoldBufferRow(bufferRow)
|
|
|
|
# Public: Determine whether the given row in buffer coordinates is foldable.
|
|
#
|
|
# A *foldable* row is a row that *starts* a row range that can be folded.
|
|
#
|
|
# bufferRow - A {Number}
|
|
#
|
|
# Returns a {Boolean}.
|
|
isFoldableAtBufferRow: (bufferRow) ->
|
|
@languageMode.isFoldableAtBufferRow(bufferRow)
|
|
|
|
# TODO: Rename to foldRowRange?
|
|
createFold: (startRow, endRow) ->
|
|
@displayBuffer.createFold(startRow, endRow)
|
|
|
|
# {Delegates to: DisplayBuffer.destroyFoldWithId}
|
|
destroyFoldWithId: (id) ->
|
|
@displayBuffer.destroyFoldWithId(id)
|
|
|
|
# Remove any {Fold}s found that intersect the given buffer row.
|
|
destroyFoldsIntersectingBufferRange: (bufferRange) ->
|
|
for row in [bufferRange.start.row..bufferRange.end.row]
|
|
@unfoldBufferRow(row)
|
|
|
|
# Public: Fold the given buffer row if it isn't currently folded, and unfold
|
|
# it otherwise.
|
|
toggleFoldAtBufferRow: (bufferRow) ->
|
|
if @isFoldedAtBufferRow(bufferRow)
|
|
@unfoldBufferRow(bufferRow)
|
|
else
|
|
@foldBufferRow(bufferRow)
|
|
|
|
# Public: Determine whether the most recently added cursor's row is folded.
|
|
#
|
|
# Returns a {Boolean}.
|
|
isFoldedAtCursorRow: ->
|
|
@isFoldedAtScreenRow(@getCursorScreenRow())
|
|
|
|
# Public: Determine whether the given row in buffer coordinates is folded.
|
|
#
|
|
# bufferRow - A {Number}
|
|
#
|
|
# Returns a {Boolean}.
|
|
isFoldedAtBufferRow: (bufferRow) ->
|
|
@displayBuffer.isFoldedAtBufferRow(bufferRow)
|
|
|
|
# Public: Determine whether the given row in screen coordinates is folded.
|
|
#
|
|
# screenRow - A {Number}
|
|
#
|
|
# Returns a {Boolean}.
|
|
isFoldedAtScreenRow: (screenRow) ->
|
|
@displayBuffer.isFoldedAtScreenRow(screenRow)
|
|
|
|
# {Delegates to: DisplayBuffer.largestFoldContainingBufferRow}
|
|
largestFoldContainingBufferRow: (bufferRow) ->
|
|
@displayBuffer.largestFoldContainingBufferRow(bufferRow)
|
|
|
|
# {Delegates to: DisplayBuffer.largestFoldStartingAtScreenRow}
|
|
largestFoldStartingAtScreenRow: (screenRow) ->
|
|
@displayBuffer.largestFoldStartingAtScreenRow(screenRow)
|
|
|
|
# {Delegates to: DisplayBuffer.outermostFoldsForBufferRowRange}
|
|
outermostFoldsInBufferRowRange: (startRow, endRow) ->
|
|
@displayBuffer.outermostFoldsInBufferRowRange(startRow, endRow)
|
|
|
|
# Move lines intersection the most recent selection up by one row in screen
|
|
# coordinates.
|
|
moveLineUp: ->
|
|
selection = @getSelectedBufferRange()
|
|
return if selection.start.row is 0
|
|
lastRow = @buffer.getLastRow()
|
|
return if selection.isEmpty() and selection.start.row is lastRow and @buffer.getLastLine() is ''
|
|
|
|
@transact =>
|
|
foldedRows = []
|
|
rows = [selection.start.row..selection.end.row]
|
|
if selection.start.row isnt selection.end.row and selection.end.column is 0
|
|
rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
|
|
|
|
# Move line around the fold that is directly above the selection
|
|
precedingScreenRow = @screenPositionForBufferPosition([selection.start.row]).translate([-1])
|
|
precedingBufferRow = @bufferPositionForScreenPosition(precedingScreenRow).row
|
|
if fold = @largestFoldContainingBufferRow(precedingBufferRow)
|
|
insertDelta = fold.getBufferRange().getRowCount()
|
|
else
|
|
insertDelta = 1
|
|
|
|
for row in rows
|
|
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
|
|
bufferRange = fold.getBufferRange()
|
|
startRow = bufferRange.start.row
|
|
endRow = bufferRange.end.row
|
|
foldedRows.push(startRow - insertDelta)
|
|
else
|
|
startRow = row
|
|
endRow = row
|
|
|
|
insertPosition = Point.fromObject([startRow - insertDelta])
|
|
endPosition = Point.min([endRow + 1], @buffer.getEndPosition())
|
|
lines = @buffer.getTextInRange([[startRow], endPosition])
|
|
if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
|
|
lines = "#{lines}\n"
|
|
|
|
@buffer.deleteRows(startRow, endRow)
|
|
|
|
# Make sure the inserted text doesn't go into an existing fold
|
|
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
|
|
@unfoldBufferRow(insertPosition.row)
|
|
foldedRows.push(insertPosition.row + endRow - startRow + fold.getBufferRange().getRowCount())
|
|
|
|
@buffer.insert(insertPosition, lines)
|
|
|
|
# Restore folds that existed before the lines were moved
|
|
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
|
|
@foldBufferRow(foldedRow)
|
|
|
|
@setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true, autoscroll: true)
|
|
|
|
# Move lines intersecting the most recent selection down by one row in screen
|
|
# coordinates.
|
|
moveLineDown: ->
|
|
selection = @getSelectedBufferRange()
|
|
lastRow = @buffer.getLastRow()
|
|
return if selection.end.row is lastRow
|
|
return if selection.end.row is lastRow - 1 and @buffer.getLastLine() is ''
|
|
|
|
@transact =>
|
|
foldedRows = []
|
|
rows = [selection.end.row..selection.start.row]
|
|
if selection.start.row isnt selection.end.row and selection.end.column is 0
|
|
rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
|
|
|
|
# Move line around the fold that is directly below the selection
|
|
followingScreenRow = @screenPositionForBufferPosition([selection.end.row]).translate([1])
|
|
followingBufferRow = @bufferPositionForScreenPosition(followingScreenRow).row
|
|
if fold = @largestFoldContainingBufferRow(followingBufferRow)
|
|
insertDelta = fold.getBufferRange().getRowCount()
|
|
else
|
|
insertDelta = 1
|
|
|
|
for row in rows
|
|
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
|
|
bufferRange = fold.getBufferRange()
|
|
startRow = bufferRange.start.row
|
|
endRow = bufferRange.end.row
|
|
foldedRows.push(endRow + insertDelta)
|
|
else
|
|
startRow = row
|
|
endRow = row
|
|
|
|
if endRow + 1 is lastRow
|
|
endPosition = [endRow, @buffer.lineLengthForRow(endRow)]
|
|
else
|
|
endPosition = [endRow + 1]
|
|
lines = @buffer.getTextInRange([[startRow], endPosition])
|
|
@buffer.deleteRows(startRow, endRow)
|
|
|
|
insertPosition = Point.min([startRow + insertDelta], @buffer.getEndPosition())
|
|
if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
|
|
lines = "\n#{lines}"
|
|
|
|
# Make sure the inserted text doesn't go into an existing fold
|
|
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
|
|
@unfoldBufferRow(insertPosition.row)
|
|
foldedRows.push(insertPosition.row + fold.getBufferRange().getRowCount())
|
|
|
|
@buffer.insert(insertPosition, lines)
|
|
|
|
# Restore folds that existed before the lines were moved
|
|
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
|
|
@foldBufferRow(foldedRow)
|
|
|
|
@setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true, autoscroll: true)
|
|
|
|
# Duplicate the most recent cursor's current line.
|
|
duplicateLines: ->
|
|
@transact =>
|
|
for selection in @getSelectionsOrderedByBufferPosition().reverse()
|
|
selectedBufferRange = selection.getBufferRange()
|
|
if selection.isEmpty()
|
|
{start} = selection.getScreenRange()
|
|
selection.selectToScreenPosition([start.row + 1, 0])
|
|
|
|
[startRow, endRow] = selection.getBufferRowRange()
|
|
endRow++
|
|
|
|
foldedRowRanges =
|
|
@outermostFoldsInBufferRowRange(startRow, endRow)
|
|
.map (fold) -> fold.getBufferRowRange()
|
|
|
|
rangeToDuplicate = [[startRow, 0], [endRow, 0]]
|
|
textToDuplicate = @getTextInBufferRange(rangeToDuplicate)
|
|
textToDuplicate = '\n' + textToDuplicate if endRow > @getLastBufferRow()
|
|
@buffer.insert([endRow, 0], textToDuplicate)
|
|
|
|
delta = endRow - startRow
|
|
selection.setBufferRange(selectedBufferRange.translate([delta, 0]))
|
|
for [foldStartRow, foldEndRow] in foldedRowRanges
|
|
@createFold(foldStartRow + delta, foldEndRow + delta)
|
|
|
|
# Deprecated: Use {::duplicateLines} instead.
|
|
duplicateLine: ->
|
|
deprecate("Use Editor::duplicateLines() instead")
|
|
@duplicateLines()
|
|
|
|
# Public: Mutate the text of all the selections in a single transaction.
|
|
#
|
|
# All the changes made inside the given {Function} can be reverted with a
|
|
# single call to {::undo}.
|
|
#
|
|
# fn - A {Function} that will be called with each {Selection}.
|
|
mutateSelectedText: (fn) ->
|
|
@transact => fn(selection) for selection in @getSelections()
|
|
|
|
replaceSelectedText: (options={}, fn) ->
|
|
{selectWordIfEmpty} = options
|
|
@mutateSelectedText (selection) ->
|
|
range = selection.getBufferRange()
|
|
if selectWordIfEmpty and selection.isEmpty()
|
|
selection.selectWord()
|
|
text = selection.getText()
|
|
selection.deleteSelectedText()
|
|
selection.insertText(fn(text))
|
|
selection.setBufferRange(range)
|
|
|
|
# Public: Get the {DisplayBufferMarker} for the given marker id.
|
|
getMarker: (id) ->
|
|
@displayBuffer.getMarker(id)
|
|
|
|
# Public: Get all {DisplayBufferMarker}s.
|
|
getMarkers: ->
|
|
@displayBuffer.getMarkers()
|
|
|
|
# Public: Find all {DisplayBufferMarker}s that match the given properties.
|
|
#
|
|
# This method finds markers based on the given properties. Markers can be
|
|
# associated with custom properties that will be compared with basic equality.
|
|
# In addition, there are several special properties that will be compared
|
|
# with the range of the markers rather than their properties.
|
|
#
|
|
# properties - An {Object} containing properties that each returned marker
|
|
# must satisfy. Markers can be associated with custom properties, which are
|
|
# compared with basic equality. In addition, several reserved properties
|
|
# can be used to filter markers based on their current range:
|
|
# :startBufferRow - Only include markers starting at this row in buffer
|
|
# coordinates.
|
|
# :endBufferRow - Only include markers ending at this row in buffer
|
|
# coordinates.
|
|
# :containsBufferRange - Only include markers containing this {Range} or
|
|
# in range-compatible {Array} in buffer coordinates.
|
|
# :containsBufferPosition - Only include markers containing this {Point}
|
|
# or {Array} of `[row, column]` in buffer coordinates.
|
|
findMarkers: (properties) ->
|
|
@displayBuffer.findMarkers(properties)
|
|
|
|
# Public: Mark the given range in screen coordinates.
|
|
#
|
|
# range - A {Range} or range-compatible {Array}.
|
|
# options - See {TextBuffer::markRange}.
|
|
#
|
|
# Returns a {DisplayBufferMarker}.
|
|
markScreenRange: (args...) ->
|
|
@displayBuffer.markScreenRange(args...)
|
|
|
|
# Public: Mark the given range in buffer coordinates.
|
|
#
|
|
# range - A {Range} or range-compatible {Array}.
|
|
# options - See {TextBuffer::markRange}.
|
|
#
|
|
# Returns a {DisplayBufferMarker}.
|
|
markBufferRange: (args...) ->
|
|
@displayBuffer.markBufferRange(args...)
|
|
|
|
# Public: Mark the given position in screen coordinates.
|
|
#
|
|
# position - A {Point} or {Array} of `[row, column]`.
|
|
# options - See {TextBuffer::markRange}.
|
|
#
|
|
# Returns a {DisplayBufferMarker}.
|
|
markScreenPosition: (args...) ->
|
|
@displayBuffer.markScreenPosition(args...)
|
|
|
|
# Public: Mark the given position in buffer coordinates.
|
|
#
|
|
# position - A {Point} or {Array} of `[row, column]`.
|
|
# options - See {TextBuffer::markRange}.
|
|
#
|
|
# Returns a {DisplayBufferMarker}.
|
|
markBufferPosition: (args...) ->
|
|
@displayBuffer.markBufferPosition(args...)
|
|
|
|
# {Delegates to: DisplayBuffer.destroyMarker}
|
|
destroyMarker: (args...) ->
|
|
@displayBuffer.destroyMarker(args...)
|
|
|
|
# Public: Get the number of markers in this editor's buffer.
|
|
#
|
|
# Returns a {Number}.
|
|
getMarkerCount: ->
|
|
@buffer.getMarkerCount()
|
|
|
|
# Public: Determine if there are multiple cursors.
|
|
hasMultipleCursors: ->
|
|
@getCursors().length > 1
|
|
|
|
# Public: Get an Array of all {Cursor}s.
|
|
getCursors: -> new Array(@cursors...)
|
|
|
|
# Public: Get the most recently added {Cursor}.
|
|
getCursor: ->
|
|
_.last(@cursors)
|
|
|
|
# Public: Add a cursor at the position in screen coordinates.
|
|
#
|
|
# Returns a {Cursor}.
|
|
addCursorAtScreenPosition: (screenPosition) ->
|
|
@markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
|
|
@getLastSelection().cursor
|
|
|
|
# Public: Add a cursor at the given position in buffer coordinates.
|
|
#
|
|
# Returns a {Cursor}.
|
|
addCursorAtBufferPosition: (bufferPosition) ->
|
|
@markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
|
|
@getLastSelection().cursor
|
|
|
|
# Add a cursor based on the given {DisplayBufferMarker}.
|
|
addCursor: (marker) ->
|
|
cursor = new Cursor(editor: this, marker: marker)
|
|
@cursors.push(cursor)
|
|
@emit 'cursor-added', cursor
|
|
cursor
|
|
|
|
# Remove the given cursor from this editor.
|
|
removeCursor: (cursor) ->
|
|
_.remove(@cursors, cursor)
|
|
|
|
# Add a {Selection} based on the given {DisplayBufferMarker}.
|
|
#
|
|
# marker - The {DisplayBufferMarker} to highlight
|
|
# options - An {Object} that pertains to the {Selection} constructor.
|
|
#
|
|
# Returns the new {Selection}.
|
|
addSelection: (marker, options={}) ->
|
|
unless marker.getAttributes().preserveFolds
|
|
@destroyFoldsIntersectingBufferRange(marker.getBufferRange())
|
|
cursor = @addCursor(marker)
|
|
selection = new Selection(_.extend({editor: this, marker, cursor}, options))
|
|
@selections.push(selection)
|
|
selectionBufferRange = selection.getBufferRange()
|
|
@mergeIntersectingSelections()
|
|
if selection.destroyed
|
|
for selection in @getSelections()
|
|
if selection.intersectsBufferRange(selectionBufferRange)
|
|
return selection
|
|
else
|
|
@emit 'selection-added', selection
|
|
selection
|
|
|
|
# Public: Add a selection for the given range in buffer coordinates.
|
|
#
|
|
# bufferRange - A {Range}
|
|
# options - An options {Object}:
|
|
# :reversed - A {Boolean} indicating whether to create the selection in a
|
|
# reversed orientation.
|
|
#
|
|
# Returns the added {Selection}.
|
|
addSelectionForBufferRange: (bufferRange, options={}) ->
|
|
@markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
|
@getLastSelection()
|
|
|
|
# Public: Set the selected range in buffer coordinates. If there are multiple
|
|
# selections, they are reduced to a single selection with the given range.
|
|
#
|
|
# bufferRange - A {Range} or range-compatible {Array}.
|
|
# options - An options {Object}:
|
|
# :reversed - A {Boolean} indicating whether to create the selection in a
|
|
# reversed orientation.
|
|
setSelectedBufferRange: (bufferRange, options) ->
|
|
@setSelectedBufferRanges([bufferRange], options)
|
|
|
|
# Public: Set the selected range in screen coordinates. If there are multiple
|
|
# selections, they are reduced to a single selection with the given range.
|
|
#
|
|
# screenRange - A {Range} or range-compatible {Array}.
|
|
# options - An options {Object}:
|
|
# :reversed - A {Boolean} indicating whether to create the selection in a
|
|
# reversed orientation.
|
|
setSelectedScreenRange: (screenRange, options) ->
|
|
@setSelectedBufferRange(@bufferRangeForScreenRange(screenRange, options), options)
|
|
|
|
# Public: Set the selected ranges in buffer coordinates. If there are multiple
|
|
# selections, they are replaced by new selections with the given ranges.
|
|
#
|
|
# bufferRanges - An {Array} of {Range}s or range-compatible {Array}s.
|
|
# options - An options {Object}:
|
|
# :reversed - A {Boolean} indicating whether to create the selection in a
|
|
# reversed orientation.
|
|
setSelectedBufferRanges: (bufferRanges, options={}) ->
|
|
throw new Error("Passed an empty array to setSelectedBufferRanges") unless bufferRanges.length
|
|
|
|
selections = @getSelections()
|
|
selection.destroy() for selection in selections[bufferRanges.length...]
|
|
|
|
@mergeIntersectingSelections options, =>
|
|
for bufferRange, i in bufferRanges
|
|
bufferRange = Range.fromObject(bufferRange)
|
|
if selections[i]
|
|
selections[i].setBufferRange(bufferRange, options)
|
|
else
|
|
@addSelectionForBufferRange(bufferRange, options)
|
|
|
|
# Remove the given selection.
|
|
removeSelection: (selection) ->
|
|
_.remove(@selections, selection)
|
|
@emit 'selection-removed', selection
|
|
|
|
# Reduce one or more selections to a single empty selection based on the most
|
|
# recently added cursor.
|
|
clearSelections: ->
|
|
@consolidateSelections()
|
|
@getSelection().clear()
|
|
|
|
# Reduce multiple selections to the most recently added selection.
|
|
consolidateSelections: ->
|
|
selections = @getSelections()
|
|
if selections.length > 1
|
|
selection.destroy() for selection in selections[0...-1]
|
|
true
|
|
else
|
|
false
|
|
|
|
selectionScreenRangeChanged: (selection) ->
|
|
@emit 'selection-screen-range-changed', selection
|
|
|
|
# Public: Get current {Selection}s.
|
|
#
|
|
# Returns: An {Array} of {Selection}s.
|
|
getSelections: -> new Array(@selections...)
|
|
|
|
# Public: Get the most recent {Selection} or the selection at the given
|
|
# index.
|
|
#
|
|
# index - Optional. The index of the selection to return, based on the order
|
|
# in which the selections were added.
|
|
#
|
|
# Returns a {Selection}.
|
|
# or the at the specified index.
|
|
getSelection: (index) ->
|
|
index ?= @selections.length - 1
|
|
@selections[index]
|
|
|
|
# Public: Get the most recently added {Selection}.
|
|
#
|
|
# Returns a {Selection}.
|
|
getLastSelection: ->
|
|
_.last(@selections)
|
|
|
|
# Public: Get all {Selection}s, ordered by their position in the buffer
|
|
# instead of the order in which they were added.
|
|
#
|
|
# Returns an {Array} of {Selection}s.
|
|
getSelectionsOrderedByBufferPosition: ->
|
|
@getSelections().sort (a, b) -> a.compare(b)
|
|
|
|
# Public: Get the last {Selection} based on its position in the buffer.
|
|
#
|
|
# Returns a {Selection}.
|
|
getLastSelectionInBuffer: ->
|
|
_.last(@getSelectionsOrderedByBufferPosition())
|
|
|
|
# Public: Determine if a given range in buffer coordinates intersects a
|
|
# selection.
|
|
#
|
|
# bufferRange - A {Range} or range-compatible {Array}.
|
|
#
|
|
# Returns a {Boolean}.
|
|
selectionIntersectsBufferRange: (bufferRange) ->
|
|
_.any @getSelections(), (selection) ->
|
|
selection.intersectsBufferRange(bufferRange)
|
|
|
|
# Public: Move the cursor to the given position in screen coordinates.
|
|
#
|
|
# If there are multiple cursors, they will be consolidated to a single cursor.
|
|
#
|
|
# position - A {Point} or {Array} of `[row, column]`
|
|
# options - An {Object} combining options for {::clipScreenPosition} with:
|
|
# :autoscroll - Determines whether the editor scrolls to the new cursor's
|
|
# position. Defaults to true.
|
|
setCursorScreenPosition: (position, options) ->
|
|
@moveCursors (cursor) -> cursor.setScreenPosition(position, options)
|
|
|
|
# Public: Get the position of the most recently added cursor in screen
|
|
# coordinates.
|
|
#
|
|
# Returns a {Point}.
|
|
getCursorScreenPosition: ->
|
|
@getCursor().getScreenPosition()
|
|
|
|
# Public: Get the row of the most recently added cursor in screen coordinates.
|
|
#
|
|
# Returns the screen row {Number}.
|
|
getCursorScreenRow: ->
|
|
@getCursor().getScreenRow()
|
|
|
|
# Public: Move the cursor to the given position in buffer coordinates.
|
|
#
|
|
# If there are multiple cursors, they will be consolidated to a single cursor.
|
|
#
|
|
# position - A {Point} or {Array} of `[row, column]`
|
|
# options - An {Object} combining options for {::clipScreenPosition} with:
|
|
# :autoscroll - Determines whether the editor scrolls to the new cursor's
|
|
# position. Defaults to true.
|
|
setCursorBufferPosition: (position, options) ->
|
|
@moveCursors (cursor) -> cursor.setBufferPosition(position, options)
|
|
|
|
# Public: Get the position of the most recently added cursor in buffer
|
|
# coordinates.
|
|
#
|
|
# Returns a {Point}.
|
|
getCursorBufferPosition: ->
|
|
@getCursor().getBufferPosition()
|
|
|
|
# Public: Get the {Range} of the most recently added selection in screen
|
|
# coordinates.
|
|
#
|
|
# Returns a {Range}.
|
|
getSelectedScreenRange: ->
|
|
@getLastSelection().getScreenRange()
|
|
|
|
# Public: Get the {Range} of the most recently added selection in buffer
|
|
# coordinates.
|
|
#
|
|
# Returns a {Range}.
|
|
getSelectedBufferRange: ->
|
|
@getLastSelection().getBufferRange()
|
|
|
|
# Public: Get the {Range}s of all selections in buffer coordinates.
|
|
#
|
|
# The ranges are sorted by their position in the buffer.
|
|
#
|
|
# Returns an {Array} of {Range}s.
|
|
getSelectedBufferRanges: ->
|
|
selection.getBufferRange() for selection in @getSelectionsOrderedByBufferPosition()
|
|
|
|
# Public: Get the {Range}s of all selections in screen coordinates.
|
|
#
|
|
# The ranges are sorted by their position in the buffer.
|
|
#
|
|
# Returns an {Array} of {Range}s.
|
|
getSelectedScreenRanges: ->
|
|
selection.getScreenRange() for selection in @getSelectionsOrderedByBufferPosition()
|
|
|
|
# Public: Get the selected text of the most recently added selection.
|
|
#
|
|
# Returns a {String}.
|
|
getSelectedText: ->
|
|
@getLastSelection().getText()
|
|
|
|
# Public: Get the text in the given {Range} in buffer coordinates.
|
|
#
|
|
# range - A {Range} or range-compatible {Array}.
|
|
#
|
|
# Returns a {String}.
|
|
getTextInBufferRange: (range) ->
|
|
@buffer.getTextInRange(range)
|
|
|
|
# Public: Set the text in the given {Range} in buffer coordinates.
|
|
#
|
|
# range - A {Range} or range-compatible {Array}.
|
|
# text - A {String}
|
|
#
|
|
# Returns the {Range} of the newly-inserted text.
|
|
setTextInBufferRange: (range, text) -> @getBuffer().setTextInRange(range, text)
|
|
|
|
# Public: Get the {Range} of the paragraph surrounding the most recently added
|
|
# cursor.
|
|
#
|
|
# Returns a {Range}.
|
|
getCurrentParagraphBufferRange: ->
|
|
@getCursor().getCurrentParagraphBufferRange()
|
|
|
|
# Public: Returns the word surrounding the most recently added cursor.
|
|
#
|
|
# options - See {Cursor::getBeginningOfCurrentWordBufferPosition}.
|
|
getWordUnderCursor: (options) ->
|
|
@getTextInBufferRange(@getCursor().getCurrentWordBufferRange(options))
|
|
|
|
# Public: Move every cursor up one row in screen coordinates.
|
|
moveCursorUp: (lineCount) ->
|
|
@moveCursors (cursor) -> cursor.moveUp(lineCount, moveToEndOfSelection: true)
|
|
|
|
# Public: Move every cursor down one row in screen coordinates.
|
|
moveCursorDown: (lineCount) ->
|
|
@moveCursors (cursor) -> cursor.moveDown(lineCount, moveToEndOfSelection: true)
|
|
|
|
# Public: Move every cursor left one column.
|
|
moveCursorLeft: ->
|
|
@moveCursors (cursor) -> cursor.moveLeft(moveToEndOfSelection: true)
|
|
|
|
# Public: Move every cursor right one column.
|
|
moveCursorRight: ->
|
|
@moveCursors (cursor) -> cursor.moveRight(moveToEndOfSelection: true)
|
|
|
|
# Public: Move every cursor to the top of the buffer.
|
|
#
|
|
# If there are multiple cursors, they will be merged into a single cursor.
|
|
moveCursorToTop: ->
|
|
@moveCursors (cursor) -> cursor.moveToTop()
|
|
|
|
# Public: Move every cursor to the bottom of the buffer.
|
|
#
|
|
# If there are multiple cursors, they will be merged into a single cursor.
|
|
moveCursorToBottom: ->
|
|
@moveCursors (cursor) -> cursor.moveToBottom()
|
|
|
|
# Public: Move every cursor to the beginning of its line in screen coordinates.
|
|
moveCursorToBeginningOfScreenLine: ->
|
|
@moveCursors (cursor) -> cursor.moveToBeginningOfScreenLine()
|
|
|
|
# Public: Move every cursor to the beginning of its line in buffer coordinates.
|
|
moveCursorToBeginningOfLine: ->
|
|
@moveCursors (cursor) -> cursor.moveToBeginningOfLine()
|
|
|
|
# Public: Move every cursor to the first non-whitespace character of its line.
|
|
moveCursorToFirstCharacterOfLine: ->
|
|
@moveCursors (cursor) -> cursor.moveToFirstCharacterOfLine()
|
|
|
|
# Public: Move every cursor to the end of its line in screen coordinates.
|
|
moveCursorToEndOfScreenLine: ->
|
|
@moveCursors (cursor) -> cursor.moveToEndOfScreenLine()
|
|
|
|
# Public: Move every cursor to the end of its line in buffer coordinates.
|
|
moveCursorToEndOfLine: ->
|
|
@moveCursors (cursor) -> cursor.moveToEndOfLine()
|
|
|
|
# Public: Move every cursor to the beginning of its surrounding word.
|
|
moveCursorToBeginningOfWord: ->
|
|
@moveCursors (cursor) -> cursor.moveToBeginningOfWord()
|
|
|
|
# Public: Move every cursor to the end of its surrounding word.
|
|
moveCursorToEndOfWord: ->
|
|
@moveCursors (cursor) -> cursor.moveToEndOfWord()
|
|
|
|
# Public: Move every cursor to the beginning of the next word.
|
|
moveCursorToBeginningOfNextWord: ->
|
|
@moveCursors (cursor) -> cursor.moveToBeginningOfNextWord()
|
|
|
|
# Public: Move every cursor to the previous word boundary.
|
|
moveCursorToPreviousWordBoundary: ->
|
|
@moveCursors (cursor) -> cursor.moveToPreviousWordBoundary()
|
|
|
|
# Public: Move every cursor to the next word boundary.
|
|
moveCursorToNextWordBoundary: ->
|
|
@moveCursors (cursor) -> cursor.moveToNextWordBoundary()
|
|
|
|
scrollToCursorPosition: ->
|
|
@getCursor().autoscroll()
|
|
|
|
pageUp: ->
|
|
@setScrollTop(@getScrollTop() - @getHeight())
|
|
|
|
pageDown: ->
|
|
@setScrollTop(@getScrollTop() + @getHeight())
|
|
|
|
moveCursors: (fn) ->
|
|
@movingCursors = true
|
|
@batchUpdates =>
|
|
fn(cursor) for cursor in @getCursors()
|
|
@mergeCursors()
|
|
@movingCursors = false
|
|
@emit 'cursors-moved'
|
|
|
|
cursorMoved: (event) ->
|
|
@emit 'cursor-moved', event
|
|
@emit 'cursors-moved' unless @movingCursors
|
|
|
|
# Public: Select from the current cursor position to the given position in
|
|
# screen coordinates.
|
|
#
|
|
# This method may merge selections that end up intesecting.
|
|
#
|
|
# position - An instance of {Point}, with a given `row` and `column`.
|
|
selectToScreenPosition: (position) ->
|
|
lastSelection = @getLastSelection()
|
|
lastSelection.selectToScreenPosition(position)
|
|
@mergeIntersectingSelections(reversed: lastSelection.isReversed())
|
|
|
|
# Public: Move the cursor of each selection one character rightward while
|
|
# preserving the selection's tail position.
|
|
#
|
|
# This method may merge selections that end up intesecting.
|
|
selectRight: ->
|
|
@expandSelectionsForward (selection) => selection.selectRight()
|
|
|
|
# Public: Move the cursor of each selection one character leftward while
|
|
# preserving the selection's tail position.
|
|
#
|
|
# This method may merge selections that end up intesecting.
|
|
selectLeft: ->
|
|
@expandSelectionsBackward (selection) => selection.selectLeft()
|
|
|
|
# Public: Move the cursor of each selection one character upward while
|
|
# preserving the selection's tail position.
|
|
#
|
|
# This method may merge selections that end up intesecting.
|
|
selectUp: (rowCount) ->
|
|
@expandSelectionsBackward (selection) => selection.selectUp(rowCount)
|
|
|
|
# Public: Move the cursor of each selection one character downward while
|
|
# preserving the selection's tail position.
|
|
#
|
|
# This method may merge selections that end up intesecting.
|
|
selectDown: (rowCount) ->
|
|
@expandSelectionsForward (selection) => selection.selectDown(rowCount)
|
|
|
|
# Public: Select from the top of the buffer to the end of the last selection
|
|
# in the buffer.
|
|
#
|
|
# This method merges multiple selections into a single selection.
|
|
selectToTop: ->
|
|
@expandSelectionsBackward (selection) => selection.selectToTop()
|
|
|
|
# Public: Select all text in the buffer.
|
|
#
|
|
# This method merges multiple selections into a single selection.
|
|
selectAll: ->
|
|
@expandSelectionsForward (selection) => selection.selectAll()
|
|
|
|
# Public: Selects from the top of the first selection in the buffer to the end
|
|
# of the buffer.
|
|
#
|
|
# This method merges multiple selections into a single selection.
|
|
selectToBottom: ->
|
|
@expandSelectionsForward (selection) => selection.selectToBottom()
|
|
|
|
# Public: Move the cursor of each selection to the beginning of its line
|
|
# while preserving the selection's tail position.
|
|
#
|
|
# This method may merge selections that end up intesecting.
|
|
selectToBeginningOfLine: ->
|
|
@expandSelectionsBackward (selection) => selection.selectToBeginningOfLine()
|
|
|
|
# Public: Move the cursor of each selection to the first non-whitespace
|
|
# character of its line while preserving the selection's tail position. If the
|
|
# cursor is already on the first character of the line, move it to the
|
|
# beginning of the line.
|
|
#
|
|
# This method may merge selections that end up intersecting.
|
|
selectToFirstCharacterOfLine: ->
|
|
@expandSelectionsBackward (selection) => selection.selectToFirstCharacterOfLine()
|
|
|
|
# Public: Move the cursor of each selection to the end of its line while
|
|
# preserving the selection's tail position.
|
|
#
|
|
# This method may merge selections that end up intersecting.
|
|
selectToEndOfLine: ->
|
|
@expandSelectionsForward (selection) => selection.selectToEndOfLine()
|
|
|
|
# Public: For each selection, move its cursor to the preceding word boundary
|
|
# while maintaining the selection's tail position.
|
|
#
|
|
# This method may merge selections that end up intersecting.
|
|
selectToPreviousWordBoundary: ->
|
|
@expandSelectionsBackward (selection) => selection.selectToPreviousWordBoundary()
|
|
|
|
# Public: For each selection, move its cursor to the next word boundary while
|
|
# maintaining the selection's tail position.
|
|
#
|
|
# This method may merge selections that end up intersecting.
|
|
selectToNextWordBoundary: ->
|
|
@expandSelectionsForward (selection) => selection.selectToNextWordBoundary()
|
|
|
|
# Public: For each cursor, select the containing line.
|
|
#
|
|
# This method merges selections on successive lines.
|
|
selectLine: ->
|
|
@expandSelectionsForward (selection) => selection.selectLine()
|
|
|
|
# Public: Add a similarly-shaped selection to the next eligible line below
|
|
# each selection.
|
|
#
|
|
# Operates on all selections. If the selection is empty, adds an empty
|
|
# selection to the next following non-empty line as close to the current
|
|
# selection's column as possible. If the selection is non-empty, adds a
|
|
# selection to the next line that is long enough for a non-empty selection
|
|
# starting at the same column as the current selection to be added to it.
|
|
addSelectionBelow: ->
|
|
@expandSelectionsForward (selection) => selection.addSelectionBelow()
|
|
|
|
# Public: Add a similarly-shaped selection to the next eligible line above
|
|
# each selection.
|
|
#
|
|
# Operates on all selections. If the selection is empty, adds an empty
|
|
# selection to the next preceding non-empty line as close to the current
|
|
# selection's column as possible. If the selection is non-empty, adds a
|
|
# selection to the next line that is long enough for a non-empty selection
|
|
# starting at the same column as the current selection to be added to it.
|
|
addSelectionAbove: ->
|
|
@expandSelectionsBackward (selection) => selection.addSelectionAbove()
|
|
|
|
# Public: Split multi-line selections into one selection per line.
|
|
#
|
|
# Operates on all selections. This method breaks apart all multi-line
|
|
# selections to create multiple single-line selections that cumulatively cover
|
|
# the same original area.
|
|
splitSelectionsIntoLines: ->
|
|
for selection in @getSelections()
|
|
range = selection.getBufferRange()
|
|
continue if range.isSingleLine()
|
|
|
|
selection.destroy()
|
|
{start, end} = range
|
|
@addSelectionForBufferRange([start, [start.row, Infinity]])
|
|
{row} = start
|
|
while ++row < end.row
|
|
@addSelectionForBufferRange([[row, 0], [row, Infinity]])
|
|
@addSelectionForBufferRange([[end.row, 0], [end.row, end.column]])
|
|
|
|
# Public: For each selection, transpose the selected text.
|
|
#
|
|
# If the selection is empty, the characters preceding and following the cursor
|
|
# are swapped. Otherwise, the selected characters are reversed.
|
|
transpose: ->
|
|
@mutateSelectedText (selection) =>
|
|
if selection.isEmpty()
|
|
selection.selectRight()
|
|
text = selection.getText()
|
|
selection.delete()
|
|
selection.cursor.moveLeft()
|
|
selection.insertText text
|
|
else
|
|
selection.insertText selection.getText().split('').reverse().join('')
|
|
|
|
# Public: Convert the selected text to upper case.
|
|
#
|
|
# For each selection, if the selection is empty, converts the containing word
|
|
# to upper case. Otherwise convert the selected text to upper case.
|
|
upperCase: ->
|
|
@replaceSelectedText selectWordIfEmpty:true, (text) => text.toUpperCase()
|
|
|
|
# Public: Convert the selected text to lower case.
|
|
#
|
|
# For each selection, if the selection is empty, converts the containing word
|
|
# to upper case. Otherwise convert the selected text to upper case.
|
|
lowerCase: ->
|
|
@replaceSelectedText selectWordIfEmpty:true, (text) => text.toLowerCase()
|
|
|
|
# Convert multiple lines to a single line.
|
|
#
|
|
# Operates on all selections. If the selection is empty, joins the current
|
|
# line with the next line. Otherwise it joins all lines that intersect the
|
|
# selection.
|
|
#
|
|
# Joining a line means that multiple lines are converted to a single line with
|
|
# the contents of each of the original non-empty lines separated by a space.
|
|
joinLines: ->
|
|
@mutateSelectedText (selection) -> selection.joinLines()
|
|
|
|
# Public: Expand selections to the beginning of their containing word.
|
|
#
|
|
# Operates on all selections. Moves the cursor to the beginning of the
|
|
# containing word while preserving the selection's tail position.
|
|
selectToBeginningOfWord: ->
|
|
@expandSelectionsBackward (selection) => selection.selectToBeginningOfWord()
|
|
|
|
# Public: Expand selections to the end of their containing word.
|
|
#
|
|
# Operates on all selections. Moves the cursor to the end of the containing
|
|
# word while preserving the selection's tail position.
|
|
selectToEndOfWord: ->
|
|
@expandSelectionsForward (selection) => selection.selectToEndOfWord()
|
|
|
|
# Public: Expand selections to the beginning of the next word.
|
|
#
|
|
# Operates on all selections. Moves the cursor to the beginning of the next
|
|
# word while preserving the selection's tail position.
|
|
selectToBeginningOfNextWord: ->
|
|
@expandSelectionsForward (selection) => selection.selectToBeginningOfNextWord()
|
|
|
|
# Public: Select the word containing each cursor.
|
|
selectWord: ->
|
|
@expandSelectionsForward (selection) => selection.selectWord()
|
|
|
|
# Public: Select the range of the given marker if it is valid.
|
|
#
|
|
# marker - A {DisplayBufferMarker}
|
|
#
|
|
# Returns the selected {Range} or `undefined` if the marker is invalid.
|
|
selectMarker: (marker) ->
|
|
if marker.isValid()
|
|
range = marker.getBufferRange()
|
|
@setSelectedBufferRange(range)
|
|
range
|
|
|
|
# Merge cursors that have the same screen position
|
|
mergeCursors: ->
|
|
positions = []
|
|
for cursor in @getCursors()
|
|
position = cursor.getBufferPosition().toString()
|
|
if position in positions
|
|
cursor.destroy()
|
|
else
|
|
positions.push(position)
|
|
|
|
# Calls the given function with each selection, then merges selections
|
|
expandSelectionsForward: (fn) ->
|
|
@mergeIntersectingSelections =>
|
|
fn(selection) for selection in @getSelections()
|
|
|
|
# Calls the given function with each selection, then merges selections in the
|
|
# reversed orientation
|
|
expandSelectionsBackward: (fn) ->
|
|
@mergeIntersectingSelections reversed: true, =>
|
|
fn(selection) for selection in @getSelections()
|
|
|
|
finalizeSelections: ->
|
|
selection.finalize() for selection in @getSelections()
|
|
|
|
# Merges intersecting selections. If passed a function, it executes
|
|
# the function with merging suppressed, then merges intersecting selections
|
|
# afterward.
|
|
mergeIntersectingSelections: (args...) ->
|
|
fn = args.pop() if _.isFunction(_.last(args))
|
|
options = args.pop() ? {}
|
|
|
|
return fn?() if @suppressSelectionMerging
|
|
|
|
if fn?
|
|
@suppressSelectionMerging = true
|
|
result = fn()
|
|
@suppressSelectionMerging = false
|
|
|
|
reducer = (disjointSelections, selection) ->
|
|
intersectingSelection = _.find(disjointSelections, (s) -> s.intersectsWith(selection))
|
|
if intersectingSelection?
|
|
intersectingSelection.merge(selection, options)
|
|
disjointSelections
|
|
else
|
|
disjointSelections.concat([selection])
|
|
|
|
_.reduce(@getSelections(), reducer, [])
|
|
|
|
preserveCursorPositionOnBufferReload: ->
|
|
cursorPosition = null
|
|
@subscribe @buffer, "will-reload", =>
|
|
cursorPosition = @getCursorBufferPosition()
|
|
@subscribe @buffer, "reloaded", =>
|
|
@setCursorBufferPosition(cursorPosition) if cursorPosition
|
|
cursorPosition = null
|
|
|
|
# Public: Get the current {Grammar} of this editor.
|
|
getGrammar: ->
|
|
@displayBuffer.getGrammar()
|
|
|
|
# Public: Set the current {Grammar} of this editor.
|
|
#
|
|
# Assigning a grammar will cause the editor to re-tokenize based on the new
|
|
# grammar.
|
|
setGrammar: (grammar) ->
|
|
@displayBuffer.setGrammar(grammar)
|
|
|
|
# Reload the grammar based on the file name.
|
|
reloadGrammar: ->
|
|
@displayBuffer.reloadGrammar()
|
|
|
|
shouldAutoIndent: ->
|
|
atom.config.get("editor.autoIndent")
|
|
|
|
# Public: Batch multiple operations as a single undo/redo step.
|
|
#
|
|
# Any group of operations that are logically grouped from the perspective of
|
|
# undoing and redoing should be performed in a transaction. If you want to
|
|
# abort the transaction, call {::abortTransaction} to terminate the function's
|
|
# execution and revert any changes performed up to the abortion.
|
|
#
|
|
# fn - A {Function} to call inside the transaction.
|
|
transact: (fn) ->
|
|
@batchUpdates =>
|
|
@buffer.transact(fn)
|
|
|
|
# Public: Start an open-ended transaction.
|
|
#
|
|
# Call {::commitTransaction} or {::abortTransaction} to terminate the
|
|
# transaction. If you nest calls to transactions, only the outermost
|
|
# transaction is considered. You must match every begin with a matching
|
|
# commit, but a single call to abort will cancel all nested transactions.
|
|
beginTransaction: -> @buffer.beginTransaction()
|
|
|
|
# Public: Commit an open-ended transaction started with {::beginTransaction}
|
|
# and push it to the undo stack.
|
|
#
|
|
# If transactions are nested, only the outermost commit takes effect.
|
|
commitTransaction: -> @buffer.commitTransaction()
|
|
|
|
# Public: Abort an open transaction, undoing any operations performed so far
|
|
# within the transaction.
|
|
abortTransaction: -> @buffer.abortTransaction()
|
|
|
|
batchUpdates: (fn) ->
|
|
@emit 'batched-updates-started'
|
|
result = fn()
|
|
@emit 'batched-updates-ended'
|
|
result
|
|
|
|
inspect: ->
|
|
"<Editor #{@id}>"
|
|
|
|
logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
|
|
|
|
handleGrammarChange: ->
|
|
@unfoldAll()
|
|
@emit 'grammar-changed'
|
|
|
|
handleMarkerCreated: (marker) =>
|
|
if marker.matchesAttributes(@getSelectionMarkerAttributes())
|
|
@addSelection(marker)
|
|
|
|
getSelectionMarkerAttributes: ->
|
|
type: 'selection', editorId: @id, invalidate: 'never'
|
|
|
|
getVerticalScrollMargin: -> @displayBuffer.getVerticalScrollMargin()
|
|
setVerticalScrollMargin: (verticalScrollMargin) -> @displayBuffer.setVerticalScrollMargin(verticalScrollMargin)
|
|
|
|
getHorizontalScrollMargin: -> @displayBuffer.getHorizontalScrollMargin()
|
|
setHorizontalScrollMargin: (horizontalScrollMargin) -> @displayBuffer.setHorizontalScrollMargin(horizontalScrollMargin)
|
|
|
|
getLineHeight: -> @displayBuffer.getLineHeight()
|
|
setLineHeight: (lineHeight) -> @displayBuffer.setLineHeight(lineHeight)
|
|
|
|
getScopedCharWidth: (scopeNames, char) -> @displayBuffer.getScopedCharWidth(scopeNames, char)
|
|
setScopedCharWidth: (scopeNames, char, width) -> @displayBuffer.setScopedCharWidth(scopeNames, char, width)
|
|
|
|
getScopedCharWidths: (scopeNames) -> @displayBuffer.getScopedCharWidths(scopeNames)
|
|
|
|
clearScopedCharWidths: -> @displayBuffer.clearScopedCharWidths()
|
|
|
|
getDefaultCharWidth: -> @displayBuffer.getDefaultCharWidth()
|
|
setDefaultCharWidth: (defaultCharWidth) -> @displayBuffer.setDefaultCharWidth(defaultCharWidth)
|
|
|
|
setHeight: (height) -> @displayBuffer.setHeight(height)
|
|
getHeight: -> @displayBuffer.getHeight()
|
|
|
|
getClientHeight: -> @displayBuffer.getClientHeight()
|
|
|
|
setWidth: (width) -> @displayBuffer.setWidth(width)
|
|
getWidth: -> @displayBuffer.getWidth()
|
|
|
|
getScrollTop: -> @displayBuffer.getScrollTop()
|
|
setScrollTop: (scrollTop) -> @displayBuffer.setScrollTop(scrollTop)
|
|
|
|
getScrollBottom: -> @displayBuffer.getScrollBottom()
|
|
setScrollBottom: (scrollBottom) -> @displayBuffer.setScrollBottom(scrollBottom)
|
|
|
|
getScrollLeft: -> @displayBuffer.getScrollLeft()
|
|
setScrollLeft: (scrollLeft) -> @displayBuffer.setScrollLeft(scrollLeft)
|
|
|
|
getScrollRight: -> @displayBuffer.getScrollRight()
|
|
setScrollRight: (scrollRight) -> @displayBuffer.setScrollRight(scrollRight)
|
|
|
|
getScrollHeight: -> @displayBuffer.getScrollHeight()
|
|
getScrollWidth: (scrollWidth) -> @displayBuffer.getScrollWidth(scrollWidth)
|
|
|
|
getVisibleRowRange: -> @displayBuffer.getVisibleRowRange()
|
|
|
|
intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow)
|
|
|
|
selectionIntersectsVisibleRowRange: (selection) -> @displayBuffer.selectionIntersectsVisibleRowRange(selection)
|
|
|
|
pixelPositionForScreenPosition: (screenPosition) -> @displayBuffer.pixelPositionForScreenPosition(screenPosition)
|
|
|
|
pixelPositionForBufferPosition: (bufferPosition) -> @displayBuffer.pixelPositionForBufferPosition(bufferPosition)
|
|
|
|
screenPositionForPixelPosition: (pixelPosition) -> @displayBuffer.screenPositionForPixelPosition(pixelPosition)
|
|
|
|
pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange)
|
|
|
|
scrollToScreenRange: (screenRange) -> @displayBuffer.scrollToScreenRange(screenRange)
|
|
|
|
scrollToScreenPosition: (screenPosition) -> @displayBuffer.scrollToScreenPosition(screenPosition)
|
|
|
|
scrollToBufferPosition: (bufferPosition) -> @displayBuffer.scrollToBufferPosition(bufferPosition)
|
|
|
|
horizontallyScrollable: -> @displayBuffer.horizontallyScrollable()
|
|
|
|
verticallyScrollable: -> @displayBuffer.verticallyScrollable()
|
|
|
|
getHorizontalScrollbarHeight: -> @displayBuffer.getHorizontalScrollbarHeight()
|
|
setHorizontalScrollbarHeight: (height) -> @displayBuffer.setHorizontalScrollbarHeight(height)
|
|
|
|
getVerticalScrollbarWidth: -> @displayBuffer.getVerticalScrollbarWidth()
|
|
setVerticalScrollbarWidth: (width) -> @displayBuffer.setVerticalScrollbarWidth(width)
|
|
|
|
# Deprecated: Call {::joinLines} instead.
|
|
joinLine: ->
|
|
deprecate("Use Editor::joinLines() instead")
|
|
@joinLines()
|