diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2018-09-19 02:05:08 -0400 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2018-09-19 02:05:08 -0400 |
commit | bd34fee86d66a610d1e0bb392186dc73e883e2db (patch) | |
tree | aeb522388f6c28e7cfa3d5a1e2a948123c4b1ff3 | |
parent | 36d9ec82c50b037becc204f9fc05d94c4d391ccf (diff) | |
download | cmd2-git-bd34fee86d66a610d1e0bb392186dc73e883e2db.tar.gz |
Wrote function and test command to print alerts
-rw-r--r-- | cmd2/cmd2.py | 70 | ||||
-rw-r--r-- | cmd2/rl_utils.py | 45 |
2 files changed, 113 insertions, 2 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 888d9531..874fdf6c 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -50,7 +50,7 @@ from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer from .parsing import StatementParser, Statement # Set up readline -from .rl_utils import rl_type, RlType +from .rl_utils import rl_type, RlType, rl_get_point if rl_type == RlType.NONE: # pragma: no cover rl_warning = "Readline features including tab completion have been disabled since no \n" \ "supported version of readline was found. To resolve this, install \n" \ @@ -3191,6 +3191,74 @@ Script should contain one command per line, just like command would be typed in runner = unittest.TextTestRunner() runner.run(testcase) + def print_alert(self, alert_msg: str) -> None: + """ + Used to display an important message to the user while they are at the prompt. + The current prompt and input lines are erased, then the alert is printed in their place, and + finally the prompt and input lines restored below the alert. To the user it appears as if + an alert message is printed above the prompt. + + :param alert_msg: the message to display to the user + """ + if rl_type == RlType.NONE: + return + + import shutil + import colorama.ansi as ansi + from colorama import Cursor + + visible_prompt = self.visible_prompt + + # Get the size of the terminal + terminal_size = shutil.get_terminal_size() + + # Figure out how many lines the prompt and user input take up + total_str_size = len(visible_prompt) + len(readline.get_line_buffer()) + num_input_lines = int(total_str_size / terminal_size.columns) + 1 + + # Get the cursor's offset from the beginning of the first input line + cursor_input_offset = len(visible_prompt) + rl_get_point() + + # Calculate what input line the cursor is on + cursor_input_line = int(cursor_input_offset / terminal_size.columns) + 1 + + # Create a string that will clear all input lines and print the alert + terminal_str = '' + + # Move the cursor down to the last input line + if cursor_input_line != num_input_lines: + terminal_str += Cursor.DOWN(num_input_lines - cursor_input_line) + + # Clear each input line from the bottom up so that the cursor ends up on the original first input line + terminal_str += (ansi.clear_line() + Cursor.UP(1)) * (num_input_lines - 1) + terminal_str += ansi.clear_line() + + # Print the alert + terminal_str += alert_msg + '\n' + + # Write the string to the terminal + if rl_type == RlType.GNU: + sys.stdout.write(terminal_str) + elif rl_type == RlType.PYREADLINE: + readline.rl.mode.console.write(terminal_str) + + # Redraw the prompt and input line + rl_force_redisplay() + + def worker(self): + import time + while True: + alert_msg = "\n***********************************************\n" \ + " Message failed to send\n" \ + "***********************************************" + time.sleep(5) + self.print_alert(alert_msg) + + def do_alert(self, args): + import threading + printer = threading.Thread(target=self.worker, daemon=True) + printer.start() + def cmdloop(self, intro: Optional[str]=None) -> None: """This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2. 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 |