forked from jpbruinsslot/slack-term
-
Notifications
You must be signed in to change notification settings - Fork 0
/
input.go
209 lines (173 loc) · 5.21 KB
/
input.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package components
import (
"github.com/erroneousboat/termui"
runewidth "github.com/mattn/go-runewidth"
)
// Input is the definition of an Input component
type Input struct {
Par *termui.Par
Text []rune
CursorPositionScreen int
CursorPositionText int
Offset int
}
// CreateInput is the constructor of the Input struct
func CreateInputComponent() *Input {
input := &Input{
Par: termui.NewPar(""),
Text: make([]rune, 0),
CursorPositionScreen: 0,
CursorPositionText: 0,
Offset: 0,
}
input.Par.Height = 3
return input
}
// Buffer implements interface termui.Bufferer
func (i *Input) Buffer() termui.Buffer {
buf := i.Par.Buffer()
// Set visible cursor, get char at screen cursor position
char := buf.At(i.Par.InnerX()+i.CursorPositionScreen, i.Par.Block.InnerY())
buf.Set(
i.Par.InnerX()+i.CursorPositionScreen,
i.Par.Block.InnerY(),
termui.Cell{
Ch: char.Ch,
Fg: i.Par.TextBgColor,
Bg: i.Par.TextFgColor,
},
)
return buf
}
// GetHeight implements interface termui.GridBufferer
func (i *Input) GetHeight() int {
return i.Par.Block.GetHeight()
}
// SetWidth implements interface termui.GridBufferer
func (i *Input) SetWidth(w int) {
i.Par.SetWidth(w)
}
// SetX implements interface termui.GridBufferer
func (i *Input) SetX(x int) {
i.Par.SetX(x)
}
// SetY implements interface termui.GridBufferer
func (i *Input) SetY(y int) {
i.Par.SetY(y)
}
// Insert will insert a given key at the place of the current CursorPositionText
func (i *Input) Insert(key rune) {
// Append key to the left side
left := make([]rune, len(i.Text[0:i.CursorPositionText]))
copy(left, i.Text[0:i.CursorPositionText])
left = append(left, key)
// Combine left and right side
i.Text = append(left, i.Text[i.CursorPositionText:]...)
i.MoveCursorRight()
}
// Backspace will remove a character in front of the CursorPositionText
func (i *Input) Backspace() {
if i.CursorPositionText > 0 {
i.MoveCursorLeft()
i.Text = append(i.Text[0:i.CursorPositionText], i.Text[i.CursorPositionText+1:]...)
i.Par.Text = string(i.Text[i.Offset:])
}
}
// Delete will remove a character at the CursorPositionText
func (i *Input) Delete() {
if i.CursorPositionText < len(i.Text) {
i.Text = append(i.Text[0:i.CursorPositionText], i.Text[i.CursorPositionText+1:]...)
i.Par.Text = string(i.Text[i.Offset:])
}
}
// MoveCursorRight will increase the current CursorPositionText with 1
func (i *Input) MoveCursorRight() {
if i.CursorPositionText < len(i.Text) {
i.CursorPositionText++
i.ScrollRight()
}
i.Par.Text = string(i.Text[i.Offset:])
}
// MoveCursorLeft will decrease the current CursorPositionText with 1
func (i *Input) MoveCursorLeft() {
if i.CursorPositionText > 0 {
i.CursorPositionText--
i.ScrollLeft()
}
i.Par.Text = string(i.Text[i.Offset:])
}
func (i *Input) ScrollLeft() {
// Is the cursor at the far left of the Input component?
if i.CursorPositionScreen == 0 {
// Decrease offset to show what is on the left side
if i.Offset > 0 {
i.Offset--
}
} else {
i.CursorPositionScreen -= i.GetRuneWidthRight()
}
}
func (i *Input) ScrollRight() {
// Is the cursor at the far right of the Input component, cursor
// isn't at the end of the text
if (i.CursorPositionScreen + i.GetRuneWidthLeft()) > i.Par.InnerBounds().Dx()-1 {
// Increase offset to show what is on the right side
if i.Offset < len(i.Text) {
i.Offset = i.CalculateOffset()
i.CursorPositionScreen = i.GetRuneWidthOffsetToCursor()
}
} else {
i.CursorPositionScreen += i.GetRuneWidthLeft()
}
}
// CalculateOffset will, based on the width of the runes on the
// left of the text cursor, calculate the offset that needs to
// be used by the Inpute Component
func (i *Input) CalculateOffset() int {
var offset int
var currentRuneWidth int
for j := (i.CursorPositionText - 1); currentRuneWidth < i.GetMaxWidth()-1; j-- {
currentRuneWidth += runewidth.RuneWidth(i.Text[j])
offset = j
}
return offset
}
// GetRunWidthOffsetToCursor will get the rune width of all
// the runes from the offset until the text cursor
func (i *Input) GetRuneWidthOffsetToCursor() int {
return runewidth.StringWidth(string(i.Text[i.Offset:i.CursorPositionText]))
}
// GetRuneWidthLeft will get the width of a rune on the left side
// of the CursorPositionText
func (i *Input) GetRuneWidthLeft() int {
return runewidth.RuneWidth(i.Text[i.CursorPositionText-1])
}
// GetRuneWidthLeft will get the width of a rune on the right side
// of the CursorPositionText
func (i *Input) GetRuneWidthRight() int {
return runewidth.RuneWidth(i.Text[i.CursorPositionText])
}
// IsEmpty will return true when the input is empty
func (i *Input) IsEmpty() bool {
if i.Par.Text == "" {
return true
}
return false
}
// Clear will empty the input and move the cursor to the start position
func (i *Input) Clear() {
i.Text = make([]rune, 0)
i.Par.Text = ""
i.CursorPositionScreen = 0
i.CursorPositionText = 0
i.Offset = 0
}
// GetText returns the text currently in the input
func (i *Input) GetText() string {
return string(i.Text)
}
// GetMaxWidth returns the maximum number of positions
// the Input component can display
func (i *Input) GetMaxWidth() int {
return i.Par.InnerBounds().Dx() - 1
}