92 lines
2.7 KiB
Go
92 lines
2.7 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
)
|
|
|
|
// textSelection tracks only the focused text object. Its cached content is
|
|
// bounded by the native snapshot and is retired when focus changes.
|
|
type textSelection struct {
|
|
id atspi.ObjectID
|
|
start, end int
|
|
text string
|
|
}
|
|
|
|
const selectionFragmentLimit = 120
|
|
const lineSelectionFragmentLimit = 4096
|
|
|
|
func (selection *textSelection) reset(snapshot atspi.Snapshot) {
|
|
selection.id = snapshot.ID()
|
|
selection.start, selection.end = snapshot.SelectionStart, snapshot.SelectionEnd
|
|
selection.text = snapshot.SelectionText
|
|
}
|
|
|
|
func (selection *textSelection) changed(snapshot atspi.Snapshot) string {
|
|
return selection.changedWithLimit(snapshot, selectionFragmentLimit)
|
|
}
|
|
|
|
func (selection *textSelection) changedWithLimit(snapshot atspi.Snapshot, fragmentLimit int) string {
|
|
start, end := snapshot.SelectionStart, snapshot.SelectionEnd
|
|
if selection.id != snapshot.ID() {
|
|
selection.reset(snapshot)
|
|
return ""
|
|
}
|
|
oldStart, oldEnd, oldText := selection.start, selection.end, selection.text
|
|
selection.reset(snapshot)
|
|
if start == oldStart && end == oldEnd && oldText == snapshot.SelectionText {
|
|
return ""
|
|
}
|
|
if end <= start {
|
|
if oldEnd > oldStart {
|
|
return "selection removed"
|
|
}
|
|
return ""
|
|
}
|
|
if oldEnd <= oldStart {
|
|
return selectionFragment(snapshot.SelectionText, "selected", fragmentLimit)
|
|
}
|
|
var changes []string
|
|
if start > oldStart {
|
|
changes = append(changes, selectionFragment(selectionSlice(oldText, 0, min(oldEnd, start)-oldStart), "unselected", fragmentLimit))
|
|
}
|
|
if end < oldEnd {
|
|
changes = append(changes, selectionFragment(selectionSlice(oldText, max(oldStart, end)-oldStart, oldEnd-oldStart), "unselected", fragmentLimit))
|
|
}
|
|
if start < oldStart {
|
|
changes = append(changes, selectionFragment(selectionSlice(snapshot.SelectionText, 0, min(end, oldStart)-start), "selected", fragmentLimit))
|
|
}
|
|
if end > oldEnd {
|
|
changes = append(changes, selectionFragment(selectionSlice(snapshot.SelectionText, max(start, oldEnd)-start, end-start), "selected", fragmentLimit))
|
|
}
|
|
if len(changes) == 0 {
|
|
return "selection changed"
|
|
}
|
|
return strings.Join(changes, ", ")
|
|
}
|
|
|
|
func selectionSlice(value string, start, end int) string {
|
|
runes := []rune(value)
|
|
if start < 0 || end > len(runes) || start >= end {
|
|
return ""
|
|
}
|
|
return string(runes[start:end])
|
|
}
|
|
|
|
func selectionFragment(value, change string, limit int) string {
|
|
if utf8.RuneCountInString(value) > limit {
|
|
return change
|
|
}
|
|
value = strings.Join(strings.Fields(value), " ")
|
|
if value == "" {
|
|
return change
|
|
}
|
|
return value + " " + change
|
|
}
|