在这里完成 Bryan Oakley 的回答是如何解析字符串并使用 Text 小部件中的标签应用颜色:
-
为您想要的颜色/格式代码配置标签
text.tag_configure(<tag name>, **options)
标签选项列表可用,例如here。
-
创建一个字典,将每个颜色代码与相应的标签相关联
-
在Text 小部件中插入不带颜色代码的字符串。您可以使用regular expression 删除颜色代码:
re.sub(r"\x1b\[((\d+;)*\d+)m", "", <string>)
-
使用相同的正则表达式,解析字符串以查找颜色代码并应用标签。这里有几个棘手的部分:
- 颜色代码位置与文本索引不匹配,因为代码已从小部件中的文本中删除,因此您需要跟踪每行的累积偏移量。
- 您需要等到找到“关闭”代码(例如颜色更改或
\x1b[0m)才能添加带有text.tag_add(<tag name>, <start>, <stop>) 的标签,因此您需要跟踪打开的标签。李>
代码示例,并不详尽(例如,不支持同时使用粗体和斜体),但涵盖了基础知识:
import tkinter as tk
import re
root = tk.Tk()
text = tk.Text(root)
text.pack()
# dictionaries to replace formatting code with tags
ansi_font_format = {1: 'bold', 3: 'italic', 4: 'underline', 9: 'overstrike'}
ansi_font_reset = {21: 'bold', 23: 'italic', 24: 'underline', 29: 'overstrike'}
# tag configuration
text.tag_configure('bold', font=('', 9, 'bold'))
text.tag_configure('italic', font=('', 9, 'italic'))
text.tag_configure('underline', underline=True)
text.tag_configure('overstrike', overstrike=True)
# dictionaries to replace color code with tags
ansi_color_fg = {39: 'foreground default'}
ansi_color_bg = {49: 'background default'}
text.tag_configure('foreground default', foreground=text["fg"])
text.tag_configure('background default', background=text["bg"])
ansi_colors_dark = ['black', 'red', 'green', 'yellow', 'royal blue', 'magenta', 'cyan', 'light gray']
ansi_colors_light = ['dark gray', 'tomato', 'light green', 'light goldenrod', 'light blue', 'pink', 'light cyan', 'white']
for i, (col_dark, col_light) in enumerate(zip(ansi_colors_dark, ansi_colors_light)):
ansi_color_fg[30 + i] = 'foreground ' + col_dark
ansi_color_fg[90 + i] = 'foreground ' + col_light
ansi_color_bg[40 + i] = 'background ' + col_dark
ansi_color_bg[100 + i] = 'background ' + col_light
# tag configuration
text.tag_configure('foreground ' + col_dark, foreground=col_dark)
text.tag_configure('background ' + col_dark, background=col_dark)
text.tag_configure('foreground ' + col_light, foreground=col_light)
text.tag_configure('background ' + col_light, background=col_light)
# regular expressionto find ansi codes in string
ansi_regexp = re.compile(r"\x1b\[((\d+;)*\d+)m")
def insert_ansi(txt, index="insert"):
first_line, first_char = map(int, str(text.index(index)).split("."))
if index == "end":
first_line -= 1
lines = txt.splitlines()
if not lines:
return
# insert text without ansi codes
text.insert(index, ansi_regexp.sub('', txt))
# find all ansi codes in txt and apply corresponding tags
opened_tags = {} # we need to keep track of the opened tags to be able to do
# text.tag_add(tag, start, end) when we reach a "closing" ansi code
def apply_formatting(code, code_index):
if code == 0: # reset all by closing all opened tag
for tag, start in opened_tags.items():
text.tag_add(tag, start, code_index)
opened_tags.clear()
elif code in ansi_font_format: # open font formatting tag
tag = ansi_font_format[code]
opened_tags[tag] = code_index
elif code in ansi_font_reset: # close font formatting tag
tag = ansi_font_reset[code]
if tag in opened_tags:
text.tag_add(tag, opened_tags[tag], code_index)
opened_tags.remove(tag)
elif code in ansi_color_fg: # open foreground color tag (and close previously opened one if any)
for tag in tuple(opened_tags):
if tag.startswith('foreground'):
text.tag_add(tag, opened_tags[tag], code_index)
opened_tags.remove(tag)
opened_tags[ansi_color_fg[code]] = code_index
elif code in ansi_color_bg: # open background color tag (and close previously opened one if any)
for tag in tuple(opened_tags):
if tag.startswith('background'):
text.tag_add(tag, opened_tags[tag], code_index)
opened_tags.remove(tag)
opened_tags[ansi_color_bg[code]] = code_index
def find_ansi(line_txt, line_nb, char_offset):
delta = -char_offset # difference between the character position in the original line and in the text widget
# (initial offset due to insertion position if first line + extra offset due to deletion of ansi codes)
for match in ansi_regexp.finditer(line_txt):
codes = [int(c) for c in match.groups()[0].split(';')]
start, end = match.span()
for code in codes:
apply_formatting(code, "{}.{}".format(line_nb, start - delta))
delta += end - start # take into account offste due to deletion of ansi code
find_ansi(lines[0], first_line, first_char) # first line, with initial offset due to insertion position
for line_nb, line in enumerate(lines[1:], first_line + 1):
find_ansi(line, line_nb, 0) # next lines, no offset
# close still opened tag
for tag, start in opened_tags.items():
text.tag_add(tag, start, "end")
# example for the kind of output you can get with "ls --color"
output = 'file.pdf\nfile.txt\n\x1b[0m\x1b[01;34mfolder\x1b[0m\n\x1b[01;32mscript.py\x1b[0m\ntest\n'
insert_ansi(output, "end")
Linux 中 ls --color 命令的彩色输出示例: