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
|
package main
import (
"fmt"
"github.com/charmbracelet/bubbletea"
)
// UTF-8 box drawing characters.
const (
boxHorizontal = '─'
boxVertical = '│'
boxTopLeft = '┌'
boxTopRight = '┐'
boxBottomLeft = '└'
boxBottomRight = '┘'
)
// sigDigs is the number of significant digits when printing a number.
const sigDigs = 17
// maxWidth is the maximum width that the window will grow to.
const maxWidth = 32
type UI struct {
calc Calculator
width int // Width of the window measured in characters.
height int
}
func (ui UI) Init() tea.Cmd {
return nil
}
func (ui UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
ui.width = msg.Width
ui.height = msg.Height
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "Q":
return ui, tea.Quit
case "J", "K":
ui.calc.swap()
case "D":
ui.calc.buf = ""
case "C":
ui.calc.buf = ""
ui.calc.stack = ui.calc.stack[:0]
case "A":
ui.calc.angleMode = !ui.calc.angleMode
case "N":
ui.calc.negate()
case "+", "-", "*", "/", "%", "^":
operator := msg.String()[0]
if err := ui.calc.performOperation(operator); err != nil {
panic(err)
}
case "backspace":
if len(ui.calc.buf) > 0 {
ui.calc.buf = ui.calc.buf[:len(ui.calc.buf)-1]
}
case "enter":
if fn := parseFunction(ui.calc.buf); fn != nil {
fn(ui.calc)
} else if v, err := ui.calc.parseBuffer(); err == nil {
ui.calc.stack.push(v)
}
ui.calc.buf = ""
default:
ui.calc.buf += msg.String()
}
}
return ui, nil
}
func (ui UI) View() string {
s := padding(ui)
width := min(ui.width, maxWidth)
// Angle mode.
s += fmt.Sprintf("%*s\n", width-1, ui.calc.angleMode)
// Stack.
top := boxTop(width)
bottom := boxBottom(width)
s += top + "\n"
for _, f := range ui.calc.stack {
s += fmt.Sprintf("%[1]c%[2]*.[2]*s%[1]c\n", boxVertical, width-2, printNum(f))
}
s += bottom + "\n"
// Buffer.
s += top + "\n"
s += fmt.Sprintf("%[1]c>%[2]*.[2]*s%[1]c\n", boxVertical, width-3, ui.calc.buf)
s += bottom
return s
}
func padding(ui UI) string {
var ( // Number of lines occupied by each ui element.
angleMode = 1
stack = len(ui.calc.stack) + 2
buf = 3
)
padlines := ui.height - angleMode - stack - buf
if padlines < 1 {
return ""
}
s := make([]byte, padlines)
for i := 0; i < padlines; i++ {
s[i] = '\n'
}
return string(s)
}
func printNum(v float64) string {
return fmt.Sprintf("%.*g", sigDigs, v)
}
// boxTop returns the top of a UTF-8 box, 'width' characters wide (including
// corners).
func boxTop(width int) string {
if width < 1 {
return ""
}
row := make([]rune, width)
row[0] = boxTopLeft
row[width-1] = boxTopRight
if width > 1 {
fill(row[1:width-1], boxHorizontal)
}
return string(row)
}
// boxBottom returns the botom of a UTF-8 box, 'width' characters wide
// (including corners).
func boxBottom(width int) string {
if width < 1 {
return ""
}
row := make([]rune, width)
row[0] = boxBottomLeft
row[width-1] = boxBottomRight
if width > 1 {
fill(row[1:width-1], boxHorizontal)
}
return string(row)
}
// fill fills s with c.
func fill(s []rune, c rune) {
for i := range s {
s[i] = c
}
}
// min returns the lesser of x or y.
func min(x, y int) int {
if x < y {
return x
}
return y
}
|