change: Textfeld bricht Zeilen nicht mehr um, waagerechter Balken (D49)

Gewünscht — und damit die ausdrückliche Umkehrung der D33-Abwägung
(„Verworfen: wrap=off … der Umbruch ist die wichtigere Eigenschaft"). Der dort
notierte Preis bleibt richtig; nicht bedacht war die andere Seite: Ein weicher
Umbruch zerstört die Einrückung, und die trägt in dieser Notation die
Hierarchie. Die Fortsetzung einer umbrochenen Zeile beginnt am linken Rand und
sieht aus wie ein Wurzelknoten — betroffen sind genau die langen Zeilen, also
die mit ID, Größe und Kommentar, seit D48 praktisch jede Zeile des Plans.

Umgesetzt als `wrap="off"` am Element (Eigenschaft des Feldes, nicht seiner
Gestaltung), `overflow:auto` daneben im Stylesheet.

Der Spiegel musste mit: `syncMirror()` maß mit `white-space:pre-wrap` und
fester Breite — genau die Kombination, die im Spiegel Zeilen umbräche, die im
Textfeld ungebrochen stehen; alles darunter läge zu tief und die
Zeilennummern wanderten weg. Jetzt `pre` und ohne Breitenvorgabe. Gemessen
wird trotzdem weiter statt `Zeilenhöhe × n` zu rechnen: Die Schriftgröße
unterscheidet sich zwischen Telefon und Schreibtisch, die Messung stimmt in
beiden Fällen von selbst.

Der Sprung setzt `scrollLeft` zurück. `jumpToLine()` markiert die ganze Zeile,
woraufhin der Browser von sich aus an deren Ende scrollt — man landete am
rechten Rand, ohne Einrückung, Zeichen und Statusbox zu sehen. Der Sprung
zeigt auf eine Zeile, nicht auf ihr Ende.

Nachgemessen am Plan (903 Zeilen, längste 122 Zeichen): 903 Bildzeilen zu 903
logischen Zeilen in beiden Ansichten; Schiebeweg 211 px am Schreibtisch, 432 px
auf 375 px Breite; der Zeilennummern-Streifen bleibt beim waagerechten
Verschieben stehen (linke Kante unverändert bei 180 px Versatz); Abweichung
der Zahlen am Dateiende 2 px auf 903 Zeilen (Rundung von `offsetTop`, sammelt
sich nicht auf). 236 Tests grün.

SPEC §9 (Zeilennummern) und D49 halten es fest; die D33-Stelle bekommt einen
Verweis, dass die Abwägung umgekehrt wurde — gelöscht wird dort nichts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-23 14:20:25 +02:00
co-authored by Claude Fable 5
parent 623b68799f
commit 1b8bf06973
5 changed files with 114 additions and 22 deletions
+21 -14
View File
@@ -701,22 +701,23 @@ function lineRange(line){
return {start, end: start + lines[line - 1].length};
}
/* Vertikale Position eines Zeichenoffsets im Textfeld. Zeilenhöhe × n scheitert
an weichen Umbrüchen (lange Zeilen belegen mehrere Bildzeilen), deshalb ein
unsichtbarer Spiegel mit gleicher Typografie und Breite plus Marker-Span. */
/* Vertikale Position eines Zeichenoffsets im Textfeld: ein unsichtbarer Spiegel
mit gleicher Typografie plus Marker-Span.
Seit `wrap="off"` (D49) bricht das Textfeld nicht mehr um — der Spiegel
deshalb ebenfalls `white-space:pre` und **ohne** vorgegebene Breite. Beides
gehört zusammen: Eine feste Breite bei `pre-wrap` bräche im Spiegel Zeilen um,
die im Textfeld ungebrochen stehen, und alles darunter läge zu tief.
Gemessen wird trotzdem weiter, statt `Zeilenhöhe × n` zu rechnen — die
Schriftgröße unterscheidet sich zwischen Telefon und Schreibtisch (D17), und
die Messung stimmt in beiden Fällen von selbst. */
let mirrorEl = null;
function syncMirror(){
if(!mirrorEl){
mirrorEl = document.createElement('div');
mirrorEl.setAttribute('aria-hidden', 'true');
/* `box-sizing:border-box` ist Pflicht: `src.clientWidth` **enthält** die
Innenabstände. Ohne das ist der Spiegel um genau diese 32 px breiter als
das Textfeld und bricht später um — lange Zeilen landeten dadurch zu weit
oben (fiel beim Bau der Zeilennummern auf, D33; betraf auch schon das
Scrollen beim Sprung, D25). */
mirrorEl.style.cssText = 'position:absolute;visibility:hidden;top:0;left:-9999px;' +
'box-sizing:border-box;' +
'white-space:pre-wrap;overflow-wrap:break-word;';
'box-sizing:border-box;white-space:pre;';
document.body.appendChild(mirrorEl);
}
const cs = getComputedStyle(src);
@@ -724,7 +725,6 @@ function syncMirror(){
'paddingTop','paddingLeft','paddingRight','borderTopWidth','tabSize']){
mirrorEl.style[p] = cs[p];
}
mirrorEl.style.width = src.clientWidth + 'px';
return mirrorEl;
}
const ZWSP = '';
@@ -804,9 +804,11 @@ function markCurrentLineNo(){
if(s) s.classList.add('cur');
}
src.addEventListener('scroll', syncLineNoScroll);
/* Der Umbruch hängt an der Breite: Splitter, Fenster, Drehung, Tastatur. Beim
Ziehen am Splitter kämen sonst je Bild mehrere Messungen — einmal je Bild
genügt. */
/* Seit D49 hängen die Zeilenhöhen nicht mehr an der Breite (es wird nicht mehr
umbrochen), wohl aber an der Schriftgröße — und die wechselt beim Übergang
zwischen Telefon und Schreibtisch (D17), den der Beobachter als
Größenänderung mitbekommt. Beim Ziehen am Splitter kämen sonst je Bild
mehrere Messungen; einmal je Bild genügt. */
let lineNoPending = false;
if(window.ResizeObserver) new ResizeObserver(() => {
if(lineNoPending) return;
@@ -868,6 +870,11 @@ function jumpToLine(line){
src.focus({preventScroll: true});
src.setSelectionRange(r.start, r.end);
scrollEditorToOffset(r.start);
/* Ohne Umbruch (D49) scrollt der Browser beim Markieren einer langen Zeile
bis an ihr **Ende** — man landete am rechten Rand und sähe den Anfang der
Zeile nicht, also gerade Einrückung, Zeichen und Statusbox. Der Sprung
zeigt auf eine Zeile, nicht auf ihr Ende. */
src.scrollLeft = 0;
caretLine = line;
highlightCurrentNode(true);
}