Skip to content

Commit 48dffdd

Browse files
mikelorantmaaslalani
authored andcommitted
refactor(textarea): Improve setting width
When setting the width of the textarea there were some issues preventing this from working correctly. These problems included: - If the maximum width needed to be used, the width of the textarea did not take into account the prompt and line number width. - The viewport width did not take into account the style width. The entire function was confusing to understand and a refactor was warranted. As part of this refactor, the bugs mentioned above were fixed and the code was simplified. To verify that the logic works as expected, unit tests were expanded to validate that setting the width works as expected. Signed-off-by: Michael Lorant <[email protected]>
1 parent 9030d22 commit 48dffdd

File tree

2 files changed

+628
-20
lines changed

2 files changed

+628
-20
lines changed

textarea/textarea.go

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020

2121
const (
2222
minHeight = 1
23-
minWidth = 2
2423
defaultHeight = 6
2524
defaultWidth = 40
2625
defaultCharLimit = 400
@@ -862,32 +861,40 @@ func (m *Model) moveToEnd() {
862861
// It is important that the width of the textarea be exactly the given width
863862
// and no more.
864863
func (m *Model) SetWidth(w int) {
865-
if m.MaxWidth > 0 {
866-
m.viewport.Width = clamp(w, minWidth, m.MaxWidth)
867-
} else {
868-
m.viewport.Width = max(w, minWidth)
864+
// Update prompt width only if there is no prompt function as SetPromptFunc
865+
// updates the prompt width when it is called.
866+
if m.promptFunc == nil {
867+
m.promptWidth = uniseg.StringWidth(m.Prompt)
869868
}
870869

871-
// Since the width of the textarea input is dependent on the width of the
872-
// prompt and line numbers, we need to calculate it by subtracting.
873-
inputWidth := w
874-
if m.ShowLineNumbers {
875-
inputWidth -= uniseg.StringWidth(fmt.Sprintf(m.lineNumberFormat, 0))
876-
}
870+
// Add base style borders and padding to reserved outer width.
871+
reservedOuter := m.style.Base.GetHorizontalFrameSize()
877872

878-
// Account for base style borders and padding.
879-
inputWidth -= m.style.Base.GetHorizontalFrameSize()
873+
// Add prompt width to reserved inner width.
874+
reservedInner := m.promptWidth
880875

881-
if m.promptFunc == nil {
882-
m.promptWidth = uniseg.StringWidth(m.Prompt)
876+
// Add line number width to reserved inner width.
877+
if m.ShowLineNumbers {
878+
const lnWidth = 4 // Up to 3 digits for line number plus 1 margin.
879+
reservedInner += lnWidth
883880
}
884881

885-
inputWidth -= m.promptWidth
882+
// Input width must be at least one more than the reserved inner and outer
883+
// width. This gives us a minimum input width of 1.
884+
minWidth := reservedInner + reservedOuter + 1
885+
inputWidth := max(w, minWidth)
886+
887+
// Input width must be no more than maximum width.
886888
if m.MaxWidth > 0 {
887-
m.width = clamp(inputWidth, minWidth, m.MaxWidth)
888-
} else {
889-
m.width = max(inputWidth, minWidth)
889+
inputWidth = min(inputWidth, m.MaxWidth)
890890
}
891+
892+
// Since the width of the viewport and input area is dependent on the width of
893+
// borders, prompt and line numbers, we need to calculate it by subtracting
894+
// the reserved width from them.
895+
896+
m.viewport.Width = inputWidth - reservedOuter
897+
m.width = inputWidth - reservedOuter - reservedInner
891898
}
892899

893900
// SetPromptFunc supersedes the Prompt field and sets a dynamic prompt
@@ -1163,7 +1170,7 @@ func (m Model) getPromptString(displayLine int) (prompt string) {
11631170
func (m Model) placeholderView() string {
11641171
var (
11651172
s strings.Builder
1166-
p = rw.Truncate(m.Placeholder, m.width, "...")
1173+
p = rw.Truncate(rw.Truncate(m.Placeholder, m.width, "..."), m.width, "")
11671174
style = m.style.Placeholder.Inline(true)
11681175
)
11691176

0 commit comments

Comments
 (0)