1# Hello World!
2
3This is a simple tutorial on how to create a UV application that displays
4"Hello, World!" on the screen. UV is a terminal UI toolkit that allows you to
5create terminal applications with ease. It provides a simple API to handle
6screen management, input handling, and rendering content.
7
8## Tutorial
9
10What does UV consist of? A UV application consists of a screen that displays
11content, has some sort of input sources that can be used to interact with the
12screen, and is meant to display content or programs on the screen.
13
14First, we need to create a screen that will display our program. UV comes with
15a `Terminal` screen that is used to display content on a terminal.
16
17```go
18t := uv.NewTerminal(os.Stdin, os.Stdout, os.Environ())
19// Or simply use...
20// t := uv.DefaultTerminal()
21```
22
23A terminal screen has a few properties that are unique to it. For example, a
24terminal screen can go into raw mode, which is important to disable echoing of
25input characters, and to disable signal handling so that we can receive things
26like <kbd>ctrl+c</kbd> without the terminal interfering with our program.
27
28Another important property of a terminal screen is the alternate screen buffer.
29This property puts the terminal screen into a special mode that allows us to
30display content without interfering with the normal screen buffer.
31
32In this tutorial, we will use the alternate screen buffer to display our
33program so that we don't affect the normal screen buffer.
34
35```go
36// Set the terminal to raw mode.
37if err := t.MakeRaw(); err != nil {
38 log.Fatal(err)
39}
40
41// Enter the alternate screen buffer. This will
42// only take affect once we flush or display
43// our program on the terminal screen.
44t.EnterAltScreen()
45
46// My program
47// ...
48
49// Make sure we leave the alternate screen buffer when we
50// are done with our program. This will be called automatically
51// when we use [t.Shutdown(ctx)] later.
52t.LeaveAltScreen()
53
54// Make sure we restore the terminal to its original state
55// before we exit. We don't care about errors here, but you
56// can handle them if you want. This will be called automatically
57// when we use [t.Shutdown(ctx)] later.
58_ = t.Restore() //nolint:errcheck
59```
60
61Now that we have our screen set to raw mode and in the alternate screen buffer,
62we can create our program that will be displayed on the screen. A program is an
63abstraction layer that handles different screen types and implementations. It
64only cares about displaying content on the screen.
65
66We need to start our program before we can display anything on the screen. This
67will ensure that the program and screen are initialized and ready to display
68content. Internally, this will also call `t.Start()` to start the terminal
69screen.
70
71```go
72if err := t.Start(); err != nil {
73 log.Fatalf("failed to start program: %v", err)
74}
75```
76
77Let's display a simple frame with some text in it. A frame is a container that
78holds the buffer we're displaying. The final cursor position we want our cursor
79to be at, and the viewport area we are working with to display our content.
80
81```go
82for i, r := range "Hello, World!" {
83 // We iterate over the string to display each character
84 // in a separate cell. Ideally, we want each cell
85 // to have exactly one grapheme. In this case, since
86 // we're using a simple ASCII string, we know that
87 // each character is a single grapheme with a width of 1.
88 var c uv.Cell
89 c.Content = string(r)
90 c.Width = 1
91 t.SetCell(i, 0, &c)
92}
93// Now we simply render the changes and flush them
94// to the terminal screen.
95_ = p.Display()
96```
97
98Different screen models have different ways to receive input. Some models have
99a remote control, while others have a touch screen. A terminal can receive
100input from various peripherals usually through control codes and escape
101sequences. Our terminal has a `t.Events(ctx)` method that returns a channel
102which will receive events from different terminal input sources.
103
104```go
105// We want to be able to stop the terminal input loop
106// whenever we call cancel().
107ctx, cancel := context.WithCancel(context.Background())
108defer cancel()
109
110for ev := range t.Events(ctx) {
111 switch ev := ev.(type) {
112 case uv.WindowSizeEvent:
113 // Our terminal screen is resizable. This is important
114 // as we want to inform our terminal screen with the
115 // size we'd like to display our program in.
116 // When we're using the full terminal window size,
117 // we can assume that the terminal screen will
118 // also have the same size as our program.
119 // However, with inline programs, usually we want
120 // the height to be the height of our program.
121 // So if our inline program takes 10 lines, we
122 // want to resize the terminal screen to 10 lines
123 // high.
124 width, height := ev.Width, ev.Height
125 if !altscreen {
126 height = 10
127 }
128 t.Resize(width, height)
129 case uv.KeyPressEvent:
130 if ev.MatchStrings("q", "ctrl+c") {
131 // This will stop the input loop and cancel the context.
132 cancel()
133 }
134 }
135}
136```
137
138Now that we've handled displaying our program and receiving input from the
139terminal, we need to handle the program's lifecycle. We need to make sure that
140we restore the terminal to its original state when we exit the program. A
141terminal program can be stopped gracefully using the `t.Shutdown(ctx)` method.
142
143```go
144// We need to make sure we stop the program gracefully
145// after we exit the input loop.
146if err := t.Shutdown(ctx); err != nil {
147 log.Fatal(err)
148}
149```
150
151Finally, let's put everything together and create a simple program that displays
152a frame with "Hello, World!" in it. The program will exit when we press
153<kbd>ctrl+c</kbd> or <kbd>q</kbd>.
154
155```go
156func main() {
157 t := uv.NewTerminal(os.Stdin, os.Stdout, os.Environ())
158
159 if err := t.MakeRaw(); err != nil {
160 log.Fatalf("failed to make terminal raw: %v", err)
161 }
162
163 if err := t.Start(); err != nil {
164 log.Fatalf("failed to start program: %v", err)
165 }
166
167 t.EnterAltScreen()
168
169 ctx, cancel := context.WithCancel(context.Background())
170 defer cancel()
171
172 for ev := range t.Events(ctx) {
173 switch ev := ev.(type) {
174 case uv.WindowSizeEvent:
175 width, height := ev.Width, ev.Height
176 t.Erase()
177 t.Resize(width, height)
178 case uv.KeyPressEvent:
179 if ev.MatchStrings("q", "ctrl+c") {
180 cancel()
181 }
182 }
183
184 for i, r := range "Hello, World!" {
185 var c uv.Cell
186 c.Content = string(r)
187 c.Width = 1
188 t.SetCell(i, 0, &c)
189 }
190 if err := t.Display(); err != nil {
191 log.Fatal(err)
192 }
193 }
194
195 if err := t.Shutdown(ctx); err != nil {
196 log.Fatal(err)
197 }
198}
199```
200
201---
202
203Part of [Charm](https://charm.sh).
204
205<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
206
207Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة