Codex application override added.

This commit is contained in:
Storm Dragon committed 2026-10-28 20:46:12 -04:00
1 parent b887f6ea95
commit 7c53cb39d4
23 files changed
+1771 -155

No files matched your search

+5 -2
View File
@@ -9,6 +9,7 @@ import (
"slices"
"git.stormux.org/storm/fenrir/internal/applicationoverrides"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/codex"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/networkmanager"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/ninjam"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/rogue"
@@ -189,6 +190,8 @@ func (a *application) select_override(id string) {
}
var adapter applicationoverrides.Adapter
switch id {
case "codex":
adapter = &codex.Adapter{}
case "ninjam":
adapter = &ninjam.Adapter{}
case "networkmanager":
@@ -318,8 +321,8 @@ func (a *application) open_applications() {
a.voice.Say(i18n.T("Settings are busy"))
return
}
items := []string{i18n.T("Default"), i18n.T("NetworkManager"), i18n.T("Ninjam"), i18n.T("Rogue")}
ids := []string{"default", "networkmanager", "ninjam", "rogue"}
items := []string{i18n.T("Default"), i18n.T("NetworkManager"), i18n.T("Ninjam"), i18n.T("Rogue"), i18n.T("Codex")}
ids := []string{"default", "networkmanager", "ninjam", "rogue", "codex"}
position := 0
if selected := a.overrides[a.lastTerminal]; selected != nil {
position = slices.Index(ids, selected.id)
+3 -1
View File
@@ -181,8 +181,9 @@ func TestApplicationsMenuActivatesOnlyOnEnterAndReleasesCapture(t *testing.T) {
{"down", "NetworkManager"},
{"down", "Ninjam"},
{"down", "Rogue"},
{"down", "Codex"},
{"down", "Default"},
{"up", "Rogue"},
{"up", "Codex"},
} {
a.ui.handle(interaction.Key{Name: step.key})
if got := voice.messages[len(voice.messages)-1]; got != step.want {
@@ -218,6 +219,7 @@ func TestApplicationsMenuActivatesOnlyOnEnterAndReleasesCapture(t *testing.T) {
}
a.open_applications()
a.ui.handle(interaction.Key{Name: "down"}) // Rogue
a.ui.handle(interaction.Key{Name: "down"}) // Codex
a.ui.handle(interaction.Key{Name: "down"}) // list wraps to Default
a.ui.handle(interaction.Key{Name: "enter"})
if a.saves_unavailable() || a.engine.Tracking_mode() != "cursor" {
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"encoding/json"
"os"
"strings"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/codex"
)
func (a *application) codex_event(text string) string {
kind, payload, _ := strings.Cut(text, " ")
if kind != "CODEX" {
return "Unsupported application event"
}
var event codex.Event
if err := json.Unmarshal([]byte(payload), &event); err != nil {
return "Invalid Codex event JSON"
}
if a.options.TerminalMode {
if event.Window == "" || event.Window != os.Getenv("WINDOWID") || event.Terminal != 1 {
return "Codex event belongs to another terminal"
}
} else if event.Window != "" || event.Terminal != a.lastTerminal {
return "Codex event belongs to another terminal"
}
selected := a.overrides[event.Terminal]
if selected == nil || selected.id != "codex" {
return "Codex override is not selected"
}
if !selected.initialized || a.overrideTransition {
a.overrideDirty = true
return "Codex override is loading"
}
adapter, ok := selected.adapter.(*codex.Adapter)
if !ok {
return "Codex override is unavailable"
}
if err := adapter.Accept(event); err != nil {
return err.Error()
}
a.overrideDirty = true
return ""
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"encoding/json"
"os"
"slices"
"strings"
"testing"
"time"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/codex"
"git.stormux.org/storm/fenrir/internal/command"
"git.stormux.org/storm/fenrir/internal/control"
)
func TestCodexEventRoutingWholeMessagesMuteReviewAndExit(t *testing.T) {
t.Setenv("WINDOWID", "42")
a, voice := persistence_application(t, true)
a.options.TerminalMode = true
snapshot := override_screen(1, "OpenAI Codex")
override_read(t, a, snapshot)
a.select_override("codex")
override_read(t, a, snapshot)
base := codex.Event{Version: 1, Epoch: 1, Window: "42", Terminal: 1, Session: "fixture-session", Kind: "status", Status: "working"}
send := func(event codex.Event, uid int) string {
t.Helper()
encoded, err := json.Marshal(event)
if err != nil {
t.Fatal(err)
}
reply := make(chan string, 1)
a.remote_request(control.Request{UID: uid, Text: "APPLICATION CODEX " + string(encoded), Reply: reply})
return <-reply
}
other := base
other.Window = "43"
if reply := send(other, os.Geteuid()); !strings.Contains(reply, "another terminal") {
t.Fatal(reply)
}
if reply := send(base, os.Geteuid()+1); !strings.Contains(reply, "not permitted") {
t.Fatal(reply)
}
a.settings.live.Remote.EnableCommandRemote = false
if reply := send(base, os.Geteuid()); !strings.Contains(reply, "disabled") {
t.Fatal(reply)
}
a.settings.live.Remote.EnableCommandRemote = true
if reply := send(base, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
a.handle_command(command.ReadLiveRegion10)
if got := voice.messages[len(voice.messages)-1]; got != "Codex working" {
t.Fatal(got)
}
message := base
message.Kind, message.ID, message.Parts, message.Text = "message", "first", 2, "Complete "
start := len(voice.messages)
if reply := send(message, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, snapshot)
if len(voice.messages) != start {
t.Fatal("partial text spoken", voice.messages[start:])
}
message.Part, message.Text = 1, "message"
if reply := send(message, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, snapshot)
if got := voice.messages[len(voice.messages)-1]; got != "Complete message" {
t.Fatal(got)
}
message.Part, message.Parts, message.Text = 0, 1, "Complete message"
start = len(voice.messages)
if reply := send(message, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, override_screen(1, "file output and repeated streaming text"))
if len(voice.messages) != start {
t.Fatal("duplicate or tool output spoken", voice.messages[start:])
}
a.handle_command(command.Mute)
message.ID, message.Text = "muted", "Message while muted"
if reply := send(message, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, snapshot)
if len(voice.messages) != start {
t.Fatal("mute bypassed")
}
a.handle_command(command.Mute)
a.handle_command(command.ReadLiveRegion1)
if got := voice.messages[len(voice.messages)-1]; got != "Message while muted" {
t.Fatal(got)
}
// Stored replies must not replace raw screen review.
a.handle_command(command.CurrentLine)
if slices.Contains(voice.messages[len(voice.messages)-1:], "Message while muted") {
t.Fatal("history replaced raw review")
}
base.Status = "closed"
if reply := send(base, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, override_screen(1, "[Username@host directory] $"))
if a.overrideCurrent != nil || a.overrides[1] != nil {
t.Fatal("shell did not restore defaults")
}
}
func TestCodexWorkingToneUsesExistingSoundGates(t *testing.T) {
for _, mode := range []string{"enabled", "sound off", "progress off", "muted", "automatic off", "menu"} {
t.Run(mode, func(t *testing.T) {
a, _ := persistence_application(t, true)
snapshot := override_screen(1, "OpenAI Codex")
override_read(t, a, snapshot)
a.select_override("codex")
override_read(t, a, snapshot)
sounds := &fakeSounds{}
a.cues = sounds
a.settings.live.Presentation.Sound.Enabled = mode != "sound off"
a.settings.live.Presentation.Progress.Enabled = mode != "progress off"
a.settings.live.Reading.Automatic = mode != "automatic off"
a.configure_reader()
if mode == "muted" {
a.handle_command(command.Mute)
}
if mode == "menu" {
a.open_applications()
}
adapter := a.overrideCurrent.adapter.(*codex.Adapter)
if err := adapter.Accept(codex.Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "status", Status: "working"}); err != nil {
t.Fatal(err)
}
override_read(t, a, snapshot)
want := mode == "enabled" || mode == "muted" || mode == "automatic off"
if (len(sounds.progress) > 0) != want {
t.Fatal(mode, sounds.progress)
}
if err := adapter.Accept(codex.Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "status", Status: "permission"}); err != nil {
t.Fatal(err)
}
override_read(t, a, snapshot)
if _, ok := a.engine.Next_progress(time.Now().Add(2 * time.Second)); ok {
t.Fatal("approval wait retained working beep")
}
})
}
}
+14 -1
View File
@@ -188,6 +188,12 @@ func (a *application) remote_request(request control.Request) {
} else {
section, text, _ := strings.Cut(request.Text, " ")
switch strings.ToUpper(section) {
case "APPLICATION":
if !a.settings.live.Remote.EnableCommandRemote {
response = "Command remote control is disabled"
} else {
response = a.codex_event(text)
}
case "COMMAND":
if !a.settings.live.Remote.EnableCommandRemote {
response = "Command remote control is disabled"
@@ -245,7 +251,14 @@ func (a *application) remote_request(request control.Request) {
func remote_reply(reply chan string, response string) {
if response != "" {
slog.Warn("remote request rejected", "error", response, "at", time.Now())
// An opt-in bridge may be attached while its override is unselected,
// loading, or on another VT. Its normal retry must not fill the log.
switch response {
case "Codex override is not selected", "Codex override is loading", "Codex event belongs to another terminal":
slog.Debug("application event deferred", "reason", response, "at", time.Now())
default:
slog.Warn("remote request rejected", "error", response, "at", time.Now())
}
}
select {
case reply <- response:
@@ -0,0 +1,169 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
// Package codex presents completed messages supplied by the opt-in Codex bridge.
package codex
import (
"fmt"
"slices"
"strings"
"git.stormux.org/storm/fenrir/internal/applicationoverrides"
"git.stormux.org/storm/fenrir/internal/command"
"git.stormux.org/storm/fenrir/internal/config"
"git.stormux.org/storm/fenrir/internal/i18n"
"git.stormux.org/storm/fenrir/internal/presentation"
"git.stormux.org/storm/fenrir/internal/screen"
)
// Event is a versioned, terminal-addressed packet, never a reader command.
// Message chunks are committed only after the entire completed message arrives.
type Event struct {
Version int `json:"version"`
Window string `json:"window"`
Terminal int `json:"terminal"`
Session string `json:"session"`
Epoch int64 `json:"epoch"`
Kind string `json:"kind"`
Status string `json:"status,omitempty"`
ID string `json:"id,omitempty"`
Text string `json:"text,omitempty"`
Part int `json:"part,omitempty"`
Parts int `json:"parts,omitempty"`
Baseline bool `json:"baseline,omitempty"`
}
type storedMessage struct{ id, text string }
type Adapter struct {
session, status string
epoch int64
history []storedMessage
pending []presentation.Message
chunkID string
chunks []string
chunkBytes int
chunkParts int
closed bool
}
func (*Adapter) Name() string { return "Codex" }
func (*Adapter) Configure(settings *config.Settings) {
settings.Review.LeaveReviewOnCursorChange = true
}
// Suspension discards automatic announcements, retaining history and deduplication.
func (a *Adapter) Reset() { a.pending = nil }
func (a *Adapter) Close() { *a = Adapter{} }
func (a *Adapter) Accept(event Event) error {
if event.Version != 1 || event.Session == "" || len(event.Session) > 128 || event.Epoch < 1 {
return fmt.Errorf("invalid Codex event version or session")
}
if event.Epoch < a.epoch || a.session != "" && a.session != event.Session && (event.Kind != "status" || event.Epoch <= a.epoch) {
return fmt.Errorf("another Codex session is attached")
}
if event.Kind == "status" {
switch event.Status {
case "idle", "working", "permission", "question", "done", "interrupted", "closed", "error":
default:
return fmt.Errorf("invalid Codex status")
}
} else if event.Kind != "message" || event.ID == "" || len(event.ID) > 256 || event.Parts < 1 || event.Parts > 4096 || event.Part < 0 || event.Part >= event.Parts || len(event.Text) > 6000 {
return fmt.Errorf("invalid Codex message chunk")
}
if a.session != event.Session {
*a = Adapter{session: event.Session, epoch: event.Epoch}
}
a.epoch = event.Epoch
if event.Kind == "status" {
a.status, a.closed = event.Status, event.Status == "closed"
return nil
}
if slices.ContainsFunc(a.history, func(message storedMessage) bool { return message.id == event.ID }) {
return nil
}
if event.Part == 0 {
a.chunkID, a.chunks, a.chunkBytes = event.ID, nil, 0
a.chunkParts = event.Parts
}
if a.chunkID != event.ID || len(a.chunks) != event.Part || a.chunkParts != event.Parts {
return fmt.Errorf("Codex chunks must arrive in order")
}
a.chunkBytes += len(event.Text)
if a.chunkBytes > 1024*1024 {
a.chunkID, a.chunks, a.chunkBytes = "", nil, 0
return fmt.Errorf("Codex message exceeds 1 MiB")
}
a.chunks = append(a.chunks, event.Text)
if len(a.chunks) != event.Parts {
return nil
}
text := strings.TrimSpace(strings.Join(a.chunks, ""))
a.chunkID, a.chunks, a.chunkBytes = "", nil, 0
if text == "" {
return nil
}
a.history = append([]storedMessage{{event.ID, text}}, a.history...)
if len(a.history) > 9 {
a.history = a.history[:9]
}
if !event.Baseline {
a.pending = append(a.pending, presentation.Message{Text: text})
}
return nil
}
func (a *Adapter) Observe(next screen.Snapshot) applicationoverrides.Frame {
frame := applicationoverrides.Frame{Recognized: !a.closed, OwnsPresentation: !a.closed, InputCursor: true, Activity: !a.closed && a.status == "working"}
// Only the composer can produce automatic typing/deletion echo. Tool output
// and transcript rows must never be interpreted as text typed by the user.
for row := next.Rows - 1; row >= max(0, next.Rows-12); row-- {
line := string(next.Line(row))
if col := strings.IndexRune(line, '›'); col >= 0 && strings.TrimSpace(line[:col]) == "" {
left := len([]rune(line[:col])) + 2
if next.Y >= row && next.Y < next.Rows && next.X >= left {
frame.Input = &screen.Region{Left: left, Top: row, Right: next.Cols - 1, Bottom: next.Y}
}
break
}
}
frame.Updates, a.pending = a.pending, nil
return frame
}
func (a *Adapter) Command(id command.ID) (presentation.CommandResult, bool) {
ids := []command.ID{command.ReadLiveRegion1, command.ReadLiveRegion2, command.ReadLiveRegion3, command.ReadLiveRegion4, command.ReadLiveRegion5, command.ReadLiveRegion6, command.ReadLiveRegion7, command.ReadLiveRegion8, command.ReadLiveRegion9}
if slot := slices.Index(ids, id); slot >= 0 {
text := i18n.T("No Codex message received")
if slot < len(a.history) {
text = a.history[slot].text
}
return presentation.CommandResult{Text: text}, true
}
if id == command.ListReadingRegions {
return presentation.CommandResult{Text: i18n.T("Codex regions: 1 to 9 recent messages, newest first; 0 status")}, true
}
if id == command.ReadLiveRegion10 {
text := i18n.T("Waiting for Codex bridge")
switch a.status {
case "idle":
text = i18n.T("Codex ready")
case "working":
text = i18n.T("Codex working")
case "permission":
text = i18n.T("Codex waiting for approval")
case "question":
text = i18n.T("Codex waiting for an answer")
case "done":
text = i18n.T("Codex finished")
case "interrupted":
text = i18n.T("Codex interrupted")
case "error":
text = i18n.T("Codex bridge error")
}
return presentation.CommandResult{Text: text}, true
}
return presentation.CommandResult{}, false
}
@@ -0,0 +1,102 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package codex
import (
"strings"
"testing"
"git.stormux.org/storm/fenrir/internal/command"
"git.stormux.org/storm/fenrir/internal/screen"
)
func accept_event(t *testing.T, a *Adapter, event Event) {
t.Helper()
event.Version, event.Session, event.Epoch = 1, "fixture-session", 1
if err := a.Accept(event); err != nil {
t.Fatal(err)
}
}
func TestCompleteChunksDeduplicationHistoryAndStatus(t *testing.T) {
a := &Adapter{}
accept_event(t, a, Event{Kind: "status", Status: "working"})
accept_event(t, a, Event{Kind: "message", ID: "first", Text: "Whole ", Parts: 2})
if frame := a.Observe(screen.Snapshot{}); len(frame.Updates) != 0 {
t.Fatal("partial message spoken")
}
accept_event(t, a, Event{Kind: "message", ID: "first", Text: "reply", Part: 1, Parts: 2})
frame := a.Observe(screen.Snapshot{})
if !frame.OwnsPresentation || len(frame.Updates) != 1 || frame.Updates[0].Text != "Whole reply" {
t.Fatal(frame)
}
accept_event(t, a, Event{Kind: "message", ID: "first", Text: "Whole reply", Parts: 1})
if len(a.Observe(screen.Snapshot{}).Updates) != 0 {
t.Fatal("duplicate spoken")
}
accept_event(t, a, Event{Kind: "message", ID: "second", Text: "Old reply", Parts: 1, Baseline: true})
if len(a.Observe(screen.Snapshot{}).Updates) != 0 {
t.Fatal("baseline spoken")
}
if got, _ := a.Command(command.ReadLiveRegion1); got.Text != "Old reply" {
t.Fatal(got)
}
if got, _ := a.Command(command.ReadLiveRegion2); got.Text != "Whole reply" {
t.Fatal(got)
}
if got, _ := a.Command(command.ReadLiveRegion10); got.Text != "Codex working" {
t.Fatal(got)
}
a.Reset()
if got, _ := a.Command(command.ReadLiveRegion2); got.Text != "Whole reply" {
t.Fatal("suspension lost history")
}
accept_event(t, a, Event{Kind: "status", Status: "closed"})
if a.Observe(screen.Snapshot{}).Recognized {
t.Fatal("exit retained presentation ownership")
}
}
func TestRejectInterleavedSessionOutOfOrderAndOversize(t *testing.T) {
a := &Adapter{}
accept_event(t, a, Event{Kind: "status", Status: "idle"})
if err := a.Accept(Event{Version: 1, Epoch: 1, Session: "another-session", Kind: "status", Status: "working"}); err == nil {
t.Fatal("another session attached")
}
if err := a.Accept(Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "message", ID: "test", Part: 1, Parts: 2, Text: "tail"}); err == nil {
t.Fatal("out of order accepted")
}
if err := a.Accept(Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "message", ID: "test", Parts: 1, Text: strings.Repeat("x", 6001)}); err == nil {
t.Fatal("oversize chunk accepted")
}
}
func TestConversationSwitchRejectsPreviousFollower(t *testing.T) {
a := &Adapter{}
accept_event(t, a, Event{Kind: "status", Status: "working"})
accept_event(t, a, Event{Kind: "message", ID: "old", Text: "Previous conversation", Parts: 1})
if err := a.Accept(Event{Version: 1, Epoch: 2, Session: "new-session", Kind: "status", Status: "idle"}); err != nil {
t.Fatal(err)
}
if len(a.history) != 0 || len(a.pending) != 0 {
t.Fatal("conversation history leaked")
}
if err := a.Accept(Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "status", Status: "working"}); err == nil {
t.Fatal("old follower replaced new conversation")
}
}
func TestInputLimitedToComposer(t *testing.T) {
a := &Adapter{}
s := screen.Snapshot{Active: true, Rows: 8, Cols: 40, X: 5, Y: 6, Cells: []rune(strings.Repeat(" ", 320))}
copy(s.Line(6), []rune("› hello"))
frame := a.Observe(s)
if frame.Input == nil || frame.Input.Top != 6 || frame.Input.Left != 2 {
t.Fatal(frame.Input)
}
s.Y = 2
if a.Observe(s).Input != nil {
t.Fatal("transcript treated as composer")
}
}
@@ -39,6 +39,9 @@ type Frame struct {
Regions []Region
// Input limits typing/deletion echo to an application's actual input field.
Input *screen.Region
// InputCursor preserves navigation feedback inside Input while automatic
// terminal diffs remain suppressed. Activity is indeterminate working state.
InputCursor, Activity bool
}
type Adapter interface {
+69 -25
View File
@@ -71,11 +71,11 @@ msgstr "Wütend"
msgid "Apple"
msgstr "Apfel"
#: internal/app/application_overrides.go:328
#: internal/app/application_overrides.go:331
msgid "Application"
msgstr "Anwendung"
#: internal/app/application_overrides.go:327
#: internal/app/application_overrides.go:330
msgid "Applications"
msgstr "Anwendungen"
@@ -172,7 +172,7 @@ msgstr "Schmetterling"
msgid "Canada"
msgstr "Kanada"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:332
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Abbrechen"
@@ -274,6 +274,42 @@ msgstr "Import aus der Zwischenablage fehlgeschlagen"
msgid "Clipboard sync"
msgstr "Zwischenablage synchronisieren"
#: internal/app/application_overrides.go:324
msgid "Codex"
msgstr "Codex"
#: internal/applicationoverrides/codex/codex.go:164
msgid "Codex bridge error"
msgstr "Fehler der Codex-Brücke"
#: internal/applicationoverrides/codex/codex.go:160
msgid "Codex finished"
msgstr "Codex fertig"
#: internal/applicationoverrides/codex/codex.go:162
msgid "Codex interrupted"
msgstr "Codex unterbrochen"
#: internal/applicationoverrides/codex/codex.go:152
msgid "Codex ready"
msgstr "Codex bereit"
#: internal/applicationoverrides/codex/codex.go:146
msgid "Codex regions: 1 to 9 recent messages, newest first; 0 status"
msgstr "Codex-Bereiche: 1 bis 9 letzte Nachrichten, neueste zuerst; 0 Status"
#: internal/applicationoverrides/codex/codex.go:158
msgid "Codex waiting for an answer"
msgstr "Codex wartet auf eine Antwort"
#: internal/applicationoverrides/codex/codex.go:156
msgid "Codex waiting for approval"
msgstr "Codex wartet auf Genehmigung"
#: internal/applicationoverrides/codex/codex.go:154
msgid "Codex working"
msgstr "Codex arbeitet"
#: internal/emoji/catalog.go:68
msgid "Coffee"
msgstr "Kaffee"
@@ -298,13 +334,13 @@ msgstr "Cool"
msgid "Could not check spelling: {0}"
msgstr "Rechtschreibung konnte nicht geprüft werden: {0}"
#: internal/app/application_overrides.go:281
#: internal/app/application_overrides.go:84
#: internal/app/application_overrides.go:284
#: internal/app/application_overrides.go:85
msgid "Could not load application settings: {0}"
msgstr "Anwendungseinstellungen konnten nicht geladen werden: {0}"
#: internal/app/application_overrides.go:332
#: internal/app/application_overrides.go:336
#: internal/app/application_overrides.go:335
#: internal/app/application_overrides.go:339
msgid "Could not open Applications: {0}"
msgstr "Anwendungen konnten nicht geöffnet werden: {0}"
@@ -321,8 +357,8 @@ msgstr "Fenrir-Menü konnte nicht geöffnet werden: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "Rechtschreibmenü konnte nicht geöffnet werden: {0}"
#: internal/app/application_overrides.go:162
#: internal/app/application_overrides.go:299
#: internal/app/application_overrides.go:163
#: internal/app/application_overrides.go:302
msgid "Could not restore application settings: {0}"
msgstr "Anwendungseinstellungen konnten nicht wiederhergestellt werden: {0}"
@@ -351,12 +387,12 @@ msgstr "Dolch"
msgid "Date format"
msgstr "Datumsformat"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Default"
msgstr "Standard"
#: internal/app/application_overrides.go:187
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:188
#: internal/app/application_overrides.go:248
msgid "Default settings restored"
msgstr "Standardeinstellungen wiederhergestellt"
@@ -781,7 +817,7 @@ msgstr "Übelkeit"
msgid "Netherlands"
msgstr "Niederlande"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -789,7 +825,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Neuer Kanal"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Ninjam"
msgstr "Ninjam"
@@ -805,6 +841,10 @@ msgstr ""
"Ninjam-Bereiche: 1 letzte Chatnachricht; 2 ausgewählter Kanal oder "
"Bedienelement; 3 Verbindung, Tempo und Intervall"
#: internal/applicationoverrides/codex/codex.go:139
msgid "No Codex message received"
msgstr "Keine Codex-Nachricht empfangen"
#: internal/applicationoverrides/ninjam/ninjam.go:79
msgid "No chat message received"
msgstr "Keine Chatnachricht empfangen"
@@ -1061,7 +1101,7 @@ msgstr "Ring"
msgid "Rock on"
msgstr "Rock-Geste"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Rogue"
msgstr "Rogue"
@@ -1161,8 +1201,8 @@ msgstr "Live-Bereich aus einer oder zwei Markierungen festlegen"
msgid "Set window begin and end marks"
msgstr "Setze Fensteranfangs- und -endmarken"
#: internal/app/application_overrides.go:172
#: internal/app/application_overrides.go:318 internal/app/commands.go:84
#: internal/app/application_overrides.go:173
#: internal/app/application_overrides.go:321 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "Einstellungen sind beschäftigt"
@@ -1440,6 +1480,10 @@ msgstr "Lautstärke und Panorama {0}"
msgid "WTF?"
msgstr "Was zum Teufel?"
#: internal/applicationoverrides/codex/codex.go:149
msgid "Waiting for Codex bridge"
msgstr "Warten auf die Codex-Brücke"
#: internal/applicationoverrides/rogue/rogue.go:258
msgid "Wall"
msgstr "Wand"
@@ -1539,11 +1583,11 @@ msgstr "Pfeil rechts"
msgid "at"
msgstr "At-Zeichen"
#: internal/reader/reader.go:247
#: internal/reader/reader.go:259
msgid "autoread disabled"
msgstr "Automatisches Vorlesen ausgeschaltet"
#: internal/reader/reader.go:245
#: internal/reader/reader.go:257
msgid "autoread enabled"
msgstr "Automatisches Vorlesen eingeschaltet"
@@ -1693,7 +1737,7 @@ msgstr "Aktuelle Zeile"
msgid "current word."
msgstr "Aktuelles Wort."
#: internal/config/definitions.go:225 internal/reader/reader.go:240
#: internal/config/definitions.go:225 internal/reader/reader.go:252
msgid "cursor tracking"
msgstr "Cursor-Verfolgung"
@@ -1894,7 +1938,7 @@ msgstr "Kopfzeile festgelegt"
msgid "heart"
msgstr "Herz"
#: internal/config/definitions.go:225 internal/reader/reader.go:237
#: internal/config/definitions.go:225 internal/reader/reader.go:249
msgid "highlight tracking"
msgstr "Hervorhebungen verfolgen"
@@ -2217,7 +2261,7 @@ msgstr "Zeile {0}, Spalte {1} bis Zeile {2}, Spalte {3}"
msgid "screen is empty"
msgstr "Bildschirm ist leer"
#: internal/reader/reader.go:155
#: internal/reader/reader.go:158
msgid "screen {0}"
msgstr "Bildschirm {0}"
@@ -2367,7 +2411,7 @@ msgstr "überrascht"
msgid "tab"
msgstr "Tabulator"
#: internal/config/definitions.go:225 internal/reader/reader.go:234
#: internal/config/definitions.go:225 internal/reader/reader.go:246
msgid "table mode"
msgstr "Tabellenmodus"
@@ -2393,7 +2437,7 @@ msgid "unavailable"
msgstr "nicht verfügbar"
#: internal/app/tutorial.go:107 internal/app/tutorial.go:161
#: internal/reader/reader.go:249 internal/review/review.go:140
#: internal/reader/reader.go:261 internal/review/review.go:140
msgid "unbound"
msgstr "unbelegt"
@@ -2425,7 +2469,7 @@ msgstr "Zwinkern"
msgid "{0} added to dictionary"
msgstr "{0} zum Wörterbuch hinzugefügt"
#: internal/app/application_overrides.go:210
#: internal/app/application_overrides.go:213
msgid "{0} application override selected"
msgstr "Anwendungseinstellungen für {0} ausgewählt"
+71 -25
View File
@@ -71,11 +71,11 @@ msgstr "Enfadado"
msgid "Apple"
msgstr "Manzana"
#: internal/app/application_overrides.go:328
#: internal/app/application_overrides.go:331
msgid "Application"
msgstr "Aplicación"
#: internal/app/application_overrides.go:327
#: internal/app/application_overrides.go:330
msgid "Applications"
msgstr "Aplicaciones"
@@ -172,7 +172,7 @@ msgstr "Mariposa"
msgid "Canada"
msgstr "Canadá"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:332
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Cancelar"
@@ -274,6 +274,44 @@ msgstr "Error al importar el portapapeles"
msgid "Clipboard sync"
msgstr "Sincronización del portapapeles"
#: internal/app/application_overrides.go:324
msgid "Codex"
msgstr "Codex"
#: internal/applicationoverrides/codex/codex.go:164
msgid "Codex bridge error"
msgstr "Error del puente de Codex"
#: internal/applicationoverrides/codex/codex.go:160
msgid "Codex finished"
msgstr "Codex ha terminado"
#: internal/applicationoverrides/codex/codex.go:162
msgid "Codex interrupted"
msgstr "Codex interrumpido"
#: internal/applicationoverrides/codex/codex.go:152
msgid "Codex ready"
msgstr "Codex listo"
#: internal/applicationoverrides/codex/codex.go:146
msgid "Codex regions: 1 to 9 recent messages, newest first; 0 status"
msgstr ""
"Regiones de Codex: 1 a 9 mensajes recientes, el más reciente primero; 0 "
"estado"
#: internal/applicationoverrides/codex/codex.go:158
msgid "Codex waiting for an answer"
msgstr "Codex espera una respuesta"
#: internal/applicationoverrides/codex/codex.go:156
msgid "Codex waiting for approval"
msgstr "Codex espera aprobación"
#: internal/applicationoverrides/codex/codex.go:154
msgid "Codex working"
msgstr "Codex está trabajando"
#: internal/emoji/catalog.go:68
msgid "Coffee"
msgstr "Café"
@@ -298,13 +336,13 @@ msgstr "Genial"
msgid "Could not check spelling: {0}"
msgstr "No se pudo revisar la ortografía: {0}"
#: internal/app/application_overrides.go:281
#: internal/app/application_overrides.go:84
#: internal/app/application_overrides.go:284
#: internal/app/application_overrides.go:85
msgid "Could not load application settings: {0}"
msgstr "No se pudo cargar la configuración de la aplicación: {0}"
#: internal/app/application_overrides.go:332
#: internal/app/application_overrides.go:336
#: internal/app/application_overrides.go:335
#: internal/app/application_overrides.go:339
msgid "Could not open Applications: {0}"
msgstr "No se pudo abrir Aplicaciones: {0}"
@@ -321,8 +359,8 @@ msgstr "No se pudo abrir el menú de Fenrir: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "No se pudo abrir el menú de ortografía: {0}"
#: internal/app/application_overrides.go:162
#: internal/app/application_overrides.go:299
#: internal/app/application_overrides.go:163
#: internal/app/application_overrides.go:302
msgid "Could not restore application settings: {0}"
msgstr "No se pudo restaurar la configuración de la aplicación: {0}"
@@ -351,12 +389,12 @@ msgstr "Daga"
msgid "Date format"
msgstr "Formato de fecha"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Default"
msgstr "Predeterminado"
#: internal/app/application_overrides.go:187
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:188
#: internal/app/application_overrides.go:248
msgid "Default settings restored"
msgstr "Configuración predeterminada restaurada"
@@ -789,7 +827,7 @@ msgstr "Náuseas"
msgid "Netherlands"
msgstr "Países Bajos"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -797,7 +835,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Nuevo canal"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Ninjam"
msgstr "Ninjam"
@@ -813,6 +851,10 @@ msgstr ""
"Regiones de Ninjam: 1 último mensaje de chat; 2 canal o control "
"seleccionado; 3 conexión, tempo e intervalo"
#: internal/applicationoverrides/codex/codex.go:139
msgid "No Codex message received"
msgstr "No se ha recibido ningún mensaje de Codex"
#: internal/applicationoverrides/ninjam/ninjam.go:79
msgid "No chat message received"
msgstr "No se ha recibido ningún mensaje de chat"
@@ -1068,7 +1110,7 @@ msgstr "Anillo"
msgid "Rock on"
msgstr "Gesto de rock"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Rogue"
msgstr "Rogue"
@@ -1168,8 +1210,8 @@ msgstr "Definir una región dinámica a partir de una o dos marcas"
msgid "Set window begin and end marks"
msgstr "Establece una marca de principio y otra para el final de la ventana"
#: internal/app/application_overrides.go:172
#: internal/app/application_overrides.go:318 internal/app/commands.go:84
#: internal/app/application_overrides.go:173
#: internal/app/application_overrides.go:321 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "La configuración está ocupada"
@@ -1445,6 +1487,10 @@ msgstr "Volumen y panorámica {0}"
msgid "WTF?"
msgstr "¿Pero qué demonios?"
#: internal/applicationoverrides/codex/codex.go:149
msgid "Waiting for Codex bridge"
msgstr "Esperando al puente de Codex"
#: internal/applicationoverrides/rogue/rogue.go:258
msgid "Wall"
msgstr "Pared"
@@ -1544,11 +1590,11 @@ msgstr "flecha derecha"
msgid "at"
msgstr "arroba"
#: internal/reader/reader.go:247
#: internal/reader/reader.go:259
msgid "autoread disabled"
msgstr "Lectura automática desactivada"
#: internal/reader/reader.go:245
#: internal/reader/reader.go:257
msgid "autoread enabled"
msgstr "Lectura automática activada"
@@ -1698,7 +1744,7 @@ msgstr "línea actual"
msgid "current word."
msgstr "Palabra actual."
#: internal/config/definitions.go:225 internal/reader/reader.go:240
#: internal/config/definitions.go:225 internal/reader/reader.go:252
msgid "cursor tracking"
msgstr "Seguimiento del cursor"
@@ -1901,7 +1947,7 @@ msgstr "fila de encabezado definida"
msgid "heart"
msgstr "corazón"
#: internal/config/definitions.go:225 internal/reader/reader.go:237
#: internal/config/definitions.go:225 internal/reader/reader.go:249
msgid "highlight tracking"
msgstr "Seguimiento del texto resaltado"
@@ -2225,7 +2271,7 @@ msgstr "fila {0}, columna {1} hasta fila {2}, columna {3}"
msgid "screen is empty"
msgstr "La pantalla está vacía"
#: internal/reader/reader.go:155
#: internal/reader/reader.go:158
msgid "screen {0}"
msgstr "pantalla {0}"
@@ -2373,7 +2419,7 @@ msgstr "sorprendido"
msgid "tab"
msgstr "tabulación"
#: internal/config/definitions.go:225 internal/reader/reader.go:234
#: internal/config/definitions.go:225 internal/reader/reader.go:246
msgid "table mode"
msgstr "modo de tabla"
@@ -2399,7 +2445,7 @@ msgid "unavailable"
msgstr "no disponible"
#: internal/app/tutorial.go:107 internal/app/tutorial.go:161
#: internal/reader/reader.go:249 internal/review/review.go:140
#: internal/reader/reader.go:261 internal/review/review.go:140
msgid "unbound"
msgstr "sin asignar"
@@ -2431,7 +2477,7 @@ msgstr "guiño"
msgid "{0} added to dictionary"
msgstr "{0} añadido al diccionario"
#: internal/app/application_overrides.go:210
#: internal/app/application_overrides.go:213
msgid "{0} application override selected"
msgstr "Ajustes de aplicación seleccionados para {0}"
+64 -20
View File
@@ -61,11 +61,11 @@ msgstr ""
msgid "Apple"
msgstr ""
#: internal/app/application_overrides.go:328
#: internal/app/application_overrides.go:331
msgid "Application"
msgstr ""
#: internal/app/application_overrides.go:327
#: internal/app/application_overrides.go:330
msgid "Applications"
msgstr ""
@@ -161,7 +161,7 @@ msgstr ""
msgid "Canada"
msgstr ""
#: internal/app/application_overrides.go:329 internal/preferences/settings.go:93
#: internal/app/application_overrides.go:332 internal/preferences/settings.go:93
msgid "Cancel"
msgstr ""
@@ -261,6 +261,42 @@ msgstr ""
msgid "Clipboard sync"
msgstr ""
#: internal/app/application_overrides.go:324
msgid "Codex"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:164
msgid "Codex bridge error"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:160
msgid "Codex finished"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:162
msgid "Codex interrupted"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:152
msgid "Codex ready"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:146
msgid "Codex regions: 1 to 9 recent messages, newest first; 0 status"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:158
msgid "Codex waiting for an answer"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:156
msgid "Codex waiting for approval"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:154
msgid "Codex working"
msgstr ""
#: internal/emoji/catalog.go:68
msgid "Coffee"
msgstr ""
@@ -285,11 +321,11 @@ msgstr ""
msgid "Could not check spelling: {0}"
msgstr ""
#: internal/app/application_overrides.go:281 internal/app/application_overrides.go:84
#: internal/app/application_overrides.go:284 internal/app/application_overrides.go:85
msgid "Could not load application settings: {0}"
msgstr ""
#: internal/app/application_overrides.go:332 internal/app/application_overrides.go:336
#: internal/app/application_overrides.go:335 internal/app/application_overrides.go:339
msgid "Could not open Applications: {0}"
msgstr ""
@@ -305,7 +341,7 @@ msgstr ""
msgid "Could not open Spellcheck Menu: {0}"
msgstr ""
#: internal/app/application_overrides.go:162 internal/app/application_overrides.go:299
#: internal/app/application_overrides.go:163 internal/app/application_overrides.go:302
msgid "Could not restore application settings: {0}"
msgstr ""
@@ -333,11 +369,11 @@ msgstr ""
msgid "Date format"
msgstr ""
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Default"
msgstr ""
#: internal/app/application_overrides.go:187 internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:188 internal/app/application_overrides.go:248
msgid "Default settings restored"
msgstr ""
@@ -749,7 +785,7 @@ msgstr ""
msgid "Netherlands"
msgstr ""
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "NetworkManager"
msgstr ""
@@ -757,7 +793,7 @@ msgstr ""
msgid "New channel"
msgstr ""
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Ninjam"
msgstr ""
@@ -769,6 +805,10 @@ msgstr ""
msgid "Ninjam regions: 1 latest chat message; 2 selected channel or control; 3 connection, tempo and interval"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:139
msgid "No Codex message received"
msgstr ""
#: internal/applicationoverrides/ninjam/ninjam.go:79
msgid "No chat message received"
msgstr ""
@@ -1021,7 +1061,7 @@ msgstr ""
msgid "Rock on"
msgstr ""
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Rogue"
msgstr ""
@@ -1121,7 +1161,7 @@ msgstr ""
msgid "Set window begin and end marks"
msgstr ""
#: internal/app/application_overrides.go:172 internal/app/application_overrides.go:318 internal/app/commands.go:84
#: internal/app/application_overrides.go:173 internal/app/application_overrides.go:321 internal/app/commands.go:84
msgid "Settings are busy"
msgstr ""
@@ -1393,6 +1433,10 @@ msgstr ""
msgid "WTF?"
msgstr ""
#: internal/applicationoverrides/codex/codex.go:149
msgid "Waiting for Codex bridge"
msgstr ""
#: internal/applicationoverrides/rogue/rogue.go:258
msgid "Wall"
msgstr ""
@@ -1485,11 +1529,11 @@ msgstr ""
msgid "at"
msgstr ""
#: internal/reader/reader.go:247
#: internal/reader/reader.go:259
msgid "autoread disabled"
msgstr ""
#: internal/reader/reader.go:245
#: internal/reader/reader.go:257
msgid "autoread enabled"
msgstr ""
@@ -1629,7 +1673,7 @@ msgstr ""
msgid "current word."
msgstr ""
#: internal/config/definitions.go:225 internal/reader/reader.go:240
#: internal/config/definitions.go:225 internal/reader/reader.go:252
msgid "cursor tracking"
msgstr ""
@@ -1817,7 +1861,7 @@ msgstr ""
msgid "heart"
msgstr ""
#: internal/config/definitions.go:225 internal/reader/reader.go:237
#: internal/config/definitions.go:225 internal/reader/reader.go:249
msgid "highlight tracking"
msgstr ""
@@ -2117,7 +2161,7 @@ msgstr ""
msgid "screen is empty"
msgstr ""
#: internal/reader/reader.go:155
#: internal/reader/reader.go:158
msgid "screen {0}"
msgstr ""
@@ -2261,7 +2305,7 @@ msgstr ""
msgid "tab"
msgstr ""
#: internal/config/definitions.go:225 internal/reader/reader.go:234
#: internal/config/definitions.go:225 internal/reader/reader.go:246
msgid "table mode"
msgstr ""
@@ -2285,7 +2329,7 @@ msgstr ""
msgid "unavailable"
msgstr ""
#: internal/app/tutorial.go:107 internal/app/tutorial.go:161 internal/reader/reader.go:249 internal/review/review.go:140
#: internal/app/tutorial.go:107 internal/app/tutorial.go:161 internal/reader/reader.go:261 internal/review/review.go:140
msgid "unbound"
msgstr ""
@@ -2317,7 +2361,7 @@ msgstr ""
msgid "{0} added to dictionary"
msgstr ""
#: internal/app/application_overrides.go:210
#: internal/app/application_overrides.go:213
msgid "{0} application override selected"
msgstr ""
+70 -25
View File
@@ -72,11 +72,11 @@ msgstr "Złość"
msgid "Apple"
msgstr "Jabłko"
#: internal/app/application_overrides.go:328
#: internal/app/application_overrides.go:331
msgid "Application"
msgstr "Aplikacja"
#: internal/app/application_overrides.go:327
#: internal/app/application_overrides.go:330
msgid "Applications"
msgstr "Aplikacje"
@@ -173,7 +173,7 @@ msgstr "Motyl"
msgid "Canada"
msgstr "Kanada"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:332
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Anuluj"
@@ -275,6 +275,43 @@ msgstr "Importowanie schowka nie powiodło się"
msgid "Clipboard sync"
msgstr "Synchronizacja schowka"
#: internal/app/application_overrides.go:324
msgid "Codex"
msgstr "Codex"
#: internal/applicationoverrides/codex/codex.go:164
msgid "Codex bridge error"
msgstr "Błąd mostu Codex"
#: internal/applicationoverrides/codex/codex.go:160
msgid "Codex finished"
msgstr "Codex zakończył pracę"
#: internal/applicationoverrides/codex/codex.go:162
msgid "Codex interrupted"
msgstr "Codex przerwany"
#: internal/applicationoverrides/codex/codex.go:152
msgid "Codex ready"
msgstr "Codex gotowy"
#: internal/applicationoverrides/codex/codex.go:146
msgid "Codex regions: 1 to 9 recent messages, newest first; 0 status"
msgstr ""
"Obszary Codex: od 1 do 9 ostatnie wiadomości, najnowsze najpierw; 0 stan"
#: internal/applicationoverrides/codex/codex.go:158
msgid "Codex waiting for an answer"
msgstr "Codex czeka na odpowiedź"
#: internal/applicationoverrides/codex/codex.go:156
msgid "Codex waiting for approval"
msgstr "Codex czeka na zatwierdzenie"
#: internal/applicationoverrides/codex/codex.go:154
msgid "Codex working"
msgstr "Codex pracuje"
#: internal/emoji/catalog.go:68
msgid "Coffee"
msgstr "Kawa"
@@ -299,13 +336,13 @@ msgstr "Wyluzowanie"
msgid "Could not check spelling: {0}"
msgstr "Nie można sprawdzić pisowni: {0}"
#: internal/app/application_overrides.go:281
#: internal/app/application_overrides.go:84
#: internal/app/application_overrides.go:284
#: internal/app/application_overrides.go:85
msgid "Could not load application settings: {0}"
msgstr "Nie można wczytać ustawień aplikacji: {0}"
#: internal/app/application_overrides.go:332
#: internal/app/application_overrides.go:336
#: internal/app/application_overrides.go:335
#: internal/app/application_overrides.go:339
msgid "Could not open Applications: {0}"
msgstr "Nie można otworzyć menu aplikacji: {0}"
@@ -322,8 +359,8 @@ msgstr "Nie można otworzyć menu Fenrira: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "Nie można otworzyć menu sprawdzania pisowni: {0}"
#: internal/app/application_overrides.go:162
#: internal/app/application_overrides.go:299
#: internal/app/application_overrides.go:163
#: internal/app/application_overrides.go:302
msgid "Could not restore application settings: {0}"
msgstr "Nie można przywrócić ustawień aplikacji: {0}"
@@ -352,12 +389,12 @@ msgstr "Sztylet"
msgid "Date format"
msgstr "Format daty"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Default"
msgstr "Domyślne"
#: internal/app/application_overrides.go:187
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:188
#: internal/app/application_overrides.go:248
msgid "Default settings restored"
msgstr "Przywrócono ustawienia domyślne"
@@ -785,7 +822,7 @@ msgstr "Nudności"
msgid "Netherlands"
msgstr "Holandia"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -793,7 +830,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Nowy kanał"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Ninjam"
msgstr "Ninjam"
@@ -809,6 +846,10 @@ msgstr ""
"Regiony Ninjam: 1 ostatnia wiadomość czatu; 2 wybrany kanał lub element "
"sterujący; 3 połączenie, tempo i interwał"
#: internal/applicationoverrides/codex/codex.go:139
msgid "No Codex message received"
msgstr "Nie otrzymano wiadomości od Codex"
#: internal/applicationoverrides/ninjam/ninjam.go:79
msgid "No chat message received"
msgstr "Nie odebrano wiadomości czatu"
@@ -1063,7 +1104,7 @@ msgstr "Pierścień"
msgid "Rock on"
msgstr "Gest rockowy"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Rogue"
msgstr "Rogue"
@@ -1163,8 +1204,8 @@ msgstr "Ustaw obszar dynamiczny za pomocą jednego lub dwóch znaczników"
msgid "Set window begin and end marks"
msgstr "Ustaw początek i koniec okna."
#: internal/app/application_overrides.go:172
#: internal/app/application_overrides.go:318 internal/app/commands.go:84
#: internal/app/application_overrides.go:173
#: internal/app/application_overrides.go:321 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "Ustawienia są zajęte"
@@ -1441,6 +1482,10 @@ msgstr "Głośność i panorama {0}"
msgid "WTF?"
msgstr "Co do diabła?"
#: internal/applicationoverrides/codex/codex.go:149
msgid "Waiting for Codex bridge"
msgstr "Oczekiwanie na most Codex"
#: internal/applicationoverrides/rogue/rogue.go:258
msgid "Wall"
msgstr "Ściana"
@@ -1540,11 +1585,11 @@ msgstr "strzałka w prawo"
msgid "at"
msgstr "małpa"
#: internal/reader/reader.go:247
#: internal/reader/reader.go:259
msgid "autoread disabled"
msgstr "Wyłączono automatyczne ogłaszanie dynamicznych zmian treści."
#: internal/reader/reader.go:245
#: internal/reader/reader.go:257
msgid "autoread enabled"
msgstr "Włączono ogłaszanie dynamicznych zmian treści."
@@ -1694,7 +1739,7 @@ msgstr "Aktualna linia"
msgid "current word."
msgstr "Aktualne słowo"
#: internal/config/definitions.go:225 internal/reader/reader.go:240
#: internal/config/definitions.go:225 internal/reader/reader.go:252
msgid "cursor tracking"
msgstr "Śledzenie kursora."
@@ -1894,7 +1939,7 @@ msgstr "ustawiono wiersz nagłówka"
msgid "heart"
msgstr "serce"
#: internal/config/definitions.go:225 internal/reader/reader.go:237
#: internal/config/definitions.go:225 internal/reader/reader.go:249
msgid "highlight tracking"
msgstr "Śledzenie podświetlenia."
@@ -2217,7 +2262,7 @@ msgstr "wiersz {0}, kolumna {1} do wiersza {2}, kolumny {3}"
msgid "screen is empty"
msgstr "Ekran jest pusty."
#: internal/reader/reader.go:155
#: internal/reader/reader.go:158
msgid "screen {0}"
msgstr "Ekran {0}"
@@ -2365,7 +2410,7 @@ msgstr "zaskoczenie"
msgid "tab"
msgstr "tabulator"
#: internal/config/definitions.go:225 internal/reader/reader.go:234
#: internal/config/definitions.go:225 internal/reader/reader.go:246
msgid "table mode"
msgstr "tryb tabeli"
@@ -2391,7 +2436,7 @@ msgid "unavailable"
msgstr "niedostępne"
#: internal/app/tutorial.go:107 internal/app/tutorial.go:161
#: internal/reader/reader.go:249 internal/review/review.go:140
#: internal/reader/reader.go:261 internal/review/review.go:140
msgid "unbound"
msgstr "nieprzypisane"
@@ -2423,7 +2468,7 @@ msgstr "mrugnięcie"
msgid "{0} added to dictionary"
msgstr "Dodano {0} do słownika"
#: internal/app/application_overrides.go:210
#: internal/app/application_overrides.go:213
msgid "{0} application override selected"
msgstr "Wybrano ustawienia aplikacji {0}"
+70 -25
View File
@@ -70,11 +70,11 @@ msgstr "Zangado"
msgid "Apple"
msgstr "Maçã"
#: internal/app/application_overrides.go:328
#: internal/app/application_overrides.go:331
msgid "Application"
msgstr "Aplicativo"
#: internal/app/application_overrides.go:327
#: internal/app/application_overrides.go:330
msgid "Applications"
msgstr "Aplicativos"
@@ -171,7 +171,7 @@ msgstr "Borboleta"
msgid "Canada"
msgstr "Canadá"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:332
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Cancelar"
@@ -273,6 +273,43 @@ msgstr "Falha ao importar a área de transferência"
msgid "Clipboard sync"
msgstr "Sincronização da área de transferência"
#: internal/app/application_overrides.go:324
msgid "Codex"
msgstr "Codex"
#: internal/applicationoverrides/codex/codex.go:164
msgid "Codex bridge error"
msgstr "Erro na ponte do Codex"
#: internal/applicationoverrides/codex/codex.go:160
msgid "Codex finished"
msgstr "Codex terminou"
#: internal/applicationoverrides/codex/codex.go:162
msgid "Codex interrupted"
msgstr "Codex interrompido"
#: internal/applicationoverrides/codex/codex.go:152
msgid "Codex ready"
msgstr "Codex pronto"
#: internal/applicationoverrides/codex/codex.go:146
msgid "Codex regions: 1 to 9 recent messages, newest first; 0 status"
msgstr ""
"Regiões do Codex: 1 a 9 mensagens recentes, a mais recente primeiro; 0 estado"
#: internal/applicationoverrides/codex/codex.go:158
msgid "Codex waiting for an answer"
msgstr "Codex aguarda uma resposta"
#: internal/applicationoverrides/codex/codex.go:156
msgid "Codex waiting for approval"
msgstr "Codex aguarda aprovação"
#: internal/applicationoverrides/codex/codex.go:154
msgid "Codex working"
msgstr "Codex está a trabalhar"
#: internal/emoji/catalog.go:68
msgid "Coffee"
msgstr "Café"
@@ -297,13 +334,13 @@ msgstr "Legal"
msgid "Could not check spelling: {0}"
msgstr "Não foi possível verificar a ortografia: {0}"
#: internal/app/application_overrides.go:281
#: internal/app/application_overrides.go:84
#: internal/app/application_overrides.go:284
#: internal/app/application_overrides.go:85
msgid "Could not load application settings: {0}"
msgstr "Não foi possível carregar as configurações do aplicativo: {0}"
#: internal/app/application_overrides.go:332
#: internal/app/application_overrides.go:336
#: internal/app/application_overrides.go:335
#: internal/app/application_overrides.go:339
msgid "Could not open Applications: {0}"
msgstr "Não foi possível abrir Aplicativos: {0}"
@@ -320,8 +357,8 @@ msgstr "Não foi possível abrir o menu do Fenrir: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "Não foi possível abrir o menu de ortografia: {0}"
#: internal/app/application_overrides.go:162
#: internal/app/application_overrides.go:299
#: internal/app/application_overrides.go:163
#: internal/app/application_overrides.go:302
msgid "Could not restore application settings: {0}"
msgstr "Não foi possível restaurar as configurações do aplicativo: {0}"
@@ -350,12 +387,12 @@ msgstr "Adaga"
msgid "Date format"
msgstr "Formato da data"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Default"
msgstr "Padrão"
#: internal/app/application_overrides.go:187
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:188
#: internal/app/application_overrides.go:248
msgid "Default settings restored"
msgstr "Configurações padrão restauradas"
@@ -787,7 +824,7 @@ msgstr "Enjoado"
msgid "Netherlands"
msgstr "Países Baixos"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -795,7 +832,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Novo canal"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Ninjam"
msgstr "Ninjam"
@@ -811,6 +848,10 @@ msgstr ""
"Regiões do Ninjam: 1 última mensagem do chat; 2 canal ou controle "
"selecionado; 3 conexão, andamento e intervalo"
#: internal/applicationoverrides/codex/codex.go:139
msgid "No Codex message received"
msgstr "Nenhuma mensagem do Codex recebida"
#: internal/applicationoverrides/ninjam/ninjam.go:79
msgid "No chat message received"
msgstr "Nenhuma mensagem de chat recebida"
@@ -1067,7 +1108,7 @@ msgstr "Anel"
msgid "Rock on"
msgstr "Gesto de rock"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Rogue"
msgstr "Rogue"
@@ -1167,8 +1208,8 @@ msgstr "Definir uma região dinâmica a partir de uma ou duas marcas"
msgid "Set window begin and end marks"
msgstr "Definir marcas de início e fim da janela"
#: internal/app/application_overrides.go:172
#: internal/app/application_overrides.go:318 internal/app/commands.go:84
#: internal/app/application_overrides.go:173
#: internal/app/application_overrides.go:321 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "As configurações estão ocupadas"
@@ -1446,6 +1487,10 @@ msgstr "Volume e panorama {0}"
msgid "WTF?"
msgstr "Mas que diabos?"
#: internal/applicationoverrides/codex/codex.go:149
msgid "Waiting for Codex bridge"
msgstr "A aguardar a ponte do Codex"
#: internal/applicationoverrides/rogue/rogue.go:258
msgid "Wall"
msgstr "Parede"
@@ -1545,11 +1590,11 @@ msgstr "seta para a direita"
msgid "at"
msgstr "arroba"
#: internal/reader/reader.go:247
#: internal/reader/reader.go:259
msgid "autoread disabled"
msgstr "leitura automática desativada"
#: internal/reader/reader.go:245
#: internal/reader/reader.go:257
msgid "autoread enabled"
msgstr "leitura automática ativada"
@@ -1700,7 +1745,7 @@ msgstr "linha atual"
msgid "current word."
msgstr "palavra atual."
#: internal/config/definitions.go:225 internal/reader/reader.go:240
#: internal/config/definitions.go:225 internal/reader/reader.go:252
msgid "cursor tracking"
msgstr "rastreamento do cursor"
@@ -1901,7 +1946,7 @@ msgstr "linha de cabeçalho definida"
msgid "heart"
msgstr "coração"
#: internal/config/definitions.go:225 internal/reader/reader.go:237
#: internal/config/definitions.go:225 internal/reader/reader.go:249
msgid "highlight tracking"
msgstr "rastreamento em destaque"
@@ -2229,7 +2274,7 @@ msgstr "linha {0}, coluna {1} até a linha {2}, coluna {3}"
msgid "screen is empty"
msgstr "a tela está vazia"
#: internal/reader/reader.go:155
#: internal/reader/reader.go:158
msgid "screen {0}"
msgstr "tela {0}"
@@ -2378,7 +2423,7 @@ msgstr "surpreso"
msgid "tab"
msgstr "tabulação"
#: internal/config/definitions.go:225 internal/reader/reader.go:234
#: internal/config/definitions.go:225 internal/reader/reader.go:246
msgid "table mode"
msgstr "modo de tabela"
@@ -2404,7 +2449,7 @@ msgid "unavailable"
msgstr "indisponível"
#: internal/app/tutorial.go:107 internal/app/tutorial.go:161
#: internal/reader/reader.go:249 internal/review/review.go:140
#: internal/reader/reader.go:261 internal/review/review.go:140
msgid "unbound"
msgstr "sem atalho"
@@ -2436,7 +2481,7 @@ msgstr "piscadela"
msgid "{0} added to dictionary"
msgstr "{0} adicionado ao dicionário"
#: internal/app/application_overrides.go:210
#: internal/app/application_overrides.go:213
msgid "{0} application override selected"
msgstr "Configurações do aplicativo {0} selecionadas"
+70 -25
View File
@@ -69,11 +69,11 @@ msgstr "Злость"
msgid "Apple"
msgstr "Яблоко"
#: internal/app/application_overrides.go:328
#: internal/app/application_overrides.go:331
msgid "Application"
msgstr "Приложение"
#: internal/app/application_overrides.go:327
#: internal/app/application_overrides.go:330
msgid "Applications"
msgstr "Приложения"
@@ -170,7 +170,7 @@ msgstr "Бабочка"
msgid "Canada"
msgstr "Канада"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:332
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Отмена"
@@ -272,6 +272,43 @@ msgstr "Не удалось импортировать буфер обмена"
msgid "Clipboard sync"
msgstr "Синхронизация буфера обмена"
#: internal/app/application_overrides.go:324
msgid "Codex"
msgstr "Codex"
#: internal/applicationoverrides/codex/codex.go:164
msgid "Codex bridge error"
msgstr "Ошибка моста Codex"
#: internal/applicationoverrides/codex/codex.go:160
msgid "Codex finished"
msgstr "Codex завершил работу"
#: internal/applicationoverrides/codex/codex.go:162
msgid "Codex interrupted"
msgstr "Codex прерван"
#: internal/applicationoverrides/codex/codex.go:152
msgid "Codex ready"
msgstr "Codex готов"
#: internal/applicationoverrides/codex/codex.go:146
msgid "Codex regions: 1 to 9 recent messages, newest first; 0 status"
msgstr ""
"Области Codex: от 1 до 9 последние сообщения, сначала новые; 0 состояние"
#: internal/applicationoverrides/codex/codex.go:158
msgid "Codex waiting for an answer"
msgstr "Codex ждёт ответа"
#: internal/applicationoverrides/codex/codex.go:156
msgid "Codex waiting for approval"
msgstr "Codex ждёт подтверждения"
#: internal/applicationoverrides/codex/codex.go:154
msgid "Codex working"
msgstr "Codex работает"
#: internal/emoji/catalog.go:68
msgid "Coffee"
msgstr "Кофе"
@@ -296,13 +333,13 @@ msgstr "Круто"
msgid "Could not check spelling: {0}"
msgstr "Не удалось проверить правописание: {0}"
#: internal/app/application_overrides.go:281
#: internal/app/application_overrides.go:84
#: internal/app/application_overrides.go:284
#: internal/app/application_overrides.go:85
msgid "Could not load application settings: {0}"
msgstr "Не удалось загрузить настройки приложения: {0}"
#: internal/app/application_overrides.go:332
#: internal/app/application_overrides.go:336
#: internal/app/application_overrides.go:335
#: internal/app/application_overrides.go:339
msgid "Could not open Applications: {0}"
msgstr "Не удалось открыть меню приложений: {0}"
@@ -319,8 +356,8 @@ msgstr "Не удалось открыть меню Fenrir: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "Не удалось открыть меню проверки правописания: {0}"
#: internal/app/application_overrides.go:162
#: internal/app/application_overrides.go:299
#: internal/app/application_overrides.go:163
#: internal/app/application_overrides.go:302
msgid "Could not restore application settings: {0}"
msgstr "Не удалось восстановить настройки приложения: {0}"
@@ -349,12 +386,12 @@ msgstr "Кинжал"
msgid "Date format"
msgstr "Формат даты"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Default"
msgstr "По умолчанию"
#: internal/app/application_overrides.go:187
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:188
#: internal/app/application_overrides.go:248
msgid "Default settings restored"
msgstr "Настройки по умолчанию восстановлены"
@@ -780,7 +817,7 @@ msgstr "Тошнота"
msgid "Netherlands"
msgstr "Нидерланды"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -788,7 +825,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Новый канал"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Ninjam"
msgstr "Ninjam"
@@ -804,6 +841,10 @@ msgstr ""
"Области Ninjam: 1 последнее сообщение чата; 2 выбранный канал или элемент "
"управления; 3 соединение, темп и интервал"
#: internal/applicationoverrides/codex/codex.go:139
msgid "No Codex message received"
msgstr "Сообщений от Codex не получено"
#: internal/applicationoverrides/ninjam/ninjam.go:79
msgid "No chat message received"
msgstr "Сообщения чата не получены"
@@ -1059,7 +1100,7 @@ msgstr "Кольцо"
msgid "Rock on"
msgstr "Рокерский жест"
#: internal/app/application_overrides.go:321
#: internal/app/application_overrides.go:324
msgid "Rogue"
msgstr "Rogue"
@@ -1159,8 +1200,8 @@ msgstr "Задать динамическую область по одной и
msgid "Set window begin and end marks"
msgstr "Установка начала и конца окна"
#: internal/app/application_overrides.go:172
#: internal/app/application_overrides.go:318 internal/app/commands.go:84
#: internal/app/application_overrides.go:173
#: internal/app/application_overrides.go:321 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "Настройки заняты"
@@ -1438,6 +1479,10 @@ msgstr "Громкость и панорама {0}"
msgid "WTF?"
msgstr "Какого чёрта?"
#: internal/applicationoverrides/codex/codex.go:149
msgid "Waiting for Codex bridge"
msgstr "Ожидание моста Codex"
#: internal/applicationoverrides/rogue/rogue.go:258
msgid "Wall"
msgstr "Стена"
@@ -1537,11 +1582,11 @@ msgstr "стрелка вправо"
msgid "at"
msgstr "собака"
#: internal/reader/reader.go:247
#: internal/reader/reader.go:259
msgid "autoread disabled"
msgstr "Автоматическое чтение нового текста выключено"
#: internal/reader/reader.go:245
#: internal/reader/reader.go:257
msgid "autoread enabled"
msgstr "Автоматическое чтение нового текста включено"
@@ -1691,7 +1736,7 @@ msgstr "Текущая строка."
msgid "current word."
msgstr "Текущее слово."
#: internal/config/definitions.go:225 internal/reader/reader.go:240
#: internal/config/definitions.go:225 internal/reader/reader.go:252
msgid "cursor tracking"
msgstr "Отслеживание курсора"
@@ -1890,7 +1935,7 @@ msgstr "строка заголовков задана"
msgid "heart"
msgstr "сердце"
#: internal/config/definitions.go:225 internal/reader/reader.go:237
#: internal/config/definitions.go:225 internal/reader/reader.go:249
msgid "highlight tracking"
msgstr "Отслеживание выделения"
@@ -2213,7 +2258,7 @@ msgstr "от строки {0}, столбца {1} до строки {2}, сто
msgid "screen is empty"
msgstr "Экран пустой."
#: internal/reader/reader.go:155
#: internal/reader/reader.go:158
msgid "screen {0}"
msgstr "Экран {0}"
@@ -2362,7 +2407,7 @@ msgstr "удивление"
msgid "tab"
msgstr "табуляция"
#: internal/config/definitions.go:225 internal/reader/reader.go:234
#: internal/config/definitions.go:225 internal/reader/reader.go:246
msgid "table mode"
msgstr "режим таблицы"
@@ -2388,7 +2433,7 @@ msgid "unavailable"
msgstr "недоступно"
#: internal/app/tutorial.go:107 internal/app/tutorial.go:161
#: internal/reader/reader.go:249 internal/review/review.go:140
#: internal/reader/reader.go:261 internal/review/review.go:140
msgid "unbound"
msgstr "не назначено"
@@ -2420,7 +2465,7 @@ msgstr "подмигивание"
msgid "{0} added to dictionary"
msgstr "{0} добавлено в словарь"
#: internal/app/application_overrides.go:210
#: internal/app/application_overrides.go:213
msgid "{0} application override selected"
msgstr "Выбраны настройки приложения {0}"
@@ -0,0 +1,108 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package reader
import (
"reflect"
"testing"
"time"
"git.stormux.org/storm/fenrir/internal/applicationoverrides"
"git.stormux.org/storm/fenrir/internal/command"
"git.stormux.org/storm/fenrir/internal/presentation"
"git.stormux.org/storm/fenrir/internal/screen"
)
func TestApplicationComposerCursorFeedback(t *testing.T) {
for _, mode := range []string{"left", "right", "status redraw", "leave review", "passive", "cursor off", "automatic off", "outside input", "edited input", "other adapter"} {
t.Run(mode, func(t *testing.T) {
r := New()
r.Configure_review(true, true, true, true)
before := fixture("Working", "› hello")
before.X, before.Y = 7, 1
frame := applicationoverrides.Frame{Recognized: true, OwnsPresentation: true, InputCursor: true, Input: &screen.Region{Left: 2, Top: 1, Right: 19, Bottom: 1}}
r.Update_application(before, frame)
next := fixture("Working", "› hello")
next.X, next.Y = 6, 1
want := []presentation.Message{presentation.Character_message('o')}
if mode == "right" {
before.X = 5
r.Update_application(before, frame)
}
if mode == "status redraw" {
copy(next.Line(0), []rune("Reading file"))
}
if mode == "leave review" {
r.Command(command.CurrentLine)
}
if mode != "passive" {
r.Note_input(true, false, false)
}
switch mode {
case "passive":
want = nil
case "cursor off":
r.Configure(true, false)
want = nil
case "automatic off":
r.Configure(false, true)
want = nil
case "outside input":
next.Y = 0
want = nil
case "edited input":
next.Line(1)[2] = 'X'
want = nil
case "other adapter":
frame.InputCursor = false
want = nil
}
if got := r.Update_application(next, frame); !reflect.DeepEqual(got, want) {
t.Fatal(mode, got, want)
}
if mode == "leave review" && r.review.Active() {
t.Fatal("direct navigation remained in detached review")
}
})
}
}
func TestApplicationActivityUsesSoundTimerWithoutScreenRedraws(t *testing.T) {
r := New()
r.Configure_progress(true)
r.Configure(false, false) // Working cues are independent of speech/automatic reading.
s := fixture("Working", "› ")
frame := applicationoverrides.Frame{Recognized: true, OwnsPresentation: true, Activity: true}
r.Update_application(s, frame)
now := time.Now()
for _, elapsed := range []time.Duration{0, 4 * time.Second, 8 * time.Second} {
if value, ok := r.Next_progress(now.Add(elapsed)); !ok || value != 50 {
t.Fatal(elapsed, value, ok)
}
if _, ok := r.Next_progress(now.Add(elapsed + 3999*time.Millisecond)); ok {
t.Fatal("activity flooded")
}
}
frame.Activity = false
r.Update_application(s, frame)
if r.Progress_active() {
t.Fatal("idle retained activity")
}
if _, ok := r.Next_progress(now.Add(12 * time.Second)); ok {
t.Fatal("idle beeped")
}
frame.Activity = true
r.Configure_progress(false)
r.Update_application(s, frame)
if _, ok := r.Next_progress(now.Add(16 * time.Second)); ok {
t.Fatal("progress toggle ignored")
}
r.Configure_progress(true)
r.Update_application(s, frame)
s.Active = false
r.Update_application(s, frame)
if _, ok := r.Next_progress(now.Add(20 * time.Second)); ok {
t.Fatal("inactive terminal beeped")
}
}
+10
View File
@@ -4,10 +4,20 @@
package reader
import (
"git.stormux.org/storm/fenrir/internal/presentation"
"git.stormux.org/storm/fenrir/internal/screen"
"slices"
)
// Cursor feedback requires unchanged composer text. Comparing masked snapshots
// allows simultaneous tool/status redraws outside the input field to stay quiet.
func input_cursor_feedback(previous, next screen.Snapshot) []presentation.Message {
if !slices.Equal(previous.Cells, next.Cells) {
return nil
}
return changes(previous, next, true)
}
// SessionState preserves shortcut-only state as well as configuration. Screen,
// input timestamps and detached review positions are deliberately not restored.
type SessionState struct {
+17 -3
View File
@@ -32,10 +32,24 @@ func (r *Reader) Configure_progress(enabled bool) {
}
r.progressEnabled = enabled
}
func (r *Reader) Next_progress(now time.Time) (float64, bool) { return r.progress.Next(now) }
func (r *Reader) Reset_progress() { r.progress.Reset() }
func (r *Reader) Next_progress(now time.Time) (float64, bool) {
if r.applicationActivity && r.progressEnabled {
// Use the existing spinner's fixed pitch, once every four seconds. This reports
// activity, not a guessed completion percentage, and needs no repaint.
if !r.applicationInputAt.IsZero() && now.Sub(r.applicationInputAt) < 300*time.Millisecond ||
!r.applicationActivityAt.IsZero() && now.Sub(r.applicationActivityAt) < 4*time.Second {
return 0, false
}
r.applicationActivityAt = now
return 50, true
}
return r.progress.Next(now)
}
func (r *Reader) Reset_progress() { r.progress.Reset(); r.applicationActivity = false }
func (r *Reader) Progress_active() bool { return r.progress.Active() }
func (r *Reader) Progress_active() bool {
return r.applicationActivity && r.progressEnabled || r.progress.Active()
}
func (r *Reader) Note_deletion() { r.progressDeletionAt = time.Now() }
+13 -1
View File
@@ -51,6 +51,8 @@ type Reader struct {
lastCompletedWord string
inputAt time.Time
applicationInputAt time.Time
applicationActivity bool
applicationActivityAt time.Time
attributeConfigured, attributeResumeMode string
attributeMode, attributeFormat string
lastAttribute screen.Attribute
@@ -93,6 +95,7 @@ func (r *Reader) Update(next screen.Snapshot) []presentation.Message {
func (r *Reader) Update_application(next screen.Snapshot, frame applicationoverrides.Frame) (output []presentation.Message) {
r.completedWords = nil
previous := r.screen
r.applicationActivity = next.Active && frame.Recognized && frame.OwnsPresentation && frame.Activity && r.progressEnabled
// Python runs cursor-change commands only after recent direct application
// input (300 ms). Reader commands and passive output must preserve review.
if !r.keepReviewOnCursorChange && r.review.Active() && !r.applicationInputAt.IsZero() && time.Since(r.applicationInputAt) < 300*time.Millisecond &&
@@ -203,7 +206,16 @@ func (r *Reader) Update_application(next screen.Snapshot, frame applicationoverr
return nil
}
if frame.OwnsPresentation {
return r.deletion_echo(deleted, next)
if len(deleted) > 0 {
return r.deletion_echo(deleted, next)
}
if frame.InputCursor && frame.Input != nil && r.cursorEcho && !r.review.Active() &&
!r.applicationInputAt.IsZero() && time.Since(r.applicationInputAt) < 300*time.Millisecond &&
frame.Input.Contains(previous.X, previous.Y, next.Cols) && frame.Input.Contains(next.X, next.Y, next.Cols) &&
regions.allows(next.X, next.Y, next.Cols, true, true) {
return input_cursor_feedback(typingPrevious, typingNext)
}
return nil
}
readingPrevious := regions.filtered_snapshot(previous, true, true)
readingNext := r.Automatic_snapshot()