Files

97 lines
4.0 KiB
Go

// 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 <https://www.gnu.org/licenses/gpl-3.0.html>.
package reader
import (
"slices"
"unicode/utf8"
"git.stormux.org/storm/fenrir/internal/diagnostic"
"git.stormux.org/storm/fenrir/internal/presentation"
"git.stormux.org/storm/fenrir/internal/screen"
)
// AttributeChange contains only geometry and appearance, never cell text.
// End is exclusive. Keep details bounded even during a whole-screen repaint.
type AttributeChange struct {
Row, Start, End int
Before, After screen.Attribute
}
func (r *Reader) trace_update(previous, next screen.Snapshot, switched bool, pendingInput int, path string, output []presentation.Message) {
comparable := previous.Rows == next.Rows && previous.Cols == next.Cols &&
next.Rows > 0 && next.Cols > 0 && len(previous.Cells) == next.Rows*next.Cols && len(next.Cells) == len(previous.Cells)
previousAttributes := len(previous.Attributes) > 0 && len(previous.Attributes) == len(previous.Cells)
nextAttributes := len(next.Attributes) > 0 && len(next.Attributes) == len(next.Cells)
textRows, attributeRows := []int{}, []int{}
attributeChanges := []AttributeChange{}
truncated := false
changedCells, changedAttributes := 0, 0
if comparable {
for row := range next.Rows {
if !slices.Equal(previous.Line(row), next.Line(row)) {
textRows = append(textRows, row)
}
rowChanged := false
for col := range next.Cols {
index := row*next.Cols + col
if previous.Cells[index] != next.Cells[index] {
changedCells++
}
if !previousAttributes || !nextAttributes || previous.Attributes[index] == next.Attributes[index] {
continue
}
rowChanged = true
changedAttributes++
before, after := previous.Attributes[index], next.Attributes[index]
last := len(attributeChanges) - 1
if last >= 0 && attributeChanges[last].Row == row && attributeChanges[last].End == col &&
attributeChanges[last].Before == before && attributeChanges[last].After == after {
attributeChanges[last].End++
} else if len(attributeChanges) < 32 {
attributeChanges = append(attributeChanges, AttributeChange{Row: row, Start: col, End: col + 1, Before: before, After: after})
} else {
truncated = true
}
}
if rowChanged {
attributeRows = append(attributeRows, row)
}
}
}
outputLengths := make([]int, len(output))
for index, message := range output {
outputLengths[index] = utf8.RuneCountInString(message.Text)
}
diagnostic.Record("reader update",
"terminal", next.Terminal, "previous_terminal", previous.Terminal,
"generation", next.Generation, "previous_generation", previous.Generation,
"active", next.Active, "switched", switched,
"size", [2]int{next.Rows, next.Cols}, "previous_size", [2]int{previous.Rows, previous.Cols},
"cursor", [2]int{next.X, next.Y}, "previous_cursor", [2]int{previous.X, previous.Y},
"mode", r.trackingMode, "automatic", r.automatic, "cursor_echo", r.cursorEcho,
"review", r.review.Active(), "pending_typing", pendingInput,
"path", path, "comparable", comparable,
"text_rows", textRows, "text_cells", changedCells,
"previous_attributes", previousAttributes, "attributes", nextAttributes,
"attribute_rows", attributeRows, "attribute_cells", changedAttributes,
"attribute_changes", attributeChanges,
"attribute_changes_truncated", truncated,
"messages", len(output), "message_lengths", outputLengths)
}