Outline trimming can remove trailing CJK wide characters

Aperta Adatta ai principianti
#6,735 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
62/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
cli

Direzione di ricerca

Il ramo finale di line_trim() in src/textual/_segment_tools.py passa una lunghezza in code point a split_cells(), che si aspetta un numero di celle. Esegui prima lo script di riproduzione per confermare l'output errato, poi controlla il ramo iniziale per il modello corretto. È completato quando i caratteri larghi finali restano entro il budget di celle, con test di regressione per l'ASCII e uno o due caratteri CJK finali. Conferma con un maintainer il comportamento previsto del riempimento con spazi prima di aprire una PR.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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 main commit 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).

Lingua principale
Python
Stelle
37.6k
Fork
1.4k
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di Textualize/textual

Tutte le issue di Textualize/textual

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.