Skip to content

Commit 3509614

Browse files
committed
fix for line wrapping, added switch for word wrap
1 parent d273049 commit 3509614

File tree

12 files changed

+204
-40
lines changed

12 files changed

+204
-40
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.8.7] - 2020-03-31
9+
10+
### Fixed
11+
12+
- Broken wrapping of long lines
13+
- Fixed wrapping in Syntax
14+
15+
### Changed
16+
17+
- Added word_wrap option to Syntax, which defaults to False.
18+
- Added word_wrap option to Traceback.
19+
20+
## [0.8.6] - 2020-03-29
21+
22+
### Added
23+
24+
- Experimental Jupyter notebook support: from rich.jupyter import print
25+
826
## [0.8.5] - 2020-03-29
927

1028
### Changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "rich"
33
homepage = "https://github.com/willmcgugan/rich"
44
documentation = "https://rich.readthedocs.io/en/latest/"
5-
version = "0.8.5"
5+
version = "0.8.7"
66
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
77
authors = ["Will McGugan <[email protected]>"]
88
license = "MIT"
@@ -18,6 +18,7 @@ classifiers = [
1818
"Programming Language :: Python :: 3.7",
1919
"Programming Language :: Python :: 3.8",
2020
]
21+
include = ["rich/py.typed"]
2122

2223

2324
[tool.poetry.dependencies]

rich/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def comparison(renderable1, renderable2) -> Table:
8686
return table
8787

8888
table.add_row(
89-
"Chinese Japanese & Korean support",
89+
"CJK support",
9090
Panel("该库支持中文,日文和韩文文本!", expand=False, style="red", box=box.DOUBLE_EDGE,),
9191
)
9292

rich/_wrap.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,28 @@ def divide_line(text: str, width: int) -> List[int]:
2222
append = divides.append
2323
line_position = 0
2424
for start, _end, word in words(text):
25-
if line_position + cell_len(word.rstrip()) > width:
26-
if line_position and start:
27-
append(start)
28-
line_position = cell_len(word)
29-
else:
30-
for last, line in loop_last(chop_cells(text, width)):
25+
word_length = cell_len(word.rstrip())
26+
if line_position + word_length > width:
27+
if word_length > width:
28+
for last, line in loop_last(
29+
chop_cells(word, width, position=line_position)
30+
):
3131
if last:
3232
line_position = cell_len(line)
3333
else:
3434
start += len(line)
3535
append(start)
36+
elif line_position and start:
37+
append(start)
38+
line_position = cell_len(word)
3639
else:
3740
line_position += cell_len(word)
3841
return divides
42+
43+
44+
if __name__ == "__main__": # pragma: no cover
45+
from .console import Console
46+
47+
console = Console(width=10)
48+
console.print("12345 abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ 12345")
49+
print(chop_cells("abcdefghijklmnopqrstuvwxyz", 10, position=2))

rich/cells.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def cell_len(text: str, _cache: LRUCache[str, int] = LRUCache(1024 * 4)) -> int:
2525
return total_size
2626

2727

28-
@lru_cache(maxsize=5000)
2928
def get_character_cell_size(character: str) -> int:
3029
"""Get the cell size of a character.
3130
@@ -40,6 +39,19 @@ def get_character_cell_size(character: str) -> int:
4039
if 127 > codepoint > 31:
4140
# Shortcut for ascii
4241
return 1
42+
return _get_codepoint_cell_size(codepoint)
43+
44+
45+
@lru_cache(maxsize=5000)
46+
def _get_codepoint_cell_size(codepoint: int) -> int:
47+
"""Get the cell size of a character.
48+
49+
Args:
50+
character (str): A single character.
51+
52+
Returns:
53+
int: Number of cells (0, 1 or 2) occupied by that character.
54+
"""
4355

4456
_table = CELL_WIDTHS
4557
lower_bound = 0
@@ -79,13 +91,13 @@ def set_cell_size(text: str, total: int) -> str:
7991
return text
8092

8193

82-
def chop_cells(text: str, max_size: int) -> List[str]:
94+
def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]:
8395
"""Break text in to equal (cell) length strings."""
8496
_get_character_cell_size = get_character_cell_size
8597
characters = [
8698
(character, _get_character_cell_size(character)) for character in text
8799
][::-1]
88-
total_size = 0
100+
total_size = position
89101
lines: List[List[str]] = [[]]
90102
append = lines[-1].append
91103

rich/console.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,6 @@ def __console__(
139139
_null_highlighter = NullHighlighter()
140140

141141

142-
class RichRenderable:
143-
def __init__(self, rich_cast: Callable[[], ConsoleRenderable]) -> None:
144-
self.rich_cast = rich_cast
145-
146-
def __console__(
147-
self, console: "Console", options: "ConsoleOptions"
148-
) -> RenderResult:
149-
yield self.rich_cast()
150-
151-
152142
class RenderGroup:
153143
def __init__(self, *renderables: RenderableType, fit: bool = True) -> None:
154144
"""Takes a group of renderables and returns a renderable object,
@@ -781,17 +771,21 @@ def print_exception(
781771
width: Optional[int] = 88,
782772
extra_lines: int = 3,
783773
theme: Optional[str] = None,
774+
word_wrap: bool = False,
784775
) -> None:
785776
"""Prints a rich render of the last exception and traceback.
786777
787778
Args:
788779
code_width (Optional[int], optional): Number of characters used to render code. Defaults to 88.
789780
extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
790781
theme (str, optional): Override pygments theme used in traceback
782+
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
791783
"""
792784
from .traceback import Traceback
793785

794-
traceback = Traceback(width=width, extra_lines=extra_lines, theme=theme)
786+
traceback = Traceback(
787+
width=width, extra_lines=extra_lines, theme=theme, word_wrap=word_wrap
788+
)
795789
self.print(traceback)
796790

797791
def log(

rich/jupyter.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import io
2+
from typing import Any, IO, Union
3+
4+
from .console import Console as BaseConsole
5+
from .style import Style
6+
7+
JUPYTER_HTML_FORMAT = """\
8+
<pre style="white-space:pre;overflow-x:auto;line-height:1em;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
9+
"""
10+
11+
12+
class JupyterRenderable:
13+
"""A shim to write html to Jupyter notebook."""
14+
15+
def __init__(self, text: str, html: str) -> None:
16+
self.text = text
17+
self.html = html
18+
19+
def __str__(self) -> str:
20+
return self.text
21+
22+
def _repr_html_(self) -> str:
23+
return self.html
24+
25+
26+
class Console(BaseConsole):
27+
def __init__(self, **kwargs) -> None:
28+
kwargs["file"] = io.StringIO()
29+
kwargs["record"] = True
30+
if "width" not in kwargs:
31+
kwargs["width"] = 100
32+
super().__init__(**kwargs)
33+
34+
def is_terminal(self) -> bool:
35+
return True
36+
37+
def print( # type: ignore
38+
self,
39+
*objects: Any,
40+
sep=" ",
41+
end="\n",
42+
file: IO[str] = None,
43+
style: Union[str, Style] = None,
44+
emoji: bool = None,
45+
markup: bool = None,
46+
highlight: bool = None,
47+
flush: bool = False
48+
) -> JupyterRenderable:
49+
r"""Print to the console.
50+
51+
Args:
52+
objects (positional args): Objects to log to the terminal.
53+
sep (str, optional): String to write between print data. Defaults to " ".
54+
end (str, optional): String to write at end of print data. Defaults to "\n".
55+
style (Union[str, Style], optional): A style to apply to output. Defaults to None.
56+
emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None.
57+
markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None
58+
highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None.
59+
"""
60+
if file is None:
61+
super().print(
62+
*objects,
63+
sep=sep,
64+
end=end,
65+
style=style,
66+
emoji=emoji,
67+
markup=markup,
68+
highlight=highlight,
69+
)
70+
else:
71+
Console(file=file).print(
72+
*objects,
73+
sep=sep,
74+
end=end,
75+
style=style,
76+
emoji=emoji,
77+
markup=markup,
78+
highlight=highlight,
79+
)
80+
81+
html = self.export_html(code_format=JUPYTER_HTML_FORMAT, inline_styles=True)
82+
text = self.file.getvalue() # type: ignore
83+
self.file = io.StringIO()
84+
jupyter_renderable = JupyterRenderable(text, html)
85+
return jupyter_renderable
86+
87+
88+
console = Console()
89+
print = console.print

rich/py.typed

Whitespace-only changes.

rich/syntax.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class Syntax:
3333
highlight_lines (Set[int]): A set of line numbers to highlight.
3434
code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
3535
tab_size (int, optional): Size of tabs. Defaults to 4.
36+
word_wrap (bool, optional): Enable word wrapping.
3637
"""
3738

3839
def __init__(
@@ -48,6 +49,7 @@ def __init__(
4849
highlight_lines: Set[int] = None,
4950
code_width: Optional[int] = None,
5051
tab_size: int = 4,
52+
word_wrap: bool = False
5153
) -> None:
5254
self.code = code
5355
self.lexer_name = lexer_name
@@ -58,6 +60,7 @@ def __init__(
5860
self.highlight_lines = highlight_lines or set()
5961
self.code_width = code_width
6062
self.tab_size = tab_size
63+
self.word_wrap = word_wrap
6164

6265
self._style_cache: Dict[Any, Style] = {}
6366
if not isinstance(theme, str) and issubclass(theme, PygmentsStyle):
@@ -82,6 +85,7 @@ def from_path(
8285
highlight_lines: Set[int] = None,
8386
code_width: Optional[int] = None,
8487
tab_size: int = 4,
88+
word_wrap: bool = False,
8589
) -> "Syntax":
8690
"""Construct a Syntax object from a file.
8791
@@ -97,6 +101,7 @@ def from_path(
97101
highlight_lines (Set[int]): A set of line numbers to highlight.
98102
code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
99103
tab_size (int, optional): Size of tabs. Defaults to 4.
104+
word_wrap (bool, optional): Enable word wrapping of code.
100105
101106
Returns:
102107
[Syntax]: A Syntax object that may be printed to the console
@@ -118,6 +123,7 @@ def from_path(
118123
start_line=start_line,
119124
highlight_lines=highlight_lines,
120125
code_width=code_width,
126+
word_wrap=word_wrap,
121127
)
122128

123129
def _get_theme_style(self, token_type) -> Style:
@@ -208,7 +214,11 @@ def __measure__(self, console: "Console", max_width: int) -> "Measurement":
208214
return Measurement(max_width, max_width)
209215

210216
def __console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
211-
code_width = options.max_width if self.code_width is None else self.code_width
217+
code_width = (
218+
(options.max_width - self._numbers_column_width - 1)
219+
if self.code_width is None
220+
else self.code_width
221+
)
212222
code = self.code
213223
if self.dedent:
214224
code = textwrap.dedent(code)
@@ -231,7 +241,7 @@ def __console__(self, console: Console, options: ConsoleOptions) -> RenderResult
231241
lines = lines[line_offset:end_line]
232242

233243
numbers_column_width = self._numbers_column_width
234-
render_options = options.update(width=code_width + numbers_column_width)
244+
render_options = options.update(width=code_width)
235245

236246
(
237247
background_style,
@@ -241,15 +251,18 @@ def __console__(self, console: Console, options: ConsoleOptions) -> RenderResult
241251

242252
highlight_line = self.highlight_lines.__contains__
243253
_Segment = Segment
244-
padding = _Segment(" " * numbers_column_width, background_style)
254+
padding = _Segment(" " * numbers_column_width + " ", background_style)
245255
new_line = _Segment("\n")
246256

247257
line_pointer = "❱ "
248258

249259
for line_no, line in enumerate(lines, self.start_line + line_offset):
250-
wrapped_lines = console.render_lines(
251-
line, render_options, style=background_style
252-
)
260+
if self.word_wrap:
261+
wrapped_lines = console.render_lines(
262+
line, render_options, style=background_style
263+
)
264+
else:
265+
wrapped_lines = [list(line.render(console, render_options, end=""))]
253266
for first, wrapped_line in loop_first(wrapped_lines):
254267
if first:
255268
line_column = str(line_no).rjust(numbers_column_width - 2) + " "
@@ -276,5 +289,5 @@ def __console__(self, console: Console, options: ConsoleOptions) -> RenderResult
276289

277290
console = Console()
278291

279-
syntax = Syntax.from_path(sys.argv[1], line_numbers=False)
292+
syntax = Syntax.from_path(sys.argv[1], line_numbers=True, word_wrap=True)
280293
console.print(syntax)

0 commit comments

Comments
 (0)