Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

textarea: support dynamic prompts #211

Merged
merged 2 commits into from
Sep 2, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 71 additions & 8 deletions textarea/textarea.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,28 @@ type Model struct {
Err error

// General settings.
Prompt string
Placeholder string
ShowLineNumbers bool

// Prompt is printed at the beginning of each line.
//
// When changing the value of Prompt after the model has been
// initialized, ensure that SetWidth() gets called afterwards.
//
// See also SetPromptFunc().
Prompt string

// Placeholder is the text displayed when the user
// hasn't entered anything yet.
Placeholder string

// ShowLineNumbers, if enabled, causes line numbers to be printed
// after the prompt.
ShowLineNumbers bool

// EndOfBufferCharacter is displayed at the end of the input.
EndOfBufferCharacter rune
KeyMap KeyMap

// KeyMap encodes the keybindings recognized by the widget.
KeyMap KeyMap

// Styling. FocusedStyle and BlurredStyle are used to style the textarea in
// focused and blurred states.
Expand All @@ -140,6 +157,13 @@ type Model struct {
// accept. If 0 or less, there's no limit.
CharLimit int

// If promptFunc is set, it replaces Prompt as a generator for
// prompt strings at the beginning of each line.
promptFunc func(line int) string

// promptWidth is the width of the prompt.
promptWidth int

// width is the maximum number of characters that can be displayed at once.
// If 0 or less this setting is ignored.
width int
Expand Down Expand Up @@ -682,10 +706,26 @@ func (m *Model) SetWidth(w int) {
// Account for base style borders and padding.
inputWidth -= m.style.Base.GetHorizontalFrameSize()

inputWidth -= rw.StringWidth(m.Prompt)
if m.promptFunc == nil {
m.promptWidth = rw.StringWidth(m.Prompt)
}

inputWidth -= m.promptWidth
m.width = clamp(inputWidth, minWidth, maxWidth)
}

// SetPromptFunc supersedes the Prompt field and sets a dynamic prompt
// instead.
// If the function returns a prompt that is shorter than the
// specified promptWidth, it will be padded to the left.
// If it returns a prompt that is longer, display artifacts
// may occur; the caller is responsible for computing an adequate
// promptWidth.
func (m *Model) SetPromptFunc(promptWidth int, fn func(lineIdx int) string) {
m.promptFunc = fn
m.promptWidth = promptWidth
}

// Height returns the current height of the textarea.
func (m Model) Height() int {
return m.height
Expand Down Expand Up @@ -848,6 +888,7 @@ func (m Model) View() string {

var newLines int

displayLine := 0
for l, line := range m.value {
wrappedLines := wrap(line, m.width)

Expand All @@ -858,7 +899,10 @@ func (m Model) View() string {
}

for wl, wrappedLine := range wrappedLines {
s.WriteString(style.Render(m.style.Prompt.Render(m.Prompt)))
prompt := m.getPromptString(displayLine)
prompt = m.style.Prompt.Render(prompt)
s.WriteString(style.Render(prompt))
displayLine++

if m.ShowLineNumbers {
if wl == 0 {
Expand Down Expand Up @@ -907,7 +951,10 @@ func (m Model) View() string {
// Always show at least `m.Height` lines at all times.
// To do this we can simply pad out a few extra new lines in the view.
for i := 0; i < m.height; i++ {
s.WriteString(m.style.Prompt.Render(m.Prompt))
prompt := m.getPromptString(displayLine)
prompt = m.style.Prompt.Render(prompt)
s.WriteString(prompt)
displayLine++

if m.ShowLineNumbers {
lineNumber := m.style.EndOfBuffer.Render((fmt.Sprintf(m.lineNumberFormat, string(m.EndOfBufferCharacter))))
Expand All @@ -920,6 +967,19 @@ func (m Model) View() string {
return m.style.Base.Render(m.viewport.View())
}

func (m Model) getPromptString(displayLine int) (prompt string) {
prompt = m.Prompt
if m.promptFunc == nil {
return prompt
}
prompt = m.promptFunc(displayLine)
pl := rw.StringWidth(prompt)
if pl < m.promptWidth {
prompt = fmt.Sprintf("%*s%s", m.promptWidth-pl, "", prompt)
}
return prompt
}

// placeholderView returns the prompt and placeholder view, if any.
func (m Model) placeholderView() string {
var (
Expand All @@ -928,7 +988,8 @@ func (m Model) placeholderView() string {
style = m.style.Placeholder.Inline(true)
)

prompt := m.style.Prompt.Render(m.Prompt)
prompt := m.getPromptString(0)
prompt = m.style.Prompt.Render(prompt)
s.WriteString(m.style.CursorLine.Render(prompt))

if m.ShowLineNumbers {
Expand All @@ -945,6 +1006,8 @@ func (m Model) placeholderView() string {
// The rest of the new lines
for i := 1; i < m.height; i++ {
s.WriteRune('\n')
prompt := m.getPromptString(i)
prompt = m.style.Prompt.Render(prompt)
s.WriteString(prompt)

if m.ShowLineNumbers {
Expand Down