From a85f8995b34d078c05f857b32570df5ad1d9ef30 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sat, 12 Jun 2021 12:32:20 -0400 Subject: [PATCH 1/4] utils.format_grid strip ansi support adds option to ignore ansi markups when calculating grid lengths. evennia.utils.tests.test_utils.TestFormatgrid passes --- evennia/utils/utils.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index ee8274efe5..bf8710b3b8 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1837,7 +1837,10 @@ def percentile(iterable, percent, key=lambda x: x): return d0 + d1 -def format_grid(elements, width=78, sep=" ", verbatim_elements=None): +from evennia.utils.ansi import strip_ansi + + +def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ansi=True): """ This helper function makes a 'grid' output, where it distributes the given string-elements as evenly as possible to fill out the given width. @@ -1854,12 +1857,17 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): be inserted into the grid at the correct position and may be surrounded by padding unless filling the entire line. This is useful for embedding decorations in the grid, such as horizontal bars. + ignore_ansi (bool, optional): Ignore ansi markups when calculating white spacing. Returns: list: The grid as a list of ready-formatted rows. We return it like this to make it easier to insert decorations between rows, such as horizontal bars. """ + def ansi_len(text): + """Use arg ignore_ansi to calculate length of text in element.""" + return len(strip_ansi(text)) if ignore_ansi else len(text) + def _minimal_rows(elements): """ Minimalistic distribution with minimal spacing, good for single-line @@ -1867,8 +1875,8 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): """ rows = [""] for element in elements: - rowlen = len(rows[-1]) - elen = len(element) + rowlen = ansi_len((rows[-1])) + elen = ansi_len((element)) if rowlen + elen <= width: rows[-1] += element else: @@ -1880,8 +1888,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): Dynamic-space, good for making even columns in a multi-line grid but will look strange for a single line. """ - - wls = [len(elem) for elem in elements] + wls = [ansi_len((elem)) for elem in elements] wls_percentile = [wl for iw, wl in enumerate(wls) if iw not in verbatim_elements] if wls_percentile: @@ -1896,7 +1903,8 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): # one line per row, output directly since this is trivial # we use rstrip here to remove extra spaces added by sep return [ - crop(element.rstrip(), width) + " " * max(0, width - len(element.rstrip())) + crop(element.rstrip(), width) + " " \ + * max(0, width - ansi_len((element.rstrip()))) for iel, element in enumerate(elements) ] @@ -1908,7 +1916,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): for ie, element in enumerate(elements): wl = wls[ie] - lrow = len(row) + lrow = ansi_len((row)) # debug = row.replace(" ", ".") if lrow + wl > width: @@ -1947,7 +1955,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): if ie >= nelements - 1: # last element, make sure to store - row += " " * max(0, width - len(row)) + row += " " * max(0, width - ansi_len((row))) rows.append(row) return rows @@ -1960,7 +1968,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): # add sep to all but the very last element elements = [elements[ie] + sep for ie in range(nelements - 1)] + [elements[-1]] - if sum(len(element) for element in elements) <= width: + if sum(ansi_len((element)) for element in elements) <= width: # grid fits in one line return _minimal_rows(elements) else: From 6cf1f65722eacef0d88bc04695c2abf85975d015 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sat, 12 Jun 2021 12:35:07 -0400 Subject: [PATCH 2/4] CmdHelp clickable topics Added clickable topics throughout CmdHelp. unit test evennia.commands.default.tests.TestHelp passes. --- evennia/commands/default/help.py | 84 +++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index c559ad019b..1a2f815590 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -97,6 +97,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # separator between subtopics: subtopic_separator_char = r"/" + # should topics disply their help entry when clicked + clickable_topics = True + def msg_help(self, text): """ messages text to the caller, adding an extra oob argument to indicate @@ -121,21 +124,22 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): self.msg(text=(text, {"type": "help"})) def format_help_entry(self, topic="", help_text="", aliases=None, suggested=None, - subtopics=None): - """ - This visually formats the help entry. + subtopics=None, click_topics=True): + """This visually formats the help entry. This method can be overriden to customize the way a help entry is displayed. Args: - title (str): The title of the help entry. - help_text (str): Text of the help entry. - aliases (list): List of help-aliases (displayed in header). - suggested (list): Strings suggested reading (based on title). - subtopics (list): A list of strings - the subcategories available + title (str, optional): The title of the help entry. + help_text (str, optional): Text of the help entry. + aliases (list, optional): List of help-aliases (displayed in header). + suggested (list, optional): Strings suggested reading (based on title). + subtopics (list, optional): A list of strings - the subcategories available for this entry. + click_topics (bool, optional): Should help topics be clickable. Default is True. - Returns the formatted string, ready to be sent. + Returns: + help_message (str): Help entry formated for console. """ separator = "|C" + "-" * self.client_width() + "|n" @@ -153,7 +157,13 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): help_text = "\n" + dedent(help_text.strip('\n')) if help_text else "" if subtopics: - subtopics = [f"|w{topic}/{subtop}|n" for subtop in subtopics] + if click_topics: + subtopics = [ + f"|lchelp {topic}/{subtop}|lt|w{topic}/{subtop}|n|le" + for subtop in subtopics + ] + else: + subtopics = [f"|w{topic}/{subtop}|n" for subtop in subtopics] subtopics = ( "\n|CSubtopics:|n\n {}".format( "\n ".join(format_grid(subtopics, width=self.client_width()))) @@ -162,7 +172,10 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): subtopics = '' if suggested: - suggested = [f"|w{sug}|n" for sug in suggested] + if click_topics: + suggested = [f"|lchelp {sug}|lt|w{sug}|n|le" for sug in suggested] + else: + suggested = [f"|w{sug}|n" for sug in suggested] suggested = ( "\n|COther topic suggestions:|n\n{}".format( "\n ".join(format_grid(suggested, width=self.client_width()))) @@ -176,9 +189,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): return "\n".join(part.rstrip() for part in partorder if part) - def format_help_index(self, cmd_help_dict=None, db_help_dict=None, title_lone_category=False): - """ - Output a category-ordered g for displaying the main help, grouped by + def format_help_index(self, cmd_help_dict=None, db_help_dict=None, title_lone_category=False, + click_topics=True): + """Output a category-ordered g for displaying the main help, grouped by category. Args: @@ -190,14 +203,15 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): be titled with the category name or not. While pointless in a general index, the title should probably show when explicitly listing the category itself. + click_topics (bool, optional): Should help topics be clickable. Default is True. Returns: str: The help index organized into a grid. - The input are the - pre-loaded help files for commands and database-helpfiles - respectively. You can override this method to return a - custom display of the list of commands and topics. + Notes + The input are the pre-loaded help files for commands and database-helpfiles + respectively. You can override this method to return a custom display of the list of + commands and topics. """ def _group_by_category(help_dict): @@ -207,7 +221,16 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): if len(help_dict) == 1 and not title_lone_category: # don't list categories if there is only one for category in help_dict: + # gather and sort the entries from the help dictionary entries = sorted(set(help_dict.get(category, []))) + + # make the help topics clickable + if click_topics: + entries = [ + f'|lchelp {entry}|lt{entry}|le' for entry in entries + ] + + # add the entries to the grid grid.extend(entries) else: # list the categories @@ -222,7 +245,16 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): ) verbatim_elements.append(len(grid) - 1) + # gather and sort the entries from the help dictionary entries = sorted(set(help_dict.get(category, []))) + + # make the help topics clickable + if click_topics: + entries = [ + f'|lchelp {entry}|lt{entry}|le' for entry in entries + ] + + # add the entries to the grid grid.extend(entries) return grid, verbatim_elements @@ -449,6 +481,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): """ caller = self.caller query, subtopics, cmdset = self.topic, self.subtopics, self.cmdset + clickable_topics = self.clickable_topics if not query: # list all available help entries, grouped by category. We want to @@ -470,7 +503,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # generate the index and display output = self.format_help_index(cmd_help_by_category, - file_db_help_by_category) + file_db_help_by_category, + click_topics=clickable_topics) self.msg_help(output) return @@ -526,7 +560,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): output = self.format_help_entry( topic=None, # this will give a no-match style title help_text=help_text, - suggested=suggestions + suggested=suggestions, + click_topics=clickable_topics ) self.msg_help(output) @@ -542,7 +577,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): if category_lower == topic.help_category] output = self.format_help_index({category: cmds_in_category}, {category: topics_in_category}, - title_lone_category=True) + title_lone_category=True, + click_topics=clickable_topics) self.msg_help(output) return @@ -596,7 +632,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): output = self.format_help_entry( topic=topic, help_text=f"No help entry found for '{checked_topic}'", - subtopics=subtopic_index + subtopics=subtopic_index, + click_topics=clickable_topics ) self.msg_help(output) return @@ -616,7 +653,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): help_text=help_text, aliases=aliases if not subtopics else None, subtopics=subtopic_index, - suggested=suggested + suggested=suggested, + click_topics=clickable_topics ) self.msg_help(output) From da6e1edb29c2a908ebedf6595243b00ef0b1c21e Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Tue, 15 Jun 2021 12:47:24 -0400 Subject: [PATCH 3/4] CmdHelp clickable global setting False Created a global setting for clickable topics in the help command. Default is False. --- evennia/commands/default/help.py | 3 ++- evennia/settings_default.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index 1a2f815590..93201f07db 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -28,6 +28,7 @@ from evennia.help.utils import help_search_with_index, parse_entry_for_subcatego COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) HELP_MORE_ENABLED = settings.HELP_MORE_ENABLED DEFAULT_HELP_CATEGORY = settings.DEFAULT_HELP_CATEGORY +HELP_CLICKABLE_TOPICS = settings.HELP_CLICKABLE_TOPICS # limit symbol import for API __all__ = ("CmdHelp", "CmdSetHelp") @@ -98,7 +99,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): subtopic_separator_char = r"/" # should topics disply their help entry when clicked - clickable_topics = True + clickable_topics = HELP_CLICKABLE_TOPICS def msg_help(self, text): """ diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 1450fc6c52..227d3db96c 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -614,6 +614,9 @@ DEFAULT_HELP_CATEGORY = "general" # File-based help entries. These are modules containing dicts defining help # entries. They can be used together with in-database entries created in-game. FILE_HELP_ENTRY_MODULES = ["world.help_entries"] +# if topics listed in help should be clickable +# clickable links only work on clients that support MXP +HELP_CLICKABLE_TOPICS = False ###################################################################### # FuncParser From 02fc0f998e8cca04a88501e54c163248095e2521 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sat, 19 Jun 2021 11:46:46 -0400 Subject: [PATCH 4/4] Change to utils.display_len evennia.utils.tests.test_utils.TestFormatGrid evennia.commands.default.tests.TestHelp Both pass --- evennia/utils/utils.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index bf8710b3b8..fe0184f655 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1837,10 +1837,7 @@ def percentile(iterable, percent, key=lambda x: x): return d0 + d1 -from evennia.utils.ansi import strip_ansi - - -def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ansi=True): +def format_grid(elements, width=78, sep=" ", verbatim_elements=None): """ This helper function makes a 'grid' output, where it distributes the given string-elements as evenly as possible to fill out the given width. @@ -1864,9 +1861,6 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ans like this to make it easier to insert decorations between rows, such as horizontal bars. """ - def ansi_len(text): - """Use arg ignore_ansi to calculate length of text in element.""" - return len(strip_ansi(text)) if ignore_ansi else len(text) def _minimal_rows(elements): """ @@ -1875,8 +1869,8 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ans """ rows = [""] for element in elements: - rowlen = ansi_len((rows[-1])) - elen = ansi_len((element)) + rowlen = display_len((rows[-1])) + elen = display_len((element)) if rowlen + elen <= width: rows[-1] += element else: @@ -1888,7 +1882,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ans Dynamic-space, good for making even columns in a multi-line grid but will look strange for a single line. """ - wls = [ansi_len((elem)) for elem in elements] + wls = [display_len((elem)) for elem in elements] wls_percentile = [wl for iw, wl in enumerate(wls) if iw not in verbatim_elements] if wls_percentile: @@ -1904,7 +1898,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ans # we use rstrip here to remove extra spaces added by sep return [ crop(element.rstrip(), width) + " " \ - * max(0, width - ansi_len((element.rstrip()))) + * max(0, width - display_len((element.rstrip()))) for iel, element in enumerate(elements) ] @@ -1916,7 +1910,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ans for ie, element in enumerate(elements): wl = wls[ie] - lrow = ansi_len((row)) + lrow = display_len((row)) # debug = row.replace(" ", ".") if lrow + wl > width: @@ -1955,7 +1949,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ans if ie >= nelements - 1: # last element, make sure to store - row += " " * max(0, width - ansi_len((row))) + row += " " * max(0, width - display_len((row))) rows.append(row) return rows @@ -1968,7 +1962,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None, ignore_ans # add sep to all but the very last element elements = [elements[ie] + sep for ie in range(nelements - 1)] + [elements[-1]] - if sum(ansi_len((element)) for element in elements) <= width: + if sum(display_len((element)) for element in elements) <= width: # grid fits in one line return _minimal_rows(elements) else: