okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Ayman Bagabasdocs: update README and tutorial5027f0c

upstream
3.9 KiB175 linesraw

Tutorial: Hello World

This tutorial walks through building a simple Ultraviolet application that displays centered "Hello, World!" text. You'll learn the core concepts: creating a terminal, managing a screen, handling input events, and rendering.

Creating a Terminal

A Terminal manages the console, input event loop, and screen state.

t := uv.DefaultTerminal()

You can also create a terminal with a custom console and options:

con := uv.NewConsole(os.Stdin, os.Stdout, os.Environ())
t := uv.NewTerminal(con, &uv.Options{
    Logger: myLogger, // optional, for debugging I/O
})

Getting the Screen

The terminal's screen is where you draw content, manage the alternate screen buffer, and set cells.

scr := t.Screen()

Alternate Screen

The alternate screen buffer lets your application display content without affecting the user's scrollback. Most fullscreen TUIs use it.

scr.EnterAltScreen()

Starting the Terminal

Starting puts the console into raw mode (disabling echoing and line buffering so we receive individual keypresses like <kbd>ctrl+c</kbd>), initializes the input event loop, and prepares the screen for rendering.

if err := t.Start(); err != nil {
    log.Fatalf("failed to start terminal: %v", err)
}
defer t.Stop()

Stop() restores the console, exits the alternate screen, and cleans up. It is safe to call multiple times and supports suspend/resume cycles.

Drawing Text

You can set individual cells directly:

for i, r := range "Hello, World!" {
    scr.SetCell(i, 0, &uv.Cell{Content: string(r), Width: 1})
}

Or use the screen helper package for convenience:

ctx := screen.NewContext(scr)
ctx.DrawString("Hello, World!", 0, 0)

The Context supports styled text, links, and wrapping. It implements io.Writer, so you can use fmt.Fprint and friends.

Rendering

Drawing to the screen is a two-step process:

  1. Render — computes the minimal diff between the current and new screen state, writing ANSI escape sequences to an internal buffer.
  2. Flush — writes the buffer to the terminal. This is the only step that performs real I/O and can return an error.
scr.Render()
if err := scr.Flush(); err != nil {
    log.Fatalf("flush failed: %v", err)
}

Handling Events

The terminal provides a channel of input events. Range over it to process keyboard, mouse, and resize events:

for ev := range t.Events() {
    switch ev := ev.(type) {
    case uv.WindowSizeEvent:
        scr.Resize(ev.Width, ev.Height)
    case uv.KeyPressEvent:
        if ev.MatchString("q", "ctrl+c") {
            return
        }
    }
}

MatchString accepts key names and modifier combinations like "ctrl+a", "shift+enter", or "alt+tab".

Putting It Together

Here's the complete program—centered "Hello, World!" that redraws on resize and exits on q or ctrl+c:

package main

import (
	"log"

	uv "github.com/charmbracelet/ultraviolet"
	"github.com/charmbracelet/ultraviolet/screen"
)

func main() {
	t := uv.DefaultTerminal()
	scr := t.Screen()

	scr.EnterAltScreen()

	if err := t.Start(); err != nil {
		log.Fatalf("failed to start terminal: %v", err)
	}
	defer t.Stop()

	ctx := screen.NewContext(scr)
	text := "Hello, World!"
	textWidth := scr.StringWidth(text)

	display := func() {
		screen.Clear(scr)
		bounds := scr.Bounds()
		x := (bounds.Dx() - textWidth) / 2
		y := bounds.Dy() / 2
		ctx.DrawString(text, x, y)
		scr.Render()
		scr.Flush()
	}

	for ev := range t.Events() {
		switch ev := ev.(type) {
		case uv.WindowSizeEvent:
			scr.Resize(ev.Width, ev.Height)
			display()
		case uv.KeyPressEvent:
			if ev.MatchString("q", "ctrl+c") {
				return
			}
		}
	}
}

Part of Charm.

<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>

Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة

1# Tutorial: Hello World
2
3This tutorial walks through building a simple Ultraviolet application that
4displays centered "Hello, World!" text. You'll learn the core concepts:
5creating a terminal, managing a screen, handling input events, and rendering.
6
7## Creating a Terminal
8
9A `Terminal` manages the console, input event loop, and screen state.
10
11```go
12t := uv.DefaultTerminal()
13```
14
15You can also create a terminal with a custom console and options:
16
17```go
18con := uv.NewConsole(os.Stdin, os.Stdout, os.Environ())
19t := uv.NewTerminal(con, &uv.Options{
20    Logger: myLogger, // optional, for debugging I/O
21})
22```
23
24## Getting the Screen
25
26The terminal's screen is where you draw content, manage the alternate screen
27buffer, and set cells.
28
29```go
30scr := t.Screen()
31```
32
33## Alternate Screen
34
35The alternate screen buffer lets your application display content without
36affecting the user's scrollback. Most fullscreen TUIs use it.
37
38```go
39scr.EnterAltScreen()
40```
41
42## Starting the Terminal
43
44Starting puts the console into raw mode (disabling echoing and line buffering
45so we receive individual keypresses like <kbd>ctrl+c</kbd>), initializes the
46input event loop, and prepares the screen for rendering.
47
48```go
49if err := t.Start(); err != nil {
50    log.Fatalf("failed to start terminal: %v", err)
51}
52defer t.Stop()
53```
54
55`Stop()` restores the console, exits the alternate screen, and cleans up. It
56is safe to call multiple times and supports suspend/resume cycles.
57
58## Drawing Text
59
60You can set individual cells directly:
61
62```go
63for i, r := range "Hello, World!" {
64    scr.SetCell(i, 0, &uv.Cell{Content: string(r), Width: 1})
65}
66```
67
68Or use the `screen` helper package for convenience:
69
70```go
71ctx := screen.NewContext(scr)
72ctx.DrawString("Hello, World!", 0, 0)
73```
74
75The `Context` supports styled text, links, and wrapping. It implements
76`io.Writer`, so you can use `fmt.Fprint` and friends.
77
78## Rendering
79
80Drawing to the screen is a two-step process:
81
821. **Render** — computes the minimal diff between the current and new screen
83   state, writing ANSI escape sequences to an internal buffer.
842. **Flush** — writes the buffer to the terminal. This is the only step that
85   performs real I/O and can return an error.
86
87```go
88scr.Render()
89if err := scr.Flush(); err != nil {
90    log.Fatalf("flush failed: %v", err)
91}
92```
93
94## Handling Events
95
96The terminal provides a channel of input events. Range over it to process
97keyboard, mouse, and resize events:
98
99```go
100for ev := range t.Events() {
101    switch ev := ev.(type) {
102    case uv.WindowSizeEvent:
103        scr.Resize(ev.Width, ev.Height)
104    case uv.KeyPressEvent:
105        if ev.MatchString("q", "ctrl+c") {
106            return
107        }
108    }
109}
110```
111
112`MatchString` accepts key names and modifier combinations like `"ctrl+a"`,
113`"shift+enter"`, or `"alt+tab"`.
114
115## Putting It Together
116
117Here's the complete program—centered "Hello, World!" that redraws on resize
118and exits on `q` or `ctrl+c`:
119
120```go
121package main
122
123import (
124	"log"
125
126	uv "github.com/charmbracelet/ultraviolet"
127	"github.com/charmbracelet/ultraviolet/screen"
128)
129
130func main() {
131	t := uv.DefaultTerminal()
132	scr := t.Screen()
133
134	scr.EnterAltScreen()
135
136	if err := t.Start(); err != nil {
137		log.Fatalf("failed to start terminal: %v", err)
138	}
139	defer t.Stop()
140
141	ctx := screen.NewContext(scr)
142	text := "Hello, World!"
143	textWidth := scr.StringWidth(text)
144
145	display := func() {
146		screen.Clear(scr)
147		bounds := scr.Bounds()
148		x := (bounds.Dx() - textWidth) / 2
149		y := bounds.Dy() / 2
150		ctx.DrawString(text, x, y)
151		scr.Render()
152		scr.Flush()
153	}
154
155	for ev := range t.Events() {
156		switch ev := ev.(type) {
157		case uv.WindowSizeEvent:
158			scr.Resize(ev.Width, ev.Height)
159			display()
160		case uv.KeyPressEvent:
161			if ev.MatchString("q", "ctrl+c") {
162				return
163			}
164		}
165	}
166}
167```
168
169---
170
171Part of [Charm](https://charm.sh).
172
173<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
174
175Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة