aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py
diff options
context:
space:
mode:
authorIvan Blinkov <ivan@blinkov.ru>2022-02-10 16:47:10 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 16:47:10 +0300
commit1aeb9a455974457866f78722ad98114bafc84e8a (patch)
treee4340eaf1668684d83a0a58c36947c5def5350ad /contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py
parentbd5ef432f5cfb1e18851381329d94665a4c22470 (diff)
downloadydb-1aeb9a455974457866f78722ad98114bafc84e8a.tar.gz
Restoring authorship annotation for Ivan Blinkov <ivan@blinkov.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py')
-rw-r--r--contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py1308
1 files changed, 654 insertions, 654 deletions
diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py
index 25d817ddd0d..68d6829bad3 100644
--- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py
+++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py
@@ -1,41 +1,41 @@
-"""
+"""
The `Document` that implements all the text operations/querying.
-"""
-from __future__ import unicode_literals
-
+"""
+from __future__ import unicode_literals
+
import bisect
-import re
-import six
-import string
+import re
+import six
+import string
import weakref
from six.moves import range, map
-
+
from .selection import SelectionType, SelectionState, PasteMode
-from .clipboard import ClipboardData
-
-__all__ = ('Document',)
-
-
-# Regex for finding "words" in documents. (We consider a group of alnum
-# characters a word, but also a group of special characters a word, as long as
-# it doesn't contain a space.)
-# (This is a 'word' in Vi.)
-_FIND_WORD_RE = re.compile(r'([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)')
-_FIND_CURRENT_WORD_RE = re.compile(r'^([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)')
-_FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r'^(([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)\s*)')
-
-# Regex for finding "WORDS" in documents.
-# (This is a 'WORD in Vi.)
-_FIND_BIG_WORD_RE = re.compile(r'([^\s]+)')
-_FIND_CURRENT_BIG_WORD_RE = re.compile(r'^([^\s]+)')
-_FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r'^([^\s]+\s*)')
-
+from .clipboard import ClipboardData
+
+__all__ = ('Document',)
+
+
+# Regex for finding "words" in documents. (We consider a group of alnum
+# characters a word, but also a group of special characters a word, as long as
+# it doesn't contain a space.)
+# (This is a 'word' in Vi.)
+_FIND_WORD_RE = re.compile(r'([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)')
+_FIND_CURRENT_WORD_RE = re.compile(r'^([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)')
+_FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r'^(([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)\s*)')
+
+# Regex for finding "WORDS" in documents.
+# (This is a 'WORD in Vi.)
+_FIND_BIG_WORD_RE = re.compile(r'([^\s]+)')
+_FIND_CURRENT_BIG_WORD_RE = re.compile(r'^([^\s]+)')
+_FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r'^([^\s]+\s*)')
+
# Share the Document._cache between all Document instances.
# (Document instances are considered immutable. That means that if another
# `Document` is constructed with the same text, it should have the same
# `_DocumentCache`.)
_text_to_document_cache = weakref.WeakValueDictionary() # Maps document.text to DocumentCache instance.
-
+
class _ImmutableLineList(list):
"""
@@ -65,42 +65,42 @@ class _DocumentCache(object):
self.line_indexes = None
-class Document(object):
- """
- This is a immutable class around the text and cursor position, and contains
- methods for querying this data, e.g. to give the text before the cursor.
-
- This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer`
- object, and accessed as the `document` property of that class.
-
- :param text: string
- :param cursor_position: int
- :param selection: :class:`.SelectionState`
- """
+class Document(object):
+ """
+ This is a immutable class around the text and cursor position, and contains
+ methods for querying this data, e.g. to give the text before the cursor.
+
+ This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer`
+ object, and accessed as the `document` property of that class.
+
+ :param text: string
+ :param cursor_position: int
+ :param selection: :class:`.SelectionState`
+ """
__slots__ = ('_text', '_cursor_position', '_selection', '_cache')
-
- def __init__(self, text='', cursor_position=None, selection=None):
- assert isinstance(text, six.text_type), 'Got %r' % text
- assert selection is None or isinstance(selection, SelectionState)
-
- # Check cursor position. It can also be right after the end. (Where we
- # insert text.)
- assert cursor_position is None or cursor_position <= len(text), AssertionError(
- 'cursor_position=%r, len_text=%r' % (cursor_position, len(text)))
-
- # By default, if no cursor position was given, make sure to put the
- # cursor position is at the end of the document. This is what makes
- # sense in most places.
- if cursor_position is None:
- cursor_position = len(text)
-
+
+ def __init__(self, text='', cursor_position=None, selection=None):
+ assert isinstance(text, six.text_type), 'Got %r' % text
+ assert selection is None or isinstance(selection, SelectionState)
+
+ # Check cursor position. It can also be right after the end. (Where we
+ # insert text.)
+ assert cursor_position is None or cursor_position <= len(text), AssertionError(
+ 'cursor_position=%r, len_text=%r' % (cursor_position, len(text)))
+
+ # By default, if no cursor position was given, make sure to put the
+ # cursor position is at the end of the document. This is what makes
+ # sense in most places.
+ if cursor_position is None:
+ cursor_position = len(text)
+
# Keep these attributes private. A `Document` really has to be
# considered to be immutable, because otherwise the caching will break
# things. Because of that, we wrap these into read-only properties.
self._text = text
self._cursor_position = cursor_position
self._selection = selection
-
+
# Cache for lines/indexes. (Shared with other Document instances that
# contain the same text.
try:
@@ -115,10 +115,10 @@ class Document(object):
# self._cache = _text_to_document_cache.setdefault(self.text, _DocumentCache())
# assert self._cache
- def __repr__(self):
- return '%s(%r, %r)' % (self.__class__.__name__, self.text, self.cursor_position)
-
- @property
+ def __repr__(self):
+ return '%s(%r, %r)' % (self.__class__.__name__, self.text, self.cursor_position)
+
+ @property
def text(self):
" The document text. "
return self._text
@@ -134,47 +134,47 @@ class Document(object):
return self._selection
@property
- def current_char(self):
- """ Return character under cursor or an empty string. """
- return self._get_char_relative_to_cursor(0) or ''
-
- @property
- def char_before_cursor(self):
- """ Return character before the cursor or an empty string. """
- return self._get_char_relative_to_cursor(-1) or ''
-
- @property
- def text_before_cursor(self):
- return self.text[:self.cursor_position:]
-
- @property
- def text_after_cursor(self):
- return self.text[self.cursor_position:]
-
- @property
- def current_line_before_cursor(self):
- """ Text from the start of the line until the cursor. """
+ def current_char(self):
+ """ Return character under cursor or an empty string. """
+ return self._get_char_relative_to_cursor(0) or ''
+
+ @property
+ def char_before_cursor(self):
+ """ Return character before the cursor or an empty string. """
+ return self._get_char_relative_to_cursor(-1) or ''
+
+ @property
+ def text_before_cursor(self):
+ return self.text[:self.cursor_position:]
+
+ @property
+ def text_after_cursor(self):
+ return self.text[self.cursor_position:]
+
+ @property
+ def current_line_before_cursor(self):
+ """ Text from the start of the line until the cursor. """
_, _, text = self.text_before_cursor.rpartition('\n')
return text
-
- @property
- def current_line_after_cursor(self):
- """ Text from the cursor until the end of the line. """
+
+ @property
+ def current_line_after_cursor(self):
+ """ Text from the cursor until the end of the line. """
text, _, _ = self.text_after_cursor.partition('\n')
return text
-
- @property
- def lines(self):
+
+ @property
+ def lines(self):
"""
Array of all the lines.
"""
- # Cache, because this one is reused very often.
+ # Cache, because this one is reused very often.
if self._cache.lines is None:
self._cache.lines = _ImmutableLineList(self.text.split('\n'))
-
+
return self._cache.lines
-
- @property
+
+ @property
def _line_start_indexes(self):
"""
Array pointing to the start indexes of all the lines.
@@ -203,75 +203,75 @@ class Document(object):
return self._cache.line_indexes
@property
- def lines_from_current(self):
- """
- Array of the lines starting from the current line, until the last line.
- """
- return self.lines[self.cursor_position_row:]
-
- @property
- def line_count(self):
- r""" Return the number of lines in this document. If the document ends
- with a trailing \n, that counts as the beginning of a new line. """
- return len(self.lines)
-
- @property
- def current_line(self):
- """ Return the text on the line where the cursor is. (when the input
- consists of just one line, it equals `text`. """
- return self.current_line_before_cursor + self.current_line_after_cursor
-
- @property
- def leading_whitespace_in_current_line(self):
- """ The leading whitespace in the left margin of the current line. """
- current_line = self.current_line
- length = len(current_line) - len(current_line.lstrip())
- return current_line[:length]
-
- def _get_char_relative_to_cursor(self, offset=0):
- """
- Return character relative to cursor position, or empty string
- """
- try:
- return self.text[self.cursor_position + offset]
- except IndexError:
- return ''
-
- @property
- def on_first_line(self):
- """
- True when we are at the first line.
- """
- return self.cursor_position_row == 0
-
- @property
- def on_last_line(self):
- """
- True when we are at the last line.
- """
- return self.cursor_position_row == self.line_count - 1
-
- @property
- def cursor_position_row(self):
- """
- Current row. (0-based.)
- """
+ def lines_from_current(self):
+ """
+ Array of the lines starting from the current line, until the last line.
+ """
+ return self.lines[self.cursor_position_row:]
+
+ @property
+ def line_count(self):
+ r""" Return the number of lines in this document. If the document ends
+ with a trailing \n, that counts as the beginning of a new line. """
+ return len(self.lines)
+
+ @property
+ def current_line(self):
+ """ Return the text on the line where the cursor is. (when the input
+ consists of just one line, it equals `text`. """
+ return self.current_line_before_cursor + self.current_line_after_cursor
+
+ @property
+ def leading_whitespace_in_current_line(self):
+ """ The leading whitespace in the left margin of the current line. """
+ current_line = self.current_line
+ length = len(current_line) - len(current_line.lstrip())
+ return current_line[:length]
+
+ def _get_char_relative_to_cursor(self, offset=0):
+ """
+ Return character relative to cursor position, or empty string
+ """
+ try:
+ return self.text[self.cursor_position + offset]
+ except IndexError:
+ return ''
+
+ @property
+ def on_first_line(self):
+ """
+ True when we are at the first line.
+ """
+ return self.cursor_position_row == 0
+
+ @property
+ def on_last_line(self):
+ """
+ True when we are at the last line.
+ """
+ return self.cursor_position_row == self.line_count - 1
+
+ @property
+ def cursor_position_row(self):
+ """
+ Current row. (0-based.)
+ """
row, _ = self._find_line_start_index(self.cursor_position)
return row
-
- @property
- def cursor_position_col(self):
- """
- Current column. (0-based.)
- """
+
+ @property
+ def cursor_position_col(self):
+ """
+ Current column. (0-based.)
+ """
# (Don't use self.text_before_cursor to calculate this. Creating
# substrings and doing rsplit is too expensive for getting the cursor
# position.)
_, line_start_index = self._find_line_start_index(self.cursor_position)
return self.cursor_position - line_start_index
-
+
def _find_line_start_index(self, index):
- """
+ """
For the index of a character at a certain line, calculate the index of
the first character on that line.
@@ -284,23 +284,23 @@ class Document(object):
def translate_index_to_position(self, index):
"""
- Given an index for the text, return the corresponding (row, col) tuple.
- (0-based. Returns (0, 0) for index=0.)
- """
+ Given an index for the text, return the corresponding (row, col) tuple.
+ (0-based. Returns (0, 0) for index=0.)
+ """
# Find start of this line.
row, row_index = self._find_line_start_index(index)
col = index - row_index
+
+ return row, col
+
- return row, col
-
-
- def translate_row_col_to_index(self, row, col):
- """
- Given a (row, col) tuple, return the corresponding index.
- (Row and col params are 0-based.)
+ def translate_row_col_to_index(self, row, col):
+ """
+ Given a (row, col) tuple, return the corresponding index.
+ (Row and col params are 0-based.)
Negative row/col values are turned into zero.
- """
+ """
try:
result = self._line_start_indexes[row]
line = self.lines[row]
@@ -311,239 +311,239 @@ class Document(object):
else:
result = self._line_start_indexes[-1]
line = self.lines[-1]
-
+
result += max(0, min(col, len(line)))
- # Keep in range. (len(self.text) is included, because the cursor can be
- # right after the end of the text as well.)
- result = max(0, min(result, len(self.text)))
- return result
-
- @property
- def is_cursor_at_the_end(self):
- """ True when the cursor is at the end of the text. """
- return self.cursor_position == len(self.text)
-
- @property
- def is_cursor_at_the_end_of_line(self):
- """ True when the cursor is at the end of this line. """
+ # Keep in range. (len(self.text) is included, because the cursor can be
+ # right after the end of the text as well.)
+ result = max(0, min(result, len(self.text)))
+ return result
+
+ @property
+ def is_cursor_at_the_end(self):
+ """ True when the cursor is at the end of the text. """
+ return self.cursor_position == len(self.text)
+
+ @property
+ def is_cursor_at_the_end_of_line(self):
+ """ True when the cursor is at the end of this line. """
return self.current_char in ('\n', '')
-
- def has_match_at_current_position(self, sub):
- """
- `True` when this substring is found at the cursor position.
- """
+
+ def has_match_at_current_position(self, sub):
+ """
+ `True` when this substring is found at the cursor position.
+ """
return self.text.find(sub, self.cursor_position) == self.cursor_position
-
- def find(self, sub, in_current_line=False, include_current_position=False,
+
+ def find(self, sub, in_current_line=False, include_current_position=False,
ignore_case=False, count=1):
- """
- Find `text` after the cursor, return position relative to the cursor
- position. Return `None` if nothing was found.
-
- :param count: Find the n-th occurance.
- """
- assert isinstance(ignore_case, bool)
-
- if in_current_line:
- text = self.current_line_after_cursor
- else:
- text = self.text_after_cursor
-
- if not include_current_position:
- if len(text) == 0:
- return # (Otherwise, we always get a match for the empty string.)
- else:
- text = text[1:]
-
- flags = re.IGNORECASE if ignore_case else 0
- iterator = re.finditer(re.escape(sub), text, flags)
-
- try:
- for i, match in enumerate(iterator):
- if i + 1 == count:
- if include_current_position:
- return match.start(0)
- else:
- return match.start(0) + 1
- except StopIteration:
- pass
-
- def find_all(self, sub, ignore_case=False):
- """
- Find all occurances of the substring. Return a list of absolute
- positions in the document.
- """
- flags = re.IGNORECASE if ignore_case else 0
- return [a.start() for a in re.finditer(re.escape(sub), self.text, flags)]
-
- def find_backwards(self, sub, in_current_line=False, ignore_case=False, count=1):
- """
- Find `text` before the cursor, return position relative to the cursor
- position. Return `None` if nothing was found.
-
- :param count: Find the n-th occurance.
- """
- if in_current_line:
- before_cursor = self.current_line_before_cursor[::-1]
- else:
- before_cursor = self.text_before_cursor[::-1]
-
- flags = re.IGNORECASE if ignore_case else 0
- iterator = re.finditer(re.escape(sub[::-1]), before_cursor, flags)
-
- try:
- for i, match in enumerate(iterator):
- if i + 1 == count:
- return - match.start(0) - len(sub)
- except StopIteration:
- pass
-
- def get_word_before_cursor(self, WORD=False):
- """
- Give the word before the cursor.
- If we have whitespace before the cursor this returns an empty string.
- """
- if self.text_before_cursor[-1:].isspace():
- return ''
- else:
- return self.text_before_cursor[self.find_start_of_previous_word(WORD=WORD):]
-
- def find_start_of_previous_word(self, count=1, WORD=False):
- """
- Return an index relative to the cursor position pointing to the start
- of the previous word. Return `None` if nothing was found.
- """
- # Reverse the text before the cursor, in order to do an efficient
- # backwards search.
- text_before_cursor = self.text_before_cursor[::-1]
-
- regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
- iterator = regex.finditer(text_before_cursor)
-
- try:
- for i, match in enumerate(iterator):
- if i + 1 == count:
- return - match.end(1)
- except StopIteration:
- pass
-
- def find_boundaries_of_current_word(self, WORD=False, include_leading_whitespace=False,
- include_trailing_whitespace=False):
- """
- Return the relative boundaries (startpos, endpos) of the current word under the
- cursor. (This is at the current line, because line boundaries obviously
- don't belong to any word.)
- If not on a word, this returns (0,0)
- """
- text_before_cursor = self.current_line_before_cursor[::-1]
- text_after_cursor = self.current_line_after_cursor
-
- def get_regex(include_whitespace):
- return {
- (False, False): _FIND_CURRENT_WORD_RE,
- (False, True): _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE,
- (True, False): _FIND_CURRENT_BIG_WORD_RE,
- (True, True): _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE,
- }[(WORD, include_whitespace)]
-
- match_before = get_regex(include_leading_whitespace).search(text_before_cursor)
- match_after = get_regex(include_trailing_whitespace).search(text_after_cursor)
-
- # When there is a match before and after, and we're not looking for
- # WORDs, make sure that both the part before and after the cursor are
- # either in the [a-zA-Z_] alphabet or not. Otherwise, drop the part
- # before the cursor.
- if not WORD and match_before and match_after:
- c1 = self.text[self.cursor_position - 1]
- c2 = self.text[self.cursor_position]
- alphabet = string.ascii_letters + '0123456789_'
-
- if (c1 in alphabet) != (c2 in alphabet):
- match_before = None
-
- return (
- - match_before.end(1) if match_before else 0,
- match_after.end(1) if match_after else 0
- )
-
- def get_word_under_cursor(self, WORD=False):
- """
- Return the word, currently below the cursor.
- This returns an empty string when the cursor is on a whitespace region.
- """
- start, end = self.find_boundaries_of_current_word(WORD=WORD)
- return self.text[self.cursor_position + start: self.cursor_position + end]
-
- def find_next_word_beginning(self, count=1, WORD=False):
- """
- Return an index relative to the cursor position pointing to the start
- of the next word. Return `None` if nothing was found.
- """
+ """
+ Find `text` after the cursor, return position relative to the cursor
+ position. Return `None` if nothing was found.
+
+ :param count: Find the n-th occurance.
+ """
+ assert isinstance(ignore_case, bool)
+
+ if in_current_line:
+ text = self.current_line_after_cursor
+ else:
+ text = self.text_after_cursor
+
+ if not include_current_position:
+ if len(text) == 0:
+ return # (Otherwise, we always get a match for the empty string.)
+ else:
+ text = text[1:]
+
+ flags = re.IGNORECASE if ignore_case else 0
+ iterator = re.finditer(re.escape(sub), text, flags)
+
+ try:
+ for i, match in enumerate(iterator):
+ if i + 1 == count:
+ if include_current_position:
+ return match.start(0)
+ else:
+ return match.start(0) + 1
+ except StopIteration:
+ pass
+
+ def find_all(self, sub, ignore_case=False):
+ """
+ Find all occurances of the substring. Return a list of absolute
+ positions in the document.
+ """
+ flags = re.IGNORECASE if ignore_case else 0
+ return [a.start() for a in re.finditer(re.escape(sub), self.text, flags)]
+
+ def find_backwards(self, sub, in_current_line=False, ignore_case=False, count=1):
+ """
+ Find `text` before the cursor, return position relative to the cursor
+ position. Return `None` if nothing was found.
+
+ :param count: Find the n-th occurance.
+ """
+ if in_current_line:
+ before_cursor = self.current_line_before_cursor[::-1]
+ else:
+ before_cursor = self.text_before_cursor[::-1]
+
+ flags = re.IGNORECASE if ignore_case else 0
+ iterator = re.finditer(re.escape(sub[::-1]), before_cursor, flags)
+
+ try:
+ for i, match in enumerate(iterator):
+ if i + 1 == count:
+ return - match.start(0) - len(sub)
+ except StopIteration:
+ pass
+
+ def get_word_before_cursor(self, WORD=False):
+ """
+ Give the word before the cursor.
+ If we have whitespace before the cursor this returns an empty string.
+ """
+ if self.text_before_cursor[-1:].isspace():
+ return ''
+ else:
+ return self.text_before_cursor[self.find_start_of_previous_word(WORD=WORD):]
+
+ def find_start_of_previous_word(self, count=1, WORD=False):
+ """
+ Return an index relative to the cursor position pointing to the start
+ of the previous word. Return `None` if nothing was found.
+ """
+ # Reverse the text before the cursor, in order to do an efficient
+ # backwards search.
+ text_before_cursor = self.text_before_cursor[::-1]
+
+ regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
+ iterator = regex.finditer(text_before_cursor)
+
+ try:
+ for i, match in enumerate(iterator):
+ if i + 1 == count:
+ return - match.end(1)
+ except StopIteration:
+ pass
+
+ def find_boundaries_of_current_word(self, WORD=False, include_leading_whitespace=False,
+ include_trailing_whitespace=False):
+ """
+ Return the relative boundaries (startpos, endpos) of the current word under the
+ cursor. (This is at the current line, because line boundaries obviously
+ don't belong to any word.)
+ If not on a word, this returns (0,0)
+ """
+ text_before_cursor = self.current_line_before_cursor[::-1]
+ text_after_cursor = self.current_line_after_cursor
+
+ def get_regex(include_whitespace):
+ return {
+ (False, False): _FIND_CURRENT_WORD_RE,
+ (False, True): _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE,
+ (True, False): _FIND_CURRENT_BIG_WORD_RE,
+ (True, True): _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE,
+ }[(WORD, include_whitespace)]
+
+ match_before = get_regex(include_leading_whitespace).search(text_before_cursor)
+ match_after = get_regex(include_trailing_whitespace).search(text_after_cursor)
+
+ # When there is a match before and after, and we're not looking for
+ # WORDs, make sure that both the part before and after the cursor are
+ # either in the [a-zA-Z_] alphabet or not. Otherwise, drop the part
+ # before the cursor.
+ if not WORD and match_before and match_after:
+ c1 = self.text[self.cursor_position - 1]
+ c2 = self.text[self.cursor_position]
+ alphabet = string.ascii_letters + '0123456789_'
+
+ if (c1 in alphabet) != (c2 in alphabet):
+ match_before = None
+
+ return (
+ - match_before.end(1) if match_before else 0,
+ match_after.end(1) if match_after else 0
+ )
+
+ def get_word_under_cursor(self, WORD=False):
+ """
+ Return the word, currently below the cursor.
+ This returns an empty string when the cursor is on a whitespace region.
+ """
+ start, end = self.find_boundaries_of_current_word(WORD=WORD)
+ return self.text[self.cursor_position + start: self.cursor_position + end]
+
+ def find_next_word_beginning(self, count=1, WORD=False):
+ """
+ Return an index relative to the cursor position pointing to the start
+ of the next word. Return `None` if nothing was found.
+ """
if count < 0:
return self.find_previous_word_beginning(count=-count, WORD=WORD)
- regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
- iterator = regex.finditer(self.text_after_cursor)
-
- try:
- for i, match in enumerate(iterator):
- # Take first match, unless it's the word on which we're right now.
- if i == 0 and match.start(1) == 0:
- count += 1
-
- if i + 1 == count:
- return match.start(1)
- except StopIteration:
- pass
-
- def find_next_word_ending(self, include_current_position=False, count=1, WORD=False):
- """
- Return an index relative to the cursor position pointing to the end
- of the next word. Return `None` if nothing was found.
- """
+ regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
+ iterator = regex.finditer(self.text_after_cursor)
+
+ try:
+ for i, match in enumerate(iterator):
+ # Take first match, unless it's the word on which we're right now.
+ if i == 0 and match.start(1) == 0:
+ count += 1
+
+ if i + 1 == count:
+ return match.start(1)
+ except StopIteration:
+ pass
+
+ def find_next_word_ending(self, include_current_position=False, count=1, WORD=False):
+ """
+ Return an index relative to the cursor position pointing to the end
+ of the next word. Return `None` if nothing was found.
+ """
if count < 0:
return self.find_previous_word_ending(count=-count, WORD=WORD)
- if include_current_position:
- text = self.text_after_cursor
- else:
- text = self.text_after_cursor[1:]
-
- regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
- iterable = regex.finditer(text)
-
- try:
- for i, match in enumerate(iterable):
- if i + 1 == count:
- value = match.end(1)
-
- if include_current_position:
- return value
- else:
- return value + 1
-
- except StopIteration:
- pass
-
- def find_previous_word_beginning(self, count=1, WORD=False):
- """
- Return an index relative to the cursor position pointing to the start
+ if include_current_position:
+ text = self.text_after_cursor
+ else:
+ text = self.text_after_cursor[1:]
+
+ regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
+ iterable = regex.finditer(text)
+
+ try:
+ for i, match in enumerate(iterable):
+ if i + 1 == count:
+ value = match.end(1)
+
+ if include_current_position:
+ return value
+ else:
+ return value + 1
+
+ except StopIteration:
+ pass
+
+ def find_previous_word_beginning(self, count=1, WORD=False):
+ """
+ Return an index relative to the cursor position pointing to the start
of the previous word. Return `None` if nothing was found.
- """
+ """
if count < 0:
return self.find_next_word_beginning(count=-count, WORD=WORD)
- regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
- iterator = regex.finditer(self.text_before_cursor[::-1])
-
- try:
- for i, match in enumerate(iterator):
- if i + 1 == count:
- return - match.end(1)
- except StopIteration:
- pass
-
+ regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
+ iterator = regex.finditer(self.text_before_cursor[::-1])
+
+ try:
+ for i, match in enumerate(iterator):
+ if i + 1 == count:
+ return - match.end(1)
+ except StopIteration:
+ pass
+
def find_previous_word_ending(self, count=1, WORD=False):
"""
Return an index relative to the cursor position pointing to the end
@@ -568,93 +568,93 @@ class Document(object):
except StopIteration:
pass
- def find_next_matching_line(self, match_func, count=1):
- """
- Look downwards for empty lines.
- Return the line index, relative to the current line.
- """
- result = None
-
- for index, line in enumerate(self.lines[self.cursor_position_row + 1:]):
- if match_func(line):
- result = 1 + index
- count -= 1
-
- if count == 0:
- break
-
- return result
-
- def find_previous_matching_line(self, match_func, count=1):
- """
- Look upwards for empty lines.
- Return the line index, relative to the current line.
- """
- result = None
-
- for index, line in enumerate(self.lines[:self.cursor_position_row][::-1]):
- if match_func(line):
- result = -1 - index
- count -= 1
-
- if count == 0:
- break
-
- return result
-
- def get_cursor_left_position(self, count=1):
- """
- Relative position for cursor left.
- """
+ def find_next_matching_line(self, match_func, count=1):
+ """
+ Look downwards for empty lines.
+ Return the line index, relative to the current line.
+ """
+ result = None
+
+ for index, line in enumerate(self.lines[self.cursor_position_row + 1:]):
+ if match_func(line):
+ result = 1 + index
+ count -= 1
+
+ if count == 0:
+ break
+
+ return result
+
+ def find_previous_matching_line(self, match_func, count=1):
+ """
+ Look upwards for empty lines.
+ Return the line index, relative to the current line.
+ """
+ result = None
+
+ for index, line in enumerate(self.lines[:self.cursor_position_row][::-1]):
+ if match_func(line):
+ result = -1 - index
+ count -= 1
+
+ if count == 0:
+ break
+
+ return result
+
+ def get_cursor_left_position(self, count=1):
+ """
+ Relative position for cursor left.
+ """
if count < 0:
return self.get_cursor_right_position(-count)
- return - min(self.cursor_position_col, count)
-
- def get_cursor_right_position(self, count=1):
- """
- Relative position for cursor_right.
- """
+ return - min(self.cursor_position_col, count)
+
+ def get_cursor_right_position(self, count=1):
+ """
+ Relative position for cursor_right.
+ """
if count < 0:
return self.get_cursor_left_position(-count)
- return min(count, len(self.current_line_after_cursor))
-
+ return min(count, len(self.current_line_after_cursor))
+
def get_cursor_up_position(self, count=1, preferred_column=None):
- """
- Return the relative cursor position (character index) where we would be if the
- user pressed the arrow-up button.
+ """
+ Return the relative cursor position (character index) where we would be if the
+ user pressed the arrow-up button.
:param preferred_column: When given, go to this column instead of
staying at the current column.
- """
- assert count >= 1
+ """
+ assert count >= 1
column = self.cursor_position_col if preferred_column is None else preferred_column
-
+
return self.translate_row_col_to_index(
max(0, self.cursor_position_row - count), column) - self.cursor_position
-
+
def get_cursor_down_position(self, count=1, preferred_column=None):
"""
Return the relative cursor position (character index) where we would be if the
user pressed the arrow-down button.
-
+
:param preferred_column: When given, go to this column instead of
staying at the current column.
"""
assert count >= 1
column = self.cursor_position_col if preferred_column is None else preferred_column
-
+
return self.translate_row_col_to_index(
self.cursor_position_row + count, column) - self.cursor_position
-
+
def find_enclosing_bracket_right(self, left_ch, right_ch, end_pos=None):
"""
Find the right bracket enclosing current position. Return the relative
position to the cursor position.
-
+
When `end_pos` is given, don't look past the position.
- """
+ """
if self.current_char == right_ch:
return 0
@@ -678,137 +678,137 @@ class Document(object):
return i - self.cursor_position
def find_enclosing_bracket_left(self, left_ch, right_ch, start_pos=None):
- """
+ """
Find the left bracket enclosing current position. Return the relative
position to the cursor position.
-
+
When `start_pos` is given, don't look past the position.
"""
if self.current_char == left_ch:
return 0
-
+
if start_pos is None:
start_pos = 0
else:
start_pos = max(0, start_pos)
-
+
stack = 1
-
+
# Look backward.
for i in range(self.cursor_position - 1, start_pos - 1, -1):
c = self.text[i]
-
+
if c == right_ch:
stack += 1
elif c == left_ch:
stack -= 1
-
+
if stack == 0:
return i - self.cursor_position
-
+
def find_matching_bracket_position(self, start_pos=None, end_pos=None):
- """
- Return relative cursor position of matching [, (, { or < bracket.
+ """
+ Return relative cursor position of matching [, (, { or < bracket.
When `start_pos` or `end_pos` are given. Don't look past the positions.
- """
-
+ """
+
# Look for a match.
- for A, B in '()', '[]', '{}', '<>':
- if self.current_char == A:
+ for A, B in '()', '[]', '{}', '<>':
+ if self.current_char == A:
return self.find_enclosing_bracket_right(A, B, end_pos=end_pos) or 0
- elif self.current_char == B:
+ elif self.current_char == B:
return self.find_enclosing_bracket_left(A, B, start_pos=start_pos) or 0
-
- return 0
-
- def get_start_of_document_position(self):
- """ Relative position for the start of the document. """
- return - self.cursor_position
-
- def get_end_of_document_position(self):
- """ Relative position for the end of the document. """
- return len(self.text) - self.cursor_position
-
- def get_start_of_line_position(self, after_whitespace=False):
- """ Relative position for the start of this line. """
- if after_whitespace:
- current_line = self.current_line
- return len(current_line) - len(current_line.lstrip()) - self.cursor_position_col
- else:
- return - len(self.current_line_before_cursor)
-
- def get_end_of_line_position(self):
- """ Relative position for the end of this line. """
- return len(self.current_line_after_cursor)
-
- def last_non_blank_of_current_line_position(self):
- """
- Relative position for the last non blank character of this line.
- """
+
+ return 0
+
+ def get_start_of_document_position(self):
+ """ Relative position for the start of the document. """
+ return - self.cursor_position
+
+ def get_end_of_document_position(self):
+ """ Relative position for the end of the document. """
+ return len(self.text) - self.cursor_position
+
+ def get_start_of_line_position(self, after_whitespace=False):
+ """ Relative position for the start of this line. """
+ if after_whitespace:
+ current_line = self.current_line
+ return len(current_line) - len(current_line.lstrip()) - self.cursor_position_col
+ else:
+ return - len(self.current_line_before_cursor)
+
+ def get_end_of_line_position(self):
+ """ Relative position for the end of this line. """
+ return len(self.current_line_after_cursor)
+
+ def last_non_blank_of_current_line_position(self):
+ """
+ Relative position for the last non blank character of this line.
+ """
return len(self.current_line.rstrip()) - self.cursor_position_col - 1
-
- def get_column_cursor_position(self, column):
- """
- Return the relative cursor position for this column at the current
- line. (It will stay between the boundaries of the line in case of a
- larger number.)
- """
- line_length = len(self.current_line)
- current_column = self.cursor_position_col
- column = max(0, min(line_length, column))
-
- return column - current_column
-
+
+ def get_column_cursor_position(self, column):
+ """
+ Return the relative cursor position for this column at the current
+ line. (It will stay between the boundaries of the line in case of a
+ larger number.)
+ """
+ line_length = len(self.current_line)
+ current_column = self.cursor_position_col
+ column = max(0, min(line_length, column))
+
+ return column - current_column
+
def selection_range(self): # XXX: shouldn't this return `None` if there is no selection???
- """
- Return (from, to) tuple of the selection.
- start and end position are included.
-
- This doesn't take the selection type into account. Use
- `selection_ranges` instead.
- """
- if self.selection:
- from_, to = sorted([self.cursor_position, self.selection.original_cursor_position])
- else:
- from_, to = self.cursor_position, self.cursor_position
-
- return from_, to
-
- def selection_ranges(self):
- """
- Return a list of (from, to) tuples for the selection or none if nothing
- was selected. start and end position are always included in the
- selection.
-
- This will yield several (from, to) tuples in case of a BLOCK selection.
- """
- if self.selection:
- from_, to = sorted([self.cursor_position, self.selection.original_cursor_position])
-
- if self.selection.type == SelectionType.BLOCK:
- from_line, from_column = self.translate_index_to_position(from_)
- to_line, to_column = self.translate_index_to_position(to)
- from_column, to_column = sorted([from_column, to_column])
- lines = self.lines
-
- for l in range(from_line, to_line + 1):
- line_length = len(lines[l])
- if from_column < line_length:
- yield (self.translate_row_col_to_index(l, from_column),
- self.translate_row_col_to_index(l, min(line_length - 1, to_column)))
- else:
- # In case of a LINES selection, go to the start/end of the lines.
- if self.selection.type == SelectionType.LINES:
+ """
+ Return (from, to) tuple of the selection.
+ start and end position are included.
+
+ This doesn't take the selection type into account. Use
+ `selection_ranges` instead.
+ """
+ if self.selection:
+ from_, to = sorted([self.cursor_position, self.selection.original_cursor_position])
+ else:
+ from_, to = self.cursor_position, self.cursor_position
+
+ return from_, to
+
+ def selection_ranges(self):
+ """
+ Return a list of (from, to) tuples for the selection or none if nothing
+ was selected. start and end position are always included in the
+ selection.
+
+ This will yield several (from, to) tuples in case of a BLOCK selection.
+ """
+ if self.selection:
+ from_, to = sorted([self.cursor_position, self.selection.original_cursor_position])
+
+ if self.selection.type == SelectionType.BLOCK:
+ from_line, from_column = self.translate_index_to_position(from_)
+ to_line, to_column = self.translate_index_to_position(to)
+ from_column, to_column = sorted([from_column, to_column])
+ lines = self.lines
+
+ for l in range(from_line, to_line + 1):
+ line_length = len(lines[l])
+ if from_column < line_length:
+ yield (self.translate_row_col_to_index(l, from_column),
+ self.translate_row_col_to_index(l, min(line_length - 1, to_column)))
+ else:
+ # In case of a LINES selection, go to the start/end of the lines.
+ if self.selection.type == SelectionType.LINES:
from_ = max(0, self.text.rfind('\n', 0, from_) + 1)
-
+
if self.text.find('\n', to) >= 0:
to = self.text.find('\n', to)
- else:
+ else:
to = len(self.text) - 1
-
- yield from_, to
-
+
+ yield from_, to
+
def selection_range_at_line(self, row):
"""
If the selection spans a portion of the given line, return a (from, to) tuple.
@@ -840,107 +840,107 @@ class Document(object):
return from_column, to_column
- def cut_selection(self):
- """
- Return a (:class:`.Document`, :class:`.ClipboardData`) tuple, where the
- document represents the new document when the selection is cut, and the
- clipboard data, represents whatever has to be put on the clipboard.
- """
- if self.selection:
- cut_parts = []
- remaining_parts = []
- new_cursor_position = self.cursor_position
-
- last_to = 0
- for from_, to in self.selection_ranges():
- if last_to == 0:
- new_cursor_position = from_
-
- remaining_parts.append(self.text[last_to:from_])
- cut_parts.append(self.text[from_:to + 1])
- last_to = to + 1
-
- remaining_parts.append(self.text[last_to:])
-
- cut_text = '\n'.join(cut_parts)
- remaining_text = ''.join(remaining_parts)
-
- # In case of a LINES selection, don't include the trailing newline.
- if self.selection.type == SelectionType.LINES and cut_text.endswith('\n'):
- cut_text = cut_text[:-1]
-
- return (Document(text=remaining_text, cursor_position=new_cursor_position),
- ClipboardData(cut_text, self.selection.type))
- else:
- return self, ClipboardData('')
-
+ def cut_selection(self):
+ """
+ Return a (:class:`.Document`, :class:`.ClipboardData`) tuple, where the
+ document represents the new document when the selection is cut, and the
+ clipboard data, represents whatever has to be put on the clipboard.
+ """
+ if self.selection:
+ cut_parts = []
+ remaining_parts = []
+ new_cursor_position = self.cursor_position
+
+ last_to = 0
+ for from_, to in self.selection_ranges():
+ if last_to == 0:
+ new_cursor_position = from_
+
+ remaining_parts.append(self.text[last_to:from_])
+ cut_parts.append(self.text[from_:to + 1])
+ last_to = to + 1
+
+ remaining_parts.append(self.text[last_to:])
+
+ cut_text = '\n'.join(cut_parts)
+ remaining_text = ''.join(remaining_parts)
+
+ # In case of a LINES selection, don't include the trailing newline.
+ if self.selection.type == SelectionType.LINES and cut_text.endswith('\n'):
+ cut_text = cut_text[:-1]
+
+ return (Document(text=remaining_text, cursor_position=new_cursor_position),
+ ClipboardData(cut_text, self.selection.type))
+ else:
+ return self, ClipboardData('')
+
def paste_clipboard_data(self, data, paste_mode=PasteMode.EMACS, count=1):
- """
- Return a new :class:`.Document` instance which contains the result if
- we would paste this data at the current cursor position.
-
+ """
+ Return a new :class:`.Document` instance which contains the result if
+ we would paste this data at the current cursor position.
+
:param paste_mode: Where to paste. (Before/after/emacs.)
- :param count: When >1, Paste multiple times.
- """
- assert isinstance(data, ClipboardData)
+ :param count: When >1, Paste multiple times.
+ """
+ assert isinstance(data, ClipboardData)
assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS)
-
+
before = (paste_mode == PasteMode.VI_BEFORE)
after = (paste_mode == PasteMode.VI_AFTER)
- if data.type == SelectionType.CHARACTERS:
+ if data.type == SelectionType.CHARACTERS:
if after:
- new_text = (self.text[:self.cursor_position + 1] + data.text * count +
- self.text[self.cursor_position + 1:])
+ new_text = (self.text[:self.cursor_position + 1] + data.text * count +
+ self.text[self.cursor_position + 1:])
else:
new_text = self.text_before_cursor + data.text * count + self.text_after_cursor
-
+
new_cursor_position = self.cursor_position + len(data.text) * count
if before:
new_cursor_position -= 1
- elif data.type == SelectionType.LINES:
- l = self.cursor_position_row
- if before:
- lines = self.lines[:l] + [data.text] * count + self.lines[l:]
- new_text = '\n'.join(lines)
- new_cursor_position = len(''.join(self.lines[:l])) + l
- else:
- lines = self.lines[:l + 1] + [data.text] * count + self.lines[l + 1:]
- new_cursor_position = len(''.join(self.lines[:l + 1])) + l + 1
- new_text = '\n'.join(lines)
-
- elif data.type == SelectionType.BLOCK:
+ elif data.type == SelectionType.LINES:
+ l = self.cursor_position_row
+ if before:
+ lines = self.lines[:l] + [data.text] * count + self.lines[l:]
+ new_text = '\n'.join(lines)
+ new_cursor_position = len(''.join(self.lines[:l])) + l
+ else:
+ lines = self.lines[:l + 1] + [data.text] * count + self.lines[l + 1:]
+ new_cursor_position = len(''.join(self.lines[:l + 1])) + l + 1
+ new_text = '\n'.join(lines)
+
+ elif data.type == SelectionType.BLOCK:
lines = self.lines[:]
- start_line = self.cursor_position_row
- start_column = self.cursor_position_col + (0 if before else 1)
-
- for i, line in enumerate(data.text.split('\n')):
- index = i + start_line
- if index >= len(lines):
- lines.append('')
-
- lines[index] = lines[index].ljust(start_column)
- lines[index] = lines[index][:start_column] + line * count + lines[index][start_column:]
-
- new_text = '\n'.join(lines)
- new_cursor_position = self.cursor_position + (0 if before else 1)
-
- return Document(text=new_text, cursor_position=new_cursor_position)
-
- def empty_line_count_at_the_end(self):
- """
- Return number of empty lines at the end of the document.
- """
- count = 0
- for line in self.lines[::-1]:
- if not line or line.isspace():
- count += 1
- else:
- break
-
- return count
-
+ start_line = self.cursor_position_row
+ start_column = self.cursor_position_col + (0 if before else 1)
+
+ for i, line in enumerate(data.text.split('\n')):
+ index = i + start_line
+ if index >= len(lines):
+ lines.append('')
+
+ lines[index] = lines[index].ljust(start_column)
+ lines[index] = lines[index][:start_column] + line * count + lines[index][start_column:]
+
+ new_text = '\n'.join(lines)
+ new_cursor_position = self.cursor_position + (0 if before else 1)
+
+ return Document(text=new_text, cursor_position=new_cursor_position)
+
+ def empty_line_count_at_the_end(self):
+ """
+ Return number of empty lines at the end of the document.
+ """
+ count = 0
+ for line in self.lines[::-1]:
+ if not line or line.isspace():
+ count += 1
+ else:
+ break
+
+ return count
+
def start_of_paragraph(self, count=1, before=False):
"""
Return the start of the current paragraph. (Relative cursor position.)
@@ -971,31 +971,31 @@ class Document(object):
else:
return len(self.text_after_cursor)
- # Modifiers.
-
- def insert_after(self, text):
- """
- Create a new document, with this text inserted after the buffer.
- It keeps selection ranges and cursor position in sync.
- """
- return Document(
- text=self.text + text,
- cursor_position=self.cursor_position,
- selection=self.selection)
-
- def insert_before(self, text):
- """
- Create a new document, with this text inserted before the buffer.
- It keeps selection ranges and cursor position in sync.
- """
- selection_state = self.selection
-
- if selection_state:
- selection_state = SelectionState(
- original_cursor_position=selection_state.original_cursor_position + len(text),
- type=selection_state.type)
-
- return Document(
- text=text + self.text,
- cursor_position=self.cursor_position + len(text),
- selection=selection_state)
+ # Modifiers.
+
+ def insert_after(self, text):
+ """
+ Create a new document, with this text inserted after the buffer.
+ It keeps selection ranges and cursor position in sync.
+ """
+ return Document(
+ text=self.text + text,
+ cursor_position=self.cursor_position,
+ selection=self.selection)
+
+ def insert_before(self, text):
+ """
+ Create a new document, with this text inserted before the buffer.
+ It keeps selection ranges and cursor position in sync.
+ """
+ selection_state = self.selection
+
+ if selection_state:
+ selection_state = SelectionState(
+ original_cursor_position=selection_state.original_cursor_position + len(text),
+ type=selection_state.type)
+
+ return Document(
+ text=text + self.text,
+ cursor_position=self.cursor_position + len(text),
+ selection=selection_state)