mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-08-17 01:40:36 +03:00
docs: renderer documentation (#175)
* docs(readme): add some context to the examples * docs(readme): revert to render-function-based initial example * docs(readme): drop extraneous stringer usage * docs(readme): copyedits * docs(renderer): minor documentation improvements * docs(readme): edit renderer section to focus on custom outputs * docs(readme): for now just use SetString to illustrate stringer * docs(readme): re-add Ayman's clever stringer example * docs(examples): tidy up wish example * docs(examples): improve wish example * docs(examples): session is an io.Writer Co-authored-by: Ayman Bagabas <ayman.bagabas@gmail.com> * docs(examples): add missing pty argument Co-authored-by: Ayman Bagabas <ayman.bagabas@gmail.com> * docs(example): remove extra space * fix(examples): use termenv output --------- Co-authored-by: Ayman Bagabas <ayman.bagabas@gmail.com>
This commit is contained in:
parent
b3440ac41f
commit
19ca9a3f8c
36
README.md
36
README.md
@ -20,7 +20,6 @@ Users familiar with CSS will feel at home with Lip Gloss.
|
||||
import "github.com/charmbracelet/lipgloss"
|
||||
|
||||
var style = lipgloss.NewStyle().
|
||||
SetString("Hello, kitty.").
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#FAFAFA")).
|
||||
Background(lipgloss.Color("#7D56F4")).
|
||||
@ -28,7 +27,7 @@ var style = lipgloss.NewStyle().
|
||||
PaddingLeft(4).
|
||||
Width(22)
|
||||
|
||||
fmt.Println(style)
|
||||
fmt.Println(style.Render("Hello, kitty"))
|
||||
```
|
||||
|
||||
## Colors
|
||||
@ -300,7 +299,7 @@ someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda")
|
||||
Generally, you just call the `Render(string...)` method on a `lipgloss.Style`:
|
||||
|
||||
```go
|
||||
style := lipgloss.NewStyle(lipgloss.WithString("Hello,")).Bold(true)
|
||||
style := lipgloss.NewStyle().Bold(true).SetString("Hello,")
|
||||
fmt.Println(style.Render("kitty.")) // Hello, kitty.
|
||||
fmt.Println(style.Render("puppy.")) // Hello, puppy.
|
||||
```
|
||||
@ -308,29 +307,32 @@ fmt.Println(style.Render("puppy.")) // Hello, puppy.
|
||||
But you could also use the Stringer interface:
|
||||
|
||||
```go
|
||||
var style = lipgloss.NewStyle(lipgloss.WithString("你好,猫咪。")).Bold(true)
|
||||
|
||||
fmt.Println(style)
|
||||
var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true)
|
||||
fmt.Println(style) // 你好,猫咪。
|
||||
```
|
||||
|
||||
### Custom Renderers
|
||||
|
||||
Use custom renderers to enforce rendering your styles in a specific way. You can
|
||||
specify the color profile to use, True Color, ANSI 256, 8-bit ANSI, or good ol'
|
||||
ASCII. You can also specify whether or not to assume dark background colors.
|
||||
Custom renderers allow you to render to a specific outputs. This is
|
||||
particularly important when you want to render to different outputs and
|
||||
correctly detect the color profile and dark background status for each, such as
|
||||
in a server-client situation.
|
||||
|
||||
```go
|
||||
renderer := lipgloss.NewRenderer(
|
||||
lipgloss.WithColorProfile(termenv.ANSI256),
|
||||
lipgloss.WithDarkBackground(true),
|
||||
)
|
||||
func myLittleHandler(sess ssh.Session) {
|
||||
// Create a renderer for the client.
|
||||
renderer := lipgloss.NewRenderer(sess)
|
||||
|
||||
var style = renderer.NewStyle().Background(lipgloss.AdaptiveColor{Light: "63", Dark: "228"})
|
||||
fmt.Println(style.Render("Lip Gloss")) // This will always use the dark background color
|
||||
// Create a new style on the renderer.
|
||||
style := renderer.NewStyle().Background(lipgloss.AdaptiveColor{Light: "63", Dark: "228"})
|
||||
|
||||
// Render. The color profile and dark background state will be correctly detected.
|
||||
io.WriteString(sess, style.Render("Heyyyyyyy"))
|
||||
}
|
||||
```
|
||||
|
||||
This is also useful when using lipgloss with an SSH server like [Wish][wish].
|
||||
See the [ssh example][ssh-example] for more details.
|
||||
For an example on using a custom renderer over SSH with [Wish][wish] see the
|
||||
[SSH example][ssh-example].
|
||||
|
||||
## Utilities
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package main
|
||||
|
||||
// This example demonstrates various Lip Gloss style and layout features.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
@ -1,5 +1,14 @@
|
||||
package main
|
||||
|
||||
// This example demonstrates how to use a custom Lip Gloss renderer with Wish,
|
||||
// a package for building custom SSH servers.
|
||||
//
|
||||
// The big advantage to using custom renderers here is that we can accurately
|
||||
// detect the background color and color profile for each client and render
|
||||
// against that accordingly.
|
||||
//
|
||||
// For details on wish see: https://github.com/charmbracelet/wish/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
@ -14,6 +23,41 @@ import (
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
// Available styles.
|
||||
type styles struct {
|
||||
bold lipgloss.Style
|
||||
faint lipgloss.Style
|
||||
italic lipgloss.Style
|
||||
underline lipgloss.Style
|
||||
strikethrough lipgloss.Style
|
||||
red lipgloss.Style
|
||||
green lipgloss.Style
|
||||
yellow lipgloss.Style
|
||||
blue lipgloss.Style
|
||||
magenta lipgloss.Style
|
||||
cyan lipgloss.Style
|
||||
gray lipgloss.Style
|
||||
}
|
||||
|
||||
// Create new styles against a given renderer.
|
||||
func makeStyles(r *lipgloss.Renderer) styles {
|
||||
return styles{
|
||||
bold: r.NewStyle().SetString("bold").Bold(true),
|
||||
faint: r.NewStyle().SetString("faint").Faint(true),
|
||||
italic: r.NewStyle().SetString("italic").Italic(true),
|
||||
underline: r.NewStyle().SetString("underline").Underline(true),
|
||||
strikethrough: r.NewStyle().SetString("strikethrough").Strikethrough(true),
|
||||
red: r.NewStyle().SetString("red").Foreground(lipgloss.Color("#E88388")),
|
||||
green: r.NewStyle().SetString("green").Foreground(lipgloss.Color("#A8CC8C")),
|
||||
yellow: r.NewStyle().SetString("yellow").Foreground(lipgloss.Color("#DBAB79")),
|
||||
blue: r.NewStyle().SetString("blue").Foreground(lipgloss.Color("#71BEF2")),
|
||||
magenta: r.NewStyle().SetString("magenta").Foreground(lipgloss.Color("#D290E4")),
|
||||
cyan: r.NewStyle().SetString("cyan").Foreground(lipgloss.Color("#66C2CD")),
|
||||
gray: r.NewStyle().SetString("gray").Foreground(lipgloss.Color("#B9BFCA")),
|
||||
}
|
||||
}
|
||||
|
||||
// Bridge Wish and Termenv so we can query for a user's terminal capabilities.
|
||||
type sshOutput struct {
|
||||
ssh.Session
|
||||
tty *os.File
|
||||
@ -23,6 +67,10 @@ func (s *sshOutput) Write(p []byte) (int, error) {
|
||||
return s.Session.Write(p)
|
||||
}
|
||||
|
||||
func (s *sshOutput) Read(p []byte) (int, error) {
|
||||
return s.Session.Read(p)
|
||||
}
|
||||
|
||||
func (s *sshOutput) Fd() uintptr {
|
||||
return s.tty.Fd()
|
||||
}
|
||||
@ -44,86 +92,104 @@ func (s *sshEnviron) Environ() []string {
|
||||
return s.environ
|
||||
}
|
||||
|
||||
func outputFromSession(s ssh.Session) *termenv.Output {
|
||||
sshPty, _, _ := s.Pty()
|
||||
// Create a termenv.Output from the session.
|
||||
func outputFromSession(sess ssh.Session) *termenv.Output {
|
||||
sshPty, _, _ := sess.Pty()
|
||||
_, tty, err := pty.Open()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
o := &sshOutput{
|
||||
Session: s,
|
||||
Session: sess,
|
||||
tty: tty,
|
||||
}
|
||||
environ := s.Environ()
|
||||
environ := sess.Environ()
|
||||
environ = append(environ, fmt.Sprintf("TERM=%s", sshPty.Term))
|
||||
e := &sshEnviron{
|
||||
environ: environ,
|
||||
e := &sshEnviron{environ: environ}
|
||||
// We need to use unsafe mode here because the ssh session is not running
|
||||
// locally and we already know that the session is a TTY.
|
||||
return termenv.NewOutput(o, termenv.WithUnsafe(), termenv.WithEnvironment(e))
|
||||
}
|
||||
|
||||
// Handle SSH requests.
|
||||
func handler(next ssh.Handler) ssh.Handler {
|
||||
return func(sess ssh.Session) {
|
||||
// Get client's output.
|
||||
clientOutput := outputFromSession(sess)
|
||||
|
||||
pty, _, active := sess.Pty()
|
||||
if !active {
|
||||
next(sess)
|
||||
return
|
||||
}
|
||||
width := pty.Window.Width
|
||||
|
||||
// Initialize new renderer for the client.
|
||||
renderer := lipgloss.NewRenderer(sess)
|
||||
renderer.SetOutput(clientOutput)
|
||||
|
||||
// Initialize new styles against the renderer.
|
||||
styles := makeStyles(renderer)
|
||||
|
||||
str := strings.Builder{}
|
||||
|
||||
fmt.Fprintf(&str, "\n\n%s %s %s %s %s",
|
||||
styles.bold,
|
||||
styles.faint,
|
||||
styles.italic,
|
||||
styles.underline,
|
||||
styles.strikethrough,
|
||||
)
|
||||
|
||||
fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s",
|
||||
styles.red,
|
||||
styles.green,
|
||||
styles.yellow,
|
||||
styles.blue,
|
||||
styles.magenta,
|
||||
styles.cyan,
|
||||
styles.gray,
|
||||
)
|
||||
|
||||
fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s\n\n",
|
||||
styles.red,
|
||||
styles.green,
|
||||
styles.yellow,
|
||||
styles.blue,
|
||||
styles.magenta,
|
||||
styles.cyan,
|
||||
styles.gray,
|
||||
)
|
||||
|
||||
fmt.Fprintf(&str, "%s %t %s\n\n", styles.bold.Copy().UnsetString().Render("Has dark background?"),
|
||||
renderer.HasDarkBackground(),
|
||||
renderer.Output().BackgroundColor())
|
||||
|
||||
block := renderer.Place(width,
|
||||
lipgloss.Height(str.String()), lipgloss.Center, lipgloss.Center, str.String(),
|
||||
lipgloss.WithWhitespaceChars("/"),
|
||||
lipgloss.WithWhitespaceForeground(lipgloss.AdaptiveColor{Light: "250", Dark: "236"}),
|
||||
)
|
||||
|
||||
// Render to client.
|
||||
wish.WriteString(sess, block)
|
||||
|
||||
next(sess)
|
||||
}
|
||||
return termenv.NewOutput(o, termenv.WithEnvironment(e))
|
||||
}
|
||||
|
||||
func main() {
|
||||
addr := ":3456"
|
||||
port := 3456
|
||||
s, err := wish.NewServer(
|
||||
wish.WithAddress(addr),
|
||||
wish.WithAddress(fmt.Sprintf(":%d", port)),
|
||||
wish.WithHostKeyPath("ssh_example"),
|
||||
wish.WithMiddleware(
|
||||
func(sh ssh.Handler) ssh.Handler {
|
||||
return func(s ssh.Session) {
|
||||
output := outputFromSession(s)
|
||||
pty, _, active := s.Pty()
|
||||
if !active {
|
||||
sh(s)
|
||||
return
|
||||
}
|
||||
w, _ := pty.Window.Width, pty.Window.Height
|
||||
|
||||
renderer := lipgloss.NewRenderer(lipgloss.WithTermenvOutput(output),
|
||||
lipgloss.WithColorProfile(termenv.TrueColor))
|
||||
str := strings.Builder{}
|
||||
fmt.Fprintf(&str, "\n%s %s %s %s %s",
|
||||
renderer.NewStyle().SetString("bold").Bold(true),
|
||||
renderer.NewStyle().SetString("faint").Faint(true),
|
||||
renderer.NewStyle().SetString("italic").Italic(true),
|
||||
renderer.NewStyle().SetString("underline").Underline(true),
|
||||
renderer.NewStyle().SetString("crossout").Strikethrough(true),
|
||||
)
|
||||
|
||||
fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s",
|
||||
renderer.NewStyle().SetString("red").Foreground(lipgloss.Color("#E88388")),
|
||||
renderer.NewStyle().SetString("green").Foreground(lipgloss.Color("#A8CC8C")),
|
||||
renderer.NewStyle().SetString("yellow").Foreground(lipgloss.Color("#DBAB79")),
|
||||
renderer.NewStyle().SetString("blue").Foreground(lipgloss.Color("#71BEF2")),
|
||||
renderer.NewStyle().SetString("magenta").Foreground(lipgloss.Color("#D290E4")),
|
||||
renderer.NewStyle().SetString("cyan").Foreground(lipgloss.Color("#66C2CD")),
|
||||
renderer.NewStyle().SetString("gray").Foreground(lipgloss.Color("#B9BFCA")),
|
||||
)
|
||||
|
||||
fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s\n\n",
|
||||
renderer.NewStyle().SetString("red").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#E88388")),
|
||||
renderer.NewStyle().SetString("green").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#A8CC8C")),
|
||||
renderer.NewStyle().SetString("yellow").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#DBAB79")),
|
||||
renderer.NewStyle().SetString("blue").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#71BEF2")),
|
||||
renderer.NewStyle().SetString("magenta").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#D290E4")),
|
||||
renderer.NewStyle().SetString("cyan").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#66C2CD")),
|
||||
renderer.NewStyle().SetString("gray").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#B9BFCA")),
|
||||
)
|
||||
|
||||
fmt.Fprintf(&str, "%s %t\n", renderer.NewStyle().SetString("Has dark background?").Bold(true), renderer.HasDarkBackground())
|
||||
fmt.Fprintln(&str)
|
||||
|
||||
wish.WriteString(s, renderer.Place(w, lipgloss.Height(str.String()), lipgloss.Center, lipgloss.Center, str.String()))
|
||||
|
||||
sh(s)
|
||||
}
|
||||
},
|
||||
lm.Middleware(),
|
||||
),
|
||||
wish.WithMiddleware(handler, lm.Middleware()),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Listening on %s", addr)
|
||||
log.Printf("SSH server listening on port %d", port)
|
||||
log.Printf("To connect from your local machine run: ssh localhost -p %d", port)
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
22
renderer.go
22
renderer.go
@ -15,7 +15,7 @@ type Renderer struct {
|
||||
hasDarkBackground *bool
|
||||
}
|
||||
|
||||
// RendererOption is a function that can be used to configure a Renderer.
|
||||
// RendererOption is a function that can be used to configure a [Renderer].
|
||||
type RendererOption func(r *Renderer)
|
||||
|
||||
// DefaultRenderer returns the default renderer.
|
||||
@ -68,10 +68,10 @@ func ColorProfile() termenv.Profile {
|
||||
//
|
||||
// Available color profiles are:
|
||||
//
|
||||
// termenv.Ascii (no color, 1-bit)
|
||||
// termenv.ANSI (16 colors, 4-bit)
|
||||
// termenv.ANSI256 (256 colors, 8-bit)
|
||||
// termenv.TrueColor (16,777,216 colors, 24-bit)
|
||||
// termenv.Ascii // no color, 1-bit
|
||||
// termenv.ANSI //16 colors, 4-bit
|
||||
// termenv.ANSI256 // 256 colors, 8-bit
|
||||
// termenv.TrueColor // 16,777,216 colors, 24-bit
|
||||
//
|
||||
// This function is thread-safe.
|
||||
func (r *Renderer) SetColorProfile(p termenv.Profile) {
|
||||
@ -88,10 +88,10 @@ func (r *Renderer) SetColorProfile(p termenv.Profile) {
|
||||
//
|
||||
// Available color profiles are:
|
||||
//
|
||||
// termenv.Ascii (no color, 1-bit)
|
||||
// termenv.ANSI (16 colors, 4-bit)
|
||||
// termenv.ANSI256 (256 colors, 8-bit)
|
||||
// termenv.TrueColor (16,777,216 colors, 24-bit)
|
||||
// termenv.Ascii // no color, 1-bit
|
||||
// termenv.ANSI //16 colors, 4-bit
|
||||
// termenv.ANSI256 // 256 colors, 8-bit
|
||||
// termenv.TrueColor // 16,777,216 colors, 24-bit
|
||||
//
|
||||
// This function is thread-safe.
|
||||
func SetColorProfile(p termenv.Profile) {
|
||||
@ -103,7 +103,9 @@ func HasDarkBackground() bool {
|
||||
return renderer.HasDarkBackground()
|
||||
}
|
||||
|
||||
// HasDarkBackground returns whether or not the terminal has a dark background.
|
||||
// HasDarkBackground returns whether or not the renderer will render to a dark
|
||||
// background. A dark background can either be auto-detected, or set explicitly
|
||||
// on the renderer.
|
||||
func (r *Renderer) HasDarkBackground() bool {
|
||||
if r.hasDarkBackground != nil {
|
||||
return *r.hasDarkBackground
|
||||
|
Loading…
Reference in New Issue
Block a user