From 5d01a08a5a161ab41b9662bf08c071d202c11741 Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Sat, 16 May 2026 18:58:56 -0400 Subject: [PATCH] textinput: render the full placeholder when Width is 0 The buffer for the placeholder runes was sized as Width()+1, so a Width of 0 left exactly one slot. copy(p, []rune(m.Placeholder)) silently dropped everything past the first character and View() then rendered a one-letter placeholder. Size the buffer to the longer of Width+1 and the placeholder itself so all runes survive when the input has no explicit width. The "width set" branch still trims to fit; the unset branch now renders the placeholder in full. Closes #950. Signed-off-by: Charlie Tonneslan --- textinput/textinput.go | 15 +++++++++++++-- textinput/textinput_test.go | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/textinput/textinput.go b/textinput/textinput.go index 363089b2d..55f88deaa 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -745,8 +745,19 @@ func (m Model) placeholderView() string { render = styles.Placeholder.Render ) - p := make([]rune, m.Width()+1) - copy(p, []rune(m.Placeholder)) + // Size the buffer to the longer of Width and the placeholder + // itself so that, when Width is 0 ("no width") or shorter than + // the placeholder, copy still pulls in every rune. The old + // `m.Width()+1` left exactly one slot when Width was 0, so all + // but the first character of the placeholder fell on the floor. + // See #950. + placeholderRunes := []rune(m.Placeholder) + size := m.Width() + 1 + if size < len(placeholderRunes) { + size = len(placeholderRunes) + } + p := make([]rune, size) + copy(p, placeholderRunes) m.virtualCursor.TextStyle = styles.Placeholder m.virtualCursor.SetChar(string(p[:1])) diff --git a/textinput/textinput_test.go b/textinput/textinput_test.go index b5e344b99..b4726b6ee 100644 --- a/textinput/textinput_test.go +++ b/textinput/textinput_test.go @@ -48,6 +48,28 @@ func Test_SlicingOutsideCap(t *testing.T) { textinput.View() } +// Regression for #950. When SetWidth(0) is used (the documented +// "no width" mode), the placeholder buffer was sized to exactly 1 +// rune and copy lost everything past the first character. The +// rendered placeholder was a single letter regardless of length. +func TestPlaceholderZeroWidth(t *testing.T) { + ti := New() + ti.Placeholder = "Something" + ti.SetWidth(0) + + got := ti.View() + // View output carries ANSI styling, so check each rune of the + // placeholder appears somewhere in the string in order. + idx := 0 + for _, r := range "Something" { + i := strings.IndexRune(got[idx:], r) + if i < 0 { + t.Fatalf("expected full placeholder %q to appear in view %q", "Something", got) + } + idx += i + 1 + } +} + func TestChinesePlaceholder(t *testing.T) { t.Skip("Skipping flaky test, the returned view seems incorrect. TODO: Needs investigation.") textinput := New()