Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,168 @@
|
||||
// Cthulhu screen reader.
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package review
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||
)
|
||||
|
||||
// move_caret_line handles line and document boundaries under the cursor lock
|
||||
// held by MoveCaret. Text-unit movement has its separate traversal helper.
|
||||
func move_caret_object_line(ctx context.Context, reader DocumentCaretReader, doc atspi.DocumentSnapshot, place *documentPlace, indices []int, current int, command DocumentCaretCommand) (DocumentCaretMove, error) {
|
||||
unit := atspi.DocumentCaretLine
|
||||
if command == CaretReadLine {
|
||||
unit = atspi.DocumentCaretLineRemainder
|
||||
}
|
||||
position := 0
|
||||
for position < len(indices) && indices[position] < current {
|
||||
position++
|
||||
}
|
||||
backwards := command == CaretPreviousLine || command == CaretDocumentEnd
|
||||
if command == CaretDocumentStart {
|
||||
position = 0
|
||||
} else if command == CaretDocumentEnd {
|
||||
position = len(indices) - 1
|
||||
}
|
||||
offset := place.offset
|
||||
if position == len(indices) || indices[position] != current || command == CaretDocumentStart {
|
||||
offset = 0
|
||||
}
|
||||
if command == CaretDocumentEnd {
|
||||
offset = doc.Nodes[indices[position]].Snapshot.CharacterCount - 1
|
||||
}
|
||||
if backwards && command != CaretDocumentEnd && (position == len(indices) || indices[position] != current) {
|
||||
position--
|
||||
if position < 0 {
|
||||
return DocumentCaretMove{Position: documentPosition(doc, *place)}, nil
|
||||
}
|
||||
offset = doc.Nodes[indices[position]].Snapshot.CharacterCount - 1
|
||||
}
|
||||
initialPosition := position
|
||||
for attempts := 0; attempts < len(indices)+1; attempts++ {
|
||||
if position < 0 || position >= len(indices) {
|
||||
return DocumentCaretMove{Position: documentPosition(doc, *place)}, nil
|
||||
}
|
||||
node := doc.Nodes[indices[position]]
|
||||
if node.Snapshot.CharacterCount == 0 || wholeCaretNode(node.Snapshot) {
|
||||
if (command == CaretNextLine || command == CaretPreviousLine) && node.Reference == place.object {
|
||||
if backwards {
|
||||
position--
|
||||
if position >= 0 {
|
||||
offset = doc.Nodes[indices[position]].Snapshot.CharacterCount - 1
|
||||
}
|
||||
} else {
|
||||
position++
|
||||
offset = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
place.object, place.offset, place.origin = node.Reference, 0, CaretNavigation
|
||||
return DocumentCaretMove{Position: documentPosition(doc, *place), Moved: true}, nil
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if command == CaretReadLine && offset >= node.Snapshot.CharacterCount {
|
||||
position++
|
||||
offset = 0
|
||||
continue
|
||||
}
|
||||
if offset >= node.Snapshot.CharacterCount {
|
||||
offset = node.Snapshot.CharacterCount - 1
|
||||
}
|
||||
piece, readErr := reader.ReadDocumentCaret(ctx, node.Reference, offset, unit)
|
||||
if readErr != nil && !errors.Is(readErr, atspi.ErrNoDocumentCaret) {
|
||||
return DocumentCaretMove{}, readErr
|
||||
}
|
||||
if readErr == nil && piece.End > piece.Start && strings.TrimSpace(piece.Text) != "\ufffc" {
|
||||
nextOffset := offset
|
||||
sameCurrent := node.Reference == place.object ||
|
||||
place.origin == StructuralNavigation && position == initialPosition && indices[position] >= current
|
||||
switch command {
|
||||
case CaretNextLine:
|
||||
if sameCurrent {
|
||||
nextOffset = piece.End
|
||||
if nextOffset < piece.CharacterCount {
|
||||
piece, readErr = reader.ReadDocumentCaret(ctx, node.Reference, nextOffset, unit)
|
||||
if readErr != nil {
|
||||
return DocumentCaretMove{}, readErr
|
||||
}
|
||||
} else {
|
||||
position++
|
||||
if position >= len(indices) {
|
||||
return DocumentCaretMove{Position: documentPosition(doc, *place)}, nil
|
||||
}
|
||||
offset = 0
|
||||
continue
|
||||
}
|
||||
}
|
||||
nextOffset = piece.Start
|
||||
case CaretPreviousLine:
|
||||
if sameCurrent {
|
||||
if piece.Start > 0 {
|
||||
piece, readErr = reader.ReadDocumentCaret(ctx, node.Reference, piece.Start-1, unit)
|
||||
if readErr != nil {
|
||||
return DocumentCaretMove{}, readErr
|
||||
}
|
||||
} else {
|
||||
position--
|
||||
if position < 0 {
|
||||
return DocumentCaretMove{Position: documentPosition(doc, *place)}, nil
|
||||
}
|
||||
offset = doc.Nodes[indices[position]].Snapshot.CharacterCount - 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
nextOffset = piece.Start
|
||||
case CaretLineStart:
|
||||
nextOffset = piece.Start
|
||||
case CaretLineEnd, CaretDocumentEnd:
|
||||
nextOffset = max(piece.Start, piece.End-1)
|
||||
case CaretDocumentStart:
|
||||
nextOffset = piece.Start
|
||||
}
|
||||
if piece.End <= piece.Start || strings.TrimSpace(piece.Text) == "\ufffc" {
|
||||
// An embedded-object placeholder is a container, not spoken text.
|
||||
if backwards {
|
||||
position--
|
||||
if position >= 0 {
|
||||
offset = doc.Nodes[indices[position]].Snapshot.CharacterCount - 1
|
||||
}
|
||||
} else {
|
||||
position++
|
||||
offset = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
spoken := piece.Text
|
||||
if command == CaretLineStart || command == CaretLineEnd {
|
||||
character, charErr := reader.ReadDocumentCaret(ctx, node.Reference, nextOffset, atspi.DocumentCaretCharacter)
|
||||
if charErr != nil {
|
||||
return DocumentCaretMove{}, charErr
|
||||
}
|
||||
spoken = character.Text
|
||||
}
|
||||
place.object = node.Reference
|
||||
place.offset = nextOffset
|
||||
place.origin = CaretNavigation
|
||||
return DocumentCaretMove{Position: documentPosition(doc, *place), Text: spoken, RawText: piece.RawText, Misspelled: piece.Misspelled, Moved: true, Start: piece.Start, End: piece.End}, nil
|
||||
}
|
||||
if backwards {
|
||||
position--
|
||||
if position >= 0 {
|
||||
offset = doc.Nodes[indices[position]].Snapshot.CharacterCount - 1
|
||||
}
|
||||
} else {
|
||||
position++
|
||||
offset = 0
|
||||
}
|
||||
}
|
||||
return DocumentCaretMove{Position: documentPosition(doc, *place)}, nil
|
||||
}
|
||||
Reference in new issue
Block a user