Skip to content

Commit 6948a1e

Browse files
authored
nREPL server should not send color ANSI esc seqs to the clients (#1040)
Hi, could you please review patch to prevent the nREPL server sending color ANSI esc seqs to the clients. It fixes #1039. I modified the path from the nREPL server down to the Pygments call to accept a `disable_color` parameter, which stops color formatting. I've also updated the tests to reflect this change. The `src/basilisp/lang/source.py` test is currently placed under `tests/basilisp/lang`, though I noticed the other tests in the `src/basilisp/` subdirectories don't follow this structure -- not sure why that is. I'm happy to relocate it to `tests/basilisp` if needed. Thanks --------- Co-authored-by: ikappaki <[email protected]>
1 parent c1e67c2 commit 6948a1e

File tree

10 files changed

+118
-24
lines changed

10 files changed

+118
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Changed
99
* The compiler will no longer require `Var` indirection for top-level `do` forms unless those forms specify `^:use-var-indirection` metadata (which currently is only used in the `ns` macro) (#1034)
10+
* nREPL server no longer sends ANSI color escape sequences in exception messages to clients (#1039)
1011

1112
### Fixed
1213
* Fix a bug where the compiler would always generate inline function definitions even if the `inline-functions` compiler option is disabled (#1023)

src/basilisp/contrib/nrepl_server.lpy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@
147147
(send-value request send-fn [result {:ns (str *ns*)}]))
148148
(catch python/Exception e
149149
(debug :eval-exception e)
150-
(let [msg (->> (basilisp.lang.exception/format_exception e (type e) (.-__traceback__ e))
150+
(let [msg (->> (basilisp.lang.exception/format_exception e (type e) (.-__traceback__ e)
151+
** :disable-color true)
151152
(str/join ""))]
152153
(handle-error send-fn (assoc request :ns (str *ns*)) msg)))
153154
(finally

src/basilisp/lang/compiler/exception.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,12 @@ def format_compiler_exception( # pylint: disable=too-many-branches,unused-argum
114114
e: CompilerException,
115115
tp: Optional[Type[Exception]] = None,
116116
tb: Optional[TracebackType] = None,
117+
disable_color: Optional[bool] = None,
117118
) -> List[str]:
118-
"""Format a compiler exception as a list of newline-terminated strings."""
119+
"""Format a compiler exception as a list of newline-terminated strings.
120+
121+
If `disable_color` is True, no color formatting will be applied to the source
122+
code."""
119123
context_exc: Optional[BaseException] = e.__cause__
120124

121125
lines = [os.linesep]
@@ -154,7 +158,9 @@ def format_compiler_exception( # pylint: disable=too-many-branches,unused-argum
154158
# derive source lines, but use the inner cause exception to place a marker
155159
# around the error.
156160
if line is not None and (
157-
context_lines := format_source_context(e.filename, line, end_line=end_line)
161+
context_lines := format_source_context(
162+
e.filename, line, end_line=end_line, disable_color=disable_color
163+
)
158164
):
159165
lines.append(f" context:{os.linesep}")
160166
lines.append(os.linesep)

src/basilisp/lang/exception.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,21 @@ def __str__(self):
2525

2626

2727
@functools.singledispatch
28-
def format_exception(
28+
def format_exception( # pylint: disable=unused-argument
2929
e: Optional[BaseException],
3030
tp: Optional[Type[BaseException]] = None,
3131
tb: Optional[TracebackType] = None,
32+
disable_color: Optional[bool] = None,
3233
) -> List[str]:
3334
"""Format an exception into something readable, returning a list of newline
3435
terminated strings.
3536
3637
For the majority of Python exceptions, this will just be the result from calling
3738
`traceback.format_exception`. For Basilisp specific compilation errors, a custom
38-
output will be returned."""
39+
output will be returned.
40+
41+
If `disable_color` is True, no color formatting should be applied to the source
42+
code."""
3943
if isinstance(e, BaseException):
4044
if tp is None:
4145
tp = type(e)

src/basilisp/lang/reader.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,11 @@ def format_syntax_error( # pylint: disable=unused-argument
166166
e: SyntaxError,
167167
tp: Optional[Type[Exception]] = None,
168168
tb: Optional[TracebackType] = None,
169+
disable_color: Optional[bool] = None,
169170
) -> List[str]:
171+
"""If `disable_color` is True, no color formatting will be applied to the source
172+
code."""
173+
170174
context_exc: Optional[BaseException] = e.__cause__
171175

172176
lines = [os.linesep]
@@ -199,7 +203,11 @@ def format_syntax_error( # pylint: disable=unused-argument
199203
if (
200204
e.filename is not None
201205
and e.line is not None
202-
and (context_lines := format_source_context(e.filename, e.line))
206+
and (
207+
context_lines := format_source_context(
208+
e.filename, e.line, disable_color=disable_color
209+
)
210+
)
203211
):
204212
lines.append(f" context:{os.linesep}")
205213
lines.append(os.linesep)

src/basilisp/lang/source.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@
99
import pygments.styles
1010
except ImportError: # pragma: no cover
1111

12-
def _format_source(s: str) -> str:
12+
def _format_source(
13+
s: str, disable_color: Optional[bool] = None # pylint: disable=unused-argument
14+
) -> str:
1315
return f"{s}{os.linesep}"
1416

1517
else:
1618

17-
def _get_formatter_name() -> Optional[str]: # pragma: no cover
19+
def _get_formatter_name(
20+
disable_color: Optional[bool] = None,
21+
) -> Optional[str]: # pragma: no cover
1822
"""Get the Pygments formatter name for formatting the source code by
1923
inspecting various environment variables set by terminals.
2024
21-
If `BASILISP_NO_COLOR` is set to a truthy value, use no formatting."""
22-
if os.environ.get("BASILISP_NO_COLOR", "false").lower() in {"1", "true"}:
25+
If If `disable_color` is explicitly True or `BASILISP_NO_COLOR` is set
26+
to a truthy value, use no formatting."""
27+
if (disable_color is True) or os.environ.get(
28+
"BASILISP_NO_COLOR", "false"
29+
).lower() in {"1", "true"}:
2330
return None
2431
elif os.environ.get("COLORTERM", "") in {"truecolor", "24bit"}:
2532
return "terminal16m"
@@ -28,9 +35,13 @@ def _get_formatter_name() -> Optional[str]: # pragma: no cover
2835
else:
2936
return "terminal"
3037

31-
def _format_source(s: str) -> str: # pragma: no cover
32-
"""Format source code for terminal output."""
33-
if (formatter_name := _get_formatter_name()) is None:
38+
def _format_source(
39+
s: str, disable_color: Optional[bool] = None
40+
) -> str: # pragma: no cover
41+
"""Format source code for terminal output.
42+
43+
If `disable_color` is True, no formatting will be applied to the source code."""
44+
if (formatter_name := _get_formatter_name(disable_color)) is None:
3445
return f"{s}{os.linesep}"
3546
return pygments.highlight(
3647
s,
@@ -41,15 +52,19 @@ def _format_source(s: str) -> str: # pragma: no cover
4152
)
4253

4354

44-
def format_source_context( # pylint: disable=too-many-locals
55+
def format_source_context( # pylint: disable=too-many-arguments,too-many-locals
4556
filename: str,
4657
line: int,
4758
end_line: Optional[int] = None,
4859
num_context_lines: int = 5,
4960
show_cause_marker: bool = True,
61+
disable_color: Optional[bool] = None,
5062
) -> List[str]:
5163
"""Format source code context with line numbers and identifiers for the affected
52-
line(s)."""
64+
line(s).
65+
66+
If `disable_color` is True, no color formatting will be applied to the source code.
67+
"""
5368
assert num_context_lines >= 0
5469

5570
lines = []
@@ -87,7 +102,7 @@ def format_source_context( # pylint: disable=too-many-locals
87102

88103
line_num = str(n + 1).rjust(num_justify)
89104
lines.append(
90-
f"{line_num}{line_marker}| {_format_source(source_line.rstrip())}"
105+
f"{line_num}{line_marker}| {_format_source(source_line.rstrip(), disable_color=disable_color)}"
91106
)
92107

93108
return lines

tests/basilisp/compiler_test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,12 @@ def test_shows_source_context(
220220
).lstrip()
221221
+ ("\n" if include_newline else "")
222222
)
223-
monkeypatch.setenv("BASILISP_NO_COLOR", "true")
224223
monkeypatch.syspath_prepend(source_file.parent)
225224

226225
with pytest.raises(compiler.CompilerException) as e:
227226
lcompile("(require 'compiler-test)")
228227

229-
formatted = format_exception(e.value)
228+
formatted = format_exception(e.value, disable_color=True)
230229
assert re.match(
231230
(
232231
rf"{os.linesep}"

tests/basilisp/contrib/nrepl_server_test.lpy

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -596,10 +596,7 @@
596596
" 7 | (xyz))"
597597
" 8 | "
598598
" 9 | (afn)"]
599-
(-> err
600-
;; remove ansi control chars
601-
(str/replace #"\\x1B[@-_][0-?]*[ -/]*[@-~]" "")
602-
(str/split-lines)))))
599+
(str/split-lines err))))
603600
(let [{:keys [id ex status ns]} (client-recv! client)]
604601
(is (= @id* id))
605602
(is (= "abc.xyz" ns))

tests/basilisp/reader_test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,12 @@ def test_shows_source_context(self, monkeypatch, source_file: Path):
254254
"""
255255
).strip()
256256
)
257-
monkeypatch.setenv("BASILISP_NO_COLOR", "true")
258257
monkeypatch.syspath_prepend(source_file.parent)
259258

260259
with pytest.raises(reader.SyntaxError) as e:
261260
list(reader.read_file(source_file))
262261

263-
v = format_exception(e.value)
262+
v = format_exception(e.value, disable_color=True)
264263
assert re.match(
265264
(
266265
rf"{os.linesep}"

tests/basilisp/source_test.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import os
2+
import textwrap
3+
from pathlib import Path
4+
5+
import pytest
6+
7+
from basilisp.lang import compiler as compiler
8+
from basilisp.lang.source import format_source_context
9+
10+
11+
@pytest.fixture
12+
def source_file(tmp_path: Path) -> Path:
13+
return tmp_path / "source_test.lpy"
14+
15+
16+
@pytest.fixture
17+
def source_file_path(source_file: Path) -> str:
18+
return str(source_file)
19+
20+
21+
def test_format_source_context(monkeypatch, source_file, source_file_path):
22+
source_file.write_text(
23+
textwrap.dedent(
24+
"""
25+
(ns source-test)
26+
27+
(a)
28+
(let [a 5]
29+
(b))
30+
"""
31+
)
32+
)
33+
format_c = format_source_context(source_file_path, 2, end_line=4)
34+
assert [
35+
" 1 | \x1b[37m\x1b[39;49;00m\n",
36+
" 2 > | (\x1b[34mns \x1b[39;49;00m\x1b[31msource-test\x1b[39;49;00m)\x1b[37m\x1b[39;49;00m\n",
37+
" 3 > | \x1b[37m\x1b[39;49;00m\n",
38+
" 4 > | (\x1b[32ma\x1b[39;49;00m)\x1b[37m\x1b[39;49;00m\n",
39+
" 5 | (\x1b[34mlet \x1b[39;49;00m[\x1b[31ma\x1b[39;49;00m\x1b[37m \x1b[39;49;00m\x1b[34m5\x1b[39;49;00m]\x1b[37m\x1b[39;49;00m\n",
40+
" 6 | \x1b[37m \x1b[39;49;00m(\x1b[32mb\x1b[39;49;00m))\x1b[37m\x1b[39;49;00m\n",
41+
] == format_c
42+
43+
format_nc = format_source_context(
44+
source_file_path, 2, end_line=4, disable_color=True
45+
)
46+
assert [
47+
" 1 | " + os.linesep,
48+
" 2 > | (ns source-test)" + os.linesep,
49+
" 3 > | " + os.linesep,
50+
" 4 > | (a)" + os.linesep,
51+
" 5 | (let [a 5]" + os.linesep,
52+
" 6 | (b))" + os.linesep,
53+
] == format_nc
54+
55+
monkeypatch.setenv("BASILISP_NO_COLOR", "true")
56+
format_bnc = format_source_context(source_file_path, 2, end_line=4)
57+
assert [
58+
" 1 | " + os.linesep,
59+
" 2 > | (ns source-test)" + os.linesep,
60+
" 3 > | " + os.linesep,
61+
" 4 > | (a)" + os.linesep,
62+
" 5 | (let [a 5]" + os.linesep,
63+
" 6 | (b))" + os.linesep,
64+
] == format_bnc

0 commit comments

Comments
 (0)