'''
Text Input
==========

.. versionadded:: 1.0.4

.. image:: images/textinput-mono.jpg
.. image:: images/textinput-multi.jpg

The :class:`TextInput` widget provides a box for editable plain text.

Unicode, multiline, cursor navigation, selection and clipboard features
are supported.

The :class:`TextInput` uses two different coordinate systems:

* (x, y) - coordinates in pixels, mostly used for rendering on screen.
* (col, row) - cursor index in characters / lines, used for selection
  and cursor movement.


Usage example
-------------

To create a multiline :class:`TextInput` (the 'enter' key adds a new line)::

    from kivy.uix.textinput import TextInput
    textinput = TextInput(text='Hello world')

To create a singleline :class:`TextInput`, set the :class:`TextInput.multiline`
property to False (the 'enter' key will defocus the TextInput and emit an
:meth:`TextInput.on_text_validate` event)::

    def on_enter(instance, value):
        print('User pressed enter in', instance)

    textinput = TextInput(text='Hello world', multiline=False)
    textinput.bind(on_text_validate=on_enter)

The textinput's text is stored in its :attr:`TextInput.text` property. To run a
callback when the text changes::

    def on_text(instance, value):
        print('The widget', instance, 'have:', value)

    textinput = TextInput()
    textinput.bind(text=on_text)

You can set the :class:`focus <kivy.uix.behaviors.FocusBehavior>` to a
Textinput, meaning that the input box will be highlighted and keyboard focus
will be requested::

    textinput = TextInput(focus=True)

The textinput is defocused if the 'escape' key is pressed, or if another
widget requests the keyboard. You can bind a callback to the focus property to
get notified of focus changes::

    def on_focus(instance, value):
        if value:
            print('User focused', instance)
        else:
            print('User defocused', instance)

    textinput = TextInput()
    textinput.bind(focus=on_focus)

See :class:`~kivy.uix.behaviors.FocusBehavior`, from which the
:class:`TextInput` inherits, for more details.


Selection
---------

The selection is automatically updated when the cursor position changes.
You can get the currently selected text from the
:attr:`TextInput.selection_text` property.

Handles
-------

One can enable :attr:`TextInput.use_handles` property to enable or disable
the usage of selection handles. This property is True by default on mobiles.

Selection Handles uses the class :class:`Selector` as the base class for
the selection handles. You can customize the color for selection handles
like so ::

    <Selector>
        color: 0, 1, 0, 1
        # or <Textinput_instance>.selection_color or app.selection_color

TextInput instantiates the selection handles and stores it in the following
properties. :attr:`TextInput._handle_middle`,
:attr:`TextInput._handle_left`, :attr:`TextInput._handle_right`.

You should set the selection template before the Instantiating TextInput,
so as to get the selection handles to take the changes you set to apply.

Filtering
---------

You can control which text can be added to the :class:`TextInput` by
overwriting :meth:`TextInput.insert_text`. Every string that is typed, pasted
or inserted by any other means into the :class:`TextInput` is passed through
this function. By overwriting it you can reject or change unwanted characters.

For example, to write only in capitalized characters::

    class CapitalInput(TextInput):

        def insert_text(self, substring, from_undo=False):
            s = substring.upper()
            return super().insert_text(s, from_undo=from_undo)

Or to only allow floats (0 - 9 and a single period)::

    class FloatInput(TextInput):

        pat = re.compile('[^0-9]')
        def insert_text(self, substring, from_undo=False):
            pat = self.pat
            if '.' in self.text:
                s = re.sub(pat, '', substring)
            else:
                s = '.'.join(
                    re.sub(pat, '', s)
                    for s in substring.split('.', 1)
                )
            return super().insert_text(s, from_undo=from_undo)

Default shortcuts
-----------------

=============== ========================================================
   Shortcuts    Description
--------------- --------------------------------------------------------
Left            Move cursor to left
Right           Move cursor to right
Up              Move cursor to up
Down            Move cursor to down
Home            Move cursor at the beginning of the line
End             Move cursor at the end of the line
PageUp          Move cursor to 3 lines before
PageDown        Move cursor to 3 lines after
Backspace       Delete the selection or character before the cursor
Del             Delete the selection of character after the cursor
Shift + <dir>   Start a text selection. Dir can be Up, Down, Left or
                Right
Control + c     Copy selection
Control + x     Cut selection
Control + v     Paste clipboard content
Control + a     Select all the content
Control + z     undo
Control + r     redo
=============== ========================================================

.. note::
    To enable Emacs-style keyboard shortcuts, you can use
    :class:`~kivy.uix.behaviors.emacs.EmacsBehavior`.

'''


import re
import sys
import math
from os import environ
from weakref import ref
from itertools import chain, islice

from kivy.animation import Animation
from kivy.base import EventLoop
from kivy.cache import Cache
from kivy.clock import Clock
from kivy.config import Config
from kivy.core.window import Window
from kivy.metrics import inch
from kivy.utils import boundary, platform
from kivy.uix.behaviors import FocusBehavior

from kivy.core.text import Label, DEFAULT_FONT
from kivy.graphics import Color, Rectangle, PushMatrix, PopMatrix, Callback
from kivy.graphics.context_instructions import Transform
from kivy.graphics.texture import Texture

from kivy.uix.widget import Widget
from kivy.uix.bubble import Bubble
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image

from kivy.properties import StringProperty, NumericProperty, \
    BooleanProperty, AliasProperty, OptionProperty, \
    ListProperty, ObjectProperty, VariableListProperty, ColorProperty, \
    BoundedNumericProperty

__all__ = ('TextInput', )


if 'KIVY_DOC' in environ:
    def triggered(*_, **__):
        def decorator_func(func):
            def decorated_func(*args, **kwargs):
                return func(*args, **kwargs)
            return decorated_func
        return decorator_func
else:
    from kivy.clock import triggered


Cache_register = Cache.register
Cache_append = Cache.append
Cache_get = Cache.get
Cache_remove = Cache.remove
Cache_register('textinput.label', timeout=60.)
Cache_register('textinput.width', timeout=60.)

FL_IS_LINEBREAK = 0x01
FL_IS_WORDBREAK = 0x02
FL_IS_NEWLINE = FL_IS_LINEBREAK | FL_IS_WORDBREAK

# late binding
Clipboard = None
CutBuffer = None
MarkupLabel = None
_platform = platform

# for reloading, we need to keep a list of textinput to retrigger the rendering
_textinput_list = []

# cache the result
_is_osx = sys.platform == 'darwin'

# When we are generating documentation, Config doesn't exist
_is_desktop = False
_scroll_timeout = _scroll_distance = 0
if Config:
    _is_desktop = Config.getboolean('kivy', 'desktop')
    _scroll_timeout = Config.getint('widgets', 'scroll_timeout')
    _scroll_distance = '{}sp'.format(Config.getint('widgets',
                                                   'scroll_distance'))

# register an observer to clear the textinput cache when OpenGL will reload
if 'KIVY_DOC' not in environ:

    def _textinput_clear_cache(*l):
        Cache_remove('textinput.label')
        Cache_remove('textinput.width')
        for wr in _textinput_list[:]:
            textinput = wr()
            if textinput is None:
                _textinput_list.remove(wr)
            else:
                textinput._trigger_refresh_text()
                textinput._refresh_hint_text()

    from kivy.graphics.context import get_context
    get_context().add_reload_observer(_textinput_clear_cache, True)


class Selector(ButtonBehavior, Image):
    '''Default template for the selection Handles

    In order to customize the look of the Selection Handles,
    you should adjust its template like so ::

        <Selector>
            color: 1, 0, 1, 1
    '''

    window = ObjectProperty()
    target = ObjectProperty()
    matrix = ObjectProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.always_release = True
        self.matrix = self.target.get_window_matrix()

        with self.canvas.before:
            Callback(self.update_transform)
            PushMatrix()
            self.transform = Transform()

        with self.canvas.after:
            PopMatrix()

    def update_transform(self, cb):
        matrix = self.target.get_window_matrix()
        if self.matrix != matrix:
            self.matrix = matrix
            self.transform.identity()
            self.transform.transform(self.matrix)

    def transform_touch(self, touch):
        matrix = self.matrix.inverse()
        touch.apply_transform_2d(
            lambda x, y: matrix.transform_point(x, y, 0)[:2]
        )

    def on_touch_down(self, touch):
        if self.parent is not EventLoop.window:
            return

        try:
            touch.push()
            self.transform_touch(touch)
            self._touch_diff = self.top - touch.y
            if self.collide_point(*touch.pos):
                FocusBehavior.ignored_touch.append(touch)
            return super().on_touch_down(touch)
        finally:
            touch.pop()


class TextInputCutCopyPaste(Bubble):
    # Internal class used for showing the little bubble popup when
    # copy/cut/paste happen.

    textinput = ObjectProperty(None)
    ''' Holds a reference to the TextInput this Bubble belongs to.
    '''

    but_cut = ObjectProperty(None)
    but_copy = ObjectProperty(None)
    but_paste = ObjectProperty(None)
    but_selectall = ObjectProperty(None)

    matrix = ObjectProperty(None)

    _check_parent_ev = None

    def __init__(self, **kwargs):
        self.mode = 'normal'
        super().__init__(**kwargs)
        self._check_parent_ev = Clock.schedule_interval(self._check_parent, .5)
        self.matrix = self.textinput.get_window_matrix()

        with self.canvas.before:
            Callback(self.update_transform)
            PushMatrix()
            self.transform = Transform()

        with self.canvas.after:
            PopMatrix()

    def update_transform(self, cb):
        m = self.textinput.get_window_matrix()
        if self.matrix != m:
            self.matrix = m
            self.transform.identity()
            self.transform.transform(self.matrix)

    def transform_touch(self, touch):
        matrix = self.matrix.inverse()
        touch.apply_transform_2d(
            lambda x, y: matrix.transform_point(x, y, 0)[:2])

    def on_touch_down(self, touch):
        try:
            touch.push()
            self.transform_touch(touch)
            if self.collide_point(*touch.pos):
                FocusBehavior.ignored_touch.append(touch)
            return super().on_touch_down(touch)
        finally:
            touch.pop()

    def on_touch_up(self, touch):
        try:
            touch.push()
            self.transform_touch(touch)
            for child in self.content.children:
                if ref(child) in touch.grab_list:
                    touch.grab_current = child
                    break
            return super().on_touch_up(touch)
        finally:
            touch.pop()

    def on_textinput(self, instance, value):
        global Clipboard
        if value and not Clipboard and not _is_desktop:
            value._ensure_clipboard()

    def _check_parent(self, dt):
        # this is a prevention to get the Bubble staying on the screen, if the
        # attached textinput is not on the screen anymore.
        parent = self.textinput
        while parent is not None:
            if parent == parent.parent:
                break
            parent = parent.parent
        if parent is None:
            self._check_parent_ev.cancel()
            if self.textinput:
                self.textinput._hide_cut_copy_paste()

    def on_parent(self, instance, value):
        parent = self.textinput
        mode = self.mode

        if parent:
            self.content.clear_widgets()
            if mode == 'paste':
                # show only paste on long touch
                self.but_selectall.opacity = 1
                widget_list = [self.but_selectall, ]
                if not parent.readonly:
                    widget_list.append(self.but_paste)
            elif parent.readonly:
                # show only copy for read only text input
                widget_list = (self.but_copy, )
            else:
                # normal mode
                widget_list = (self.but_cut, self.but_copy, self.but_paste)

            for widget in widget_list:
                self.content.add_widget(widget)

    def do(self, action):
        textinput = self.textinput

        if action == 'cut':
            textinput._cut(textinput.selection_text)
        elif action == 'copy':
            textinput.copy()
        elif action == 'paste':
            textinput.paste()
        elif action == 'selectall':
            textinput.select_all()
            self.mode = ''
            anim = Animation(opacity=0, d=.333)
            anim.bind(on_complete=lambda *args:
                      self.on_parent(self, self.parent))
            anim.start(self.but_selectall)
            return

        self.hide()

    def hide(self):
        parent = self.parent
        if not parent:
            return

        anim = Animation(opacity=0, d=.225)
        anim.bind(on_complete=lambda *args: parent.remove_widget(self))
        anim.start(self)


class TextInput(FocusBehavior, Widget):
    '''TextInput class. See module documentation for more information.

    :Events:
        `on_text_validate`
            Fired only in multiline=False mode when the user hits 'enter'.
            This will also unfocus the textinput.
        `on_double_tap`
            Fired when a double tap happens in the text input. The default
            behavior selects the text around the cursor position. More info at
            :meth:`on_double_tap`.
        `on_triple_tap`
            Fired when a triple tap happens in the text input. The default
            behavior selects the line around the cursor position. More info at
            :meth:`on_triple_tap`.
        `on_quad_touch`
            Fired when four fingers are touching the text input. The default
            behavior selects the whole text. More info at
            :meth:`on_quad_touch`.

    .. warning::
        When changing a :class:`TextInput` property that requires re-drawing,
        e.g. modifying the :attr:`text`, the updates occur on the next
        clock cycle and not instantly. This might cause any changes to the
        :class:`TextInput` that occur between the modification and the next
        cycle to be ignored, or to use previous values. For example, after
        a update to the :attr:`text`, changing the cursor in the same clock
        frame will move it using the previous text and will likely end up in an
        incorrect position. The solution is to schedule any updates to occur
        on the next clock cycle using
        :meth:`~kivy.clock.ClockBase.schedule_once`.

    .. Note::
        Selection is cancelled when TextInput is focused. If you need to
        show selection when TextInput is focused, you should delay
        (use Clock.schedule) the call to the functions for selecting
        text (select_all, select_text).

    .. versionchanged:: 1.10.0
        `background_disabled_active` has been removed.

    .. versionchanged:: 1.9.0

        :class:`TextInput` now inherits from
        :class:`~kivy.uix.behaviors.FocusBehavior`.
        :attr:`~kivy.uix.behaviors.FocusBehavior.keyboard_mode`,
        :meth:`~kivy.uix.behaviors.FocusBehavior.show_keyboard`,
        :meth:`~kivy.uix.behaviors.FocusBehavior.hide_keyboard`,
        :meth:`~kivy.uix.behaviors.FocusBehavior.focus`,
        and :attr:`~kivy.uix.behaviors.FocusBehavior.input_type`
        have been removed since they are now inherited
        from :class:`~kivy.uix.behaviors.FocusBehavior`.

    .. versionchanged:: 1.7.0
        `on_double_tap`, `on_triple_tap` and `on_quad_touch` events added.

    .. versionchanged:: 2.1.0
        :attr:`~kivy.uix.behaviors.FocusBehavior.keyboard_suggestions`
        is now inherited from :class:`~kivy.uix.behaviors.FocusBehavior`.
    '''

    __events__ = ('on_text_validate', 'on_double_tap', 'on_triple_tap',
                  'on_quad_touch')

    _resolved_base_dir = None

    def __init__(self, **kwargs):
        self._update_graphics_ev = Clock.create_trigger(
            self._update_graphics, -1)
        self.is_focusable = kwargs.get('is_focusable', True)
        self._cursor = [0, 0]
        self._selection = False
        self._selection_finished = True
        self._selection_touch = None
        self.selection_text = u''
        self._selection_from = None
        self._selection_to = None
        self._selection_callback = None
        self._handle_left = None
        self._handle_right = None
        self._handle_middle = None
        self._bubble = None
        self._lines_flags = []
        self._lines_labels = []
        self._lines_rects = []
        self._hint_text_flags = []
        self._hint_text_labels = []
        self._hint_text_rects = []
        self._label_cached = None
        self._line_options = None
        self._keyboard_mode = Config.get('kivy', 'keyboard_mode')
        self._command_mode = False
        self._command = ''
        self.reset_undo()
        self._touch_count = 0
        self._ctrl_l = False
        self._ctrl_r = False
        self._alt_l = False
        self._alt_r = False
        self._refresh_text_from_property_ev = None
        self._long_touch_ev = None
        self._do_blink_cursor_ev = Clock.create_trigger(
            self._do_blink_cursor, .5, interval=True)
        self._refresh_line_options_ev = None
        self._scroll_distance_x = 0
        self._scroll_distance_y = 0
        self._enable_scroll = True
        self._have_scrolled = False

        # [from; to) range of lines being partially or fully rendered
        # in TextInput's viewport
        self._visible_lines_range = 0, 0

        self.interesting_keys = {
            8: 'backspace',
            13: 'enter',
            127: 'del',
            271: 'enter',
            273: 'cursor_up',
            274: 'cursor_down',
            275: 'cursor_right',
            276: 'cursor_left',
            278: 'cursor_home',
            279: 'cursor_end',
            280: 'cursor_pgup',
            281: 'cursor_pgdown',
            303: 'shift_L',
            304: 'shift_R',
            305: 'ctrl_L',
            306: 'ctrl_R',
            308: 'alt_L',
            307: 'alt_R'
        }

        super().__init__(**kwargs)

        fbind = self.fbind
        refresh_line_options = self._trigger_refresh_line_options
        update_text_options = self._update_text_options
        trigger_update_graphics = self._trigger_update_graphics

        fbind('font_size', refresh_line_options)
        fbind('font_name', refresh_line_options)
        fbind('font_context', refresh_line_options)
        fbind('font_family', refresh_line_options)
        fbind('base_direction', refresh_line_options)
        fbind('text_language', refresh_line_options)

        def handle_readonly(instance, value):
            if value and (not _is_desktop or not self.allow_copy):
                self.is_focusable = False
            if (not (value or self.disabled) or _is_desktop and
                    self._keyboard_mode == 'system'):
                self._editable = True
            else:
                self._editable = False

        fbind('padding', update_text_options)
        fbind('tab_width', update_text_options)
        fbind('font_size', update_text_options)
        fbind('font_name', update_text_options)
        fbind('size', update_text_options)
        fbind('password', update_text_options)
        fbind('password_mask', update_text_options)

        fbind('pos', trigger_update_graphics)
        fbind('halign', trigger_update_graphics)
        fbind('readonly', handle_readonly)
        fbind('focus', self._on_textinput_focused)
        handle_readonly(self, self.readonly)

        handles = self._trigger_position_handles = Clock.create_trigger(
            self._position_handles)
        self._trigger_show_handles = Clock.create_trigger(
            self._show_handles, .05)
        self._trigger_cursor_reset = Clock.create_trigger(
            self._reset_cursor_blink)
        self._trigger_update_cutbuffer = Clock.create_trigger(
            self._update_cutbuffer)
        refresh_line_options()
        self._trigger_refresh_text()

        fbind('pos', handles)
        fbind('size', handles)

        # when the gl context is reloaded, trigger the text rendering again.
        _textinput_list.append(ref(self, TextInput._reload_remove_observer))

        if platform == 'linux':
            self._ensure_clipboard()

    def on_text_validate(self):
        pass

    def cursor_index(self, cursor=None):
        '''Return the cursor index in the text/value.
        '''
        if not cursor:
            cursor = self.cursor
        try:
            lines = self._lines
            if not lines:
                return 0

            flags = self._lines_flags
            index, cursor_row = cursor

            for _, line, flag in zip(
                range(min(cursor_row, len(lines))),
                lines,
                flags
            ):
                index += len(line)
                if flag & FL_IS_LINEBREAK:
                    index += 1

            if flags[cursor_row] & FL_IS_LINEBREAK:
                index += 1
            return index

        except IndexError:
            return 0

    def cursor_offset(self):
        '''Get the cursor x offset on the current line.
        '''
        offset = 0
        row = int(self.cursor_row)
        col = int(self.cursor_col)
        lines = self._lines
        if col and row < len(lines):
            offset = self._get_text_width(
                lines[row][:col],
                self.tab_width,
                self._label_cached
            )
        return offset

    def get_cursor_from_index(self, index):
        '''Return the (col, row) of the cursor from text index.
        '''
        index = boundary(index, 0, len(self.text))
        if index <= 0:
            return 0, 0
        flags = self._lines_flags
        lines = self._lines
        if not lines:
            return 0, 0

        i = 0
        for row, line in enumerate(lines):
            count = i + len(line)
            if flags[row] & FL_IS_LINEBREAK:
                count += 1
                i += 1
            if count >= index:
                return index - i, row
            i = count
        return int(index), int(row)

    def select_text(self, start, end):
        ''' Select a portion of text displayed in this TextInput.

        .. versionadded:: 1.4.0

        :Parameters:
            `start`
                Index of textinput.text from where to start selection
            `end`
                Index of textinput.text till which the selection should be
                displayed
        '''
        if end < start:
            raise Exception('end must be superior to start')
        text_length = len(self.text)
        self._selection_from = boundary(start, 0, text_length)
        self._selection_to = boundary(end, 0, text_length)
        self._selection_finished = True
        self._update_selection(True)
        self._update_graphics_selection()

    def select_all(self):
        ''' Select all of the text displayed in this TextInput.

        .. versionadded:: 1.4.0
        '''
        self.select_text(0, len(self.text))

    re_indent = re.compile(r'^(\s*|)')

    def _auto_indent(self, substring):
        index = self.cursor_index()
        if index > 0:
            _text = self.text
            line_start = _text.rfind('\n', 0, index)
            if line_start > -1:
                line = _text[line_start + 1:index]
                indent = self.re_indent.match(line).group()
                substring += indent
        return substring

    def insert_text(self, substring, from_undo=False):
        '''Insert new text at the current cursor position. Override this
        function in order to pre-process text for input validation.
        '''
        _lines = self._lines
        _lines_flags = self._lines_flags

        if self.readonly or not substring or not self._lines:
            return

        if isinstance(substring, bytes):
            substring = substring.decode('utf8')

        if self.replace_crlf:
            substring = substring.replace(u'\r\n', u'\n')

        self._hide_handles(EventLoop.window)

        if not from_undo and self.multiline and self.auto_indent \
                and substring == u'\n':
            substring = self._auto_indent(substring)

        mode = self.input_filter
        if mode not in (None, 'int', 'float'):
            substring = mode(substring, from_undo)
            if not substring:
                return

        col, row = self.cursor
        cindex = self.cursor_index()
        text = _lines[row]
        len_str = len(substring)
        new_text = text[:col] + substring + text[col:]
        if mode is not None:
            if mode == 'int':
                if not re.match(self._insert_int_pat, new_text):
                    return
            elif mode == 'float':
                if not re.match(self._insert_float_pat, new_text):
                    return
        self._set_line_text(row, new_text)

        if len_str > 1 or substring == u'\n' or\
            (substring == u' ' and _lines_flags[row] != FL_IS_LINEBREAK) or\
            (row + 1 < len(_lines) and
             _lines_flags[row + 1] != FL_IS_LINEBREAK) or\
            (self._get_text_width(
                new_text,
                self.tab_width,
                self._label_cached) > (self.width - self.padding[0] -
                                       self.padding[2])):
            # Avoid refreshing text on every keystroke.
            # Allows for faster typing of text when the amount of text in
            # TextInput gets large.

            (
                start, finish, lines, lines_flags, len_lines
            ) = self._get_line_from_cursor(row, new_text)

            # calling trigger here could lead to wrong cursor positioning
            # and repeating of text when keys are added rapidly in a automated
            # fashion. From Android Keyboard for example.
            self._refresh_text_from_property(
                'insert', start, finish, lines, lines_flags, len_lines
            )

        self.cursor = self.get_cursor_from_index(cindex + len_str)
        # handle undo and redo
        self._set_unredo_insert(cindex, cindex + len_str, substring, from_undo)

    def _get_line_from_cursor(self, start, new_text, lines=None,
                              lines_flags=None):
        # get current paragraph from cursor position
        if lines is None:
            lines = self._lines
        if lines_flags is None:
            lines_flags = self._lines_flags
        finish = start
        _next = start + 1
        if start > 0 and lines_flags[start] != FL_IS_LINEBREAK:
            start -= 1
            new_text = lines[start] + new_text
        i = _next
        for i in range(_next, len(lines_flags)):
            if lines_flags[i] == FL_IS_LINEBREAK:
                finish = i - 1
                break
        else:
            finish = i

        new_text = new_text + u''.join(lines[_next:finish + 1])
        lines, lines_flags = self._split_smart(new_text)

        len_lines = max(1, len(lines))
        return start, finish, lines, lines_flags, len_lines

    def _set_unredo_insert(self, ci, sci, substring, from_undo):
        # handle undo and redo
        if from_undo:
            return
        self._undo.append({
            'undo_command': ('insert', ci, sci),
            'redo_command': (ci, substring)
        })
        # reset redo when undo is appended to
        self._redo = []

    def reset_undo(self):
        '''Reset undo and redo lists from memory.

        .. versionadded:: 1.3.0

        '''
        self._redo = self._undo = []

    def do_redo(self):
        '''Do redo operation.

        .. versionadded:: 1.3.0

        This action re-does any command that has been un-done by
        do_undo/ctrl+z. This function is automatically called when
        `ctrl+r` keys are pressed.
        '''
        try:
            x_item = self._redo.pop()
            undo_type = x_item['undo_command'][0]
            _get_cusror_from_index = self.get_cursor_from_index

            if undo_type == 'insert':
                cindex, substring = x_item['redo_command']
                self.cursor = _get_cusror_from_index(cindex)
                self.insert_text(substring, True)
            elif undo_type == 'bkspc':
                self.cursor = _get_cusror_from_index(x_item['redo_command'])
                self.do_backspace(from_undo=True)
            elif undo_type == 'shiftln':
                direction, rows, cursor = x_item['redo_command'][1:]
                self._shift_lines(direction, rows, cursor, True)
            else:
                # delsel
                cindex, scindex = x_item['redo_command']
                self._selection_from = cindex
                self._selection_to = scindex
                self._selection = True
                self.delete_selection(True)
                self.cursor = _get_cusror_from_index(cindex)
            self._undo.append(x_item)
        except IndexError:
            # reached at top of undo list
            pass

    def do_undo(self):
        '''Do undo operation.

        .. versionadded:: 1.3.0

        This action un-does any edits that have been made since the last
        call to reset_undo().
        This function is automatically called when `ctrl+z` keys are pressed.
        '''
        try:
            x_item = self._undo.pop()
            undo_type = x_item['undo_command'][0]
            self.cursor = self.get_cursor_from_index(x_item['undo_command'][1])

            if undo_type == 'insert':
                cindex, scindex = x_item['undo_command'][1:]
                self._selection_from = cindex
                self._selection_to = scindex
                self._selection = True
                self.delete_selection(True)
            elif undo_type == 'bkspc':
                substring = x_item['undo_command'][2][0]
                mode = x_item['undo_command'][3]
                self.insert_text(substring, True)
                if mode == 'del':
                    self.cursor = self.get_cursor_from_index(
                        self.cursor_index() - 1)
            elif undo_type == 'shiftln':
                direction, rows, cursor = x_item['undo_command'][1:]
                self._shift_lines(direction, rows, cursor, True)
            else:
                # delsel
                substring = x_item['undo_command'][2:][0]
                self.insert_text(substring, True)
            self._redo.append(x_item)
            self.scroll_x = self.get_max_scroll_x()
        except IndexError:
            # reached at top of undo list
            pass

    def do_backspace(self, from_undo=False, mode='bkspc'):
        '''Do backspace operation from the current cursor position.
        This action might do several things:

            - removing the current selection if available.
            - removing the previous char and move the cursor back.
            - do nothing, if we are at the start.

        '''
        # IME system handles its own backspaces
        if self.readonly or self._ime_composition:
            return
        col, row = self.cursor
        _lines = self._lines
        _lines_flags = self._lines_flags
        text = _lines[row]
        cursor_index = self.cursor_index()

        if col == 0 and row == 0:
            return
        start = row
        if col == 0:
            if _lines_flags[row] == FL_IS_LINEBREAK:
                substring = u'\n'
                new_text = _lines[row - 1] + text
            else:
