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
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
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
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
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
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
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
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
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
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
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