From bd34fee86d66a610d1e0bb392186dc73e883e2db Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 19 Sep 2018 02:05:08 -0400 Subject: Wrote function and test command to print alerts --- cmd2/rl_utils.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) (limited to 'cmd2/rl_utils.py') diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 7e49ea47..634f0c5e 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -26,13 +26,41 @@ class RlType(Enum): # Check what implementation of readline we are using - rl_type = RlType.NONE # 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 + + # noinspection PyPep8Naming + def enable_win_vt100(handle: HANDLE) -> None: + """ + Enables VT100 character sequences in a Windows console + This only works on Windows 10 and up + """ + 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)) + + # If ENABLE_VIRTUAL_TERMINAL_PROCESSING is not enabled, then enable it now + if (cur_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: + 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) + + # Enable VT100 sequences for stdout and stderr + STD_OUT_HANDLE = -11 + STD_ERROR_HANDLE = -12 + enable_win_vt100(readline.rl.console.GetStdHandle(STD_OUT_HANDLE)) + 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. ############################################################################################################ @@ -96,3 +124,18 @@ def rl_force_redisplay() -> None: # 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: + """ + Returns the offset of the current cursor position in rl_line_buffer + """ + if rl_type == RlType.GNU: # pragma: no cover + return ctypes.c_int.in_dll(readline_lib, "rl_point").value + + elif rl_type == RlType.PYREADLINE: # pragma: no cover + return readline.rl.mode.l_buffer.point + + else: # pragma: no cover + return 0 -- cgit v1.2.1 From f406d5b81625a830c54b35acec6caf937c94c6da Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 19 Sep 2018 02:44:53 -0400 Subject: Wrote a prompt updater example --- cmd2/rl_utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'cmd2/rl_utils.py') diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 634f0c5e..4717c408 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -139,3 +139,17 @@ def rl_get_point() -> int: else: # pragma: no cover return 0 + + +# noinspection PyProtectedMember +def rl_set_prompt(prompt: str) -> None: + """ + Sets readline's prompt + :param prompt: the new prompt value + """ + if rl_type == RlType.GNU: # pragma: no cover + encoded_prompt = bytes(prompt, encoding='utf-8') + readline_lib.rl_set_prompt(encoded_prompt) + + elif rl_type == RlType.PYREADLINE: # pragma: no cover + readline.rl._set_prompt(prompt) -- cgit v1.2.1 From 556858e64d75fb316800ad34bda465b345ce3eb6 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 19 Sep 2018 18:06:20 -0400 Subject: Made common function to clear input lines in the terminal --- cmd2/rl_utils.py | 1 + 1 file changed, 1 insertion(+) (limited to 'cmd2/rl_utils.py') diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 4717c408..58bd6377 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -27,6 +27,7 @@ class RlType(Enum): # Check what implementation of readline we are using rl_type = RlType.NONE +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: -- cgit v1.2.1 From 9a25ec6cdf29b1efa670b16b409976eb9df7987d Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 19 Sep 2018 22:13:11 -0400 Subject: More code to support asynchronous changes to the terminal --- cmd2/rl_utils.py | 56 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 20 deletions(-) (limited to 'cmd2/rl_utils.py') diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 58bd6377..96a74b67 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -27,6 +27,8 @@ 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 @@ -37,30 +39,40 @@ if 'pyreadline' in sys.modules: from ctypes.wintypes import DWORD, HANDLE import atexit - # noinspection PyPep8Naming - def enable_win_vt100(handle: HANDLE) -> None: - """ - Enables VT100 character sequences in a Windows console - This only works on Windows 10 and up - """ - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + # Check if we are running in a terminal + if sys.stdout.isatty(): + # 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 - # Get the current mode for this handle in the console - cur_mode = DWORD(0) - readline.rl.console.GetConsoleMode(handle, byref(cur_mode)) + # Check if ENABLE_VIRTUAL_TERMINAL_PROCESSING is already enabled + if (cur_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0: + retVal = True - # If ENABLE_VIRTUAL_TERMINAL_PROCESSING is not enabled, then enable it now - if (cur_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: - readline.rl.console.SetConsoleMode(handle, cur_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + 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 - # Restore the original mode when we exit - atexit.register(readline.rl.console.SetConsoleMode, handle, cur_mode) + return retVal - # Enable VT100 sequences for stdout and stderr - STD_OUT_HANDLE = -11 - STD_ERROR_HANDLE = -12 - enable_win_vt100(readline.rl.console.GetStdHandle(STD_OUT_HANDLE)) - enable_win_vt100(readline.rl.console.GetStdHandle(STD_ERROR_HANDLE)) + # 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. @@ -103,6 +115,10 @@ 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: -- cgit v1.2.1 From c706f6a95c41392fcca0b3a93b689a19ba06a0f4 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 21 Sep 2018 17:27:58 -0400 Subject: Made sure all prompts sent to GNU readline are made safe --- cmd2/rl_utils.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'cmd2/rl_utils.py') diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 96a74b67..569ba8cf 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -164,9 +164,39 @@ def rl_set_prompt(prompt: str) -> None: Sets readline's prompt :param prompt: the new prompt value """ + safe_prompt = rl_make_safe_prompt(prompt) + if rl_type == RlType.GNU: # pragma: no cover - encoded_prompt = bytes(prompt, encoding='utf-8') + encoded_prompt = bytes(safe_prompt, encoding='utf-8') readline_lib.rl_set_prompt(encoded_prompt) elif rl_type == RlType.PYREADLINE: # pragma: no cover - readline.rl._set_prompt(prompt) + readline.rl._set_prompt(safe_prompt) + + +def rl_make_safe_prompt(prompt: str, start: str = "\x01", end: str = "\x02") -> str: + """Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes. + + :param prompt: original prompt + :param start: start code to tell GNU Readline about beginning of invisible characters + :param end: end code to tell GNU Readline about end of invisible characters + :return: prompt safe to pass to GNU Readline + """ + if rl_type == RlType.GNU: + 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 -- cgit v1.2.1 From 87551d5e9675e77cfbb7be7c499f46d9c6cbe559 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Tue, 25 Sep 2018 18:12:34 -0400 Subject: Removed terminal functions from code coverage --- cmd2/rl_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd2/rl_utils.py') diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 569ba8cf..d4e7666a 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -182,7 +182,7 @@ def rl_make_safe_prompt(prompt: str, start: str = "\x01", end: str = "\x02") -> :param end: end code to tell GNU Readline about end of invisible characters :return: prompt safe to pass to GNU Readline """ - if rl_type == RlType.GNU: + if rl_type == RlType.GNU: # pragma: no cover escaped = False result = "" -- cgit v1.2.1 From 6946e5761f4b61287bb76fc9692d4619a5dccd05 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Tue, 25 Sep 2018 18:26:10 -0400 Subject: Removed more terminal code from coverage --- cmd2/rl_utils.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'cmd2/rl_utils.py') diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index d4e7666a..3fac5696 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -40,7 +40,7 @@ if 'pyreadline' in sys.modules: import atexit # Check if we are running in a terminal - if sys.stdout.isatty(): + if sys.stdout.isatty(): # pragma: no cover # noinspection PyPep8Naming def enable_win_vt100(handle: HANDLE) -> bool: """ @@ -121,7 +121,7 @@ elif 'gnureadline' in sys.modules or 'readline' in sys.modules: # 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 @@ -130,51 +130,51 @@ 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: +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: # pragma: no cover + if rl_type == RlType.GNU: return ctypes.c_int.in_dll(readline_lib, "rl_point").value - elif rl_type == RlType.PYREADLINE: # pragma: no cover + elif rl_type == RlType.PYREADLINE: return readline.rl.mode.l_buffer.point - else: # pragma: no cover + else: return 0 # noinspection PyProtectedMember -def rl_set_prompt(prompt: str) -> None: +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: # pragma: no cover + 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: # pragma: no cover + elif rl_type == RlType.PYREADLINE: readline.rl._set_prompt(safe_prompt) -def rl_make_safe_prompt(prompt: str, start: str = "\x01", end: str = "\x02") -> str: +def rl_make_safe_prompt(prompt: str, start: str = "\x01", end: str = "\x02") -> 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 @@ -182,7 +182,7 @@ def rl_make_safe_prompt(prompt: str, start: str = "\x01", end: str = "\x02") -> :param end: end code to tell GNU Readline about end of invisible characters :return: prompt safe to pass to GNU Readline """ - if rl_type == RlType.GNU: # pragma: no cover + if rl_type == RlType.GNU: escaped = False result = "" -- cgit v1.2.1 From 39e43d6329eb517ac58f95a8ca37e7e489d446cb Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Tue, 25 Sep 2018 19:10:46 -0400 Subject: Addressing code review comments --- cmd2/rl_utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'cmd2/rl_utils.py') diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 3fac5696..0819232d 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -71,8 +71,8 @@ if 'pyreadline' in sys.modules: # 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)) + 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. @@ -174,15 +174,19 @@ def rl_set_prompt(prompt: str) -> None: # pragma: no cover readline.rl._set_prompt(safe_prompt) -def rl_make_safe_prompt(prompt: str, start: str = "\x01", end: str = "\x02") -> str: # pragma: no cover +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 - :param start: start code to tell GNU Readline about beginning of invisible characters - :param end: end code to tell GNU Readline about end of invisible characters :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 = "" -- cgit v1.2.1