diff --git a/src/cli/font.go b/src/cli/font.go index a1212445eaa2..f52d502bf157 100644 --- a/src/cli/font.go +++ b/src/cli/font.go @@ -11,7 +11,7 @@ import ( ) var ( - ttf bool + zipFolder string fontCmd = &cobra.Command{ Use: "font [install|configure]", @@ -47,7 +47,7 @@ This command is used to install fonts and configure the font in your terminal. terminal.Init(env.Shell()) - font.Run(fontName, env.Cache(), env.Root(), ttf) + font.Run(fontName, env.Cache(), env.Root(), zipFolder) return case "configure": @@ -60,6 +60,6 @@ This command is used to install fonts and configure the font in your terminal. ) func init() { - fontCmd.Flags().BoolVar(&ttf, "ttf", false, "fetch the TTF version of the font") + fontCmd.Flags().StringVar(&zipFolder, "zip-folder", "", "the folder inside the zip file to install fonts from") RootCmd.AddCommand(fontCmd) } diff --git a/src/font/cli.go b/src/font/cli.go index 1a495b1dab7c..ec77ec36dab3 100644 --- a/src/font/cli.go +++ b/src/font/cli.go @@ -72,14 +72,14 @@ const ( ) type main struct { - err error - list *list.Model - font string - families []string - spinner spinner.Model - state state - system bool - ttf bool + err error + list *list.Model + font string + zipFolder string + families []string + spinner spinner.Model + state state + system bool } func (m *main) buildFontList(nerdFonts []*Asset) { @@ -121,18 +121,18 @@ func downloadFontZip(location string) { program.Send(zipMsg(zipFile)) } -func installLocalFontZIP(zipFile string, user, ttf bool) { - data, err := os.ReadFile(zipFile) +func installLocalFontZIP(m *main) { + data, err := os.ReadFile(m.font) if err != nil { program.Send(errMsg(err)) return } - installFontZIP(data, user, ttf) + installFontZIP(data, m) } -func installFontZIP(zipFile []byte, user, ttf bool) { - families, err := InstallZIP(zipFile, user, ttf) +func installFontZIP(zipFile []byte, m *main) { + families, err := InstallZIP(zipFile, m) if err != nil { program.Send(errMsg(err)) return @@ -148,21 +148,30 @@ func (m *main) Init() tea.Cmd { if len(m.font) != 0 && !isLocalZipFile() { m.state = downloadFont + if !strings.HasPrefix(m.font, "https") { - m.font = fmt.Sprintf("https://github.com/ryanoasis/nerd-fonts/releases/latest/download/%s.zip", m.font) + if strings.HasPrefix(m.font, "CascadiaCode-") { + version := strings.TrimPrefix(m.font, "CascadiaCode-") + m.font = fmt.Sprintf("https://github.com/microsoft/cascadia-code/releases/download/v%s/%s.zip", version, m.font) + } else { + m.font = fmt.Sprintf("https://github.com/ryanoasis/nerd-fonts/releases/latest/download/%s.zip", m.font) + } } + defer func() { go downloadFontZip(m.font) }() + m.spinner.Spinner = spinner.Globe return m.spinner.Tick } defer func() { if isLocalZipFile() { - go installLocalFontZIP(m.font, m.system, m.ttf) + go installLocalFontZIP(m) return } + go getFontsList() }() @@ -171,6 +180,7 @@ func (m *main) Init() tea.Cmd { s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) m.spinner = s m.state = getFonts + if isLocalZipFile() { m.state = unzipFont } @@ -240,7 +250,7 @@ func (m *main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case zipMsg: m.state = installFont defer func() { - go installFontZIP(msg, m.system, m.ttf) + go installFontZIP(msg, m) }() m.spinner.Spinner = spinner.Dot return m, m.spinner.Tick @@ -288,6 +298,10 @@ func (m *main) View() string { 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 Cascadia Code or a custom font zip file %s", terminal.StopProgress())) //nolint: lll + } + var builder strings.Builder builder.WriteString(fmt.Sprintf("Successfully installed %s 🚀\n\n%s", m.font, terminal.StopProgress())) @@ -307,11 +321,11 @@ func (m *main) View() string { return "" } -func Run(font string, ch cache_.Cache, root, ttf bool) { +func Run(font string, ch cache_.Cache, root bool, zipFolder string) { main := &main{ - font: font, - system: root, - ttf: ttf, + font: font, + system: root, + zipFolder: zipFolder, } cache = ch diff --git a/src/font/install.go b/src/font/install.go index c4feff8bf966..673accdc92e6 100644 --- a/src/font/install.go +++ b/src/font/install.go @@ -25,13 +25,7 @@ func contains[S ~[]E, E comparable](s S, e E) bool { return false } -func InstallZIP(data []byte, user, ttf bool) ([]string, error) { - // prefer OTF over TTF; otherwise prefer the first font we find - extension := ".otf" - if ttf { - extension = ".ttf" - } - +func InstallZIP(data []byte, m *main) ([]string, error) { var families []string bytesReader := bytes.NewReader(data) @@ -42,46 +36,56 @@ func InstallZIP(data []byte, user, ttf bool) ([]string, error) { fonts := make(map[string]*Font) - for _, zf := range zipReader.File { + root := len(m.zipFolder) == 0 + + for _, file := range zipReader.File { // prevent zipslip attacks // https://security.snyk.io/research/zip-slip-vulnerability - if strings.Contains(zf.Name, "..") { + // and only process files which are in the specified folder + if strings.Contains(file.Name, "..") || !strings.HasPrefix(file.Name, m.zipFolder) { + continue + } + + fontFileName := path.Base(file.Name) + + // do not install fonts that are not in the root folder when specified as such + if root && fontFileName != file.Name { continue } - rc, err := zf.Open() + fontReader, err := file.Open() if err != nil { - return families, err + continue } - defer rc.Close() + defer fontReader.Close() - data, err := io.ReadAll(rc) + fontBytes, err := io.ReadAll(fontReader) if err != nil { - return families, err + continue } - fontData, err := newFont(path.Base(zf.Name), data) + font, err := newFont(fontFileName, fontBytes) if err != nil { continue } - if _, found := fonts[fontData.Name]; !found { - fonts[fontData.Name] = fontData + if _, found := fonts[font.Name]; !found { + fonts[font.Name] = font continue } - // respect the user's preference for TTF or OTF - first := strings.ToLower(path.Ext(fonts[fontData.Name].FileName)) - second := strings.ToLower(path.Ext(fontData.FileName)) - if first != second && second == extension { - fonts[fontData.Name] = fontData + // prefer .ttf files over other file types when we have a duplicate + first := strings.ToLower(path.Ext(fonts[font.Name].FileName)) + second := strings.ToLower(path.Ext(font.FileName)) + if first != second && second == ".ttf" { + fonts[font.Name] = font } } for _, font := range fonts { - if err = install(font, user); err != nil { - return families, err + if err = install(font, m.system); err != nil { + continue } if found := contains(families, font.Family); !found { diff --git a/website/docs/installation/fonts.mdx b/website/docs/installation/fonts.mdx index 3023de004013..8cfd0ad21637 100644 --- a/website/docs/installation/fonts.mdx +++ b/website/docs/installation/fonts.mdx @@ -34,6 +34,7 @@ Oh My Posh has a CLI to help you select and install a [Nerd Font][nerdfonts]: :::info When running as root/administrator, the fonts will be installed system-wide. When running as a regular user, the fonts will be installed in the user's font directory. +By default, Oh My Posh installs the `.ttf` version of the font in case multiple versions are available. ::: ```bash @@ -46,10 +47,10 @@ This will present a list of Nerd Font libraries, from which you can select `Mes oh-my-posh font install meslo ``` -By default, Oh My Posh installs the `.otf` version of the font. If you prefer the `.ttf` version, you can specify it with the `--ttf` flag: +If you have a font that has specific flavors of a font inside sub folders, you can specify the sub folder name: ```bash -oh-my-posh font install meslo --ttf +oh-my-posh font install --zip-folder ttf/static CascadiaCode-2407.24 ```