mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Guard Selection methods against read-only TextEditor modification
This commit is contained in:
180
src/selection.js
180
src/selection.js
@@ -407,6 +407,19 @@ class Selection {
|
||||
if (autoscroll) this.cursor.autoscroll()
|
||||
}
|
||||
|
||||
// Private: Ensure that the {TextEditor} is not marked read-only before allowing a buffer modification to occur. if
|
||||
// the editor is read-only, require an explicit opt-in option to proceed (`bypassReadOnly`) or throw an Error.
|
||||
ensureWritable (methodName, opts) {
|
||||
if (!opts.bypassReadOnly && this.editor.isReadOnly()) {
|
||||
const e = new Error('Attempt to mutate a read-only TextEditor through a Selection')
|
||||
e.detail =
|
||||
`Your package is attempting to call ${methodName} on a selection within an editor that has been marked ` +
|
||||
' read-only. Pass {bypassReadOnly: true} to modify it anyway, or test editors with .isReadOnly() before '
|
||||
' attempting modifications.'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Section: Modifying the selected text
|
||||
*/
|
||||
@@ -428,7 +441,10 @@ class Selection {
|
||||
// level between the first lines and the trailing lines.
|
||||
// * `normalizeLineEndings` (optional) {Boolean} (default: true)
|
||||
// * `undo` If `skip`, skips the undo stack for this operation.
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false)
|
||||
insertText (text, options = {}) {
|
||||
this.ensureWritable('insertText', options)
|
||||
|
||||
let desiredIndentLevel, indentAdjustment
|
||||
const oldBufferRange = this.getBufferRange()
|
||||
const wasReversed = this.isReversed()
|
||||
@@ -492,90 +508,134 @@ class Selection {
|
||||
|
||||
// Public: Removes the first character before the selection if the selection
|
||||
// is empty otherwise it deletes the selection.
|
||||
backspace () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
backspace (options = {}) {
|
||||
this.ensureWritable('backspace', options)
|
||||
if (this.isEmpty()) this.selectLeft()
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes the selection or, if nothing is selected, then all
|
||||
// characters from the start of the selection back to the previous word
|
||||
// boundary.
|
||||
deleteToPreviousWordBoundary () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteToPreviousWordBoundary (options = {}) {
|
||||
this.ensureWritable('deleteToPreviousWordBoundary', options)
|
||||
if (this.isEmpty()) this.selectToPreviousWordBoundary()
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes the selection or, if nothing is selected, then all
|
||||
// characters from the start of the selection up to the next word
|
||||
// boundary.
|
||||
deleteToNextWordBoundary () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteToNextWordBoundary (options = {}) {
|
||||
this.ensureWritable('deleteToNextWordBoundary', options)
|
||||
if (this.isEmpty()) this.selectToNextWordBoundary()
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes from the start of the selection to the beginning of the
|
||||
// current word if the selection is empty otherwise it deletes the selection.
|
||||
deleteToBeginningOfWord () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteToBeginningOfWord (options = {}) {
|
||||
this.ensureWritable('deleteToBeginningOfWord', options)
|
||||
if (this.isEmpty()) this.selectToBeginningOfWord()
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes from the beginning of the line which the selection begins on
|
||||
// all the way through to the end of the selection.
|
||||
deleteToBeginningOfLine () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteToBeginningOfLine (options = {}) {
|
||||
this.ensureWritable('deleteToBeginningOfLine', options)
|
||||
if (this.isEmpty() && this.cursor.isAtBeginningOfLine()) {
|
||||
this.selectLeft()
|
||||
} else {
|
||||
this.selectToBeginningOfLine()
|
||||
}
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes the selection or the next character after the start of the
|
||||
// selection if the selection is empty.
|
||||
delete () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
delete (options = {}) {
|
||||
this.ensureWritable('delete', options)
|
||||
if (this.isEmpty()) this.selectRight()
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: If the selection is empty, removes all text from the cursor to the
|
||||
// end of the line. If the cursor is already at the end of the line, it
|
||||
// removes the following newline. If the selection isn't empty, only deletes
|
||||
// the contents of the selection.
|
||||
deleteToEndOfLine () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteToEndOfLine (options = {}) {
|
||||
this.ensureWritable('deleteToEndOfLine', options)
|
||||
if (this.isEmpty()) {
|
||||
if (this.cursor.isAtEndOfLine()) {
|
||||
this.delete()
|
||||
this.delete(options)
|
||||
return
|
||||
}
|
||||
this.selectToEndOfLine()
|
||||
}
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes the selection or all characters from the start of the
|
||||
// selection to the end of the current word if nothing is selected.
|
||||
deleteToEndOfWord () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteToEndOfWord (options = {}) {
|
||||
this.ensureWritable('deleteToEndOfWord', options)
|
||||
if (this.isEmpty()) this.selectToEndOfWord()
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes the selection or all characters from the start of the
|
||||
// selection to the end of the current word if nothing is selected.
|
||||
deleteToBeginningOfSubword () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteToBeginningOfSubword (options = {}) {
|
||||
this.ensureWritable('deleteToBeginningOfSubword', options)
|
||||
if (this.isEmpty()) this.selectToPreviousSubwordBoundary()
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes the selection or all characters from the start of the
|
||||
// selection to the end of the current word if nothing is selected.
|
||||
deleteToEndOfSubword () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteToEndOfSubword (options = {}) {
|
||||
this.ensureWritable('deleteToEndOfSubword', options)
|
||||
if (this.isEmpty()) this.selectToNextSubwordBoundary()
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
// Public: Removes only the selected text.
|
||||
deleteSelectedText () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteSelectedText (options = {}) {
|
||||
this.ensureWritable('deleteSelectedText', options)
|
||||
const bufferRange = this.getBufferRange()
|
||||
if (!bufferRange.isEmpty()) this.editor.buffer.delete(bufferRange)
|
||||
if (this.cursor) this.cursor.setBufferPosition(bufferRange.start)
|
||||
@@ -584,7 +644,11 @@ class Selection {
|
||||
// Public: Removes the line at the beginning of the selection if the selection
|
||||
// is empty unless the selection spans multiple lines in which case all lines
|
||||
// are removed.
|
||||
deleteLine () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
deleteLine (options = {}) {
|
||||
this.ensureWritable('deleteLine', options)
|
||||
const range = this.getBufferRange()
|
||||
if (range.isEmpty()) {
|
||||
const start = this.cursor.getScreenRow()
|
||||
@@ -607,7 +671,11 @@ class Selection {
|
||||
// be separated by a single space.
|
||||
//
|
||||
// If there selection spans more than one line, all the lines are joined together.
|
||||
joinLines () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
joinLines (options = {}) {
|
||||
this.ensureWritable('joinLines', options)
|
||||
let joinMarker
|
||||
const selectedRange = this.getBufferRange()
|
||||
if (selectedRange.isEmpty()) {
|
||||
@@ -629,7 +697,7 @@ class Selection {
|
||||
})
|
||||
if (trailingWhitespaceRange) {
|
||||
this.setBufferRange(trailingWhitespaceRange)
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
}
|
||||
|
||||
const currentRow = selectedRange.start.row
|
||||
@@ -638,7 +706,7 @@ class Selection {
|
||||
(nextRow <= this.editor.buffer.getLastRow()) &&
|
||||
(this.editor.buffer.lineLengthForRow(nextRow) > 0) &&
|
||||
(this.editor.buffer.lineLengthForRow(currentRow) > 0)
|
||||
if (insertSpace) this.insertText(' ')
|
||||
if (insertSpace) this.insertText(' ', options)
|
||||
|
||||
this.cursor.moveToEndOfLine()
|
||||
|
||||
@@ -647,7 +715,7 @@ class Selection {
|
||||
this.cursor.moveRight()
|
||||
this.cursor.moveToFirstCharacterOfLine()
|
||||
})
|
||||
this.deleteSelectedText()
|
||||
this.deleteSelectedText(options)
|
||||
|
||||
if (insertSpace) this.cursor.moveLeft()
|
||||
}
|
||||
@@ -660,7 +728,11 @@ class Selection {
|
||||
}
|
||||
|
||||
// Public: Removes one level of indent from the currently selected rows.
|
||||
outdentSelectedRows () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
outdentSelectedRows (options = {}) {
|
||||
this.ensureWritable('outdentSelectedRows', options)
|
||||
const [start, end] = this.getBufferRowRange()
|
||||
const {buffer} = this.editor
|
||||
const leadingTabRegex = new RegExp(`^( {1,${this.editor.getTabLength()}}|\t)`)
|
||||
@@ -674,7 +746,11 @@ class Selection {
|
||||
|
||||
// Public: Sets the indentation level of all selected rows to values suggested
|
||||
// by the relevant grammars.
|
||||
autoIndentSelectedRows () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
autoIndentSelectedRows (options = {}) {
|
||||
this.ensureWritable('autoIndentSelectedRows', options)
|
||||
const [start, end] = this.getBufferRowRange()
|
||||
return this.editor.autoIndentBufferRows(start, end)
|
||||
}
|
||||
@@ -683,29 +759,45 @@ class Selection {
|
||||
// of a comment.
|
||||
//
|
||||
// Removes the comment if they are currently wrapped in a comment.
|
||||
toggleLineComments () {
|
||||
//
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
toggleLineComments (options = {}) {
|
||||
this.ensureWritable('toggleLineComments', options)
|
||||
this.editor.toggleLineCommentsForBufferRows(...(this.getBufferRowRange() || []))
|
||||
}
|
||||
|
||||
// Public: Cuts the selection until the end of the screen line.
|
||||
cutToEndOfLine (maintainClipboard) {
|
||||
//
|
||||
// * `maintainClipboard` {Boolean}
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
cutToEndOfLine (maintainClipboard, options = {}) {
|
||||
this.ensureWritable('cutToEndOfLine', options)
|
||||
if (this.isEmpty()) this.selectToEndOfLine()
|
||||
return this.cut(maintainClipboard)
|
||||
return this.cut(maintainClipboard, false, options.bypassReadOnly)
|
||||
}
|
||||
|
||||
// Public: Cuts the selection until the end of the buffer line.
|
||||
cutToEndOfBufferLine (maintainClipboard) {
|
||||
//
|
||||
// * `maintainClipboard` {Boolean}
|
||||
// * `options` (optional) {Object}
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
cutToEndOfBufferLine (maintainClipboard, options = {}) {
|
||||
this.ensureWritable('cutToEndOfBufferLine', options)
|
||||
if (this.isEmpty()) this.selectToEndOfBufferLine()
|
||||
this.cut(maintainClipboard)
|
||||
this.cut(maintainClipboard, false, options.bypassReadOnly)
|
||||
}
|
||||
|
||||
// Public: Copies the selection to the clipboard and then deletes it.
|
||||
//
|
||||
// * `maintainClipboard` {Boolean} (default: false) See {::copy}
|
||||
// * `fullLine` {Boolean} (default: false) See {::copy}
|
||||
cut (maintainClipboard = false, fullLine = false) {
|
||||
// * `bypassReadOnly` {Boolean} (default: false) Must be `true` to modify text within a read-only editor.
|
||||
cut (maintainClipboard = false, fullLine = false, bypassReadOnly = false) {
|
||||
this.ensureWritable('cut', {bypassReadOnly})
|
||||
this.copy(maintainClipboard, fullLine)
|
||||
this.delete()
|
||||
this.delete({bypassReadOnly})
|
||||
}
|
||||
|
||||
// Public: Copies the current selection to the clipboard.
|
||||
@@ -783,7 +875,9 @@ class Selection {
|
||||
// * `options` (optional) {Object} with the keys:
|
||||
// * `autoIndent` If `true`, the line is indented to an automatically-inferred
|
||||
// level. Otherwise, {TextEditor::getTabText} is inserted.
|
||||
indent ({autoIndent} = {}) {
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
indent ({autoIndent, bypassReadOnly} = {}) {
|
||||
this.ensureWritable('indent', {bypassReadOnly})
|
||||
const {row} = this.cursor.getBufferPosition()
|
||||
|
||||
if (this.isEmpty()) {
|
||||
@@ -793,17 +887,21 @@ class Selection {
|
||||
|
||||
if (autoIndent && delta > 0) {
|
||||
if (!this.editor.getSoftTabs()) delta = Math.max(delta, 1)
|
||||
this.insertText(this.editor.buildIndentString(delta))
|
||||
this.insertText(this.editor.buildIndentString(delta), {bypassReadOnly})
|
||||
} else {
|
||||
this.insertText(this.editor.buildIndentString(1, this.cursor.getBufferColumn()))
|
||||
this.insertText(this.editor.buildIndentString(1, this.cursor.getBufferColumn()), {bypassReadOnly})
|
||||
}
|
||||
} else {
|
||||
this.indentSelectedRows()
|
||||
this.indentSelectedRows({bypassReadOnly})
|
||||
}
|
||||
}
|
||||
|
||||
// Public: If the selection spans multiple rows, indent all of them.
|
||||
indentSelectedRows () {
|
||||
//
|
||||
// * `options` (optional) {Object} with the keys:
|
||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||
indentSelectedRows (options = {}) {
|
||||
this.ensureWritable('indentSelectedRows', options)
|
||||
const [start, end] = this.getBufferRowRange()
|
||||
for (let row = start; row <= end; row++) {
|
||||
if (this.editor.buffer.lineLengthForRow(row) !== 0) {
|
||||
|
||||
Reference in New Issue
Block a user