123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833 |
- const { Point, Range } = require('text-buffer');
- const { Emitter } = require('event-kit');
- const _ = require('underscore-plus');
- const Model = require('./model');
- const EmptyLineRegExp = /(\r\n[\t ]*\r\n)|(\n[\t ]*\n)/g;
- // Extended: The `Cursor` class represents the little blinking line identifying
- // where text can be inserted.
- //
- // Cursors belong to {TextEditor}s and have some metadata attached in the form
- // of a {DisplayMarker}.
- module.exports = class Cursor extends Model {
- // Instantiated by a {TextEditor}
- constructor(params) {
- super(params);
- this.editor = params.editor;
- this.marker = params.marker;
- this.emitter = new Emitter();
- }
- destroy() {
- this.marker.destroy();
- }
- /*
- Section: Event Subscription
- */
- // Public: Calls your `callback` when the cursor has been moved.
- //
- // * `callback` {Function}
- // * `event` {Object}
- // * `oldBufferPosition` {Point}
- // * `oldScreenPosition` {Point}
- // * `newBufferPosition` {Point}
- // * `newScreenPosition` {Point}
- // * `textChanged` {Boolean}
- // * `cursor` {Cursor} that triggered the event
- //
- // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
- onDidChangePosition(callback) {
- return this.emitter.on('did-change-position', callback);
- }
- // Public: Calls your `callback` when the cursor is destroyed
- //
- // * `callback` {Function}
- //
- // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
- onDidDestroy(callback) {
- return this.emitter.once('did-destroy', callback);
- }
- /*
- Section: Managing Cursor Position
- */
- // Public: Moves a cursor to a given screen position.
- //
- // * `screenPosition` {Array} of two numbers: the screen row, and the screen column.
- // * `options` (optional) {Object} with the following keys:
- // * `autoscroll` A Boolean which, if `true`, scrolls the {TextEditor} to wherever
- // the cursor moves to.
- setScreenPosition(screenPosition, options = {}) {
- this.changePosition(options, () => {
- this.marker.setHeadScreenPosition(screenPosition, options);
- });
- }
- // Public: Returns the screen position of the cursor as a {Point}.
- getScreenPosition() {
- return this.marker.getHeadScreenPosition();
- }
- // Public: Moves a cursor to a given buffer position.
- //
- // * `bufferPosition` {Array} of two numbers: the buffer row, and the buffer column.
- // * `options` (optional) {Object} with the following keys:
- // * `autoscroll` {Boolean} indicating whether to autoscroll to the new
- // position. Defaults to `true` if this is the most recently added cursor,
- // `false` otherwise.
- setBufferPosition(bufferPosition, options = {}) {
- this.changePosition(options, () => {
- this.marker.setHeadBufferPosition(bufferPosition, options);
- });
- }
- // Public: Returns the current buffer position as an Array.
- getBufferPosition() {
- return this.marker.getHeadBufferPosition();
- }
- // Public: Returns the cursor's current screen row.
- getScreenRow() {
- return this.getScreenPosition().row;
- }
- // Public: Returns the cursor's current screen column.
- getScreenColumn() {
- return this.getScreenPosition().column;
- }
- // Public: Retrieves the cursor's current buffer row.
- getBufferRow() {
- return this.getBufferPosition().row;
- }
- // Public: Returns the cursor's current buffer column.
- getBufferColumn() {
- return this.getBufferPosition().column;
- }
- // Public: Returns the cursor's current buffer row of text excluding its line
- // ending.
- getCurrentBufferLine() {
- return this.editor.lineTextForBufferRow(this.getBufferRow());
- }
- // Public: Returns whether the cursor is at the start of a line.
- isAtBeginningOfLine() {
- return this.getBufferPosition().column === 0;
- }
- // Public: Returns whether the cursor is on the line return character.
- isAtEndOfLine() {
- return this.getBufferPosition().isEqual(
- this.getCurrentLineBufferRange().end
- );
- }
- /*
- Section: Cursor Position Details
- */
- // Public: Returns the underlying {DisplayMarker} for the cursor.
- // Useful with overlay {Decoration}s.
- getMarker() {
- return this.marker;
- }
- // Public: Identifies if the cursor is surrounded by whitespace.
- //
- // "Surrounded" here means that the character directly before and after the
- // cursor are both whitespace.
- //
- // Returns a {Boolean}.
- isSurroundedByWhitespace() {
- const { row, column } = this.getBufferPosition();
- const range = [[row, column - 1], [row, column + 1]];
- return /^\s+$/.test(this.editor.getTextInBufferRange(range));
- }
- // Public: Returns whether the cursor is currently between a word and non-word
- // character. The non-word characters are defined by the
- // `editor.nonWordCharacters` config value.
- //
- // This method returns false if the character before or after the cursor is
- // whitespace.
- //
- // Returns a Boolean.
- isBetweenWordAndNonWord() {
- if (this.isAtBeginningOfLine() || this.isAtEndOfLine()) return false;
- const { row, column } = this.getBufferPosition();
- const range = [[row, column - 1], [row, column + 1]];
- const text = this.editor.getTextInBufferRange(range);
- if (/\s/.test(text[0]) || /\s/.test(text[1])) return false;
- const nonWordCharacters = this.getNonWordCharacters();
- return (
- nonWordCharacters.includes(text[0]) !==
- nonWordCharacters.includes(text[1])
- );
- }
- // Public: Returns whether this cursor is between a word's start and end.
- //
- // * `options` (optional) {Object}
- // * `wordRegex` A {RegExp} indicating what constitutes a "word"
- // (default: {::wordRegExp}).
- //
- // Returns a {Boolean}
- isInsideWord(options) {
- const { row, column } = this.getBufferPosition();
- const range = [[row, column], [row, Infinity]];
- const text = this.editor.getTextInBufferRange(range);
- return (
- text.search((options && options.wordRegex) || this.wordRegExp()) === 0
- );
- }
- // Public: Returns the indentation level of the current line.
- getIndentLevel() {
- if (this.editor.getSoftTabs()) {
- return this.getBufferColumn() / this.editor.getTabLength();
- } else {
- return this.getBufferColumn();
- }
- }
- // Public: Retrieves the scope descriptor for the cursor's current position.
- //
- // Returns a {ScopeDescriptor}
- getScopeDescriptor() {
- return this.editor.scopeDescriptorForBufferPosition(
- this.getBufferPosition()
- );
- }
- // Public: Retrieves the syntax tree scope descriptor for the cursor's current position.
- //
- // Returns a {ScopeDescriptor}
- getSyntaxTreeScopeDescriptor() {
- return this.editor.syntaxTreeScopeDescriptorForBufferPosition(
- this.getBufferPosition()
- );
- }
- // Public: Returns true if this cursor has no non-whitespace characters before
- // its current position.
- hasPrecedingCharactersOnLine() {
- const bufferPosition = this.getBufferPosition();
- const line = this.editor.lineTextForBufferRow(bufferPosition.row);
- const firstCharacterColumn = line.search(/\S/);
- if (firstCharacterColumn === -1) {
- return false;
- } else {
- return bufferPosition.column > firstCharacterColumn;
- }
- }
- // Public: Identifies if this cursor is the last in the {TextEditor}.
- //
- // "Last" is defined as the most recently added cursor.
- //
- // Returns a {Boolean}.
- isLastCursor() {
- return this === this.editor.getLastCursor();
- }
- /*
- Section: Moving the Cursor
- */
- // Public: Moves the cursor up one screen row.
- //
- // * `rowCount` (optional) {Number} number of rows to move (default: 1)
- // * `options` (optional) {Object} with the following keys:
- // * `moveToEndOfSelection` if true, move to the left of the selection if a
- // selection exists.
- moveUp(rowCount = 1, { moveToEndOfSelection } = {}) {
- let row, column;
- const range = this.marker.getScreenRange();
- if (moveToEndOfSelection && !range.isEmpty()) {
- ({ row, column } = range.start);
- } else {
- ({ row, column } = this.getScreenPosition());
- }
- if (this.goalColumn != null) column = this.goalColumn;
- this.setScreenPosition(
- { row: row - rowCount, column },
- { skipSoftWrapIndentation: true }
- );
- this.goalColumn = column;
- }
- // Public: Moves the cursor down one screen row.
- //
- // * `rowCount` (optional) {Number} number of rows to move (default: 1)
- // * `options` (optional) {Object} with the following keys:
- // * `moveToEndOfSelection` if true, move to the left of the selection if a
- // selection exists.
- moveDown(rowCount = 1, { moveToEndOfSelection } = {}) {
- let row, column;
- const range = this.marker.getScreenRange();
- if (moveToEndOfSelection && !range.isEmpty()) {
- ({ row, column } = range.end);
- } else {
- ({ row, column } = this.getScreenPosition());
- }
- if (this.goalColumn != null) column = this.goalColumn;
- this.setScreenPosition(
- { row: row + rowCount, column },
- { skipSoftWrapIndentation: true }
- );
- this.goalColumn = column;
- }
- // Public: Moves the cursor left one screen column.
- //
- // * `columnCount` (optional) {Number} number of columns to move (default: 1)
- // * `options` (optional) {Object} with the following keys:
- // * `moveToEndOfSelection` if true, move to the left of the selection if a
- // selection exists.
- moveLeft(columnCount = 1, { moveToEndOfSelection } = {}) {
- const range = this.marker.getScreenRange();
- if (moveToEndOfSelection && !range.isEmpty()) {
- this.setScreenPosition(range.start);
- } else {
- let { row, column } = this.getScreenPosition();
- while (columnCount > column && row > 0) {
- columnCount -= column;
- column = this.editor.lineLengthForScreenRow(--row);
- columnCount--; // subtract 1 for the row move
- }
- column = column - columnCount;
- this.setScreenPosition({ row, column }, { clipDirection: 'backward' });
- }
- }
- // Public: Moves the cursor right one screen column.
- //
- // * `columnCount` (optional) {Number} number of columns to move (default: 1)
- // * `options` (optional) {Object} with the following keys:
- // * `moveToEndOfSelection` if true, move to the right of the selection if a
- // selection exists.
- moveRight(columnCount = 1, { moveToEndOfSelection } = {}) {
- const range = this.marker.getScreenRange();
- if (moveToEndOfSelection && !range.isEmpty()) {
- this.setScreenPosition(range.end);
- } else {
- let { row, column } = this.getScreenPosition();
- const maxLines = this.editor.getScreenLineCount();
- let rowLength = this.editor.lineLengthForScreenRow(row);
- let columnsRemainingInLine = rowLength - column;
- while (columnCount > columnsRemainingInLine && row < maxLines - 1) {
- columnCount -= columnsRemainingInLine;
- columnCount--; // subtract 1 for the row move
- column = 0;
- rowLength = this.editor.lineLengthForScreenRow(++row);
- columnsRemainingInLine = rowLength;
- }
- column = column + columnCount;
- this.setScreenPosition({ row, column }, { clipDirection: 'forward' });
- }
- }
- // Public: Moves the cursor to the top of the buffer.
- moveToTop() {
- this.setBufferPosition([0, 0]);
- }
- // Public: Moves the cursor to the bottom of the buffer.
- moveToBottom() {
- const column = this.goalColumn;
- this.setBufferPosition(this.editor.getEofBufferPosition());
- this.goalColumn = column;
- }
- // Public: Moves the cursor to the beginning of the line.
- moveToBeginningOfScreenLine() {
- this.setScreenPosition([this.getScreenRow(), 0]);
- }
- // Public: Moves the cursor to the beginning of the buffer line.
- moveToBeginningOfLine() {
- this.setBufferPosition([this.getBufferRow(), 0]);
- }
- // Public: Moves the cursor to the beginning of the first character in the
- // line.
- moveToFirstCharacterOfLine() {
- let targetBufferColumn;
- const screenRow = this.getScreenRow();
- const screenLineStart = this.editor.clipScreenPosition([screenRow, 0], {
- skipSoftWrapIndentation: true
- });
- const screenLineEnd = [screenRow, Infinity];
- const screenLineBufferRange = this.editor.bufferRangeForScreenRange([
- screenLineStart,
- screenLineEnd
- ]);
- let firstCharacterColumn = null;
- this.editor.scanInBufferRange(
- /\S/,
- screenLineBufferRange,
- ({ range, stop }) => {
- firstCharacterColumn = range.start.column;
- stop();
- }
- );
- if (
- firstCharacterColumn != null &&
- firstCharacterColumn !== this.getBufferColumn()
- ) {
- targetBufferColumn = firstCharacterColumn;
- } else {
- targetBufferColumn = screenLineBufferRange.start.column;
- }
- this.setBufferPosition([
- screenLineBufferRange.start.row,
- targetBufferColumn
- ]);
- }
- // Public: Moves the cursor to the end of the line.
- moveToEndOfScreenLine() {
- this.setScreenPosition([this.getScreenRow(), Infinity]);
- }
- // Public: Moves the cursor to the end of the buffer line.
- moveToEndOfLine() {
- this.setBufferPosition([this.getBufferRow(), Infinity]);
- }
- // Public: Moves the cursor to the beginning of the word.
- moveToBeginningOfWord() {
- this.setBufferPosition(this.getBeginningOfCurrentWordBufferPosition());
- }
- // Public: Moves the cursor to the end of the word.
- moveToEndOfWord() {
- const position = this.getEndOfCurrentWordBufferPosition();
- if (position) this.setBufferPosition(position);
- }
- // Public: Moves the cursor to the beginning of the next word.
- moveToBeginningOfNextWord() {
- const position = this.getBeginningOfNextWordBufferPosition();
- if (position) this.setBufferPosition(position);
- }
- // Public: Moves the cursor to the previous word boundary.
- moveToPreviousWordBoundary() {
- const position = this.getPreviousWordBoundaryBufferPosition();
- if (position) this.setBufferPosition(position);
- }
- // Public: Moves the cursor to the next word boundary.
- moveToNextWordBoundary() {
- const position = this.getNextWordBoundaryBufferPosition();
- if (position) this.setBufferPosition(position);
- }
- // Public: Moves the cursor to the previous subword boundary.
- moveToPreviousSubwordBoundary() {
- const options = { wordRegex: this.subwordRegExp({ backwards: true }) };
- const position = this.getPreviousWordBoundaryBufferPosition(options);
- if (position) this.setBufferPosition(position);
- }
- // Public: Moves the cursor to the next subword boundary.
- moveToNextSubwordBoundary() {
- const options = { wordRegex: this.subwordRegExp() };
- const position = this.getNextWordBoundaryBufferPosition(options);
- if (position) this.setBufferPosition(position);
- }
- // Public: Moves the cursor to the beginning of the buffer line, skipping all
- // whitespace.
- skipLeadingWhitespace() {
- const position = this.getBufferPosition();
- const scanRange = this.getCurrentLineBufferRange();
- let endOfLeadingWhitespace = null;
- this.editor.scanInBufferRange(/^[ \t]*/, scanRange, ({ range }) => {
- endOfLeadingWhitespace = range.end;
- });
- if (endOfLeadingWhitespace.isGreaterThan(position))
- this.setBufferPosition(endOfLeadingWhitespace);
- }
- // Public: Moves the cursor to the beginning of the next paragraph
- moveToBeginningOfNextParagraph() {
- const position = this.getBeginningOfNextParagraphBufferPosition();
- if (position) this.setBufferPosition(position);
- }
- // Public: Moves the cursor to the beginning of the previous paragraph
- moveToBeginningOfPreviousParagraph() {
- const position = this.getBeginningOfPreviousParagraphBufferPosition();
- if (position) this.setBufferPosition(position);
- }
- /*
- Section: Local Positions and Ranges
- */
- // Public: Returns buffer position of previous word boundary. It might be on
- // the current word, or the previous word.
- //
- // * `options` (optional) {Object} with the following keys:
- // * `wordRegex` A {RegExp} indicating what constitutes a "word"
- // (default: {::wordRegExp})
- getPreviousWordBoundaryBufferPosition(options = {}) {
- const currentBufferPosition = this.getBufferPosition();
- const previousNonBlankRow = this.editor.buffer.previousNonBlankRow(
- currentBufferPosition.row
- );
- const scanRange = Range(
- Point(previousNonBlankRow || 0, 0),
- currentBufferPosition
- );
- const ranges = this.editor.buffer.findAllInRangeSync(
- options.wordRegex || this.wordRegExp(),
- scanRange
- );
- const range = ranges[ranges.length - 1];
- if (range) {
- if (
- range.start.row < currentBufferPosition.row &&
- currentBufferPosition.column > 0
- ) {
- return Point(currentBufferPosition.row, 0);
- } else if (currentBufferPosition.isGreaterThan(range.end)) {
- return Point.fromObject(range.end);
- } else {
- return Point.fromObject(range.start);
- }
- } else {
- return currentBufferPosition;
- }
- }
- // Public: Returns buffer position of the next word boundary. It might be on
- // the current word, or the previous word.
- //
- // * `options` (optional) {Object} with the following keys:
- // * `wordRegex` A {RegExp} indicating what constitutes a "word"
- // (default: {::wordRegExp})
- getNextWordBoundaryBufferPosition(options = {}) {
- const currentBufferPosition = this.getBufferPosition();
- const scanRange = Range(
- currentBufferPosition,
- this.editor.getEofBufferPosition()
- );
- const range = this.editor.buffer.findInRangeSync(
- options.wordRegex || this.wordRegExp(),
- scanRange
- );
- if (range) {
- if (range.start.row > currentBufferPosition.row) {
- return Point(range.start.row, 0);
- } else if (currentBufferPosition.isLessThan(range.start)) {
- return Point.fromObject(range.start);
- } else {
- return Point.fromObject(range.end);
- }
- } else {
- return currentBufferPosition;
- }
- }
- // Public: Retrieves the buffer position of where the current word starts.
- //
- // * `options` (optional) An {Object} with the following keys:
- // * `wordRegex` A {RegExp} indicating what constitutes a "word"
- // (default: {::wordRegExp}).
- // * `includeNonWordCharacters` A {Boolean} indicating whether to include
- // non-word characters in the default word regex.
- // Has no effect if wordRegex is set.
- // * `allowPrevious` A {Boolean} indicating whether the beginning of the
- // previous word can be returned.
- //
- // Returns a {Range}.
- getBeginningOfCurrentWordBufferPosition(options = {}) {
- const allowPrevious = options.allowPrevious !== false;
- const position = this.getBufferPosition();
- const scanRange = allowPrevious
- ? new Range(new Point(position.row - 1, 0), position)
- : new Range(new Point(position.row, 0), position);
- const ranges = this.editor.buffer.findAllInRangeSync(
- options.wordRegex || this.wordRegExp(options),
- scanRange
- );
- let result;
- for (let range of ranges) {
- if (position.isLessThanOrEqual(range.start)) break;
- if (allowPrevious || position.isLessThanOrEqual(range.end))
- result = Point.fromObject(range.start);
- }
- return result || (allowPrevious ? new Point(0, 0) : position);
- }
- // Public: Retrieves the buffer position of where the current word ends.
- //
- // * `options` (optional) {Object} with the following keys:
- // * `wordRegex` A {RegExp} indicating what constitutes a "word"
- // (default: {::wordRegExp})
- // * `includeNonWordCharacters` A Boolean indicating whether to include
- // non-word characters in the default word regex. Has no effect if
- // wordRegex is set.
- //
- // Returns a {Range}.
- getEndOfCurrentWordBufferPosition(options = {}) {
- const allowNext = options.allowNext !== false;
- const position = this.getBufferPosition();
- const scanRange = allowNext
- ? new Range(position, new Point(position.row + 2, 0))
- : new Range(position, new Point(position.row, Infinity));
- const ranges = this.editor.buffer.findAllInRangeSync(
- options.wordRegex || this.wordRegExp(options),
- scanRange
- );
- for (let range of ranges) {
- if (position.isLessThan(range.start) && !allowNext) break;
- if (position.isLessThan(range.end)) return Point.fromObject(range.end);
- }
- return allowNext ? this.editor.getEofBufferPosition() : position;
- }
- // Public: Retrieves the buffer position of where the next word starts.
- //
- // * `options` (optional) {Object}
- // * `wordRegex` A {RegExp} indicating what constitutes a "word"
- // (default: {::wordRegExp}).
- //
- // Returns a {Range}
- getBeginningOfNextWordBufferPosition(options = {}) {
- const currentBufferPosition = this.getBufferPosition();
- const start = this.isInsideWord(options)
- ? this.getEndOfCurrentWordBufferPosition(options)
- : currentBufferPosition;
- const scanRange = [start, this.editor.getEofBufferPosition()];
- let beginningOfNextWordPosition;
- this.editor.scanInBufferRange(
- options.wordRegex || this.wordRegExp(),
- scanRange,
- ({ range, stop }) => {
- beginningOfNextWordPosition = range.start;
- stop();
- }
- );
- return beginningOfNextWordPosition || currentBufferPosition;
- }
- // Public: Returns the buffer Range occupied by the word located under the cursor.
- //
- // * `options` (optional) {Object}
- // * `wordRegex` A {RegExp} indicating what constitutes a "word"
- // (default: {::wordRegExp}).
- getCurrentWordBufferRange(options = {}) {
- const position = this.getBufferPosition();
- const ranges = this.editor.buffer.findAllInRangeSync(
- options.wordRegex || this.wordRegExp(options),
- new Range(new Point(position.row, 0), new Point(position.row, Infinity))
- );
- const range = ranges.find(
- range =>
- range.end.column >= position.column &&
- range.start.column <= position.column
- );
- return range ? Range.fromObject(range) : new Range(position, position);
- }
- // Public: Returns the buffer Range for the current line.
- //
- // * `options` (optional) {Object}
- // * `includeNewline` A {Boolean} which controls whether the Range should
- // include the newline.
- getCurrentLineBufferRange(options) {
- return this.editor.bufferRangeForBufferRow(this.getBufferRow(), options);
- }
- // Public: Retrieves the range for the current paragraph.
- //
- // A paragraph is defined as a block of text surrounded by empty lines or comments.
- //
- // Returns a {Range}.
- getCurrentParagraphBufferRange() {
- return this.editor.rowRangeForParagraphAtBufferRow(this.getBufferRow());
- }
- // Public: Returns the characters preceding the cursor in the current word.
- getCurrentWordPrefix() {
- return this.editor.getTextInBufferRange([
- this.getBeginningOfCurrentWordBufferPosition(),
- this.getBufferPosition()
- ]);
- }
- /*
- Section: Visibility
- */
- /*
- Section: Comparing to another cursor
- */
- // Public: Compare this cursor's buffer position to another cursor's buffer position.
- //
- // See {Point::compare} for more details.
- //
- // * `otherCursor`{Cursor} to compare against
- compare(otherCursor) {
- return this.getBufferPosition().compare(otherCursor.getBufferPosition());
- }
- /*
- Section: Utilities
- */
- // Public: Deselects the current selection.
- clearSelection(options) {
- if (this.selection) this.selection.clear(options);
- }
- // Public: Get the RegExp used by the cursor to determine what a "word" is.
- //
- // * `options` (optional) {Object} with the following keys:
- // * `includeNonWordCharacters` A {Boolean} indicating whether to include
- // non-word characters in the regex. (default: true)
- //
- // Returns a {RegExp}.
- wordRegExp(options) {
- const nonWordCharacters = _.escapeRegExp(this.getNonWordCharacters());
- let source = `^[\t ]*$|[^\\s${nonWordCharacters}]+`;
- if (!options || options.includeNonWordCharacters !== false) {
- source += `|${`[${nonWordCharacters}]+`}`;
- }
- return new RegExp(source, 'g');
- }
- // Public: Get the RegExp used by the cursor to determine what a "subword" is.
- //
- // * `options` (optional) {Object} with the following keys:
- // * `backwards` A {Boolean} indicating whether to look forwards or backwards
- // for the next subword. (default: false)
- //
- // Returns a {RegExp}.
- subwordRegExp(options = {}) {
- const nonWordCharacters = this.getNonWordCharacters();
- const lowercaseLetters = 'a-z\\u00DF-\\u00F6\\u00F8-\\u00FF';
- const uppercaseLetters = 'A-Z\\u00C0-\\u00D6\\u00D8-\\u00DE';
- const snakeCamelSegment = `[${uppercaseLetters}]?[${lowercaseLetters}]+`;
- const segments = [
- '^[\t ]+',
- '[\t ]+$',
- `[${uppercaseLetters}]+(?![${lowercaseLetters}])`,
- '\\d+'
- ];
- if (options.backwards) {
- segments.push(`${snakeCamelSegment}_*`);
- segments.push(`[${_.escapeRegExp(nonWordCharacters)}]+\\s*`);
- } else {
- segments.push(`_*${snakeCamelSegment}`);
- segments.push(`\\s*[${_.escapeRegExp(nonWordCharacters)}]+`);
- }
- segments.push('_+');
- return new RegExp(segments.join('|'), 'g');
- }
- /*
- Section: Private
- */
- getNonWordCharacters() {
- return this.editor.getNonWordCharacters(this.getBufferPosition());
- }
- changePosition(options, fn) {
- this.clearSelection({ autoscroll: false });
- fn();
- this.goalColumn = null;
- const autoscroll =
- options && options.autoscroll != null
- ? options.autoscroll
- : this.isLastCursor();
- if (autoscroll) this.autoscroll();
- }
- getScreenRange() {
- const { row, column } = this.getScreenPosition();
- return new Range(new Point(row, column), new Point(row, column + 1));
- }
- autoscroll(options = {}) {
- options.clip = false;
- this.editor.scrollToScreenRange(this.getScreenRange(), options);
- }
- getBeginningOfNextParagraphBufferPosition() {
- const start = this.getBufferPosition();
- const eof = this.editor.getEofBufferPosition();
- const scanRange = [start, eof];
- const { row, column } = eof;
- let position = new Point(row, column - 1);
- this.editor.scanInBufferRange(
- EmptyLineRegExp,
- scanRange,
- ({ range, stop }) => {
- position = range.start.traverse(Point(1, 0));
- if (!position.isEqual(start)) stop();
- }
- );
- return position;
- }
- getBeginningOfPreviousParagraphBufferPosition() {
- const start = this.getBufferPosition();
- const { row, column } = start;
- const scanRange = [[row - 1, column], [0, 0]];
- let position = new Point(0, 0);
- this.editor.backwardsScanInBufferRange(
- EmptyLineRegExp,
- scanRange,
- ({ range, stop }) => {
- position = range.start.traverse(Point(1, 0));
- if (!position.isEqual(start)) stop();
- }
- );
- return position;
- }
- };
|