commit - 54fd27f205edbde15f090faeab6df8bd63c321ec
commit + 0bb0d5a98d03a51974236b5e5f6b8acc2d5118a0
blob - 24e0084bfb18c150e9d7dc44e56fd6fffbf38a2a
blob + f2346d8626a6f324198ff677526b52e90a3cb0e2
--- nezzontli_ctl/screens/editor.py
+++ nezzontli_ctl/screens/editor.py
import asyncio
from textual.containers import Horizontal, Vertical, VerticalScroll
+from textual.geometry import Offset
from textual.screen import ModalScreen
from textual.widgets import Footer, Markdown, Static, TextArea
)
def _sync_preview_scroll(self, old_value: float, new_value: float) -> None:
- """TextArea es un widget de líneas monoespaciadas: scroll_y ES el
- número de línea de origen que está arriba del viewport (no hace
- falta convertir a proporción). Con eso buscamos qué widget del
- preview corresponde a esa línea y lo llevamos al tope — anclado al
- contenido real, no a una altura proporcional que se desalinea en
- cuanto hay imágenes/fórmulas de altura fija."""
+ """scroll_y de TextArea está en FILAS VISUALES, no en líneas lógicas
+ del documento — con soft_wrap (el default) una sola línea larga de
+ un párrafo ocupa varias filas visuales. Usar scroll_y directo como
+ "línea" (como se hacía antes) se desalinea cada vez más a medida
+ que el documento tiene párrafos largos arriba. wrapped_document
+ traduce la fila visual de vuelta a la línea lógica real, que es la
+ misma unidad en la que se calcularon los anchors."""
if not self._preview_anchors:
return
- top_line = round(new_value)
+ text_area = self.query_one("#editor-textarea", TextArea)
+ top_row = round(new_value)
+ top_line, _column = text_area.wrapped_document.offset_to_location(
+ Offset(0, top_row)
+ )
target = self._preview_anchors[0][1]
for line, widget in self._preview_anchors:
if line <= top_line: