e38cce5f1f
Bundles the pending TUI work into a coherent batch. Bug fixes from external review: * expandPlaceholders: single-pass alternation regex over the original input prevents `#p\d+` / `#img\d+` tokens inside pasted content from being re-expanded after the bracket form is inlined. * /incognito: gate savePromptHistory and the Ctrl+V image-write branch on `!m.incognito` so the no-persistence contract holds. * history.txt: write at mode 0600 (chmod existing 0644 files), create parent dir at 0700, truncate to 500 entries on every save, slog.Warn on errors instead of swallowing. * triggerPickerAction: guard m.config.Engine before SetModel, matching the /model handler. * Picker key handler: navigation/enter/q consume, escape/ctrl+c close the picker AND fall through to global handlers (so streaming cancel and double-tap quit work with an overlay open), default swallows stray input. * Paste line count: report total non-empty lines instead of newline count, ignoring trailing newlines (no more "+0 lines" for "abc"). * Ctrl+O restored to expand-output; Ctrl+Y is the new copy-response bind. /keys help text updated; picker help entries reordered. * Tighter perms on .gnoma/pasted_image_*.png (0600). Race-safety refactor: ApplyTheme used to mutate ~25 package-level lipgloss styles in place. Replaced with an immutable themeStyles snapshot and atomic.Pointer[themeStyles] swap. Readers go through a theme() helper (one atomic load) instead of touching package vars directly. No locks, no nested-RLock risk if rendering ever moves off-thread. Includes pre-existing in-flight work: TUISection in config with persistent theme/vim settings; /copy /theme /vim slash commands; provider-name completion; session.SetProvider for the provider picker. Tests: placeholder_test.go (6 regression + happy-path cases including the pasted-content collision), history_test.go (5 cases covering perms on new and existing files, on-disk truncation, blank-input, newline flattening), provider_test.go (provider switching + picker transitions + SLM gating).
83 lines
2.4 KiB
Go
83 lines
2.4 KiB
Go
package tui
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestExpandPlaceholders_BracketFormExpandsToStoredText(t *testing.T) {
|
|
m := Model{
|
|
pastedTexts: map[string]string{"#p1": "hello world"},
|
|
}
|
|
got := m.expandPlaceholders("see [Pasted text #p1 +0 lines] end")
|
|
want := "see hello world end"
|
|
if got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestExpandPlaceholders_RawFormExpandsToStoredText(t *testing.T) {
|
|
m := Model{
|
|
pastedTexts: map[string]string{"#p1": "hello"},
|
|
}
|
|
got := m.expandPlaceholders("ref #p1 here")
|
|
want := "ref hello here"
|
|
if got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestExpandPlaceholders_UnknownIDsAreLeftAlone(t *testing.T) {
|
|
m := Model{
|
|
pastedTexts: map[string]string{"#p1": "hello"},
|
|
}
|
|
got := m.expandPlaceholders("ref #p9 here")
|
|
if got != "ref #p9 here" {
|
|
t.Errorf("unknown id should be left intact, got %q", got)
|
|
}
|
|
}
|
|
|
|
// Regression: the bug was that after the bracket form was inlined, a second
|
|
// pass scanned the resulting string for raw `#p\d+`. If the pasted content
|
|
// itself contained `#p2`, that token was silently corrupted into whatever
|
|
// `pastedTexts["#p2"]` mapped to (or stripped if absent).
|
|
func TestExpandPlaceholders_PastedContentContainingPlaceholderSyntaxSurvives(t *testing.T) {
|
|
m := Model{
|
|
pastedTexts: map[string]string{
|
|
"#p1": "look at #p2 in this snippet",
|
|
"#p2": "SHOULD_NOT_APPEAR",
|
|
},
|
|
}
|
|
got := m.expandPlaceholders("here: [Pasted text #p1 +0 lines]")
|
|
want := "here: look at #p2 in this snippet"
|
|
if got != want {
|
|
t.Errorf("pasted content was re-expanded:\n got %q\n want %q", got, want)
|
|
}
|
|
if strings.Contains(got, "SHOULD_NOT_APPEAR") {
|
|
t.Error("nested #p2 inside pasted content was wrongly expanded")
|
|
}
|
|
}
|
|
|
|
func TestExpandPlaceholders_ImageBracketFormExpandsToPath(t *testing.T) {
|
|
m := Model{
|
|
pastedImages: map[string]string{"#img1": "/tmp/x.png"},
|
|
}
|
|
got := m.expandPlaceholders("see [Pasted image #img1] end")
|
|
want := "see [Image: /tmp/x.png] end"
|
|
if got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestExpandPlaceholders_MultiplePlaceholdersInOneInput(t *testing.T) {
|
|
m := Model{
|
|
pastedTexts: map[string]string{"#p1": "AAA", "#p2": "BBB"},
|
|
pastedImages: map[string]string{"#img1": "/tmp/x.png"},
|
|
}
|
|
got := m.expandPlaceholders("[Pasted text #p1 +0 lines] then #p2 then [Pasted image #img1]")
|
|
want := "AAA then BBB then [Image: /tmp/x.png]"
|
|
if got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
}
|