diff --git a/textinput/textinput.go b/textinput/textinput.go index 9c6666e..1b02034 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -14,6 +14,7 @@ import ( "github.com/charmbracelet/bubbles/runeutil" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/ansi" rw "github.com/mattn/go-runewidth" "github.com/rivo/uniseg" ) @@ -703,40 +704,31 @@ func (m Model) placeholderView() string { var ( v string style = m.PlaceholderStyle.Inline(true).Render + p = m.PromptStyle.Render(m.Prompt) ) - p := make([]rune, m.Width+1) - copy(p, []rune(m.Placeholder)) - m.Cursor.TextStyle = m.PlaceholderStyle - m.Cursor.SetChar(string(p[:1])) + first, rest, _, _ := uniseg.FirstGraphemeClusterInString(m.Placeholder, 0) + m.Cursor.SetChar(first) v += m.Cursor.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 && uniseg.StringWidth(rest) <= 1 { return m.PromptStyle.Render(m.Prompt) + v } // If Width is set then size placeholder accordingly if m.Width > 0 { - // available width is width - len + cursor offset of 1 - minWidth := lipgloss.Width(m.Placeholder) - availWidth := m.Width - minWidth + 1 - - // if width < len, 'subtract'(add) number to len and dont add padding - if availWidth < 0 { - minWidth += availWidth - availWidth = 0 - } - // append placeholder[len] - cursor, append padding - v += style(string(p[1:minWidth])) - v += style(strings.Repeat(" ", availWidth)) + width := m.Width - lipgloss.Width(p) - lipgloss.Width(v) + placeholderRest := ansi.Truncate(rest, width, "…") + availWidth := max(0, width-lipgloss.Width(placeholderRest)) + v += style(placeholderRest) + strings.Repeat(" ", availWidth) } else { // if there is no width, the placeholder can be any length - v += style(string(p[1:])) + v += style(rest) } - return m.PromptStyle.Render(m.Prompt) + v + return p + v } // Blink is a command used to initialize cursor blinking. diff --git a/textinput/textinput_test.go b/textinput/textinput_test.go index 162167e..a055ab6 100644 --- a/textinput/textinput_test.go +++ b/textinput/textinput_test.go @@ -5,6 +5,8 @@ import ( "strconv" "strings" "testing" + + tea "github.com/charmbracelet/bubbletea" ) func Test_CurrentSuggestion(t *testing.T) { @@ -46,6 +48,30 @@ func Test_SlicingOutsideCap(t *testing.T) { textinput.View() } +func TestChinesePlaceholder(t *testing.T) { + textinput := New() + textinput.Placeholder = "输入消息..." + textinput.Width = 20 + + got := textinput.View() + expected := "> 输入消息... " + if got != expected { + t.Fatalf("expected %q but got %q", expected, got) + } +} + +func TestPlaceholderTruncate(t *testing.T) { + textinput := New() + textinput.Placeholder = "A very long placeholder, or maybe not so much" + textinput.Width = 10 + + got := textinput.View() + expected := "> A very …" + if got != expected { + t.Fatalf("expected %q but got %q", expected, got) + } +} + func ExampleValidateFunc() { creditCardNumber := New() creditCardNumber.Placeholder = "4505 **** **** 1234" @@ -78,3 +104,15 @@ func ExampleValidateFunc() { return err } } + +func keyPress(key rune) tea.Msg { + return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{key}, Alt: false} +} + +func sendString(m Model, str string) Model { + for _, k := range str { + m, _ = m.Update(keyPress(k)) + } + + return m +}