Skip to content

# cmd2.rl_utils

## `` cmd2.rl_utils

Imports the proper Readline for the platform and provides utility functions for it.

### `` STD_OUT_HANDLE `module-attribute`

    STD_OUT_HANDLE = -11

### `` STD_ERROR_HANDLE `module-attribute`

    STD_ERROR_HANDLE = -12

### `` vt100_stdout_support `module-attribute`

    vt100_stdout_support = enable_win_vt100(GetStdHandle(STD_OUT_HANDLE))

### `` vt100_stderr_support `module-attribute`

    vt100_stderr_support = enable_win_vt100(GetStdHandle(STD_ERROR_HANDLE))

### `` readline_lib `module-attribute`

    readline_lib = CDLL(__file__)

### `` rl_type `module-attribute`

    rl_type = GNU

### `` vt100_support `module-attribute`

    vt100_support = isatty()

### `` rl_warning `module-attribute`

    rl_warning = f'Readline features including tab completion have been disabled because {_rl_warn_reason}

    '

### `` RlType

Bases: `Enum`

Readline library types we support.

#### `` GNU `class-attribute` `instance-attribute`

    GNU = 1

#### `` PYREADLINE `class-attribute` `instance-attribute`

    PYREADLINE = 2

#### `` NONE `class-attribute` `instance-attribute`

    NONE = 3

### `` enable_win_vt100

    enable_win_vt100(handle)

Enable VT100 character sequences in a Windows console.

This only works on Windows 10 and up

PARAMETER | DESCRIPTION
--- | ---
`handle` | the handle on which to enable vt100 TYPE: `HANDLE`

RETURNS | DESCRIPTION
--- | ---
`bool` | True if vt100 characters are enabled for the handle.

Source code in `cmd2/rl_utils.py`

    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91

    def enable_win_vt100(handle: HANDLE) -> bool:
        """Enable VT100 character sequences in a Windows console.

        This only works on Windows 10 and up
        :param handle: the handle on which to enable vt100
        :return: True if vt100 characters are enabled for the handle.
        """
        enable_virtual_terminal_processing = 0x0004

        # Get the current mode for this handle in the console
        cur_mode = DWORD(0)
        readline.rl.console.GetConsoleMode(handle, byref(cur_mode))

        ret_val = False

        # Check if ENABLE_VIRTUAL_TERMINAL_PROCESSING is already enabled
        if (cur_mode.value & enable_virtual_terminal_processing) != 0:
            ret_val = True

        elif readline.rl.console.SetConsoleMode(handle, cur_mode.value | enable_virtual_terminal_processing):
            # Restore the original mode when we exit
            atexit.register(readline.rl.console.SetConsoleMode, handle, cur_mode)
            ret_val = True

        return ret_val

### `` pyreadline_remove_history_item

    pyreadline_remove_history_item(pos)

Remove the specified item number from the pyreadline3 history.

An implementation of remove_history_item() for pyreadline3.

PARAMETER | DESCRIPTION
--- | ---
`pos` | The 0-based position in history to remove. TYPE: `int`

Source code in `cmd2/rl_utils.py`

    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121

    def pyreadline_remove_history_item(pos: int) -> None:
        """Remove the specified item number from the pyreadline3 history.

        An implementation of remove_history_item() for pyreadline3.

        :param pos: The 0-based position in history to remove.
        """
        # Save of the current location of the history cursor
        saved_cursor = readline.rl.mode._history.history_cursor

        # Delete the history item
        del readline.rl.mode._history.history[pos]

        # Update the cursor if needed
        if saved_cursor > pos:
            readline.rl.mode._history.history_cursor -= 1

### `` rl_force_redisplay

    rl_force_redisplay()

Causes readline to display the prompt and input text wherever the cursor is and start reading input from this location.

This is the proper way to restore the input line after printing to the screen.

Source code in `cmd2/rl_utils.py`

    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172

    def rl_force_redisplay() -> None:  # pragma: no cover
        """Causes readline to display the prompt and input text wherever the cursor is and start reading input from this location.

        This is the proper way to restore the input line after printing to the screen.
        """
        if not sys.stdout.isatty():
            return

        if rl_type == RlType.GNU:
            readline_lib.rl_forced_update_display()

            # After manually updating the display, readline asks that rl_display_fixed be set to 1 for efficiency
            display_fixed = ctypes.c_int.in_dll(readline_lib, "rl_display_fixed")
            display_fixed.value = 1

        elif rl_type == RlType.PYREADLINE:
            # Call _print_prompt() first to set the new location of the prompt
            readline.rl.mode._print_prompt()
            readline.rl.mode._update_line()

### `` rl_get_point

    rl_get_point()

Return the offset of the current cursor position in rl_line_buffer.

Source code in `cmd2/rl_utils.py`

    175
    176
    177
    178
    179
    180
    181
    182
    183

    def rl_get_point() -> int:  # pragma: no cover
        """Return the offset of the current cursor position in rl_line_buffer."""
        if rl_type == RlType.GNU:
            return ctypes.c_int.in_dll(readline_lib, "rl_point").value

        if rl_type == RlType.PYREADLINE:
            return int(readline.rl.mode.l_buffer.point)

        return 0

### `` rl_get_prompt

    rl_get_prompt()

Get Readline's prompt.

Source code in `cmd2/rl_utils.py`

    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199

    def rl_get_prompt() -> str:  # pragma: no cover
        """Get Readline's prompt."""
        if rl_type == RlType.GNU:
            encoded_prompt = ctypes.c_char_p.in_dll(readline_lib, "rl_prompt").value
            prompt = '' if encoded_prompt is None else encoded_prompt.decode(encoding='utf-8')

        elif rl_type == RlType.PYREADLINE:
            prompt_data: str | bytes = readline.rl.prompt
            prompt = prompt_data.decode(encoding='utf-8') if isinstance(prompt_data, bytes) else prompt_data

        else:
            prompt = ''

        return rl_unescape_prompt(prompt)

### `` rl_get_display_prompt

    rl_get_display_prompt()

Get Readline's currently displayed prompt.

In GNU Readline, the displayed prompt sometimes differs from the prompt. This occurs in functions that use the prompt string as a message area, such as incremental search.

Source code in `cmd2/rl_utils.py`

    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212

    def rl_get_display_prompt() -> str:  # pragma: no cover
        """Get Readline's currently displayed prompt.

        In GNU Readline, the displayed prompt sometimes differs from the prompt.
        This occurs in functions that use the prompt string as a message area, such as incremental search.
        """
        if rl_type == RlType.GNU:
            encoded_prompt = ctypes.c_char_p.in_dll(readline_lib, "rl_display_prompt").value
            prompt = '' if encoded_prompt is None else encoded_prompt.decode(encoding='utf-8')
            return rl_unescape_prompt(prompt)
        return rl_get_prompt()

### `` rl_set_prompt

    rl_set_prompt(prompt)

Set Readline's prompt.

PARAMETER | DESCRIPTION
--- | ---
`prompt` | the new prompt value. TYPE: `str`

Source code in `cmd2/rl_utils.py`

    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227

    def rl_set_prompt(prompt: str) -> None:  # pragma: no cover
        """Set Readline's prompt.

        :param prompt: the new prompt value.
        """
        escaped_prompt = rl_escape_prompt(prompt)

        if rl_type == RlType.GNU:
            encoded_prompt = bytes(escaped_prompt, encoding='utf-8')
            readline_lib.rl_set_prompt(encoded_prompt)

        elif rl_type == RlType.PYREADLINE:
            readline.rl.prompt = escaped_prompt

### `` rl_escape_prompt

    rl_escape_prompt(prompt)

Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes.

PARAMETER | DESCRIPTION
--- | ---
`prompt` | original prompt TYPE: `str`

RETURNS | DESCRIPTION
--- | ---
`str` | prompt safe to pass to GNU Readline

Source code in `cmd2/rl_utils.py`

    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258

    def rl_escape_prompt(prompt: str) -> str:
        """Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes.

        :param prompt: original prompt
        :return: prompt safe to pass to GNU Readline
        """
        if rl_type == RlType.GNU:
            # start code to tell GNU Readline about beginning of invisible characters
            escape_start = "\x01"

            # end code to tell GNU Readline about end of invisible characters
            escape_end = "\x02"

            escaped = False
            result = ""

            for c in prompt:
                if c == "\x1b" and not escaped:
                    result += escape_start + c
                    escaped = True
                elif c.isalpha() and escaped:
                    result += c + escape_end
                    escaped = False
                else:
                    result += c

            return result

        return prompt

### `` rl_unescape_prompt

    rl_unescape_prompt(prompt)

Remove escape characters from a Readline prompt.

Source code in `cmd2/rl_utils.py`

    261
    262
    263
    264
    265
    266
    267
    268

    def rl_unescape_prompt(prompt: str) -> str:
        """Remove escape characters from a Readline prompt."""
        if rl_type == RlType.GNU:
            escape_start = "\x01"
            escape_end = "\x02"
            prompt = prompt.replace(escape_start, "").replace(escape_end, "")

        return prompt

### `` rl_in_search_mode

    rl_in_search_mode()

Check if readline is doing either an incremental (e.g. Ctrl-r) or non-incremental (e.g. Esc-p) search.

Source code in `cmd2/rl_utils.py`

    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296

    def rl_in_search_mode() -> bool:  # pragma: no cover
        """Check if readline is doing either an incremental (e.g. Ctrl-r) or non-incremental (e.g. Esc-p) search."""
        if rl_type == RlType.GNU:
            # GNU Readline defines constants that we can use to determine if in search mode.
            #     RL_STATE_ISEARCH    0x0000080
            #     RL_STATE_NSEARCH    0x0000100
            in_search_mode = 0x0000180

            readline_state = ctypes.c_int.in_dll(readline_lib, "rl_readline_state").value
            return bool(in_search_mode & readline_state)
        if rl_type == RlType.PYREADLINE:
            from pyreadline3.modes.emacs import (  # type: ignore[import]
                EmacsMode,
            )

            # These search modes only apply to Emacs mode, which is the default.
            if not isinstance(readline.rl.mode, EmacsMode):
                return False

            # While in search mode, the current keyevent function is set to one of the following.
            search_funcs = (
                readline.rl.mode._process_incremental_search_keyevent,
                readline.rl.mode._process_non_incremental_search_keyevent,
            )
            return readline.rl.mode.process_keyevent_queue[-1] in search_funcs
        return False