Skip to content

Commit a0910ce

Browse files
committed
wrap and overflow docs
1 parent c2f4c2a commit a0910ce

File tree

9 files changed

+235
-5
lines changed

9 files changed

+235
-5
lines changed

docs/css_types/text_wrap.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# <text-wrap>
2+
3+
The `<text-wrap>` CSS type sets how Textual wraps text.
4+
5+
## Syntax
6+
7+
The [`<text-wrap>`](./text_wrap.md) type can take any of the following values:
8+
9+
| Value | Description |
10+
| -------- | ------------------------- |
11+
| `wrap` | Word wrap text (default). |
12+
| `nowrap` | Disable text wrapping. |
13+
14+
## Examples
15+
16+
### CSS
17+
18+
```css
19+
#widget {
20+
text-wrap: nowrap; /* Disable wrapping */
21+
}
22+
```
23+
24+
### Python
25+
26+
```py
27+
widget.styles.text_wrap = "nowrap" # Disable wrapping
28+
```
29+
30+
31+
## See also
32+
33+
- [`text-overflow`](./overflow.md) is used to change what happens to overflowing text.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from textual.app import App, ComposeResult
2+
from textual.widgets import Static
3+
4+
TEXT = """I must not fear. Fear is the mind-killer. Fear is the little-death that brings total obliteration. I will face my fear."""
5+
6+
7+
class WrapApp(App):
8+
CSS_PATH = "text_overflow.tcss"
9+
10+
def compose(self) -> ComposeResult:
11+
yield Static(TEXT, id="static1")
12+
yield Static(TEXT, id="static2")
13+
yield Static(TEXT, id="static3")
14+
15+
16+
if __name__ == "__main__":
17+
app = WrapApp()
18+
app.run()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Static {
2+
height: 1fr;
3+
text-wrap: nowrap;
4+
}
5+
6+
#static1 {
7+
text-overflow: clip; # Overflowing text is clipped
8+
background: red 20%;
9+
}
10+
#static2 {
11+
text-overflow: fold; # Overflowing text is folded on to the next line
12+
background: green 20%;
13+
}
14+
#static3 {
15+
text-overflow: ellipsis; # Overflowing text is truncated with an ellipsis
16+
background: blue 20%;
17+
}

docs/examples/styles/text_wrap.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from textual.app import App, ComposeResult
2+
from textual.widgets import Static
3+
4+
TEXT = """I must not fear. Fear is the mind-killer. Fear is the little-death that brings total obliteration. I will face my fear."""
5+
6+
7+
class WrapApp(App):
8+
CSS_PATH = "text_wrap.tcss"
9+
10+
def compose(self) -> ComposeResult:
11+
yield Static(TEXT, id="static1")
12+
yield Static(TEXT, id="static2")
13+
14+
15+
if __name__ == "__main__":
16+
app = WrapApp()
17+
app.run()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Static {
2+
height: 1fr;
3+
}
4+
5+
#static1 {
6+
text-wrap: wrap; /* this is the default */
7+
background: blue 20%;
8+
}
9+
#static2 {
10+
text-wrap: nowrap; /* disable wrapping */
11+
background: green 20%;
12+
}

docs/styles/text_overflow.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# &lt;text-overflow&gt;
2+
3+
The `<text-overflow>` CSS type sets how Textual wraps text.
4+
5+
## Syntax
6+
7+
The [`<text-overflow>`](./text_overflow.md) type can take any of the following values:
8+
9+
| Value | Description |
10+
| ---------- | ----------------------------------------------------------------------------------- |
11+
| `clip` | Overflowing text will be clipped (the overflow portion is removed from the output). |
12+
| `fold` | Overflowing text will fold on to the next line. |
13+
| `ellipsis` | Overflowing text will be truncate and the last character replaced with an ellipsis. |
14+
15+
## Examples
16+
17+
In the following example we show the output of each of the values of `text_overflow`.
18+
19+
The widgets all have [text wrapping][`text-wrap`](./text_wrap.md) disabled.
20+
Since the string is longer than the width, it will overflow.
21+
22+
In the first (top) widget, "text-overflow" is set to "clip" which clips any text that is overflowing, resulting in a single line.
23+
24+
In the second widget, "text-overflow" is set to "fold", which causes the overflowing text to *fold* on to the next line.
25+
When text folds like this, it won't respect word boundaries--so you may get words broken across lines.
26+
27+
In the third widget, "text-overflow" is set to "ellipsis", which is similar to "clip", but with the last character set to an ellipsis.
28+
This option is useful to indicate to the user that there may be more text.
29+
30+
=== "Output"
31+
32+
```{.textual path="docs/examples/styles/text_overflow.py"}
33+
```
34+
35+
=== "text_overflow.py"
36+
37+
```py
38+
--8<-- "docs/examples/styles/text_overflow.py"
39+
```
40+
41+
=== "text_overflow.tcss"
42+
43+
```css
44+
--8<-- "docs/examples/styles/text_overflow.tcss"
45+
```
46+
47+
48+
### CSS
49+
50+
```css
51+
#widget {
52+
text-overflow: ellipsis;
53+
}
54+
```
55+
56+
### Python
57+
58+
```py
59+
widget.styles.text_overflow = "ellipsis"
60+
```
61+
62+
63+
## See also
64+
65+
- [`text-wrap`](./text_wrap.md) is used to control wrapping.

docs/styles/text_wrap.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<!-- This is the template file for a CSS style reference page. -->
2+
3+
# Text-wrap
4+
5+
The `text-wrap` style set how Textual should wrap text.
6+
The default value is `wrap` which will word-wrap text.
7+
You can also set this style to `nowrap` which will disable wrapping entirely.
8+
9+
## Syntax
10+
11+
--8<-- "docs/snippets/syntax_block_start.md"
12+
13+
Formal syntax description of the style
14+
style-name: <a href="../../css_types/text_wrap">&lt;text_wrap&gt;</a>;
15+
16+
--8<-- "docs/snippets/syntax_block_end.md"
17+
18+
19+
## Examples
20+
21+
In the following example we have to pieces of text.
22+
The first (top) text has the default `text-wrap` setting which will word wrap.
23+
The second has `text-wrap` set to `nowrap`, which disables text wrapping, and results in a single line.
24+
25+
=== "Output"
26+
27+
```{.textual path="docs/examples/styles/text_wrap.py"}
28+
```
29+
30+
=== "text_wrap.py"
31+
32+
```py
33+
--8<-- "docs/examples/styles/text_wrap.py"
34+
```
35+
36+
=== "text_wrap.tcss"
37+
38+
```css
39+
--8<-- "docs/examples/styles/text_wrap.tcss"
40+
```
41+
-->
42+
43+
44+
45+
## CSS
46+
47+
48+
```css
49+
text-wrap: wrap;
50+
text-wrap: nowrap;
51+
```
52+
53+
54+
## Python
55+
56+
57+
```py
58+
widget.styles.text_wrap = "wrap"
59+
widget.styles.text_wrap = "nowrap"
60+
61+
```

mkdocs-nav.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ nav:
4141
- "css_types/percentage.md"
4242
- "css_types/scalar.md"
4343
- "css_types/text_align.md"
44+
- "css_types/text_overflow.md"
4445
- "css_types/text_style.md"
46+
- "css_types/text_wrap.md"
4547
- "css_types/vertical.md"
4648
- Events:
4749
- "events/index.md"
@@ -137,6 +139,8 @@ nav:
137139
- "styles/scrollbar_size.md"
138140
- "styles/text_align.md"
139141
- "styles/text_opacity.md"
142+
- "styles/text_overflow.md"
143+
- "styles/text_wrap.md"
140144
- "styles/text_style.md"
141145
- "styles/tint.md"
142146
- "styles/visibility.md"

src/textual/content.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,13 @@ def get_span(y: int) -> tuple[int, int] | None:
361361

362362
line = line.expand_tabs(tab_size)
363363

364-
if no_wrap:
364+
if no_wrap and overflow == "fold":
365+
cuts = list(range(0, line.cell_length, width))[1:]
366+
new_lines = [
367+
FormattedLine(line, width, y=y, align=align)
368+
for line in line.divide(cuts)
369+
]
370+
elif no_wrap:
365371
if overflow == "ellipsis" and no_wrap:
366372
line = line.truncate(width, ellipsis=True)
367373
content_line = FormattedLine(line, width, y=y, align=align)
@@ -923,10 +929,7 @@ def render_segments(self, base_style: Style, end: str = "") -> list[Segment]:
923929
]
924930
return segments
925931

926-
def divide(
927-
self,
928-
offsets: Sequence[int],
929-
) -> list[Content]:
932+
def divide(self, offsets: Sequence[int]) -> list[Content]:
930933
if not offsets:
931934
return [self]
932935

0 commit comments

Comments
 (0)