diff options
-rw-r--r-- | cmd2/cmd2.py | 78 | ||||
-rw-r--r-- | cmd2/rl_utils.py | 14 |
2 files changed, 89 insertions, 3 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index d817e461..baf511f3 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, rl_get_point +from .rl_utils import rl_type, RlType, rl_get_point, rl_set_prompt 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" \ @@ -3248,6 +3248,64 @@ Script should contain one command per line, just like command would be typed in # Redraw the prompt and input line rl_force_redisplay() + def update_prompt(self, new_prompt: 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 + 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() + + # Move the cursor to the beginning of the line + terminal_str += '\r' + + # Set the new prompt + rl_set_prompt(new_prompt) + self.prompt = new_prompt + + # 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 alerter(self): import time while True: @@ -3259,8 +3317,22 @@ Script should contain one command per line, just like command would be typed in def do_alert(self, args): import threading - printer = threading.Thread(target=self.alerter, daemon=True) - printer.start() + alert_thread = threading.Thread(target=self.alerter, daemon=True) + alert_thread.start() + + def prompt_changer(self): + import time + counter = 1 + while True: + time.sleep(1) + self.update_prompt("({:>3}) ".format(counter)) + counter += 1 + + def do_update_prompt(self, args): + import threading + self.prompt = "( 0) " + change_thread = threading.Thread(target=self.prompt_changer, daemon=True) + change_thread.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 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) |