Outline trimming can remove trailing CJK wide characters
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 62/100
Research direction
The end branch of line_trim() in src/textual/_segment_tools.py passes a code-point length to split_cells(), which expects a cell count. Run the repro script first to confirm the wrong output, then check the start branch for the correct pattern. Done means trailing wide characters are kept within the cell budget, with regression tests for ASCII and one or two trailing CJK characters. Confirm the intended space-padding behavior with a maintainer before opening a PR.
Written by the indexing model from the issue text.
Description
Have you checked closed issues? Yes. I searched open and closed issues and pull requests for line_trim, CJK/wide-character trimming, outlines, and double-width truncation. #6357 also involves disappearing CJK text, but describes toast overlap and partial redraw over another widget's border; this reproduction is deterministic trimming of a widget's own outlined line.
Have you checked against the most recent version of Textual? Yes. PyPI currently lists 8.2.8. This also reproduces with Textual installed editable from current main, commit 5e5b7ef58b8c5572c13a3ff9027d756623661495.
The bug
When an outlined content line ends in double-width characters and has no trailing padding, Textual trims the end by character count rather than terminal-cell count. The cut can land inside a wide glyph and remove visible CJK text.
Complete minimal example
import asyncio
from rich.cells import cell_len
from rich.segment import Segment
from textual._segment_tools import line_trim
from textual.app import App, ComposeResult
from textual.widgets import Static
def segment_text(segments: list[Segment]) -> str:
return "".join(segment.text for segment in segments)
for segments in (
[Segment("ab"), Segment("中中")],
[Segment("ab"), Segment("中")],
):
result = segment_text(line_trim(segments, start=False, end=True))
print(repr(result), cell_len(result))
class Repro(App[None]):
def compose(self) -> ComposeResult:
yield Static("xxxxxx\nab中中\nyyyyyy")
async def main() -> None:
app = Repro()
async with app.run_test(size=(6, 5)) as pilot:
app.query_one(Static).styles.outline = ("round", "red")
await pilot.pause()
lines = [
"".join(segment.text for segment in strip)
for strip in app.screen._compositor.render_strips(app.screen.size)
]
print(repr(lines[1]))
asyncio.run(main())
Actual output:
'ab ' 3
'ab' 2
'│b │'
Removing one terminal cell should retain five cells from ab中中 ('ab中 ') and three cells from ab中 ('ab '). In the end-to-end render, both 中 characters disappear; the corresponding content line should retain one of them ('│b中 │').
Investigation and proposed direction
The end branch of line_trim() in src/textual/_segment_tools.py currently calls:
last_segment.split_cells(len(last_segment.text) - 1)
Segment.split_cells() expects a cell offset, but len(text) counts Unicode code points. The start branch already passes a cell count. A focused correction appears to be:
last_segment.split_cells(cell_len(last_segment.text) - 1)
cell_len is already imported by the module. Regression coverage can exercise ASCII, one and two trailing wide characters, and the existing start behavior. Could @willmcgugan confirm that preserving the remaining cells with Rich's space placeholder is the intended behavior? No PR has been opened pending approval under the project's AI policy.
Environment
- Windows
- CPython 3.14.6
- Textual 8.2.8 (editable from the
maincommit above) - Rich 15.0.0
- Reproduced both with direct segment calls and headless
App.run_test()
Disclosure: this report and its reproduction were prepared and verified with AI assistance (OpenCode, GPT-5.6 Sol).
- Dominant language
- Python
- Stars
- 37.6k
- Forks
- 1.4k
- PR merge metrics
- No merged PRs in 30d
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Textualize/textual
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Textualize/textual#6753 · 1 comment ·
-
8.2.8: _parse_extended_key crashes the input thread on Kitty colon sub-parameters (key:shifted:base, mods:event) — ValueError in int(), app panicsPossibly taken @NavpreetSSidhu claimed this 9 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Textualize/textual#6746 · 3 comments ·
-
text boxes never stop listeningMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Textualize/textual#6744 · 3 comments ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Textualize/textual#6738 · 4 comments ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Textualize/textual#6734 · 2 comments ·
All issues in Textualize/textual
Similar issues
-
Remove the `pull_request_target` workflow before GitHub blocks itPossibly taken @jat255 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
IBM/mcp-context-forge#7483 ·
Maintainers usually reply within 2 days
-
bug good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
[Bug Report]: OpenAI 辅助 API 连通性测试因输出上限仅 1 token 误报“未知错误”Possibly taken @fjfanjian claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Project-N-E-K-O/N.E.K.O#3640 ·
Maintainers usually reply within 1 day
-
author-page correction
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
acl-org/acl-anthology#10808 · 1 comment ·
Maintainers usually reply within 1 day