okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
5027f0c
main
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:
- Render — computes the minimal diff between the current and new screen state, writing ANSI escape sequences to an internal buffer.
- 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 mainimport ("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 { 20Logger : 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 { 50log . 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!" { 64scr . 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 83state, writing ANSI escape sequences to an internal buffer. 842. **Flush** — writes the buffer to the terminal. This is the only step that 85performs real I/O and can return an error. 86 87``` go 88scr. Render () 89if err := scr . Flush (); err != nil { 90log . 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 () { 101switch ev := ev .( type ) { 102case uv. WindowSizeEvent : 103scr . Resize ( ev . Width , ev . Height ) 104case uv. KeyPressEvent : 105if ev . MatchString ( "q" , "ctrl+c" ) { 106return 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 126uv "github.com/charmbracelet/ultraviolet" 127"github.com/charmbracelet/ultraviolet/screen" 128) 129 130func main () { 131t := uv . DefaultTerminal () 132scr := t . Screen () 133 134scr . EnterAltScreen () 135 136if err := t . Start (); err != nil { 137log . Fatalf ( "failed to start terminal: %v" , err ) 138} 139defer t . Stop () 140 141ctx := screen . NewContext ( scr ) 142text := "Hello, World!" 143textWidth := scr . StringWidth ( text ) 144 145display := func () { 146screen . Clear ( scr ) 147bounds := scr . Bounds () 148x := ( bounds . Dx () - textWidth ) / 2 149y := bounds . Dy () / 2 150ctx . DrawString ( text , x , y ) 151scr . Render () 152scr . Flush () 153} 154 155for ev := range t . Events () { 156switch ev := ev .( type ) { 157case uv. WindowSizeEvent : 158scr . Resize ( ev . Width , ev . Height ) 159display () 160case uv. KeyPressEvent : 161if ev . MatchString ( "q" , "ctrl+c" ) { 162return 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 • نحنُ نحب المصادر المفتوحة