mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2026-08-24 10:14:12 -05:00
367 lines
7.4 KiB
Go
367 lines
7.4 KiB
Go
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)
|
|
}
|