Skip to content

Commit 2521da9

Browse files
committed
Raw nREPL: Support colored output in output console #99
1 parent 16ff584 commit 2521da9

File tree

4 files changed

+134
-10
lines changed

4 files changed

+134
-10
lines changed

cs_colors.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import re, sublime
2+
3+
RE_REPLACE_GLOB = re.compile(r"\*\*|[\*\?\.\(\)\[\]\{\}\$\^\+\|]")
4+
5+
region_id = 0
6+
7+
# Colors
8+
FG_ANSI = {
9+
30: 'black',
10+
31: 'red',
11+
32: 'green',
12+
33: 'brown',
13+
34: 'blue',
14+
35: 'magenta',
15+
36: 'cyan',
16+
37: 'white',
17+
39: 'default',
18+
90: 'light_black',
19+
91: 'light_red',
20+
92: 'light_green',
21+
93: 'light_brown',
22+
94: 'light_blue',
23+
95: 'light_magenta',
24+
96: 'light_cyan',
25+
97: 'light_white'
26+
}
27+
28+
BG_ANSI = {
29+
40: 'black',
30+
41: 'red',
31+
42: 'green',
32+
43: 'brown',
33+
44: 'blue',
34+
45: 'magenta',
35+
46: 'cyan',
36+
47: 'white',
37+
49: 'default',
38+
100: 'light_black',
39+
101: 'light_red',
40+
102: 'light_green',
41+
103: 'light_brown',
42+
104: 'light_blue',
43+
105: 'light_magenta',
44+
106: 'light_cyan',
45+
107: 'light_white'
46+
}
47+
48+
SCOPES = {
49+
'red': 'redish',
50+
'green': 'greenish',
51+
'brown': 'orangish',
52+
'blue': 'bluish',
53+
'magenta': 'pinkish', # purplish
54+
'cyan': 'cyanish',
55+
'light_red': 'redish',
56+
'light_green': 'greenish',
57+
'light_brown': 'orangish',
58+
'light_blue': 'bluish',
59+
'light_magenta': 'pinkish',
60+
'light_cyan': 'cyanish'
61+
}
62+
63+
RE_UNKNOWN_ESCAPES = re.compile(r"\x1b[^a-zA-Z]*[a-zA-Z]")
64+
RE_COLOR_ESCAPES = re.compile(r"\x1b\[((?:;?\d+)*)m")
65+
RE_NOTSPACE = re.compile(r"[^\s]+")
66+
67+
def write(view, characters):
68+
decolorized = ""
69+
original_pos = 0
70+
decolorized_pos = 0
71+
fg = "default"
72+
bg = "default"
73+
regions = []
74+
def iteration(start, end, group):
75+
nonlocal decolorized, original_pos, decolorized_pos, fg, bg, regions
76+
text = characters[original_pos:start]
77+
text = RE_UNKNOWN_ESCAPES.sub("", text)
78+
decolorized += text
79+
if len(text) > 0 and (fg != "default" or bg != "default"):
80+
regions.append({"text": text,
81+
"start": decolorized_pos,
82+
"end": decolorized_pos + len(text),
83+
"fg": fg,
84+
"bg": bg})
85+
digits = re.findall(r"\d+", group) or ["0"]
86+
for digit in digits:
87+
digit = int(digit)
88+
if digit in FG_ANSI:
89+
fg = FG_ANSI[digit]
90+
if digit in BG_ANSI:
91+
bg = BG_ANSI[digit]
92+
if digit == 0:
93+
fg = 'default'
94+
bg = 'default'
95+
original_pos = end
96+
decolorized_pos += len(text)
97+
98+
for m in RE_COLOR_ESCAPES.finditer(characters):
99+
iteration(m.start(), m.end(), m.group(1))
100+
iteration(len(characters), len(characters), "")
101+
102+
insertion_point = view.size()
103+
view.run_command('append', {'characters': decolorized, 'force': True, 'scroll_to_end': True})
104+
105+
global region_id
106+
for region in regions:
107+
if scope := SCOPES.get(region['bg'], None) or SCOPES.get(region['fg'], None):
108+
for m in RE_NOTSPACE.finditer(region['text']):
109+
start = insertion_point + region['start'] + m.start()
110+
end = start + len(m.group(0))
111+
region_id += 1
112+
view.add_regions(
113+
"executor#{}".format(region_id),
114+
[sublime.Region(start, end)],
115+
'region.' + scope)

cs_conn_nrepl_raw.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os, sublime, sublime_plugin, threading
2-
from . import cs_bencode, cs_common, cs_conn, cs_eval
2+
from . import cs_bencode, cs_colors, cs_common, cs_conn, cs_eval
33

44
class ConnectionNreplRaw(cs_conn.Connection):
55
"""
@@ -132,20 +132,16 @@ def get_output_view(self):
132132
self.output_view = window.create_output_panel('repl')
133133
return self.output_view
134134

135-
def output(self, text):
136-
output_view = self.get_output_view()
137-
self.window.run_command("show_panel", {"panel": "output.repl"})
138-
output_view.run_command('append', {'characters': text, 'force': True, 'scroll_to_end': True})
139-
140135
def handle_out(self, msg):
141136
if 'out' in msg:
142-
self.output(str(self))
143-
self.output(msg['out'])
137+
self.window.run_command("show_panel", {"panel": "output.repl"})
138+
cs_colors.write(self.get_output_view(), msg['out'])
144139
return True
145140

146141
def handle_err(self, msg):
147142
if 'err' in msg:
148-
self.output(msg['err'])
143+
self.window.run_command("show_panel", {"panel": "output.repl"})
144+
cs_colors.write(self.get_output_view(), msg['err'])
149145
return True
150146

151147
def handle_done(self, msg):

script/nrepl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
if __name__ == '__main__':
55
os.chdir(os.path.dirname(__file__) + "/..")
66
subprocess.check_call(['clojure',
7-
'-Sdeps', '{:deps {nrepl/nrepl {:mvn/version "1.0.0"}}}',
7+
'-Sdeps', '{:deps {nrepl/nrepl {:mvn/version "1.0.0"}, mvxcvi/puget {:mvn/version "1.3.4"}}}',
88
'-M', '-m', 'nrepl.cmdline',
99
'--interactive'
1010
])

test_repl/colors.clj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(require ' [puget.printer :as puget])
2+
3+
(puget/cprint [nil
4+
true
5+
\space
6+
"string"
7+
{:omega 123N
8+
:alpha '(func x y)
9+
:gamma 3.14159}
10+
#{\a "heterogeneous" :set}
11+
(java.util.Currency/getInstance "USD")
12+
(java.util.Date.)
13+
(java.util.UUID/randomUUID)])

0 commit comments

Comments
 (0)