// Fenrir console screen reader. // Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . package screen import ( "slices" "unicode" ) // A TUI can scroll only its message area, leaving headers, status and input // rows fixed. Suppress the longest contiguous shifted overlap, while leaving // every other changed row available for normal feedback. Partial overlaps // require two nonblank rows: a lone repeated label or a blank area is ambiguous. func Scrolled_overlap(previous, next Snapshot) (bestStart, bestEnd int) { // Match a whole-screen scroll first, including sparse screens. Ignore // whitespace-only overlaps, which cannot establish that anything scrolled. firstText := slices.IndexFunc(next.Cells, func(cell rune) bool { return !unicode.IsSpace(cell) }) for shift := 1; shift < next.Rows && firstText >= 0 && firstText < (next.Rows-shift)*next.Cols; shift++ { if slices.Equal(previous.Cells[shift*next.Cols:], next.Cells[:(next.Rows-shift)*next.Cols]) { return 0, next.Rows - shift } } bestText := 0 for shift := 1; shift < next.Rows; shift++ { start, textRows := 0, 0 for row := 0; row <= next.Rows-shift; row++ { if row < next.Rows-shift && slices.Equal(previous.Line(row+shift), next.Line(row)) { if !slices.Equal(previous.Line(row), next.Line(row)) && slices.ContainsFunc(next.Line(row), func(cell rune) bool { return !unicode.IsSpace(cell) }) { textRows++ } continue } if textRows >= 2 && textRows > bestText { bestStart, bestEnd, bestText = start, row, textRows } start, textRows = row+1, 0 } } return bestStart, bestEnd }