From ec464656569799bffe154128897fa79c8549cbde Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Oct 2012 21:41:07 +0200 Subject: [PATCH] Implemented ansi-colour backgrounds in webclient. Added a new @color command for displaying colour spaces. Also changed a number of other features outlined in Issue 309. --- src/commands/default/cmdset_default.py | 1 + src/commands/default/general.py | 60 +++++++++++++- src/commands/default/system.py | 1 + src/server/webclient.py | 2 +- src/utils/ansi.py | 19 +++-- src/utils/text2html.py | 109 ++++++++++++++++--------- src/web/media/css/webclient.css | 46 ++++++++--- 7 files changed, 177 insertions(+), 61 deletions(-) diff --git a/src/commands/default/cmdset_default.py b/src/commands/default/cmdset_default.py index 85e3599c9a..e58a8423a2 100644 --- a/src/commands/default/cmdset_default.py +++ b/src/commands/default/cmdset_default.py @@ -29,6 +29,7 @@ class DefaultCmdSet(CmdSet): self.add(general.CmdDrop()) self.add(general.CmdSay()) self.add(general.CmdAccess()) + self.add(general.CmdColorTest()) # The help system self.add(help.CmdHelp()) diff --git a/src/commands/default/general.py b/src/commands/default/general.py index b5503b82ca..627d4a24ea 100644 --- a/src/commands/default/general.py +++ b/src/commands/default/general.py @@ -13,7 +13,7 @@ from src.commands.default.muxcommand import MuxCommand, MuxCommandOOC __all__ = ("CmdHome", "CmdLook", "CmdPassword", "CmdNick", "CmdInventory", "CmdGet", "CmdDrop", "CmdQuit", "CmdWho", "CmdSay", "CmdPose", "CmdEncoding", "CmdAccess", - "CmdOOCLook", "CmdIC", "CmdOOC") + "CmdOOCLook", "CmdIC", "CmdOOC", "CmdColorTest") AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) BASE_PLAYER_TYPECLASS = settings.BASE_PLAYER_TYPECLASS @@ -753,3 +753,61 @@ class CmdOOC(MuxCommandOOC): caller.msg("\n{GYou go OOC.{n\n") caller.execute_cmd("look") + +class CmdColorTest(MuxCommand): + """ + testing colors + + Usage: + @color ansi|xterm256 + + Print a color map along with in-mud color codes, while testing what is supported in your client. + Choices are 16-color ansi (supported in most muds) or the 256-color xterm256 standard. + No checking is done to determine your client supports color - if not you will + see rubbish appear. + """ + key = "@color" + locks = "cmd:all()" + help_category = "General" + + def func(self): + "Show color tables" + + if not self.args or not self.args in ("ansi", "xterm256"): + self.caller.msg("Usage: @color ansi|xterm256") + return + + if self.args == "ansi": + from src.utils import ansi + ap = ansi.ANSI_PARSER + # ansi colors + # show all ansi color-related codes + col1 = ["%s%s{n" % (code, code.replace("{","{{")) for code, _ in ap.ext_ansi_map[:-1]] + hi = "%ch" + col2 = ["%s%s{n" % (code, code.replace("%", "%%")) for code, _ in ap.mux_ansi_map[:-2]] + col3 = ["%s%s{n" % (hi+code, (hi+code).replace("%", "%%")) for code, _ in ap.mux_ansi_map[:-2]] + table = utils.format_table([col1, col2, col3], extra_space=1) + string = "ANSI colors:" + for row in table: + string += "\n" + "".join(row) + print string + self.caller.msg(string) + self.caller.msg("{{X and %%cx are black-on-black)") + elif self.args == "xterm256": + table = [[],[],[],[],[],[],[],[],[],[],[],[]] + for ir in range(6): + for ig in range(6): + for ib in range(6): + # foreground table + table[ir].append("%%c%i%i%i%s{n" % (ir,ig,ib, "{{%i%i%i" % (ir,ig,ib))) + # background table + table[6+ir].append("%%cb%i%i%i%%c%i%i%i%s{n" % (ir,ig,ib, + 5-ir,5-ig,5-ib, + "{{b%i%i%i" % (ir,ig,ib))) + table = utils.format_table(table) + string = "Xterm256 colors:" + for row in table: + string += "\n" + "".join(row) + self.caller.msg(string) + self.caller.msg("(e.g. %%c123 and %%cb123 also work)") + diff --git a/src/commands/default/system.py b/src/commands/default/system.py index efc162d60a..a727326ad1 100644 --- a/src/commands/default/system.py +++ b/src/commands/default/system.py @@ -690,6 +690,7 @@ class CmdServerLoad(MuxCommand): string += "\n{w On-entity Attribute cache usage:{n %5.2f MB (%i items)" % (size, count) caller.msg(string) + # class CmdPs(MuxCommand): # """ # list processes diff --git a/src/server/webclient.py b/src/server/webclient.py index 00ed4c63f5..e7acf3f9a3 100644 --- a/src/server/webclient.py +++ b/src/server/webclient.py @@ -243,7 +243,7 @@ class WebClientSession(session.Session): if raw: self.client.lineSend(self.suid, string) else: - self.client.lineSend(self.suid, parse_html(ansi.parse_ansi(string, strip_ansi=nomarkup))) + self.client.lineSend(self.suid, parse_html(string, strip_ansi=nomarkup)) return except Exception, e: logger.log_trace() diff --git a/src/utils/ansi.py b/src/utils/ansi.py index f0b7c16ba2..b4d47ea930 100644 --- a/src/utils/ansi.py +++ b/src/utils/ansi.py @@ -75,15 +75,12 @@ class ANSIParser(object): # MUX-style mappings %cr %cn etc self.mux_ansi_map = [ - (r'%r', ANSI_RETURN), - (r'%t', ANSI_TAB), - (r'%b', ANSI_SPACE), - (r'%cf', ANSI_BLINK), - (r'%ci', ANSI_INVERSE), - (r'%ch', ANSI_HILITE), - (r'%cn', ANSI_NORMAL), - (r'%cx', ANSI_BLACK), - (r'%cX', ANSI_BACK_BLACK), + # commented out by default; they (especially blink) are potentially annoying + #(r'%r', ANSI_RETURN), + #(r'%t', ANSI_TAB), + #(r'%b', ANSI_SPACE), + #(r'%cf', ANSI_BLINK), + #(r'%ci', ANSI_INVERSE), (r'%cr', ANSI_RED), (r'%cR', ANSI_BACK_RED), (r'%cg', ANSI_GREEN), @@ -98,6 +95,10 @@ class ANSIParser(object): (r'%cC', ANSI_BACK_CYAN), (r'%cw', ANSI_WHITE), (r'%cW', ANSI_BACK_WHITE), + (r'%cx', ANSI_BLACK), + (r'%cX', ANSI_BACK_BLACK), + (r'%ch', ANSI_HILITE), + (r'%cn', ANSI_NORMAL), ] # Expanded mapping {r {n etc diff --git a/src/utils/text2html.py b/src/utils/text2html.py index aaf9cd4e25..ca60a77e66 100644 --- a/src/utils/text2html.py +++ b/src/utils/text2html.py @@ -11,7 +11,7 @@ snippet #577349 on http://code.activestate.com. import re import cgi -from src.utils import ansi +from ansi import * class TextToHTMLparser(object): """ @@ -20,47 +20,80 @@ class TextToHTMLparser(object): tabstop = 4 # mapping html color name <-> ansi code. - # note that \[ is used here since they go into regexes. - colorcodes = [('white', '\033\[1m\033\[37m'), - ('cyan', '\033\[1m\033\[36m'), - ('blue', '\033\[1m\033\[34m'), - ('red', '\033\[1m\033\[31m'), - ('magenta', '\033\[1m\033\[35m'), - ('lime', '\033\[1m\033\[32m'), - ('yellow', '\033\[1m\033\[33m'), - ('gray', '\033\[37m'), - ('teal', '\033\[36m'), - ('navy', '\033\[34m'), - ('maroon', '\033\[31m'), - ('purple', '\033\[35m'), - ('green', '\033\[32m'), - ('olive', '\033\[33m')] - normalcode = '\033\[0m' - bold = '\033\[1m' - underline = '\033\[4m' - codestop = "|".join(co[1] for co in colorcodes + [("", normalcode), ("", bold), ("", underline), ("", "$")]) + hilite = ANSI_HILITE + normal = ANSI_NORMAL + underline = ANSI_UNDERLINE + colorcodes = [ + ('red', hilite + ANSI_RED), + ('maroon', ANSI_RED), + ('lime', hilite + ANSI_GREEN), + ('green', ANSI_GREEN), + ('yellow', hilite + ANSI_YELLOW), + ('olive', ANSI_YELLOW), + ('blue', hilite + ANSI_BLUE), + ('navy', ANSI_BLUE), + ('magenta', hilite + ANSI_MAGENTA), + ('purple', ANSI_MAGENTA), + ('cyan', hilite + ANSI_CYAN), + ('teal', ANSI_CYAN), + ('white', hilite + ANSI_WHITE), # pure white + ('gray', ANSI_WHITE), #light grey + ('dimgray', hilite + ANSI_BLACK), #dark grey + ('black', ANSI_BLACK), #pure black + ] + colorback = [ + ('bgred', hilite + ANSI_BACK_RED), + ('bgmaroon', ANSI_BACK_RED), + ('bglime', hilite + ANSI_BACK_GREEN), + ('bggreen', ANSI_BACK_GREEN), + ('bgyellow', hilite + ANSI_BACK_YELLOW), + ('bgolive', ANSI_BACK_YELLOW), + ('bgblue', hilite + ANSI_BACK_BLUE), + ('bgnavy', ANSI_BACK_BLUE), + ('bgmagenta', hilite + ANSI_BACK_MAGENTA), + ('bgpurple', ANSI_BACK_MAGENTA), + ('bgcyan', hilite + ANSI_BACK_CYAN), + ('bgteal', ANSI_BACK_CYAN), + ('bgwhite', hilite + ANSI_BACK_WHITE), + ('bggray', ANSI_BACK_WHITE), + ('bgdimgray', hilite + ANSI_BACK_BLACK), + ('bgblack', ANSI_BACK_BLACK), + ] + # make sure to escape [ + colorcodes = [(c, code.replace("[",r"\[")) for c, code in colorcodes] + colorback = [(c, code.replace("[",r"\[")) for c, code in colorback] + # create stop markers + fgstop = [("", c.replace("[", r"\[")) for c in (normal, hilite, underline)] + bgstop = [("", c.replace("[", r"\[")) for c in (normal,)] + fgstop = "|".join(co[1] for co in colorcodes + fgstop + [("", "$")]) + bgstop = "|".join(co[1] for co in colorback + bgstop + [("", "$")]) + + # pre-compile regexes + re_fgs = [(cname, re.compile("(?:%s)(.*?)(?=%s)" % (code, fgstop))) for cname, code in colorcodes] + re_bgs = [(cname, re.compile("(?:%s)(.*?)(?=%s)" % (code, bgstop))) for cname, code in colorback] + re_normal = re.compile(normal.replace("[", r"\[")) + re_hilite = re.compile("(?:%s)(.*)(?=%s)" % (hilite.replace("[", r"\["), fgstop)) + re_uline = re.compile("(?:%s)(.*?)(?=%s)" % (ANSI_UNDERLINE.replace("[",r"\["), fgstop)) re_string = re.compile(r'(?P[<&>])|(?P^[ \t]+)|(?P\r\n|\r|\n)', re.S|re.M|re.I) def re_color(self, text): - """Replace ansi colors with html color class names. - Let the client choose how it will display colors, if it wishes to.""" - for colorname, code in self.colorcodes: - regexp = "(?:%s)(.*?)(?=%s)" % (code, self.codestop) - text = re.sub(regexp, r'''\1''' % colorname, text) - return re.sub(self.normalcode, "", text) + """ + Replace ansi colors with html color class names. + Let the client choose how it will display colors, if it wishes to. """ + for colorname, regex in self.re_fgs: + text = regex.sub(r'''\1''' % colorname, text) + for bgname, regex in self.re_bgs: + text = regex.sub(r'''\1''' % bgname, text) + return self.re_normal.sub("", text) def re_bold(self, text): "Clean out superfluous hilights rather than set to make it match the look of telnet." - #"Replace ansi hilight with strong text element." - #regexp = "(?:%s)(.*?)(?=%s)" % (self.bold, self.codestop) - #return re.sub(regexp, r'\1', text) - return re.sub(self.bold, "", text) + return self.re_hilite.sub(r'\1', text) def re_underline(self, text): "Replace ansi underline with html underline class name." - regexp = "(?:%s)(.*?)(?=%s)" % (self.underline, self.codestop) - return re.sub(regexp, r'\1', text) + return self.re_uline.sub(r'\1', text) def remove_bells(self, text): "Remove ansi specials" @@ -99,15 +132,13 @@ class TextToHTMLparser(object): t = t.replace(' ', ' ') return t - def parse(self, text): + def parse(self, text, strip_ansi=False): """ Main access function, converts a text containing ansi codes into html statements. """ - # parse everything to ansi first - text = ansi.parse_ansi(text) - + text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=False) # convert all ansi to html result = re.sub(self.re_string, self.do_sub, text) result = self.re_color(result) @@ -118,7 +149,7 @@ class TextToHTMLparser(object): result = self.remove_backspaces(result) result = self.convert_urls(result) # clean out eventual ansi that was missed - result = ansi.parse_ansi(result, strip_ansi=True) + #result = parse_ansi(result, strip_ansi=True) return result @@ -128,8 +159,8 @@ HTML_PARSER = TextToHTMLparser() # Access function # -def parse_html(string, parser=HTML_PARSER): +def parse_html(string, strip_ansi=False, parser=HTML_PARSER): """ Parses a string, replace ansi markup with html """ - return parser.parse(string) + return parser.parse(string, strip_ansi=strip_ansi) diff --git a/src/web/media/css/webclient.css b/src/web/media/css/webclient.css index cde38ffcd4..166b541c5d 100644 --- a/src/web/media/css/webclient.css +++ b/src/web/media/css/webclient.css @@ -18,12 +18,17 @@ body { font-family: 'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace; line-height: 1.6em } + a:link, a:visited { color: #fff } a:hover, a:active { color: #ccc } +/* Set this to e.g. bolder if wanting to have ansi-highlights bolden + * stand-alone text.*/ +strong {font-weight:normal;} + /* Base style for new messages in the main message area */ .msg { - white-space: pre-wrap; + white-space: pre-wrap; padding: .5em .9em;} /*border-bottom: 1px dotted #222 } /*optional line between messages */ @@ -40,22 +45,41 @@ a:hover, a:active { color: #ccc } .err { color: #f00 } /* Style specific classes corresponding to formatted, narative text. */ -.white { color: white; } -.cyan { color: #00FFFF; } -.blue { color: blue; } .red { color: red; } -.magenta { color: #FF00FF; } -.lime { color: lime; } -.yellow { color: yellow; } -.gray { color: gray; } -.teal { color: teal; } -.navy { color: navy; } .maroon { color: maroon; } -.purple { color: purple; } +.lime { color: lime; } .green { color: green; } +.yellow { color: yellow; } .olive { color: olive; } +.blue { color: blue; } +.navy { color: navy; } +.magenta { color: #FF00FF; } +.purple { color: purple; } +.cyan { color: #00FFFF; } +.teal { color: teal; } +.white { color: white; } +.gray { color: gray; } +.dimgray {color: #696969;} +.black {color: black;} .underline { text-decoration: underline; } +.bgred { background-color: red;} +.bgmaroon { background-color: maroon;} +.bglime { background-color: lime;} +.bggreen { background-color: green;} +.bgyellow { background-color: yellow;} +.bgolive { background-color: olive;} +.bgblue { background-color: blue;} +.bgnavy { background-color: navy;} +.bgmagenta { background-color: #FF00FF;} +.bgpurple { background-color: purple;} +.bgcyan { background-color: #00FFFF;} +.bgteal { background-color: teal;} +.bgwhite { background-color: white;} +.bggray { background-color: gray;} +.bgdimgray { background-color: #696969;} +.bgblack { background-color: black;} + /* Container surrounding entire chat */ #wrapper { position: relative;