90 lines
3.2 KiB
Go
90 lines
3.2 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package review
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
)
|
|
|
|
// ReadCurrentLine leaves both the native caret and review cursors untouched.
|
|
func (worker *Worker) ReadCurrentLine(browse bool, style FlatSpeech) error {
|
|
if style > FlatPhonetic {
|
|
return ErrInvalidDirection
|
|
}
|
|
select {
|
|
case worker.requests <- request{currentLine: true, currentLineBrowse: browse, flatSpeech: style,
|
|
documentEpoch: worker.documentEpoch.Load(), queuedAt: time.Now()}:
|
|
return nil
|
|
default:
|
|
return ErrRequestQueueFull
|
|
}
|
|
}
|
|
|
|
func (worker *Worker) read_current_line(ctx context.Context, cursor *DocumentCursor, browse bool) Result {
|
|
result := Result{CurrentLine: true, CurrentLineBrowse: browse, DocumentFocus: worker.focus()}
|
|
if !result.DocumentFocus.Valid() {
|
|
result.Err = ErrNoCurrent
|
|
return result
|
|
}
|
|
deadline, cancel := context.WithTimeout(ctx, worker.captureTimeout)
|
|
defer cancel()
|
|
if !browse {
|
|
reader, ok := worker.nativeCapturerFor(result.DocumentFocus).(interface {
|
|
ReadFocusedLine(context.Context, atspi.ObjectID) (atspi.DocumentContent, error)
|
|
})
|
|
if !ok {
|
|
result.Err = atspi.ErrNoDocumentCaret
|
|
return result
|
|
}
|
|
content, err := reader.ReadFocusedLine(deadline, result.DocumentFocus)
|
|
result.Err = err
|
|
result.DocumentCaretText, result.DocumentCaretRawText = content.Text, content.RawText
|
|
result.DocumentPosition.Snapshot = content.Snapshot
|
|
result.DocumentCaretContext = content.SourceContext
|
|
return result
|
|
}
|
|
// Start a private cursor at the saved browse position. Going to line start
|
|
// before reading avoids Say All's deliberate "rest of this line" behavior.
|
|
if cursor != nil {
|
|
cursor = &DocumentCursor{tree: cursor.tree, active: cursor.active, places: append([]documentPlace(nil), cursor.places...)}
|
|
_, result.Err = cursor.syncActionFocus(deadline, result.DocumentFocus)
|
|
} else {
|
|
result.Err = ErrNoCurrent
|
|
}
|
|
if documentNeedsCapture(result.Err) {
|
|
cursor, result.Err = worker.refreshDocumentCursor(deadline, cursor, result.DocumentFocus)
|
|
}
|
|
if result.Err != nil {
|
|
return result
|
|
}
|
|
reader, ok := worker.nativeCapturerFor(result.DocumentFocus).(DocumentCaretReader)
|
|
if !ok {
|
|
result.Err = atspi.ErrNoDocumentCaret
|
|
return result
|
|
}
|
|
_, cursor, result.Err = worker.readAllMove(deadline, cursor, reader, CaretLineStart, result.DocumentFocus)
|
|
if result.Err != nil {
|
|
return result
|
|
}
|
|
movement, cursor, err := worker.readAllMove(deadline, cursor, reader, CaretReadLine, result.DocumentFocus)
|
|
result.Err = err
|
|
result.DocumentPosition, result.DocumentCaretText = movement.Position, movement.Text
|
|
result.DocumentCaretRawText, result.DocumentCaretLine = movement.RawText, movement.Line
|
|
if err == nil {
|
|
// Retain the unfiltered ancestry from the tree that produced this
|
|
// movement, including after recapture. Missing metadata keeps export
|
|
// private without preventing the local current-line announcement.
|
|
if owner, contextErr := cursor.tree.DocumentContext(movement.Position.Object); contextErr == nil {
|
|
result.DocumentCaretContext = owner.Ancestors
|
|
}
|
|
}
|
|
if result.DocumentCaretText == "" && wholeCaretNode(movement.Position.Snapshot) {
|
|
result.DocumentCaretText = movement.Position.Snapshot.Name
|
|
}
|
|
return result
|
|
}
|