refactor(cli)!: give fonts a list and an install command

Choosing a font was a five-stage terminal UI: fetch the list, scroll it,
download, unzip, install. It pulled bubbletea, lipgloss, bubbles and a
fuzzy matcher for what amounted to a non-filtering list of names with
one highlighted - and Go runs a linked package's init before main
whether or not the subcommand was invoked, so everyone typing at a
prompt paid for it on every render.

A CLI already has a way to answer "which fonts are there": print them.

  oh-my-posh font list
  oh-my-posh font install Meslo

The list can be grepped, piped and scripted, none of which a picker
allows, and install is what it always was underneath - resolve, fetch,
unzip - now reporting on a status line and a progress bar.

Removes 20 modules including go-runewidth, whose package init built a
2.2MB lookup table nothing in the prompt path ever read. The
atotto/clipboard fork this repo carried goes with them: bubbletea was
the only thing that needed it.

Binary 17,330,176 to 15,981,056 bytes (-7.8%). Init, median of 11 runs,
39.3ms to 6.8ms - on every invocation, prompt renders included.

BREAKING CHANGE: `oh-my-posh font install` now requires a font name; run
`oh-my-posh font list` to see them. The interactive picker is gone, and
with it the --headless flag, which existed to skip it.

Entire-Checkpoint: ef231d69c9bf
This commit is contained in:
Jan De Dobbeleer
2026-07-30 19:44:41 +02:00
committed by Jan De Dobbeleer
parent cc8c2add4a
commit 3b63012568
11 changed files with 210 additions and 572 deletions
+76 -59
View File
@@ -17,82 +17,99 @@ import (
var (
zipFolder string
headless bool
fontCmd = &cobra.Command{
Use: "font [install|configure]",
Use: "font",
Short: "Manage fonts",
Long: `Manage fonts.
This command is used to install fonts and configure the font in your terminal.
List the available Nerd Fonts and install one:
- install: oh-my-posh font install 3270`,
ValidArgs: []string{
"install",
"configure",
oh-my-posh font list
oh-my-posh font install Meslo`,
}
fontListCmd = &cobra.Command{
Use: "list",
Short: "List the available Nerd Fonts",
Long: `List the available Nerd Fonts.
Prints one font name per line, so it can be searched or piped:
oh-my-posh font list | grep -i mono`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
fonts, err := font.List()
if err != nil {
log.Error(err)
exitcode = 70
return
}
for _, f := range fonts {
fmt.Println(f.Name)
}
},
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
}
fontInstallCmd = &cobra.Command{
Use: "install <font>",
Short: "Install a Nerd Font",
Long: `Install a Nerd Font.
Takes a font name from ` + "`oh-my-posh font list`" + `, a URL, or the path to a local zip file:
oh-my-posh font install Meslo
oh-my-posh font install https://example.com/font.zip
oh-my-posh font install ./CascadiaCode.zip`,
Args: cobra.ExactArgs(1),
Run: func(_ *cobra.Command, args []string) {
env := &runtime.Terminal{}
env.Init(&runtime.Flags{})
sh := env.Shell()
cache.Init(sh, cache.Persist)
defer cache.Close()
terminal.Init(sh)
cfg := config.Get(configFlag, false)
cfg.TerminalFeatures.Apply()
if zipFolder != "" && !strings.HasPrefix(zipFolder, "/") {
zipFolder += "/"
}
fontName, err := font.Install(args[0], zipFolder)
if err != nil {
log.Error(err)
exitcode = 70
return
}
switch args[0] {
case "install":
var fontName string
if len(args) > 1 {
fontName = args[1]
}
env := &runtime.Terminal{}
env.Init(&runtime.Flags{})
sh := env.Shell()
cache.Init(sh, cache.Persist)
defer func() {
cache.Close()
}()
terminal.Init(sh)
cfg := config.Get(configFlag, false)
cfg.TerminalFeatures.Apply()
if !strings.HasPrefix(zipFolder, "/") {
zipFolder += "/"
}
fontName, err := font.Run(fontName, zipFolder, headless)
if err != nil {
log.Error(err)
exitcode = 70
return
}
if env.Root() {
// do not update the DSC cache if we are running as root
return
}
fontDSC := font.DSC()
fontDSC.Load()
fontDSC.Add(fontName)
fontDSC.Save()
if env.Root() {
// do not update the DSC cache if we are running as root
return
case "configure":
fmt.Println("not implemented")
default:
_ = cmd.Help()
}
fontDSC := font.DSC()
fontDSC.Load()
fontDSC.Add(fontName)
fontDSC.Save()
},
}
)
func init() {
fontCmd.Flags().StringVar(&zipFolder, "zip-folder", "", "the folder inside the zip file to install fonts from")
fontCmd.Flags().BoolVar(&headless, "headless", false, "install font without TUI")
fontInstallCmd.Flags().StringVar(&zipFolder, "zip-folder", "", "the folder inside the zip file to install fonts from")
fontCmd.AddCommand(fontListCmd)
fontCmd.AddCommand(fontInstallCmd)
fontCmd.AddCommand(dsc.Command(font.DSC()))
RootCmd.AddCommand(fontCmd)
}
+13 -5
View File
@@ -12,11 +12,13 @@ import (
"path/filepath"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cli/progress"
"github.com/jandedobbeleer/oh-my-posh/src/cli/ui"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/http"
)
func Download(fontURL string) ([]byte, error) {
// download fetches a font zip, reporting how much has arrived so a caller can draw a bar. report
// may be nil, which is what the DSC path and the tests pass.
func download(fontURL string, report func(fraction float64)) ([]byte, error) {
if zipPath, OK := cache.Get[string](cache.Device, fontURL); OK {
if b, err := os.ReadFile(zipPath); err == nil {
return b, nil
@@ -30,7 +32,7 @@ func Download(fontURL string) ([]byte, error) {
}
var b []byte
if b, err = getRemoteFile(fontURL); err != nil {
if b, err = getRemoteFile(fontURL, report); err != nil {
return nil, err
}
@@ -65,7 +67,7 @@ func isZipFile(data []byte) bool {
return contentType == "application/zip"
}
func getRemoteFile(location string) (data []byte, err error) {
func getRemoteFile(location string, report func(fraction float64)) (data []byte, err error) {
req, err := httplib.NewRequestWithContext(context.Background(), "GET", location, nil)
if err != nil {
return nil, err
@@ -82,7 +84,7 @@ func getRemoteFile(location string) (data []byte, err error) {
return data, fmt.Errorf("failed to download zip file: %s\n→ %s", resp.Status, location)
}
reader := progress.NewReader(resp.Body, resp.ContentLength, program)
reader := ui.NewReader(resp.Body, resp.ContentLength, report)
data, err = io.ReadAll(reader)
if err != nil {
@@ -91,3 +93,9 @@ func getRemoteFile(location string) (data []byte, err error) {
return
}
// Download keeps the old name for callers that need no progress reporting (see font.Apply, which
// runs under DSC with nothing drawing).
func Download(fontURL string) ([]byte, error) {
return download(fontURL, nil)
}
+99
View File
@@ -0,0 +1,99 @@
package font
import (
"fmt"
"os"
"github.com/jandedobbeleer/oh-my-posh/src/cli/ui"
"github.com/jandedobbeleer/oh-my-posh/src/text"
)
// List returns the installable Nerd Fonts, newest release first, for `oh-my-posh font list`.
func List() ([]*Asset, error) {
return fonts()
}
// Install downloads and installs one font by name, URL, or local zip path, reporting each step on
// a status line.
//
// This replaced a five-stage terminal UI - fetch the list, pick from it, download, unzip, install
// - that existed only so a name could be chosen interactively. `font list` answers that question
// better: it can be read, grepped, scripted, and piped, none of which a picker allows. What is
// left is a linear sequence, which is what it always was underneath.
func Install(name, zipFolder string) (string, error) {
status := ui.NewStatus(os.Stdout)
// A local zip is already on disk: nothing to resolve and nothing to download.
if IsLocalZipFile(name) {
data, err := os.ReadFile(name)
if err != nil {
return "", err
}
status.Start(fmt.Sprintf("Installing %s", name))
families, err := InstallZIP(data, zipFolder)
if err != nil {
status.Stop("")
return "", err
}
status.Stop(installed(name, families))
return name, nil
}
status.Start("Resolving font")
asset, err := ResolveFontAsset(name)
if err != nil {
status.Stop("")
return "", err
}
if asset.Folder != "" && zipFolder == "" {
zipFolder = asset.Folder
}
status.Stop("")
bar := ui.NewProgress(os.Stdout, fmt.Sprintf("Downloading %s", asset.Name))
zipFile, err := download(asset.URL, bar.Set)
if err != nil {
bar.Done()
return "", err
}
bar.Done()
status.Start(fmt.Sprintf("Installing %s", asset.Name))
families, err := InstallZIP(zipFile, zipFolder)
if err != nil {
status.Stop("")
return "", err
}
status.Stop(installed(asset.Name, families))
return asset.Name, nil
}
// installed words the closing line: which families a shell can now be pointed at, since the
// family name is rarely the name of the font that was asked for.
func installed(name string, families []string) string {
if len(families) == 0 {
return fmt.Sprintf("No matching font families were installed. Try --zip-folder when installing %s or a custom font zip.", name)
}
sb := text.NewBuilder()
sb.WriteString(fmt.Sprintf("Installed %s 🚀\n\nThe following font families are now available for configuration:\n", name))
for _, family := range families {
sb.WriteString(fmt.Sprintf("\n • %s", family))
}
return sb.String()
}
-366
View File
@@ -1,366 +0,0 @@
package font
import (
"fmt"
"io"
"os"
"strings"
"github.com/charmbracelet/bubbles/list"
progress_ "github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/jandedobbeleer/oh-my-posh/src/cli/progress"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/jandedobbeleer/oh-my-posh/src/text"
)
var (
program *tea.Program
)
const listHeight = 14
var (
itemStyle = lipgloss.NewStyle().PaddingLeft(3)
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(3)
helpStyle = lipgloss.NewStyle().PaddingLeft(3).PaddingBottom(1)
textStyle = lipgloss.NewStyle().Margin(1, 0, 2, 2)
)
type loadMsg []*Asset
type zipMsg []byte
type successMsg []string
type errMsg error
type state int
type itemDelegate struct{}
func (d itemDelegate) Height() int { return 1 }
func (d itemDelegate) Spacing() int { return 0 }
func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { //nolint: gocritic
i, ok := listItem.(*Asset)
if !ok {
return
}
fn := itemStyle.Render
if index == m.Index() {
fn = func(s ...string) string {
return selectedItemStyle.Render("•" + strings.Join(s, " "))
}
}
fmt.Fprint(w, fn(i.Name))
}
const (
getFonts state = iota
selectFont
downloadFont
unzipFont
installFont
quit
done
)
type main struct {
err error
list *list.Model
spinner *spinner.Model
progress *progress.Model
Asset
families []string
state state
}
func (m *main) buildFontList(nerdFonts []*Asset) {
var items []list.Item
for _, font := range nerdFonts {
items = append(items, font)
}
const defaultWidth = 20
l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
l.Title = "Select font"
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
l.Styles.PaginationStyle = paginationStyle
l.Styles.HelpStyle = helpStyle
m.list = &l
}
func getFontsList() {
fonts, err := fonts()
if err != nil {
program.Send(errMsg(err))
return
}
program.Send(loadMsg(fonts))
}
func downloadFontZip(location string) {
zipFile, err := Download(location)
if err != nil {
program.Send(errMsg(err))
return
}
program.Send(zipMsg(zipFile))
}
func installLocalFontZIP(m *main) {
data, err := os.ReadFile(m.URL)
if err != nil {
program.Send(errMsg(err))
return
}
installFontZIP(data, m)
}
func installFontZIP(zipFile []byte, m *main) {
families, err := InstallZIP(zipFile, m.Folder)
if err != nil {
program.Send(errMsg(err))
return
}
program.Send(successMsg(families))
}
func (m *main) Init() tea.Cmd {
m.progress = progress.NewModel()
s := spinner.New()
m.spinner = &s
if len(m.URL) != 0 && !IsLocalZipFile(m.URL) {
m.state = downloadFont
asset, err := ResolveFontAsset(m.URL)
if err != nil {
m.err = err
return tea.Quit
}
m.Asset = *asset
defer func() {
go downloadFontZip(asset.URL)
}()
m.spinner.Spinner = spinner.Globe
return m.spinner.Tick
}
defer func() {
if IsLocalZipFile(m.URL) {
go installLocalFontZIP(m)
return
}
go getFontsList()
}()
m.spinner.Spinner = spinner.Dot
m.spinner.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170"))
m.state = getFonts
if IsLocalZipFile(m.URL) {
m.state = unzipFont
}
return m.spinner.Tick
}
func (m *main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case loadMsg:
m.state = selectFont
m.buildFontList(msg)
return m, nil
case tea.WindowSizeMsg:
if m.list == nil {
return m, nil
}
m.list.SetWidth(msg.Width)
return m, nil
case tea.KeyMsg:
switch keypress := msg.String(); keypress {
case "ctrl+c", "q", "esc":
m.state = quit
return m, tea.Quit
case "enter":
if len(m.URL) != 0 || m.list == nil || m.list.SelectedItem() == nil {
return m, nil
}
var font *Asset
var ok bool
if font, ok = m.list.SelectedItem().(*Asset); !ok {
m.err = fmt.Errorf("no font selected")
return m, tea.Quit
}
m.state = downloadFont
m.Asset = *font
defer func() {
go downloadFontZip(font.URL)
}()
m.spinner.Spinner = spinner.Globe
return m, m.spinner.Tick
case "up", "k":
if m.list != nil {
if m.list.Index() == 0 {
m.list.Select(len(m.list.Items()) - 1)
} else {
m.list.Select(m.list.Index() - 1)
}
}
return m, nil
case "down", "j":
if m.list != nil {
if m.list.Index() == len(m.list.Items())-1 {
m.list.Select(0)
} else {
m.list.Select(m.list.Index() + 1)
}
}
return m, nil
}
case progress.Message:
return m, m.progress.SetPercent(float64(msg))
case progress_.FrameMsg:
return m, m.progress.Update(msg)
case zipMsg:
m.state = installFont
defer func() {
go installFontZIP(msg, m)
}()
m.spinner.Spinner = spinner.Dot
return m, m.spinner.Tick
case successMsg:
m.state = done
m.families = msg
return m, tea.Quit
case errMsg:
m.err = msg
return m, tea.Quit
default:
s, cmd := m.spinner.Update(msg)
m.spinner = &s
return m, cmd
}
if m.list == nil {
return m, nil
}
lst, cmd := m.list.Update(msg)
m.list = &lst
return m, cmd
}
func (m *main) View() string {
if m.err != nil {
return textStyle.Render(m.err.Error())
}
switch m.state {
case getFonts:
return textStyle.Render(fmt.Sprintf("%s Downloading font list%s", m.spinner.View(), terminal.StartProgress()))
case selectFont:
return fmt.Sprintf("\n%s%s", m.list.View(), terminal.StopProgress())
case downloadFont:
return textStyle.Render(fmt.Sprintf("Downloading %s...\n%s", m.Name, m.progress.View()))
case unzipFont:
return textStyle.Render(fmt.Sprintf("%s Extracting %s", m.spinner.View(), m.Name))
case installFont:
return textStyle.Render(fmt.Sprintf("%s Installing %s", m.spinner.View(), m.Name))
case quit:
return textStyle.Render(fmt.Sprintf("No need to install a new font? That's cool.%s", terminal.StopProgress()))
case done:
if len(m.families) == 0 {
return textStyle.Render(fmt.Sprintf("No matching font families were installed. Try setting --zip-folder to the correct folder when using CascadiaCode (MS) or a custom font zip file. %s", terminal.StopProgress())) //nolint: lll
}
sb := text.NewBuilder()
sb.WriteString(fmt.Sprintf("Successfully installed %s 🚀\n\n%s", m.Name, terminal.StopProgress()))
sb.WriteString("The following font families are now available for configuration:\n\n")
for i, family := range m.families {
sb.WriteString(fmt.Sprintf(" • %s", family))
if i < len(m.families)-1 {
sb.WriteString("\n")
}
}
return textStyle.Render(sb.String())
}
return ""
}
func Run(font, zipFolder string, headless bool) (string, error) {
if headless {
return installHeadless(font, zipFolder)
}
return tui(font, zipFolder)
}
func tui(font, zipFolder string) (string, error) {
main := &main{
Asset: Asset{
Name: font,
URL: font,
Folder: zipFolder,
},
}
program = tea.NewProgram(main)
_, err := program.Run()
return main.Name, err
}
func installHeadless(font, zipFolder string) (string, error) {
// Handle local zip file
if IsLocalZipFile(font) {
data, err := os.ReadFile(font)
if err != nil {
return "", err
}
_, err = InstallZIP(data, zipFolder)
return font, err
}
return downloadAndInstall(font, zipFolder)
}
-29
View File
@@ -1,29 +0,0 @@
package progress
import (
"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
)
type Message float64
func NewModel() *Model {
p := progress.New(progress.WithScaledGradient("#800080", "#ffc0cb"))
return &Model{Model: p}
}
type Model struct {
progress.Model
}
func (m *Model) Update(msg tea.Msg) tea.Cmd {
model, cmd := m.Model.Update(msg)
m.Model = model.(progress.Model)
return cmd
}
func (m *Model) View() string {
return m.Model.View() + terminal.SetProgress(int(m.Percent()*100))
}
-35
View File
@@ -1,35 +0,0 @@
package progress
import (
"io"
tea "github.com/charmbracelet/bubbletea"
)
func NewReader(reader io.Reader, total int64, program *tea.Program) *Reader {
return &Reader{
Reader: reader,
program: program,
total: total,
}
}
type Reader struct {
io.Reader
program *tea.Program
total int64
current int64
}
func (r *Reader) Read(p []byte) (int, error) {
n, err := r.Reader.Read(p)
r.current += int64(n)
percent := float64(r.current) / float64(r.total)
if r.program != nil {
r.program.Send(Message(percent))
}
return n, err
}
-22
View File
@@ -24,9 +24,6 @@ require (
require (
github.com/ConradIrwin/font v0.2.1
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/gookit/goutil v0.8.0
github.com/hashicorp/hcl/v2 v2.24.0
github.com/invopop/jsonschema v0.14.0
@@ -59,32 +56,15 @@ require (
dmitri.shuralyov.com/font/woff2 v0.0.0-20180220214647-957792cbbdab // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.10.0 // indirect
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.27 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect
@@ -94,5 +74,3 @@ require (
golang.org/x/tools v0.47.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/atotto/clipboard v0.1.4 => github.com/jandedobbeleer/clipboard v0.1.4-1
-47
View File
@@ -22,36 +22,10 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -61,8 +35,6 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
@@ -88,8 +60,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
github.com/jandedobbeleer/clipboard v0.1.4-1 h1:rJehm5W0a3hvjcxyB3snqLBV4yvMBBc12JyMP7ngNQw=
github.com/jandedobbeleer/clipboard v0.1.4-1/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -99,30 +69,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=
github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
@@ -131,13 +89,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
@@ -196,7 +150,6 @@ golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
+17 -5
View File
@@ -30,7 +30,7 @@ your terminal emulator to use it.
}>
<TabItem value="oh-my-posh">
Oh My Posh has a CLI to help you select and install a [Nerd Font][nerdfonts]:
Oh My Posh has a CLI to install a [Nerd Font][nerdfonts]:
:::info
When running as root/administrator, the fonts will be installed system-wide.
@@ -39,14 +39,26 @@ By default, Oh My Posh installs the `.ttf` version of the font in case multiple
:::
```bash
oh-my-posh font install
oh-my-posh font install meslo
```
This will present a list of Nerd Font libraries, from which you can select `Meslo`, which includes the
`Meslo LGM NF` font we recommend. Note that you can also install it directly via:
We recommend `Meslo`, which includes the `Meslo LGM NF` font. To see every font you can install:
```bash
oh-my-posh font install meslo
oh-my-posh font list
```
That prints one name per line, so you can search it:
```bash
oh-my-posh font list | grep -i mono
```
You can also install straight from a URL or a local zip file:
```bash
oh-my-posh font install https://example.com/font.zip
oh-my-posh font install ./CascadiaCode.zip
```
</TabItem>
+2 -2
View File
@@ -59,10 +59,10 @@ curl -s https://ohmyposh.dev/install.sh | bash -s
Oh My Posh themes use glyphs and icons from Nerd Fonts. Install a Nerd Font with:
```bash
oh-my-posh font install
oh-my-posh font install meslo
```
Recommended font: Meslo LGM NF. Set the font in your terminal emulator after installation.
List every installable font with `oh-my-posh font list`. Recommended font: Meslo LGM NF. Set the font in your terminal emulator after installation.
---
+3 -2
View File
@@ -30,10 +30,11 @@ description: "Install, configure, or troubleshoot Oh My Posh/ohmyposh: shell ini
3. **Install a Nerd Font** — required for icons and glyphs:
```bash
oh-my-posh font install
oh-my-posh font install meslo
```
Recommended: **Meslo LGM NF**. Set it in the terminal emulator's font settings after installing.
`oh-my-posh font list` prints every installable font. Recommended: **Meslo LGM NF**.
Set it in the terminal emulator's font settings after installing.
4. **Customize the prompt** → see [configuration](/skills/ohmyposh/configuration.md)