summaryrefslogtreecommitdiff
path: root/cmd2/rl_utils.py
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2018-09-30 11:17:04 -0400
committerTodd Leonhardt <todd.leonhardt@gmail.com>2018-09-30 11:17:04 -0400
commit81ad085cffc8f8af7fb7884bcc5db6fcede09df6 (patch)
treea197a6c81767f10b62d6ac6acfd46e42357a8289 /cmd2/rl_utils.py
parent85a21ef39467dc24a6f6f8c4f3ac656f24d6ff90 (diff)
parent61d5703cd3586b3460669a6260cf903c9863b240 (diff)
downloadcmd2-git-81ad085cffc8f8af7fb7884bcc5db6fcede09df6.tar.gz
Merged master into transcript_fixes branch and resolved conflicts
Diffstat (limited to 'cmd2/rl_utils.py')
-rw-r--r--cmd2/rl_utils.py116
1 files changed, 112 insertions, 4 deletions
diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py
index 7e49ea47..0819232d 100644
--- a/cmd2/rl_utils.py
+++ b/cmd2/rl_utils.py
@@ -26,13 +26,54 @@ class RlType(Enum):
# Check what implementation of readline we are using
-
rl_type = RlType.NONE
+# Tells if the terminal we are running in supports vt100 control characters
+vt100_support = False
+
# The order of this check matters since importing pyreadline will also show readline in the modules list
if 'pyreadline' in sys.modules:
rl_type = RlType.PYREADLINE
+ from ctypes import byref
+ from ctypes.wintypes import DWORD, HANDLE
+ import atexit
+
+ # Check if we are running in a terminal
+ if sys.stdout.isatty(): # pragma: no cover
+ # noinspection PyPep8Naming
+ def enable_win_vt100(handle: HANDLE) -> bool:
+ """
+ Enables 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))
+
+ retVal = False
+
+ # Check if ENABLE_VIRTUAL_TERMINAL_PROCESSING is already enabled
+ if (cur_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0:
+ retVal = 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)
+ retVal = True
+
+ return retVal
+
+ # Enable VT100 sequences for stdout and stderr
+ STD_OUT_HANDLE = -11
+ STD_ERROR_HANDLE = -12
+ vt100_support = (enable_win_vt100(readline.rl.console.GetStdHandle(STD_OUT_HANDLE)) and
+ enable_win_vt100(readline.rl.console.GetStdHandle(STD_ERROR_HANDLE)))
+
############################################################################################################
# pyreadline is incomplete in terms of the Python readline API. Add the missing functions we need.
############################################################################################################
@@ -74,9 +115,13 @@ elif 'gnureadline' in sys.modules or 'readline' in sys.modules:
import ctypes
readline_lib = ctypes.CDLL(readline.__file__)
+ # Check if we are running in a terminal
+ if sys.stdout.isatty():
+ vt100_support = True
+
# noinspection PyProtectedMember
-def rl_force_redisplay() -> None:
+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
@@ -85,14 +130,77 @@ def rl_force_redisplay() -> None:
if not sys.stdout.isatty():
return
- if rl_type == RlType.GNU: # pragma: no cover
+ 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: # pragma: no cover
+ 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()
+
+
+# noinspection PyProtectedMember
+def rl_get_point() -> int: # pragma: no cover
+ """
+ Returns 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
+
+ elif rl_type == RlType.PYREADLINE:
+ return readline.rl.mode.l_buffer.point
+
+ else:
+ return 0
+
+
+# noinspection PyProtectedMember
+def rl_set_prompt(prompt: str) -> None: # pragma: no cover
+ """
+ Sets readline's prompt
+ :param prompt: the new prompt value
+ """
+ safe_prompt = rl_make_safe_prompt(prompt)
+
+ if rl_type == RlType.GNU:
+ encoded_prompt = bytes(safe_prompt, encoding='utf-8')
+ readline_lib.rl_set_prompt(encoded_prompt)
+
+ elif rl_type == RlType.PYREADLINE:
+ readline.rl._set_prompt(safe_prompt)
+
+
+def rl_make_safe_prompt(prompt: str) -> str: # pragma: no cover
+ """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
+ start = "\x01"
+
+ # end code to tell GNU Readline about end of invisible characters
+ end = "\x02"
+
+ escaped = False
+ result = ""
+
+ for c in prompt:
+ if c == "\x1b" and not escaped:
+ result += start + c
+ escaped = True
+ elif c.isalpha() and escaped:
+ result += c + end
+ escaped = False
+ else:
+ result += c
+
+ return result
+
+ else:
+ return prompt