Files

101 lines
2.9 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 clipboard owns text history and asynchronous clipboard transfers.
package clipboard
import (
"fmt"
"unicode/utf8"
)
// MaxBytes bounds individual entries and file/helper messages.
const MaxBytes = 4 << 20
// History belongs to the application loop. New entries select the newest item;
// navigation wraps, matching Python Fenrir. Clear removes the entire history.
type History struct {
entries []string
index int
limit int
}
func New_history(limit int) *History { return &History{limit: max(1, limit)} }
func Validate_text(text string) error {
if len(text) > MaxBytes || !utf8.ValidString(text) {
return fmt.Errorf("clipboard text must be valid UTF-8 and at most 4 MiB")
}
return nil
}
func (h *History) Add(text string) error {
if err := Validate_text(text); err != nil {
return err
}
h.entries = append([]string{text}, h.entries[:min(len(h.entries), h.limit-1)]...)
h.index = 0
return nil
}
func (h *History) Current() (string, bool) {
if len(h.entries) == 0 {
return "", false
}
return h.entries[h.index], true
}
func (h *History) Move(delta int) {
if len(h.entries) > 0 {
h.index = (h.index + delta + len(h.entries)) % len(h.entries)
}
}
func (h *History) First() { h.index = 0 }
func (h *History) Last() { h.index = max(0, len(h.entries)-1) }
func (h *History) At_first() bool { return len(h.entries) > 0 && h.index == 0 }
func (h *History) At_last() bool { return len(h.entries) > 0 && h.index == len(h.entries)-1 }
func (h *History) Clear() { h.entries, h.index = nil, 0 }
// Refresh adopts shared entries while retaining this reader's selected copy.
// Count duplicates from the oldest end so a new identical copy stays distinct.
func (h *History) Refresh(state Snapshot) error {
text, selected := h.Current()
occurrence := 0
if selected {
for _, entry := range h.entries[h.index:] {
if entry == text {
occurrence++
}
}
}
if err := h.Restore(state); err != nil {
return err
}
if selected {
for i := len(h.entries) - 1; i >= 0; i-- {
if h.entries[i] == text {
occurrence--
if occurrence == 0 {
h.index = i
break
}
}
}
}
return nil
}