diff --git a/textinput/textinput.go b/textinput/textinput.go index 363089b2..3eabf8ed 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -745,15 +745,20 @@ func (m Model) placeholderView() string { render = styles.Placeholder.Render ) - p := make([]rune, m.Width()+1) - copy(p, []rune(m.Placeholder)) + placeholder := []rune(m.Placeholder) + bufLen := len(placeholder) + if w := m.Width() + 1; w > bufLen { + bufLen = w + } + p := make([]rune, bufLen) + copy(p, placeholder) m.virtualCursor.TextStyle = styles.Placeholder m.virtualCursor.SetChar(string(p[:1])) v += m.virtualCursor.View() // If the entire placeholder is already set and no padding is needed, finish - if m.Width() < 1 && len(p) <= 1 { + if m.Width() < 1 && len(placeholder) <= 1 { return styles.Prompt.Render(m.Prompt) + v } @@ -921,8 +926,7 @@ func (m Model) Cursor() *tea.Cursor { w := lipgloss.Width promptWidth := w(m.promptView()) - xOffset := m.Position() + - promptWidth + xOffset := promptWidth + uniseg.StringWidth(string(m.value[:m.pos])) if m.width > 0 { xOffset = min(xOffset, m.width+promptWidth) } diff --git a/textinput/textinput_test.go b/textinput/textinput_test.go index b5e344b9..5bd25bdb 100644 --- a/textinput/textinput_test.go +++ b/textinput/textinput_test.go @@ -2,6 +2,7 @@ package textinput import ( "fmt" + "regexp" "strconv" "strings" "testing" @@ -9,6 +10,8 @@ import ( tea "charm.land/bubbletea/v2" ) +var stripANSI = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`) + func Test_CurrentSuggestion(t *testing.T) { textinput := New() textinput.ShowSuggestions = true @@ -61,6 +64,16 @@ func TestChinesePlaceholder(t *testing.T) { } } +func TestPlaceholderZeroWidth(t *testing.T) { + ti := New() + ti.Placeholder = "Nickname" + view := ti.View() + plain := stripANSI.ReplaceAllString(view, "") + if !strings.Contains(plain, "Nickname") { + t.Fatalf("expected full placeholder in view, got %q (plain %q)", view, plain) + } +} + func TestPlaceholderTruncate(t *testing.T) { t.Skip("Skipping flaky test, the returned view seems incorrect. TODO: Needs investigation.") textinput := New() @@ -118,3 +131,22 @@ func sendString(m Model, str string) Model { return m } + +func TestCursorWideCharacterOffset(t *testing.T) { + t.Parallel() + + m := New() + m.Prompt = "" + m.Focus() + m.SetVirtualCursor(false) + m.SetValue("你好") + m.SetCursor(len([]rune("你好"))) + + c := m.Cursor() + if c == nil { + t.Fatal("expected cursor, got nil") + } + if c.Position.X != 4 { + t.Fatalf("expected cursor X offset 4 for two wide runes, got %d", c.Position.X) + } +}