diff options
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r-- | cmd2/cmd2.py | 130 |
1 files changed, 70 insertions, 60 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 2d60206c..2e4a4cb5 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -467,8 +467,10 @@ class Cmd(cmd.Cmd): # An optional hint which prints above tab completion suggestions self.completion_hint = '' - # Header which prints above CompletionItem tables - self.completion_header = '' + # Already formatted completion results. If this is populated, then cmd2 will print it instead + # of using readline's columnized results. ANSI style sequences and newlines in tab completion + # results are supported by this member. ArgparseCompleter uses this to print tab completion tables. + self.formatted_completions = '' # Used by complete() for readline tab completion self.completion_matches = [] @@ -1118,7 +1120,7 @@ class Cmd(cmd.Cmd): self.allow_appended_space = True self.allow_closing_quote = True self.completion_hint = '' - self.completion_header = '' + self.formatted_completions = '' self.completion_matches = [] self.display_matches = [] self.matches_delimited = False @@ -1645,22 +1647,6 @@ class Cmd(cmd.Cmd): return [cur_match + padding for cur_match in matches_to_display], len(padding) - def _build_completion_metadata_string(self) -> str: # pragma: no cover - """Build completion metadata string which can contain a hint and CompletionItem table header""" - metadata = '' - - # Add hint if one exists and we are supposed to display it - if self.always_show_hint and self.completion_hint: - metadata += '\n' + self.completion_hint - - # Add table header if one exists - if self.completion_header: - if not metadata: - metadata += '\n' - metadata += '\n' + self.completion_header - - return metadata - def _display_matches_gnu_readline( self, substitution: str, matches: List[str], longest_match_length: int ) -> None: # pragma: no cover @@ -1673,45 +1659,55 @@ class Cmd(cmd.Cmd): """ if rl_type == RlType.GNU: - # Check if we should show display_matches - if self.display_matches: - matches_to_display = self.display_matches + # Print hint if one exists and we are supposed to display it + hint_printed = False + if self.always_show_hint and self.completion_hint: + hint_printed = True + sys.stdout.write('\n' + self.completion_hint) - # Recalculate longest_match_length for display_matches - longest_match_length = 0 + # Check if we already have formatted results to print + if self.formatted_completions: + if not hint_printed: + sys.stdout.write('\n') + sys.stdout.write('\n' + self.formatted_completions + '\n\n') - for cur_match in matches_to_display: - cur_length = ansi.style_aware_wcswidth(cur_match) - if cur_length > longest_match_length: - longest_match_length = cur_length + # Otherwise use readline's formatter else: - matches_to_display = matches + # Check if we should show display_matches + if self.display_matches: + matches_to_display = self.display_matches - # Add padding for visual appeal - matches_to_display, padding_length = self._pad_matches_to_display(matches_to_display) - longest_match_length += padding_length + # Recalculate longest_match_length for display_matches + longest_match_length = 0 - # We will use readline's display function (rl_display_match_list()), so we - # need to encode our string as bytes to place in a C array. - encoded_substitution = bytes(substitution, encoding='utf-8') - encoded_matches = [bytes(cur_match, encoding='utf-8') for cur_match in matches_to_display] + for cur_match in matches_to_display: + cur_length = ansi.style_aware_wcswidth(cur_match) + if cur_length > longest_match_length: + longest_match_length = cur_length + else: + matches_to_display = matches + + # Add padding for visual appeal + matches_to_display, padding_length = self._pad_matches_to_display(matches_to_display) + longest_match_length += padding_length - # rl_display_match_list() expects matches to be in argv format where - # substitution is the first element, followed by the matches, and then a NULL. - # noinspection PyCallingNonCallable,PyTypeChecker - strings_array = (ctypes.c_char_p * (1 + len(encoded_matches) + 1))() + # We will use readline's display function (rl_display_match_list()), so we + # need to encode our string as bytes to place in a C array. + encoded_substitution = bytes(substitution, encoding='utf-8') + encoded_matches = [bytes(cur_match, encoding='utf-8') for cur_match in matches_to_display] - # Copy in the encoded strings and add a NULL to the end - strings_array[0] = encoded_substitution - strings_array[1:-1] = encoded_matches - strings_array[-1] = None + # rl_display_match_list() expects matches to be in argv format where + # substitution is the first element, followed by the matches, and then a NULL. + # noinspection PyCallingNonCallable,PyTypeChecker + strings_array = (ctypes.c_char_p * (1 + len(encoded_matches) + 1))() - # Print any metadata like a hint or table header - sys.stdout.write(self._build_completion_metadata_string()) + # Copy in the encoded strings and add a NULL to the end + strings_array[0] = encoded_substitution + strings_array[1:-1] = encoded_matches + strings_array[-1] = None - # Call readline's display function - # rl_display_match_list(strings_array, number of completion matches, longest match length) - readline_lib.rl_display_match_list(strings_array, len(encoded_matches), longest_match_length) + # rl_display_match_list(strings_array, number of completion matches, longest match length) + readline_lib.rl_display_match_list(strings_array, len(encoded_matches), longest_match_length) # Redraw prompt and input line rl_force_redisplay() @@ -1724,20 +1720,34 @@ class Cmd(cmd.Cmd): """ if rl_type == RlType.PYREADLINE: - # Check if we should show display_matches - if self.display_matches: - matches_to_display = self.display_matches - else: - matches_to_display = matches + # Print hint if one exists and we are supposed to display it + hint_printed = False + if self.always_show_hint and self.completion_hint: + hint_printed = True + readline.rl.mode.console.write('\n' + self.completion_hint) - # Add padding for visual appeal - matches_to_display, _ = self._pad_matches_to_display(matches_to_display) + # Check if we already have formatted results to print + if self.formatted_completions: + if not hint_printed: + readline.rl.mode.console.write('\n') + readline.rl.mode.console.write('\n' + self.formatted_completions + '\n\n') + + # Redraw the prompt and input lines + rl_force_redisplay() + + # Otherwise use pyreadline's formatter + else: + # Check if we should show display_matches + if self.display_matches: + matches_to_display = self.display_matches + else: + matches_to_display = matches - # Print any metadata like a hint or table header - readline.rl.mode.console.write(self._build_completion_metadata_string()) + # Add padding for visual appeal + matches_to_display, _ = self._pad_matches_to_display(matches_to_display) - # Display matches using actual display function. This also redraws the prompt and line. - orig_pyreadline_display(matches_to_display) + # Display matches using actual display function. This also redraws the prompt and input lines. + orig_pyreadline_display(matches_to_display) def _perform_completion( self, text: str, line: str, begidx: int, endidx: int, custom_settings: Optional[utils.CustomCompletionSettings] = None |