Merge branch 'coloring' into olm-command
This commit is contained in:
commit
062a736217
5 changed files with 239 additions and 54 deletions
|
@ -54,7 +54,12 @@ from .colors import Formatted
|
||||||
from .config import RedactType
|
from .config import RedactType
|
||||||
from .globals import SCRIPT_NAME, SERVERS, W, TYPING_NOTICE_TIMEOUT
|
from .globals import SCRIPT_NAME, SERVERS, W, TYPING_NOTICE_TIMEOUT
|
||||||
from .utf import utf8_decode
|
from .utf import utf8_decode
|
||||||
from .utils import server_ts_to_weechat, shorten_sender, string_strikethrough
|
from .utils import (
|
||||||
|
server_ts_to_weechat,
|
||||||
|
shorten_sender,
|
||||||
|
string_strikethrough,
|
||||||
|
color_pair,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
|
@ -1445,7 +1450,9 @@ class RoomBuffer(object):
|
||||||
"message{del_color}>{ncolor}").format(
|
"message{del_color}>{ncolor}").format(
|
||||||
del_color=W.color("chat_delimiters"),
|
del_color=W.color("chat_delimiters"),
|
||||||
ncolor=W.color("reset"),
|
ncolor=W.color("reset"),
|
||||||
error_color=W.color(G.CONFIG.color.error_message))
|
error_color=W.color(color_pair(
|
||||||
|
G.CONFIG.color.error_message_fg,
|
||||||
|
G.CONFIG.color.error_message_bg)))
|
||||||
|
|
||||||
last_line.message = message
|
last_line.message = message
|
||||||
|
|
||||||
|
|
132
matrix/colors.py
132
matrix/colors.py
|
@ -30,12 +30,16 @@ from typing import List
|
||||||
import webcolors
|
import webcolors
|
||||||
from pygments import highlight
|
from pygments import highlight
|
||||||
from pygments.formatter import Formatter, get_style_by_name
|
from pygments.formatter import Formatter, get_style_by_name
|
||||||
from pygments.lexers import get_lexer_by_name, guess_lexer
|
from pygments.lexers import get_lexer_by_name
|
||||||
from pygments.util import ClassNotFound
|
from pygments.util import ClassNotFound
|
||||||
|
|
||||||
from . import globals as G
|
from . import globals as G
|
||||||
from .globals import W
|
from .globals import W
|
||||||
from .utils import string_strikethrough
|
from .utils import (string_strikethrough,
|
||||||
|
string_color_and_reset,
|
||||||
|
color_pair,
|
||||||
|
text_block,
|
||||||
|
colored_text_block)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from HTMLParser import HTMLParser
|
from HTMLParser import HTMLParser
|
||||||
|
@ -59,10 +63,12 @@ class Formatted(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def textwrapper(self):
|
def textwrapper(self):
|
||||||
|
quote_pair = color_pair(G.CONFIG.color.quote_fg,
|
||||||
|
G.CONFIG.color.quote_bg)
|
||||||
return textwrap.TextWrapper(
|
return textwrap.TextWrapper(
|
||||||
width=67,
|
width=67,
|
||||||
initial_indent="{}> ".format(W.color(G.CONFIG.color.quote)),
|
initial_indent="{}> ".format(W.color(quote_pair)),
|
||||||
subsequent_indent="{}> ".format(W.color(G.CONFIG.color.quote)),
|
subsequent_indent="{}> ".format(W.color(quote_pair)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_formatted(self):
|
def is_formatted(self):
|
||||||
|
@ -271,7 +277,7 @@ class Formatted(object):
|
||||||
|
|
||||||
def to_weechat(self):
|
def to_weechat(self):
|
||||||
# TODO BG COLOR
|
# TODO BG COLOR
|
||||||
def add_attribute(string, name, value):
|
def add_attribute(string, name, value, attributes):
|
||||||
if not value:
|
if not value:
|
||||||
return string
|
return string
|
||||||
elif name == "bold":
|
elif name == "bold":
|
||||||
|
@ -299,20 +305,49 @@ class Formatted(object):
|
||||||
W.string_remove_color(string.replace("\n", ""), "")
|
W.string_remove_color(string.replace("\n", ""), "")
|
||||||
)
|
)
|
||||||
elif name == "code":
|
elif name == "code":
|
||||||
try:
|
code_color_pair = color_pair(
|
||||||
lexer = get_lexer_by_name(value)
|
G.CONFIG.color.untagged_code_fg,
|
||||||
except ClassNotFound:
|
G.CONFIG.color.untagged_code_bg
|
||||||
lexer = guess_lexer(string)
|
)
|
||||||
|
|
||||||
try:
|
margin = G.CONFIG.look.code_block_margin
|
||||||
style = get_style_by_name(G.CONFIG.look.pygments_style)
|
|
||||||
except ClassNotFound:
|
|
||||||
style = "native"
|
|
||||||
|
|
||||||
# highlight adds a newline to the end of the string, remove it
|
if attributes["preformatted"]:
|
||||||
# from the output
|
# code block
|
||||||
return highlight(string, lexer,
|
|
||||||
WeechatFormatter(style=style))[:-1]
|
try:
|
||||||
|
lexer = get_lexer_by_name(value)
|
||||||
|
except ClassNotFound:
|
||||||
|
if G.CONFIG.look.code_blocks:
|
||||||
|
return colored_text_block(
|
||||||
|
string,
|
||||||
|
margin=margin,
|
||||||
|
color_pair=code_color_pair)
|
||||||
|
else:
|
||||||
|
return string_color_and_reset(string,
|
||||||
|
code_color_pair)
|
||||||
|
|
||||||
|
try:
|
||||||
|
style = get_style_by_name(G.CONFIG.look.pygments_style)
|
||||||
|
except ClassNotFound:
|
||||||
|
style = "native"
|
||||||
|
|
||||||
|
if G.CONFIG.look.code_blocks:
|
||||||
|
code_block = text_block(string, margin=margin)
|
||||||
|
else:
|
||||||
|
code_block = string
|
||||||
|
|
||||||
|
# highlight adds a newline to the end of the string, remove
|
||||||
|
# it from the output
|
||||||
|
highlighted_code = highlight(
|
||||||
|
code_block,
|
||||||
|
lexer,
|
||||||
|
WeechatFormatter(style=style)
|
||||||
|
).rstrip()
|
||||||
|
|
||||||
|
return highlighted_code
|
||||||
|
else:
|
||||||
|
return string_color_and_reset(string, code_color_pair)
|
||||||
elif name == "fgcolor":
|
elif name == "fgcolor":
|
||||||
return "{color_on}{text}{color_off}".format(
|
return "{color_on}{text}{color_off}".format(
|
||||||
color_on=W.color(value),
|
color_on=W.color(value),
|
||||||
|
@ -337,7 +372,10 @@ class Formatted(object):
|
||||||
# terminal, but doing it the other way around results in garbage.
|
# terminal, but doing it the other way around results in garbage.
|
||||||
if "strikethrough" in attributes:
|
if "strikethrough" in attributes:
|
||||||
text = add_attribute(
|
text = add_attribute(
|
||||||
text, "strikethrough", attributes["strikethrough"]
|
text,
|
||||||
|
"strikethrough",
|
||||||
|
attributes["strikethrough"],
|
||||||
|
attributes
|
||||||
)
|
)
|
||||||
attributes.pop("strikethrough")
|
attributes.pop("strikethrough")
|
||||||
|
|
||||||
|
@ -345,19 +383,26 @@ class Formatted(object):
|
||||||
return prefix + text.replace("\n", "\n{}".format(prefix))
|
return prefix + text.replace("\n", "\n{}".format(prefix))
|
||||||
|
|
||||||
for key, value in attributes.items():
|
for key, value in attributes.items():
|
||||||
# Don't use textwrap to quote the code
|
if not value:
|
||||||
if attributes["code"] and key == "quote" and value:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
text = add_attribute(text, key, value)
|
# Don't use textwrap to quote the code
|
||||||
|
if key == "quote" and attributes["code"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Reflow inline code blocks
|
||||||
|
if key == "code" and not attributes["preformatted"]:
|
||||||
|
text = text.strip().replace('\n', ' ')
|
||||||
|
|
||||||
|
text = add_attribute(text, key, value, attributes)
|
||||||
|
|
||||||
# If we're quoted code add quotation marks now.
|
# If we're quoted code add quotation marks now.
|
||||||
if attributes["quote"] and key == "code" and value:
|
if key == "code" and attributes["quote"]:
|
||||||
|
fg = G.CONFIG.color.quote_fg
|
||||||
|
bg = G.CONFIG.color.quote_bg
|
||||||
text = indent(
|
text = indent(
|
||||||
text,
|
text,
|
||||||
"{}>{} ".format(
|
string_color_and_reset(">", color_pair(fg, bg)) + " ",
|
||||||
W.color(G.CONFIG.color.quote), W.color("reset")
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# If we're code don't remove multiple newlines blindly
|
# If we're code don't remove multiple newlines blindly
|
||||||
|
@ -382,6 +427,7 @@ DEFAULT_ATTRIBUTES = {
|
||||||
"italic": False,
|
"italic": False,
|
||||||
"underline": False,
|
"underline": False,
|
||||||
"strikethrough": False,
|
"strikethrough": False,
|
||||||
|
"preformatted": False,
|
||||||
"quote": False,
|
"quote": False,
|
||||||
"code": None,
|
"code": None,
|
||||||
"fgcolor": None,
|
"fgcolor": None,
|
||||||
|
@ -430,6 +476,8 @@ class MatrixHtmlParser(HTMLParser):
|
||||||
self._toggle_attribute("strikethrough")
|
self._toggle_attribute("strikethrough")
|
||||||
elif tag == "blockquote":
|
elif tag == "blockquote":
|
||||||
self._toggle_attribute("quote")
|
self._toggle_attribute("quote")
|
||||||
|
elif tag == "pre":
|
||||||
|
self._toggle_attribute("preformatted")
|
||||||
elif tag == "code":
|
elif tag == "code":
|
||||||
lang = None
|
lang = None
|
||||||
|
|
||||||
|
@ -480,6 +528,8 @@ class MatrixHtmlParser(HTMLParser):
|
||||||
self._toggle_attribute("underline")
|
self._toggle_attribute("underline")
|
||||||
elif tag == "del":
|
elif tag == "del":
|
||||||
self._toggle_attribute("strikethrough")
|
self._toggle_attribute("strikethrough")
|
||||||
|
elif tag == "pre":
|
||||||
|
self._toggle_attribute("preformatted")
|
||||||
elif tag == "code":
|
elif tag == "code":
|
||||||
if self.text:
|
if self.text:
|
||||||
self.add_substring(self.text, self.attributes.copy())
|
self.add_substring(self.text, self.attributes.copy())
|
||||||
|
@ -624,20 +674,20 @@ def color_line_to_weechat(color_string):
|
||||||
return line_colors[color_string]
|
return line_colors[color_string]
|
||||||
|
|
||||||
|
|
||||||
# The functions colour_dist_sq(), colour_to_6cube(), and colour_find_rgb
|
# The functions color_dist_sq(), color_to_6cube(), and color_find_rgb
|
||||||
# are python ports of the same named functions from the tmux
|
# are python ports of the same named functions from the tmux
|
||||||
# source, they are under the copyright of Nicholas Marriott, and Avi Halachmi
|
# source, they are under the copyright of Nicholas Marriott, and Avi Halachmi
|
||||||
# under the ISC license.
|
# under the ISC license.
|
||||||
# More info: https://github.com/tmux/tmux/blob/master/colour.c
|
# More info: https://github.com/tmux/tmux/blob/master/colour.c
|
||||||
|
|
||||||
|
|
||||||
def colour_dist_sq(R, G, B, r, g, b):
|
def color_dist_sq(R, G, B, r, g, b):
|
||||||
# pylint: disable=invalid-name,too-many-arguments
|
# pylint: disable=invalid-name,too-many-arguments
|
||||||
# type: (int, int, int, int, int, int) -> int
|
# type: (int, int, int, int, int, int) -> int
|
||||||
return (R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b)
|
return (R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b)
|
||||||
|
|
||||||
|
|
||||||
def colour_to_6cube(v):
|
def color_to_6cube(v):
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
# type: (int) -> int
|
# type: (int) -> int
|
||||||
if v < 48:
|
if v < 48:
|
||||||
|
@ -647,15 +697,15 @@ def colour_to_6cube(v):
|
||||||
return (v - 35) // 40
|
return (v - 35) // 40
|
||||||
|
|
||||||
|
|
||||||
def colour_find_rgb(r, g, b):
|
def color_find_rgb(r, g, b):
|
||||||
# type: (int, int, int) -> int
|
# type: (int, int, int) -> int
|
||||||
"""Convert an RGB triplet to the xterm(1) 256 colour palette.
|
"""Convert an RGB triplet to the xterm(1) 256 color palette.
|
||||||
|
|
||||||
xterm provides a 6x6x6 colour cube (16 - 231) and 24 greys (232 - 255).
|
xterm provides a 6x6x6 color cube (16 - 231) and 24 greys (232 - 255).
|
||||||
We map our RGB colour to the closest in the cube, also work out the
|
We map our RGB color to the closest in the cube, also work out the
|
||||||
closest grey, and use the nearest of the two.
|
closest grey, and use the nearest of the two.
|
||||||
|
|
||||||
Note that the xterm has much lower resolution for darker colours (they
|
Note that the xterm has much lower resolution for darker colors (they
|
||||||
are not evenly spread out), so our 6 levels are not evenly spread: 0x0,
|
are not evenly spread out), so our 6 levels are not evenly spread: 0x0,
|
||||||
0x5f (95), 0x87 (135), 0xaf (175), 0xd7 (215) and 0xff (255). Greys are
|
0x5f (95), 0x87 (135), 0xaf (175), 0xd7 (215) and 0xff (255). Greys are
|
||||||
more evenly spread (8, 18, 28 ... 238).
|
more evenly spread (8, 18, 28 ... 238).
|
||||||
|
@ -664,15 +714,15 @@ def colour_find_rgb(r, g, b):
|
||||||
q2c = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
|
q2c = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
|
||||||
|
|
||||||
# Map RGB to 6x6x6 cube.
|
# Map RGB to 6x6x6 cube.
|
||||||
qr = colour_to_6cube(r)
|
qr = color_to_6cube(r)
|
||||||
qg = colour_to_6cube(g)
|
qg = color_to_6cube(g)
|
||||||
qb = colour_to_6cube(b)
|
qb = color_to_6cube(b)
|
||||||
|
|
||||||
cr = q2c[qr]
|
cr = q2c[qr]
|
||||||
cg = q2c[qg]
|
cg = q2c[qg]
|
||||||
cb = q2c[qb]
|
cb = q2c[qb]
|
||||||
|
|
||||||
# If we have hit the colour exactly, return early.
|
# If we have hit the color exactly, return early.
|
||||||
if cr == r and cg == g and cb == b:
|
if cr == r and cg == g and cb == b:
|
||||||
return 16 + (36 * qr) + (6 * qg) + qb
|
return 16 + (36 * qr) + (6 * qg) + qb
|
||||||
|
|
||||||
|
@ -686,10 +736,10 @@ def colour_find_rgb(r, g, b):
|
||||||
|
|
||||||
grey = 8 + (10 * grey_idx)
|
grey = 8 + (10 * grey_idx)
|
||||||
|
|
||||||
# Is grey or 6x6x6 colour closest?
|
# Is grey or 6x6x6 color closest?
|
||||||
d = colour_dist_sq(cr, cg, cb, r, g, b)
|
d = color_dist_sq(cr, cg, cb, r, g, b)
|
||||||
|
|
||||||
if colour_dist_sq(grey, grey, grey, r, g, b) < d:
|
if color_dist_sq(grey, grey, grey, r, g, b) < d:
|
||||||
idx = 232 + grey_idx
|
idx = 232 + grey_idx
|
||||||
else:
|
else:
|
||||||
idx = 16 + (36 * qr) + (6 * qg) + qb
|
idx = 16 + (36 * qr) + (6 * qg) + qb
|
||||||
|
@ -728,7 +778,7 @@ def color_html_to_weechat(color):
|
||||||
if rgb_color in weechat_basic_colors:
|
if rgb_color in weechat_basic_colors:
|
||||||
return weechat_basic_colors[rgb_color]
|
return weechat_basic_colors[rgb_color]
|
||||||
|
|
||||||
return str(colour_find_rgb(*rgb_color))
|
return str(color_find_rgb(*rgb_color))
|
||||||
|
|
||||||
|
|
||||||
def color_weechat_to_html(color):
|
def color_weechat_to_html(color):
|
||||||
|
|
|
@ -497,6 +497,27 @@ class MatrixConfig(WeechatConfig):
|
||||||
"native",
|
"native",
|
||||||
"Pygments style to use for highlighting source code blocks",
|
"Pygments style to use for highlighting source code blocks",
|
||||||
),
|
),
|
||||||
|
Option(
|
||||||
|
"code_blocks",
|
||||||
|
"boolean",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"on",
|
||||||
|
("Display preformatted code blocks as rectangular areas by "
|
||||||
|
"padding them with whitespace up to the length of the longest "
|
||||||
|
"line (with optional margin)"),
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"code_block_margin",
|
||||||
|
"integer",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
100,
|
||||||
|
"2",
|
||||||
|
("Number of spaces to add as a margin around around a code "
|
||||||
|
"block"),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
network_options = [
|
network_options = [
|
||||||
|
@ -643,34 +664,80 @@ class MatrixConfig(WeechatConfig):
|
||||||
|
|
||||||
color_options = [
|
color_options = [
|
||||||
Option(
|
Option(
|
||||||
"quote",
|
"quote_fg",
|
||||||
"color",
|
"color",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"lightgreen",
|
"lightgreen",
|
||||||
("Color for matrix style blockquotes"),
|
"Foreground color for matrix style blockquotes",
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"error_message",
|
"quote_bg",
|
||||||
|
"color",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"default",
|
||||||
|
"Background counterpart of quote_fg",
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"error_message_fg",
|
||||||
"color",
|
"color",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"darkgray",
|
"darkgray",
|
||||||
("Color for error messages that appear inside a room buffer ("
|
("Foreground color for error messages that appear inside a "
|
||||||
"e.g. when a message errors out when sending or when a "
|
"room buffer (e.g. when a message errors out when sending or "
|
||||||
"message is redacted)"),
|
"when a message is redacted)"),
|
||||||
),
|
),
|
||||||
Option(
|
Option(
|
||||||
"unconfirmed_message",
|
"error_message_bg",
|
||||||
|
"color",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"default",
|
||||||
|
"Background counterpart of error_message_fg.",
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"unconfirmed_message_fg",
|
||||||
"color",
|
"color",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"darkgray",
|
"darkgray",
|
||||||
("Color for messages that are printed out but the server "
|
("Foreground color for messages that are printed out but the "
|
||||||
"hasn't confirmed the that he received them."),
|
"server hasn't confirmed the that he received them."),
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"unconfirmed_message_bg",
|
||||||
|
"color",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"default",
|
||||||
|
"Background counterpart of unconfirmed_message_fg."
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"untagged_code_fg",
|
||||||
|
"color",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"blue",
|
||||||
|
("Foreground color for code without a language specifier. "
|
||||||
|
"Also used for `inline code`."),
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"untagged_code_bg",
|
||||||
|
"color",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"default",
|
||||||
|
"Background counterpart of untagged_code_fg",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -885,7 +885,8 @@ class MatrixServer(object):
|
||||||
plain_message = message.formatted_message.to_weechat()
|
plain_message = message.formatted_message.to_weechat()
|
||||||
plain_message = W.string_remove_color(plain_message, "")
|
plain_message = W.string_remove_color(plain_message, "")
|
||||||
attributes = DEFAULT_ATTRIBUTES.copy()
|
attributes = DEFAULT_ATTRIBUTES.copy()
|
||||||
attributes["fgcolor"] = G.CONFIG.color.unconfirmed_message
|
attributes["fgcolor"] = G.CONFIG.color.unconfirmed_message_fg
|
||||||
|
attributes["bgcolor"] = G.CONFIG.color.unconfirmed_message_bg
|
||||||
new_formatted = Formatted([FormattedString(
|
new_formatted = Formatted([FormattedString(
|
||||||
plain_message,
|
plain_message,
|
||||||
attributes
|
attributes
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals, division
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
@ -105,3 +105,63 @@ def shorten_sender(sender):
|
||||||
|
|
||||||
def string_strikethrough(string):
|
def string_strikethrough(string):
|
||||||
return "".join(["{}\u0336".format(c) for c in string])
|
return "".join(["{}\u0336".format(c) for c in string])
|
||||||
|
|
||||||
|
|
||||||
|
def string_color_and_reset(string, color):
|
||||||
|
"""Color string with color, then reset all attributes."""
|
||||||
|
|
||||||
|
lines = string.split('\n')
|
||||||
|
lines = ("{}{}{}".format(W.color(color), line, W.color("reset"))
|
||||||
|
for line in lines)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def string_color(string, color):
|
||||||
|
"""Color string with color, then reset the color attribute."""
|
||||||
|
|
||||||
|
lines = string.split('\n')
|
||||||
|
lines = ("{}{}{}".format(W.color(color), line, W.color("resetcolor"))
|
||||||
|
for line in lines)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def color_pair(color_fg, color_bg):
|
||||||
|
"""Make a color pair from a pair of colors."""
|
||||||
|
|
||||||
|
if color_bg:
|
||||||
|
return "{},{}".format(color_fg, color_bg)
|
||||||
|
else:
|
||||||
|
return color_fg
|
||||||
|
|
||||||
|
|
||||||
|
def text_block(text, margin=0):
|
||||||
|
"""
|
||||||
|
Pad block of text with whitespace to form a regular block, optionally
|
||||||
|
adding a margin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# add vertical margin
|
||||||
|
vertical_margin = margin // 2
|
||||||
|
text = "{}{}{}".format(
|
||||||
|
"\n" * vertical_margin,
|
||||||
|
text,
|
||||||
|
"\n" * vertical_margin
|
||||||
|
)
|
||||||
|
|
||||||
|
lines = text.split("\n")
|
||||||
|
longest_len = max(len(l) for l in lines) + margin
|
||||||
|
|
||||||
|
# pad block and add horizontal margin
|
||||||
|
text = "\n".join(
|
||||||
|
"{pre}{line}{post}".format(
|
||||||
|
pre=" " * margin,
|
||||||
|
line=l,
|
||||||
|
post=" " * (longest_len - len(l)))
|
||||||
|
for l in lines)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def colored_text_block(text, margin=0, color_pair=""):
|
||||||
|
""" Like text_block, but also colors it."""
|
||||||
|
return string_color_and_reset(text_block(text, margin=margin), color_pair)
|
||||||
|
|
Loading…
Add table
Reference in a new issue