README.md

  1# Lip Gloss
  2
  3<p>
  4    <a href="https://stuff.charm.sh/lipgloss/lipgloss-mascot-2k.png"><img width="340" alt="Lip Gloss title treatment" src="https://github.com/charmbracelet/lipgloss/assets/25087/147cadb1-4254-43ec-ae6b-8d6ca7b029a1"></a><br>
  5    <a href="https://github.com/charmbracelet/lipgloss/releases"><img src="https://img.shields.io/github/release/charmbracelet/lipgloss.svg" alt="Latest Release"></a>
  6    <a href="https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
  7    <a href="https://github.com/charmbracelet/lipgloss/actions"><img src="https://github.com/charmbracelet/lipgloss/workflows/build/badge.svg" alt="Build Status"></a>
  8    <a href="https://www.phorm.ai/query?projectId=a0e324b6-b706-4546-b951-6671ea60c13f"><img src="https://stuff.charm.sh/misc/phorm-badge.svg" alt="phorm.ai"></a>
  9</p>
 10
 11Style definitions for nice terminal layouts. Built with TUIs in mind.
 12
 13![Lip Gloss example](https://github.com/user-attachments/assets/99c5c015-551b-4897-8cd1-bcaafa0aad5a)
 14
 15Lip Gloss takes an expressive, declarative approach to terminal rendering.
 16Users familiar with CSS will feel at home with Lip Gloss.
 17
 18```go
 19
 20import "github.com/charmbracelet/lipgloss"
 21
 22var style = lipgloss.NewStyle().
 23    Bold(true).
 24    Foreground(lipgloss.Color("#FAFAFA")).
 25    Background(lipgloss.Color("#7D56F4")).
 26    PaddingTop(2).
 27    PaddingLeft(4).
 28    Width(22)
 29
 30fmt.Println(style.Render("Hello, kitty"))
 31```
 32
 33## Colors
 34
 35Lip Gloss supports the following color profiles:
 36
 37### ANSI 16 colors (4-bit)
 38
 39```go
 40lipgloss.Color("5")  // magenta
 41lipgloss.Color("9")  // red
 42lipgloss.Color("12") // light blue
 43```
 44
 45### ANSI 256 Colors (8-bit)
 46
 47```go
 48lipgloss.Color("86")  // aqua
 49lipgloss.Color("201") // hot pink
 50lipgloss.Color("202") // orange
 51```
 52
 53### True Color (16,777,216 colors; 24-bit)
 54
 55```go
 56lipgloss.Color("#0000FF") // good ol' 100% blue
 57lipgloss.Color("#04B575") // a green
 58lipgloss.Color("#3C3C3C") // a dark gray
 59```
 60
 61...as well as a 1-bit ASCII profile, which is black and white only.
 62
 63The terminal's color profile will be automatically detected, and colors outside
 64the gamut of the current palette will be automatically coerced to their closest
 65available value.
 66
 67### Adaptive Colors
 68
 69You can also specify color options for light and dark backgrounds:
 70
 71```go
 72lipgloss.AdaptiveColor{Light: "236", Dark: "248"}
 73```
 74
 75The terminal's background color will automatically be detected and the
 76appropriate color will be chosen at runtime.
 77
 78### Complete Colors
 79
 80CompleteColor specifies exact values for True Color, ANSI256, and ANSI color
 81profiles.
 82
 83```go
 84lipgloss.CompleteColor{TrueColor: "#0000FF", ANSI256: "86", ANSI: "5"}
 85```
 86
 87Automatic color degradation will not be performed in this case and it will be
 88based on the color specified.
 89
 90### Complete Adaptive Colors
 91
 92You can use `CompleteColor` with `AdaptiveColor` to specify the exact values for
 93light and dark backgrounds without automatic color degradation.
 94
 95```go
 96lipgloss.CompleteAdaptiveColor{
 97    Light: CompleteColor{TrueColor: "#d7ffae", ANSI256: "193", ANSI: "11"},
 98    Dark:  CompleteColor{TrueColor: "#d75fee", ANSI256: "163", ANSI: "5"},
 99}
100```
101
102## Inline Formatting
103
104Lip Gloss supports the usual ANSI text formatting options:
105
106```go
107var style = lipgloss.NewStyle().
108    Bold(true).
109    Italic(true).
110    Faint(true).
111    Blink(true).
112    Strikethrough(true).
113    Underline(true).
114    Reverse(true)
115```
116
117## Block-Level Formatting
118
119Lip Gloss also supports rules for block-level formatting:
120
121```go
122// Padding
123var style = lipgloss.NewStyle().
124    PaddingTop(2).
125    PaddingRight(4).
126    PaddingBottom(2).
127    PaddingLeft(4)
128
129// Margins
130var style = lipgloss.NewStyle().
131    MarginTop(2).
132    MarginRight(4).
133    MarginBottom(2).
134    MarginLeft(4)
135```
136
137There is also shorthand syntax for margins and padding, which follows the same
138format as CSS:
139
140```go
141// 2 cells on all sides
142lipgloss.NewStyle().Padding(2)
143
144// 2 cells on the top and bottom, 4 cells on the left and right
145lipgloss.NewStyle().Margin(2, 4)
146
147// 1 cell on the top, 4 cells on the sides, 2 cells on the bottom
148lipgloss.NewStyle().Padding(1, 4, 2)
149
150// Clockwise, starting from the top: 2 cells on the top, 4 on the right, 3 on
151// the bottom, and 1 on the left
152lipgloss.NewStyle().Margin(2, 4, 3, 1)
153```
154
155## Aligning Text
156
157You can align paragraphs of text to the left, right, or center.
158
159```go
160var style = lipgloss.NewStyle().
161    Width(24).
162    Align(lipgloss.Left).  // align it left
163    Align(lipgloss.Right). // no wait, align it right
164    Align(lipgloss.Center) // just kidding, align it in the center
165```
166
167## Width and Height
168
169Setting a minimum width and height is simple and straightforward.
170
171```go
172var style = lipgloss.NewStyle().
173    SetString("What’s for lunch?").
174    Width(24).
175    Height(32).
176    Foreground(lipgloss.Color("63"))
177```
178
179## Borders
180
181Adding borders is easy:
182
183```go
184// Add a purple, rectangular border
185var style = lipgloss.NewStyle().
186    BorderStyle(lipgloss.NormalBorder()).
187    BorderForeground(lipgloss.Color("63"))
188
189// Set a rounded, yellow-on-purple border to the top and left
190var anotherStyle = lipgloss.NewStyle().
191    BorderStyle(lipgloss.RoundedBorder()).
192    BorderForeground(lipgloss.Color("228")).
193    BorderBackground(lipgloss.Color("63")).
194    BorderTop(true).
195    BorderLeft(true)
196
197// Make your own border
198var myCuteBorder = lipgloss.Border{
199    Top:         "._.:*:",
200    Bottom:      "._.:*:",
201    Left:        "|*",
202    Right:       "|*",
203    TopLeft:     "*",
204    TopRight:    "*",
205    BottomLeft:  "*",
206    BottomRight: "*",
207}
208```
209
210There are also shorthand functions for defining borders, which follow a similar
211pattern to the margin and padding shorthand functions.
212
213```go
214// Add a thick border to the top and bottom
215lipgloss.NewStyle().
216    Border(lipgloss.ThickBorder(), true, false)
217
218// Add a double border to the top and left sides. Rules are set clockwise
219// from top.
220lipgloss.NewStyle().
221    Border(lipgloss.DoubleBorder(), true, false, false, true)
222```
223
224For more on borders see [the docs][docs].
225
226## Copying Styles
227
228Just use assignment:
229
230```go
231style := lipgloss.NewStyle().Foreground(lipgloss.Color("219"))
232
233copiedStyle := style // this is a true copy
234
235wildStyle := style.Blink(true) // this is also true copy, with blink added
236
237```
238
239Since `Style` data structures contains only primitive types, assigning a style
240to another effectively creates a new copy of the style without mutating the
241original.
242
243## Inheritance
244
245Styles can inherit rules from other styles. When inheriting, only unset rules
246on the receiver are inherited.
247
248```go
249var styleA = lipgloss.NewStyle().
250    Foreground(lipgloss.Color("229")).
251    Background(lipgloss.Color("63"))
252
253// Only the background color will be inherited here, because the foreground
254// color will have been already set:
255var styleB = lipgloss.NewStyle().
256    Foreground(lipgloss.Color("201")).
257    Inherit(styleA)
258```
259
260## Unsetting Rules
261
262All rules can be unset:
263
264```go
265var style = lipgloss.NewStyle().
266    Bold(true).                        // make it bold
267    UnsetBold().                       // jk don't make it bold
268    Background(lipgloss.Color("227")). // yellow background
269    UnsetBackground()                  // never mind
270```
271
272When a rule is unset, it won't be inherited or copied.
273
274## Enforcing Rules
275
276Sometimes, such as when developing a component, you want to make sure style
277definitions respect their intended purpose in the UI. This is where `Inline`
278and `MaxWidth`, and `MaxHeight` come in:
279
280```go
281// Force rendering onto a single line, ignoring margins, padding, and borders.
282someStyle.Inline(true).Render("yadda yadda")
283
284// Also limit rendering to five cells
285someStyle.Inline(true).MaxWidth(5).Render("yadda yadda")
286
287// Limit rendering to a 5x5 cell block
288someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda")
289```
290
291## Tabs
292
293The tab character (`\t`) is rendered differently in different terminals (often
294as 8 spaces, sometimes 4). Because of this inconsistency, Lip Gloss converts
295tabs to 4 spaces at render time. This behavior can be changed on a per-style
296basis, however:
297
298```go
299style := lipgloss.NewStyle() // tabs will render as 4 spaces, the default
300style = style.TabWidth(2)    // render tabs as 2 spaces
301style = style.TabWidth(0)    // remove tabs entirely
302style = style.TabWidth(lipgloss.NoTabConversion) // leave tabs intact
303```
304
305## Rendering
306
307Generally, you just call the `Render(string...)` method on a `lipgloss.Style`:
308
309```go
310style := lipgloss.NewStyle().Bold(true).SetString("Hello,")
311fmt.Println(style.Render("kitty.")) // Hello, kitty.
312fmt.Println(style.Render("puppy.")) // Hello, puppy.
313```
314
315But you could also use the Stringer interface:
316
317```go
318var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true)
319fmt.Println(style) // 你好,猫咪。
320```
321
322## Utilities
323
324In addition to pure styling, Lip Gloss also ships with some utilities to help
325assemble your layouts.
326
327### Joining Paragraphs
328
329Horizontally and vertically joining paragraphs is a cinch.
330
331```go
332// Horizontally join three paragraphs along their bottom edges
333lipgloss.JoinHorizontal(lipgloss.Bottom, paragraphA, paragraphB, paragraphC)
334
335// Vertically join two paragraphs along their center axes
336lipgloss.JoinVertical(lipgloss.Center, paragraphA, paragraphB)
337
338// Horizontally join three paragraphs, with the shorter ones aligning 20%
339// from the top of the tallest
340lipgloss.JoinHorizontal(0.2, paragraphA, paragraphB, paragraphC)
341```
342
343### Measuring Width and Height
344
345Sometimes you’ll want to know the width and height of text blocks when building
346your layouts.
347
348```go
349// Render a block of text.
350var style = lipgloss.NewStyle().
351    Width(40).
352    Padding(2)
353var block string = style.Render(someLongString)
354
355// Get the actual, physical dimensions of the text block.
356width := lipgloss.Width(block)
357height := lipgloss.Height(block)
358
359// Here's a shorthand function.
360w, h := lipgloss.Size(block)
361```
362
363### Placing Text in Whitespace
364
365Sometimes you’ll simply want to place a block of text in whitespace.
366
367```go
368// Center a paragraph horizontally in a space 80 cells wide. The height of
369// the block returned will be as tall as the input paragraph.
370block := lipgloss.PlaceHorizontal(80, lipgloss.Center, fancyStyledParagraph)
371
372// Place a paragraph at the bottom of a space 30 cells tall. The width of
373// the text block returned will be as wide as the input paragraph.
374block := lipgloss.PlaceVertical(30, lipgloss.Bottom, fancyStyledParagraph)
375
376// Place a paragraph in the bottom right corner of a 30x80 cell space.
377block := lipgloss.Place(30, 80, lipgloss.Right, lipgloss.Bottom, fancyStyledParagraph)
378```
379
380You can also style the whitespace. For details, see [the docs][docs].
381
382## Rendering Tables
383
384Lip Gloss ships with a table rendering sub-package.
385
386```go
387import "github.com/charmbracelet/lipgloss/table"
388```
389
390Define some rows of data.
391
392```go
393rows := [][]string{
394    {"Chinese", "您好", "你好"},
395    {"Japanese", "こんにちは", "やあ"},
396    {"Arabic", "أهلين", "أهلا"},
397    {"Russian", "Здравствуйте", "Привет"},
398    {"Spanish", "Hola", "¿Qué tal?"},
399}
400```
401
402Use the table package to style and render the table.
403
404```go
405var (
406    purple    = lipgloss.Color("99")
407    gray      = lipgloss.Color("245")
408    lightGray = lipgloss.Color("241")
409
410    headerStyle  = lipgloss.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center)
411    cellStyle    = lipgloss.NewStyle().Padding(0, 1).Width(14)
412    oddRowStyle  = cellStyle.Foreground(gray)
413    evenRowStyle = cellStyle.Foreground(lightGray)
414)
415
416t := table.New().
417    Border(lipgloss.NormalBorder()).
418    BorderStyle(lipgloss.NewStyle().Foreground(purple)).
419    StyleFunc(func(row, col int) lipgloss.Style {
420        switch {
421        case row == table.HeaderRow:
422            return headerStyle
423        case row%2 == 0:
424            return evenRowStyle
425        default:
426            return oddRowStyle
427        }
428    }).
429    Headers("LANGUAGE", "FORMAL", "INFORMAL").
430    Rows(rows...)
431
432// You can also add tables row-by-row
433t.Row("English", "You look absolutely fabulous.", "How's it going?")
434```
435
436Print the table.
437
438```go
439fmt.Println(t)
440```
441
442![Table Example](https://github.com/charmbracelet/lipgloss/assets/42545625/6e4b70c4-f494-45da-a467-bdd27df30d5d)
443
444> [!WARNING]
445> Table `Rows` need to be declared before `YOffset` otherwise it does nothing.
446
447### Table Borders
448
449There are helpers to generate tables in markdown or ASCII style:
450
451#### Markdown Table
452
453```go
454table.New().Border(lipgloss.MarkdownBorder()).BorderTop(false).BorderBottom(false)
455```
456
457```
458| LANGUAGE |    FORMAL    | INFORMAL  |
459|----------|--------------|-----------|
460| Chinese  | Nǐn hǎo      | Nǐ hǎo    |
461| French   | Bonjour      | Salut     |
462| Russian  | Zdravstvuyte | Privet    |
463| Spanish  | Hola         | ¿Qué tal? |
464```
465
466#### ASCII Table
467
468```go
469table.New().Border(lipgloss.ASCIIBorder())
470```
471
472```
473+----------+--------------+-----------+
474| LANGUAGE |    FORMAL    | INFORMAL  |
475+----------+--------------+-----------+
476| Chinese  | Nǐn hǎo      | Nǐ hǎo    |
477| French   | Bonjour      | Salut     |
478| Russian  | Zdravstvuyte | Privet    |
479| Spanish  | Hola         | ¿Qué tal? |
480+----------+--------------+-----------+
481```
482
483For more on tables see [the docs](https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc) and [examples](https://github.com/charmbracelet/lipgloss/tree/master/examples/table).
484
485## Rendering Lists
486
487Lip Gloss ships with a list rendering sub-package.
488
489```go
490import "github.com/charmbracelet/lipgloss/list"
491```
492
493Define a new list.
494
495```go
496l := list.New("A", "B", "C")
497```
498
499Print the list.
500
501```go
502fmt.Println(l)
503
504// • A
505// • B
506// • C
507```
508
509Lists have the ability to nest.
510
511```go
512l := list.New(
513    "A", list.New("Artichoke"),
514    "B", list.New("Baking Flour", "Bananas", "Barley", "Bean Sprouts"),
515    "C", list.New("Cashew Apple", "Cashews", "Coconut Milk", "Curry Paste", "Currywurst"),
516    "D", list.New("Dill", "Dragonfruit", "Dried Shrimp"),
517    "E", list.New("Eggs"),
518    "F", list.New("Fish Cake", "Furikake"),
519    "J", list.New("Jicama"),
520    "K", list.New("Kohlrabi"),
521    "L", list.New("Leeks", "Lentils", "Licorice Root"),
522)
523```
524
525Print the list.
526
527```go
528fmt.Println(l)
529```
530
531<p align="center">
532<img width="600" alt="image" src="https://github.com/charmbracelet/lipgloss/assets/42545625/0dc9f440-0748-4151-a3b0-7dcf29dfcdb0">
533</p>
534
535Lists can be customized via their enumeration function as well as using
536`lipgloss.Style`s.
537
538```go
539enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("99")).MarginRight(1)
540itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")).MarginRight(1)
541
542l := list.New(
543    "Glossier",
544    "Claire’s Boutique",
545    "Nyx",
546    "Mac",
547    "Milk",
548    ).
549    Enumerator(list.Roman).
550    EnumeratorStyle(enumeratorStyle).
551    ItemStyle(itemStyle)
552```
553
554Print the list.
555
556<p align="center">
557<img width="600" alt="List example" src="https://github.com/charmbracelet/lipgloss/assets/42545625/360494f1-57fb-4e13-bc19-0006efe01561">
558</p>
559
560In addition to the predefined enumerators (`Arabic`, `Alphabet`, `Roman`, `Bullet`, `Tree`),
561you may also define your own custom enumerator:
562
563```go
564l := list.New("Duck", "Duck", "Duck", "Duck", "Goose", "Duck", "Duck")
565
566func DuckDuckGooseEnumerator(l list.Items, i int) string {
567    if l.At(i).Value() == "Goose" {
568        return "Honk →"
569    }
570    return ""
571}
572
573l = l.Enumerator(DuckDuckGooseEnumerator)
574```
575
576Print the list:
577
578<p align="center">
579<img width="600" alt="image" src="https://github.com/charmbracelet/lipgloss/assets/42545625/157aaf30-140d-4948-9bb4-dfba46e5b87e">
580</p>
581
582If you need, you can also build lists incrementally:
583
584```go
585l := list.New()
586
587for i := 0; i < repeat; i++ {
588    l.Item("Lip Gloss")
589}
590```
591
592## Rendering Trees
593
594Lip Gloss ships with a tree rendering sub-package.
595
596```go
597import "github.com/charmbracelet/lipgloss/tree"
598```
599
600Define a new tree.
601
602```go
603t := tree.Root(".").
604    Child("A", "B", "C")
605```
606
607Print the tree.
608
609```go
610fmt.Println(t)
611
612// .
613// ├── A
614// ├── B
615// └── C
616```
617
618Trees have the ability to nest.
619
620```go
621t := tree.Root(".").
622    Child("macOS").
623    Child(
624        tree.New().
625            Root("Linux").
626            Child("NixOS").
627            Child("Arch Linux (btw)").
628            Child("Void Linux"),
629        ).
630    Child(
631        tree.New().
632            Root("BSD").
633            Child("FreeBSD").
634            Child("OpenBSD"),
635    )
636```
637
638Print the tree.
639
640```go
641fmt.Println(t)
642```
643
644<p align="center">
645<img width="663" alt="Tree Example (simple)" src="https://github.com/user-attachments/assets/5ef14eb8-a5d4-4f94-8834-e15d1e714f89">
646</p>
647
648Trees can be customized via their enumeration function as well as using
649`lipgloss.Style`s.
650
651```go
652enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63")).MarginRight(1)
653rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("35"))
654itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
655
656t := tree.
657    Root("⁜ Makeup").
658    Child(
659        "Glossier",
660        "Fenty Beauty",
661        tree.New().Child(
662            "Gloss Bomb Universal Lip Luminizer",
663            "Hot Cheeks Velour Blushlighter",
664        ),
665        "Nyx",
666        "Mac",
667        "Milk",
668    ).
669    Enumerator(tree.RoundedEnumerator).
670    EnumeratorStyle(enumeratorStyle).
671    RootStyle(rootStyle).
672    ItemStyle(itemStyle)
673```
674
675Print the tree.
676
677<p align="center">
678<img width="663" alt="Tree Example (makeup)" src="https://github.com/user-attachments/assets/06d12d87-744a-4c89-bd98-45de9094a97e">
679</p>
680
681The predefined enumerators for trees are `DefaultEnumerator` and `RoundedEnumerator`.
682
683If you need, you can also build trees incrementally:
684
685```go
686t := tree.New()
687
688for i := 0; i < repeat; i++ {
689    t.Child("Lip Gloss")
690}
691```
692
693---
694
695## FAQ
696
697<details>
698<summary>
699Why are things misaligning? Why are borders at the wrong widths?
700</summary>
701<p>This is most likely due to your locale and encoding, particularly with
702regard to Chinese, Japanese, and Korean (for example, <code>zh_CN.UTF-8</code>
703or <code>ja_JP.UTF-8</code>). The most direct way to fix this is to set
704<code>RUNEWIDTH_EASTASIAN=0</code> in your environment.</p>
705
706<p>For details see <a href="https://github.com/charmbracelet/lipgloss/issues/40">https://github.com/charmbracelet/lipgloss/issues/40.</a></p>
707</details>
708
709<details>
710<summary>
711Why isn't Lip Gloss displaying colors?
712</summary>
713<p>Lip Gloss automatically degrades colors to the best available option in the
714given terminal, and if output's not a TTY it will remove color output entirely.
715This is common when running tests, CI, or when piping output elsewhere.</p>
716
717<p>If necessary, you can force a color profile in your tests with
718<a href="https://pkg.go.dev/github.com/charmbracelet/lipgloss#SetColorProfile"><code>SetColorProfile</code></a>.</p>
719
720```go
721import (
722    "github.com/charmbracelet/lipgloss"
723    "github.com/muesli/termenv"
724)
725
726lipgloss.SetColorProfile(termenv.TrueColor)
727```
728
729_Note:_ this option limits the flexibility of your application and can cause
730ANSI escape codes to be output in cases where that might not be desired. Take
731careful note of your use case and environment before choosing to force a color
732profile.
733
734</details>
735
736## What about [Bubble Tea][tea]?
737
738Lip Gloss doesn’t replace Bubble Tea. Rather, it is an excellent Bubble Tea
739companion. It was designed to make assembling terminal user interface views as
740simple and fun as possible so that you can focus on building your application
741instead of concerning yourself with low-level layout details.
742
743In simple terms, you can use Lip Gloss to help build your Bubble Tea views.
744
745[tea]: https://github.com/charmbracelet/tea
746
747## Under the Hood
748
749Lip Gloss is built on the excellent [Termenv][termenv] and [Reflow][reflow]
750libraries which deal with color and ANSI-aware text operations, respectively.
751For many use cases Termenv and Reflow will be sufficient for your needs.
752
753[termenv]: https://github.com/muesli/termenv
754[reflow]: https://github.com/muesli/reflow
755
756## Rendering Markdown
757
758For a more document-centric rendering solution with support for things like
759lists, tables, and syntax-highlighted code have a look at [Glamour][glamour],
760the stylesheet-based Markdown renderer.
761
762[glamour]: https://github.com/charmbracelet/glamour
763
764## Contributing
765
766See [contributing][contribute].
767
768[contribute]: https://github.com/charmbracelet/lipgloss/contribute
769
770## Feedback
771
772We’d love to hear your thoughts on this project. Feel free to drop us a note!
773
774- [Twitter](https://twitter.com/charmcli)
775- [The Fediverse](https://mastodon.social/@charmcli)
776- [Discord](https://charm.sh/chat)
777
778## License
779
780[MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE)
781
782---
783
784Part of [Charm](https://charm.sh).
785
786<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
787
788Charm热爱开源 • Charm loves open source
789
790[docs]: https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc
791[wish]: https://github.com/charmbracelet/wish
792[ssh-example]: examples/ssh