Codex application override added.
This commit is contained in:
1 parent
b887f6ea95
commit
7c53cb39d4
23 files changed
+1771
-155
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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}"
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user