summaryrefslogtreecommitdiff
path: root/vendor/github.com/buger/goterm/box.go
blob: 4a119c552728f9e5b14a5cea517e81070fab3b92 (plain)
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
package goterm

import (
	"bytes"
	"regexp"
	"strings"
	_ "unicode/utf8"
)

const DEFAULT_BORDER = "- │ ┌ ┐ └ ┘"

// Box allows you to create independent parts of screen, with its own buffer and borders.
// Can be used for creating modal windows
//
// Generates boxes likes this:
// ┌--------┐
// │hello   │
// │world   │
// │        │
// └--------┘
//
type Box struct {
	Buf *bytes.Buffer

	Width  int
	Height int

	// To get even padding: PaddingX ~= PaddingY*4
	PaddingX int
	PaddingY int

	// Should contain 6 border pieces separated by spaces
	//
	// Example border:
	//   "- │ ┌ ┐ └ ┘"
	Border string

	Flags int // Not used now
}

// Create new Box.
// Width and height can be relative:
//
//    // Create box with 50% with of current screen and 10 lines height
//    box := tm.NewBox(50|tm.PCT, 10, 0)
//
func NewBox(width, height int, flags int) *Box {
	width, height = GetXY(width, height)

	box := new(Box)
	box.Buf = new(bytes.Buffer)
	box.Width = width
	box.Height = height
	box.Border = DEFAULT_BORDER
	box.PaddingX = 1
	box.PaddingY = 0
	box.Flags = flags

	return box
}

func (b *Box) Write(p []byte) (int, error) {
	return b.Buf.Write(p)
}

var ANSI_RE = regexp.MustCompile(`\\0\d+\[\d+(?:;\d+)?m`)

// String renders Box
func (b *Box) String() (out string) {
	borders := strings.Split(b.Border, " ")
	lines := strings.Split(b.Buf.String(), "\n")

	// Border + padding
	prefix := borders[1] + strings.Repeat(" ", b.PaddingX)
	suffix := strings.Repeat(" ", b.PaddingX) + borders[1]

	offset := b.PaddingY + 1 // 1 is border width

	// Content width without borders and padding
	contentWidth := b.Width - (b.PaddingX+1)*2
	for y := 0; y < b.Height; y++ {
		var line string

		switch {
		// Draw borders for first line
		case y == 0:
			line = borders[2] + strings.Repeat(borders[0], b.Width-2) + borders[3]

		// Draw borders for last line
		case y == (b.Height - 1):
			line = borders[4] + strings.Repeat(borders[0], b.Width-2) + borders[5]

		// Draw top and bottom padding
		case y <= b.PaddingY || y >= (b.Height-b.PaddingY):
			line = borders[1] + strings.Repeat(" ", b.Width-2) + borders[1]

		// Render content
		default:
			if len(lines) > y-offset {
				line = lines[y-offset]
			} else {
				line = ""
			}

			r := []rune(line)

			lastAnsii := ""
			withoutAnsii := []rune{}
			withOffset := []rune{}
			i := 0

			for {
				if i >= len(r) {
					break
				}

				if r[i] == 27 {
					lastAnsii = ""
					withOffset = append(withOffset, r[i])
					lastAnsii += string(r[i])
					i++
					for {

						i++
						if i > len(r) {
							break
						}

						withOffset = append(withOffset, r[i])
						lastAnsii += string(r[i])

						if r[i] == 'm' {
							i++
							break
						}
					}
				}

				if i >= len(r) {
					break
				}

				withoutAnsii = append(withoutAnsii, r[i])

				if len(withoutAnsii) <= contentWidth {
					withOffset = append(withOffset, r[i])
				}

				i++
			}

			if len(withoutAnsii) > contentWidth {
				// If line is too large limit it
				line = string(withOffset)
			} else {
				// If line is too small enlarge it by adding spaces
				line += strings.Repeat(" ", contentWidth-len(withoutAnsii))
			}

			if lastAnsii != "" {
				line += RESET
			}

			line = prefix + line + suffix
		}

		// Don't add newline for last element
		if y != b.Height-1 {
			line += "\n"
		}

		out += line
	}

	return out
}