Skip to content

Commit

Permalink
Merge pull request #27 from knz/20230422-maxsize
Browse files Browse the repository at this point in the history
Make height/width limits configurable
  • Loading branch information
knz authored Apr 22, 2023
2 parents 1378db0 + e2a6289 commit e176cdf
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 45 deletions.
10 changes: 10 additions & 0 deletions editline/editline.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ type Model struct {
// Set to zero or less for no limit.
CharLimit int

// MaxHeight is the maximum height of the input in lines.
// Set to zero or less for no limit.
MaxHeight int

// MaxWidth is the maximum width of the input in characters.
// Set to zero or less for no limit.
MaxWidth int

// CursorMode determines how the cursor is displayed.
CursorMode cursor.Mode

Expand Down Expand Up @@ -1023,6 +1031,8 @@ func (m *Model) Reset() {
m.hctrl.c.prevValue = ""
m.hctrl.c.prevCursor = 0
m.text.CharLimit = m.CharLimit
m.text.MaxHeight = m.MaxHeight
m.text.MaxWidth = m.MaxWidth
// Width will be set by Update below on init.
m.text.SetHeight(1)
m.completions.SetHeight(1)
Expand Down
4 changes: 4 additions & 0 deletions editline/getline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ func testCmd(m tea.Model, cmd string, args ...string) (bool, tea.Model, tea.Cmd,
t.CursorMode = cursor.CursorBlink
case "hide_cursor":
t.CursorMode = cursor.CursorHide
case "limit_max_width":
t.MaxWidth = 10
case "limit_max_height":
t.MaxHeight = 3
default:
return false, t, nil, nil
}
Expand Down
67 changes: 49 additions & 18 deletions editline/internal/textarea/textarea.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const (
defaultHeight = 6
defaultWidth = 40
defaultCharLimit = 400
maxHeight = 99
maxWidth = 500
defaultMaxHeight = 99
defaultMaxWidth = 500
)

// Internal messages for clipboard operations.
Expand Down Expand Up @@ -182,6 +182,14 @@ type Model struct {
// accept. If 0 or less, there's no limit.
CharLimit int

// MaxHeight is the maximum height of the text area in rows. If 0 or less,
// there's no limit.
MaxHeight int

// MaxWidth is the maximum width of the text area in columns. If 0 or less,
// there's no limit.
MaxWidth 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
Expand Down Expand Up @@ -239,6 +247,8 @@ func New() Model {

m := Model{
CharLimit: defaultCharLimit,
MaxHeight: defaultMaxHeight,
MaxWidth: defaultMaxWidth,
Prompt: lipgloss.ThickBorder().Left + " ",
style: &blurredStyle,
FocusedStyle: focusedStyle,
Expand All @@ -248,7 +258,7 @@ func New() Model {
Cursor: cur,
KeyMap: DefaultKeyMap,

value: make([][]rune, minHeight, maxHeight),
value: make([][]rune, minHeight, defaultMaxHeight),
focus: false,
col: 0,
row: 0,
Expand Down Expand Up @@ -332,6 +342,10 @@ func (m *Model) insertRunesFromUserInput(runes []rune) {
lstart := 0
for i := 0; i < len(runes); i++ {
if runes[i] == '\n' {
// Queue a line to become a new row in the text area below.
// Beware to clamp the max capacity of the slice, to ensure no
// data from different rows get overwritten when later edits
// will modify this line.
lines = append(lines, runes[lstart:i:i])
lstart = i + 1
}
Expand All @@ -343,8 +357,8 @@ func (m *Model) insertRunesFromUserInput(runes []rune) {
}

// Obey the maximum height limit.
if len(m.value)+len(lines)-1 > maxHeight {
allowedHeight := max(0, maxHeight-len(m.value)+1)
if m.MaxHeight > 0 && len(m.value)+len(lines)-1 > m.MaxHeight {
allowedHeight := max(0, m.MaxHeight-len(m.value)+1)
lines = lines[:allowedHeight]
}

Expand All @@ -353,7 +367,7 @@ func (m *Model) insertRunesFromUserInput(runes []rune) {
return
}

// Save the reminder of the original line at the current
// Save the remainder of the original line at the current
// cursor position.
tail := make([]rune, len(m.value[m.row][m.col:]))
copy(tail, m.value[m.row][m.col:])
Expand Down Expand Up @@ -450,7 +464,7 @@ func (m *Model) CursorDown() {
m.row++
m.col = 0
} else {
// Move the cursor to the start of the next line. So that we can get
// Move the cursor to the start of the next line so that we can get
// the line information. We need to add 2 columns to account for the
// trailing space wrapping.
m.col = min(li.StartColumn+li.Width+2, len(m.value[m.row])-1)
Expand Down Expand Up @@ -539,7 +553,7 @@ func (m *Model) Focus() tea.Cmd {
return m.Cursor.Focus()
}

// Blur removes the focus state on the model. When the model is blurred it can
// Blur removes the focus state on the model. When the model is blurred it can
// not receive keyboard input and the cursor will be hidden.
func (m *Model) Blur() {
m.focus = false
Expand All @@ -549,14 +563,18 @@ func (m *Model) Blur() {

// Reset sets the input to its default state with no input.
func (m *Model) Reset() {
m.value = make([][]rune, minHeight, maxHeight)
startCap := m.MaxHeight
if startCap <= 0 {
startCap = defaultMaxHeight
}
m.value = make([][]rune, minHeight, startCap)
m.col = 0
m.row = 0
m.viewport.GotoTop()
m.SetCursor(0)
}

// rsan initializes or retrieves the rune sanitizer.
// san initializes or retrieves the rune sanitizer.
func (m *Model) san() runeutil.Sanitizer {
if m.rsan == nil {
// Textinput has all its input on a single line so collapse
Expand Down Expand Up @@ -852,12 +870,16 @@ func (m *Model) moveToEnd() {
// whether or not line numbers are being shown.
//
// Ensure that SetWidth is called after setting the Prompt and ShowLineNumbers,
// If it important that the width of the textarea be exactly the given width
// It is important that the width of the textarea be exactly the given width
// and no more.
func (m *Model) SetWidth(w int) {
m.viewport.Width = clamp(w, minWidth, maxWidth)
if m.MaxWidth > 0 {
m.viewport.Width = clamp(w, minWidth, m.MaxWidth)
} else {
m.viewport.Width = max(w, minWidth)
}

// Since the width of the textarea input is dependant on the width of the
// Since the width of the textarea input is dependent on the width of the
// prompt and line numbers, we need to calculate it by subtracting.
inputWidth := w
if m.ShowLineNumbers {
Expand All @@ -872,7 +894,11 @@ func (m *Model) SetWidth(w int) {
}

inputWidth -= m.promptWidth
m.width = clamp(inputWidth, minWidth, maxWidth)
if m.MaxWidth > 0 {
m.width = clamp(inputWidth, minWidth, m.MaxWidth)
} else {
m.width = max(inputWidth, minWidth)
}
}

// SetPromptFunc supersedes the Prompt field and sets a dynamic prompt
Expand All @@ -894,13 +920,18 @@ func (m Model) Height() int {

// SetHeight sets the height of the textarea.
func (m *Model) SetHeight(h int) {
m.height = clamp(h, minHeight, maxHeight)
m.viewport.Height = clamp(h, minHeight, maxHeight)
if m.MaxHeight > 0 {
m.height = clamp(h, minHeight, m.MaxHeight)
m.viewport.Height = clamp(h, minHeight, m.MaxHeight)
} else {
m.height = max(h, minHeight)
m.viewport.Height = max(h, minHeight)
}
}

// InsertNewline inserts a newline character at the cursor.
func (m *Model) InsertNewline() {
if len(m.value) >= maxHeight {
if m.MaxHeight > 0 && len(m.value) >= m.MaxHeight {
return
}
m.col = clamp(m.col, 0, len(m.value[m.row]))
Expand Down Expand Up @@ -1223,7 +1254,7 @@ func (m Model) cursorLineNumber() int {
return line
}

// mergeLineBelow merges the current line with the line below.
// mergeLineBelow merges the current line the cursor is on with the line below.
func (m *Model) mergeLineBelow(row int) {
if row >= len(m.value)-1 {
return
Expand Down
40 changes: 13 additions & 27 deletions editline/internal/textarea/textarea.go.diff
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--- textarea.go.orig 2023-01-24 19:21:36.201018000 +0100
+++ textarea.go 2023-02-05 12:13:30.385554000 +0100
--- textarea.go.orig 2023-04-22 18:36:07.734330000 +0200
+++ textarea.go 2023-04-22 18:38:02.341039000 +0200
@@ -1,3 +1,9 @@
+// The code below is imported from
+// https://github.com/charmbracelet/bubbles/tree/master/textarea
Expand Down Expand Up @@ -80,7 +80,7 @@
}

// LineInfo is a helper for keeping track of line information regarding
@@ -197,6 +205,9 @@
@@ -205,6 +213,9 @@
// component. When false, ignore keyboard input and hide the cursor.
focus bool

Expand All @@ -90,21 +90,7 @@
// Cursor column.
col int

@@ -321,11 +332,11 @@
lstart := 0
for i := 0; i < len(runes); i++ {
if runes[i] == '\n' {
- lines = append(lines, runes[lstart:i])
+ lines = append(lines, runes[lstart:i:i])
lstart = i + 1
}
}
- if lstart < len(runes) {
+ if lstart <= len(runes) {
// The last line did not end with a newline character.
// Take it now.
lines = append(lines, runes[lstart:])
@@ -381,6 +392,18 @@
@@ -395,6 +406,18 @@
m.SetCursor(m.col)
}

Expand All @@ -123,7 +109,7 @@
// Value returns the value of the text input.
func (m Model) Value() string {
if m.value == nil {
@@ -750,14 +773,20 @@
@@ -768,14 +791,20 @@
// LineInfo returns the number of characters from the start of the
// (soft-wrapped) line and the (soft-wrapped) line width.
func (m Model) LineInfo() LineInfo {
Expand All @@ -146,7 +132,7 @@
// We wrap around to the next line if we are at the end of the
// previous line so that we can be at the very beginning of the row
return LineInfo{
@@ -765,16 +794,16 @@
@@ -783,16 +812,16 @@
ColumnOffset: 0,
Height: len(grid),
RowOffset: i + 1,
Expand All @@ -167,13 +153,13 @@
Height: len(grid),
RowOffset: i,
StartColumn: counter,
@@ -869,6 +898,48 @@
m.viewport.Height = clamp(h, minHeight, maxHeight)
@@ -900,6 +929,48 @@
}
}

+// InsertNewline inserts a newline character at the cursor.
+func (m *Model) InsertNewline() {
+ if len(m.value) >= maxHeight {
+ if m.MaxHeight > 0 && len(m.value) >= m.MaxHeight {
+ return
+ }
+ m.col = clamp(m.col, 0, len(m.value[m.row]))
Expand Down Expand Up @@ -216,7 +202,7 @@
// Update is the Bubble Tea update loop.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
if !m.focus {
@@ -903,25 +974,9 @@
@@ -934,25 +1005,9 @@
}
m.deleteBeforeCursor()
case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
Expand Down Expand Up @@ -244,11 +230,11 @@
case key.Matches(msg, m.KeyMap.DeleteWordBackward):
if m.col <= 0 {
m.mergeLineAbove(m.row)
@@ -936,11 +991,7 @@
@@ -967,11 +1022,7 @@
}
m.deleteWordRight()
case key.Matches(msg, m.KeyMap.InsertNewline):
- if len(m.value) >= maxHeight {
- if m.MaxHeight > 0 && len(m.value) >= m.MaxHeight {
- return m, nil
- }
- m.col = clamp(m.col, 0, len(m.value[m.row]))
Expand All @@ -257,7 +243,7 @@
case key.Matches(msg, m.KeyMap.LineEnd):
m.CursorEnd()
case key.Matches(msg, m.KeyMap.LineStart):
@@ -971,9 +1022,18 @@
@@ -1002,9 +1053,18 @@
m.capitalizeRight()
case key.Matches(msg, m.KeyMap.TransposeCharacterBackward):
m.transposeLeft()
Expand Down
31 changes: 31 additions & 0 deletions editline/testdata/max_height
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
run
limit_max_height
reset
resize 40 25
----
TEA WINDOW SIZE: {40 25}
-- view:
>   ␤
M-? toggle key help • C-d erase/stop🛇

run observe=(view,value)
paste "some text\nmore lines\nagain lines\nfinal line"
----
-- view:
> some text ␤
more lines ␤
again lines  ␤
M-? toggle key help • C-d erase/stop🛇
-- value:
"some text\nmore lines\nagain lines"

run observe=(view,value)
enter some more text
----
-- view:
> some text ␤
more lines ␤
again linessome more text ␤
M-? toggle key help • C-d erase/stop🛇
-- value:
"some text\nmore lines\nagain linessome more text"
24 changes: 24 additions & 0 deletions editline/testdata/max_width
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
run
limit_max_width
reset
resize 40 25
----
TEA WINDOW SIZE: {40 25}
-- view:
>   ␤
M-? toggle key help • C-d erase/stop🛇

run observe=(view,value)
paste "some text\nmore lines\nagain lines\nfinal line"
----
-- view:
> some tex␤
more ␤
lines ␤
again ␤
lines ␤
final ␤
line  ␤
M-? toggle key help • C-d erase/stop🛇
-- value:
"some text\nmore lines\nagain lines\nfinal line"

0 comments on commit e176cdf

Please sign in to comment.