// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package review import ( "context" "git.stormux.org/storm/cthulhu/internal/atspi" ) // One move owns this wrapper. Validate the path before native reads so stale // range lengths and omitted candidates trigger recovery without consuming a // different object's text or advancing the retained cursor. type navigationTextReader struct { DocumentCaretReader tree *atspi.AccessibleTree document atspi.DocumentSnapshot anchor int readObjects []atspi.ObjectReference } func (reader *navigationTextReader) ReadDocumentCaret(ctx context.Context, object atspi.ObjectReference, offset int, unit atspi.DocumentCaretUnit) (atspi.DocumentCaretText, error) { index, found := reader.document.Index(object.ID()) if !found || reader.document.Nodes[index].Reference != object { return atspi.DocumentCaretText{}, atspi.ErrStaleObject } if err := reader.tree.ValidateDocumentCaretRange(ctx, reader.document, reader.anchor, index); err != nil { return atspi.DocumentCaretText{}, err } text, err := reader.DocumentCaretReader.ReadDocumentCaret(ctx, object, offset, unit) if err == nil { reader.readObjects = append(reader.readObjects, object) } return text, err } func (reader *navigationTextReader) did_read(object atspi.ObjectReference) bool { for _, read := range reader.readObjects { if read == object { return true } } return false } // Character blocks belong to one word command and never survive an event or // recapture. Native spelling and actual word boundaries still use normal reads. type navigationCharacterReader struct { *navigationTextReader source DocumentCharacterReader } func (reader *navigationCharacterReader) ReadDocumentCharacters(ctx context.Context, object atspi.ObjectReference, offset int) (atspi.DocumentCaretText, error) { index, found := reader.document.Index(object.ID()) if !found || reader.document.Nodes[index].Reference != object { return atspi.DocumentCaretText{}, atspi.ErrStaleObject } if err := reader.tree.ValidateDocumentCaretRange(ctx, reader.document, reader.anchor, index); err != nil { return atspi.DocumentCaretText{}, err } return reader.source.ReadDocumentCharacters(ctx, object, offset) }