Separate theme pkg

pull/300/head
Anton Medvedev 3 months ago
parent 8ca74b870d
commit 6f6c9e3585
No known key found for this signature in database

@ -111,5 +111,16 @@ func Reduce(args []string) {
return
}
fmt.Println(output)
node, err := jsonx.Parse([]byte(output))
if err != nil {
println(err.Error())
os.Exit(1)
}
if len(node.Value) > 0 && node.Value[0] == '"' {
s, _ := strconv.Unquote(string(node.Value))
fmt.Println(s)
return
}
fmt.Print(node.PrettyPrint())
}

@ -5,6 +5,8 @@ import (
"strconv"
"strings"
"unicode/utf8"
"github.com/antonmedv/fx/internal/utils"
)
type jsonParser struct {
@ -96,7 +98,7 @@ func (p *jsonParser) parseString() *Node {
var unicode string
for i := 0; i < 4; i++ {
p.next()
if !isHexDigit(p.lastChar) {
if !utils.IsHexDigit(p.lastChar) {
panic(fmt.Sprintf("Invalid Unicode escape sequence '\\u%s%c'", unicode, p.lastChar))
}
unicode += string(p.lastChar)
@ -134,7 +136,7 @@ func (p *jsonParser) parseNumber() *Node {
// Handle negative numbers
if p.lastChar == '-' {
p.next()
if !isDigit(p.lastChar) {
if !utils.IsDigit(p.lastChar) {
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
}
}
@ -143,7 +145,7 @@ func (p *jsonParser) parseNumber() *Node {
if p.lastChar == '0' {
p.next()
} else {
for isDigit(p.lastChar) {
for utils.IsDigit(p.lastChar) {
p.next()
}
}
@ -151,10 +153,10 @@ func (p *jsonParser) parseNumber() *Node {
// Decimal portion
if p.lastChar == '.' {
p.next()
if !isDigit(p.lastChar) {
if !utils.IsDigit(p.lastChar) {
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
}
for isDigit(p.lastChar) {
for utils.IsDigit(p.lastChar) {
p.next()
}
}
@ -165,10 +167,10 @@ func (p *jsonParser) parseNumber() *Node {
if p.lastChar == '+' || p.lastChar == '-' {
p.next()
}
if !isDigit(p.lastChar) {
if !utils.IsDigit(p.lastChar) {
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
}
for isDigit(p.lastChar) {
for utils.IsDigit(p.lastChar) {
p.next()
}
}

@ -2,6 +2,8 @@ package jsonx
import (
"strings"
"github.com/antonmedv/fx/internal/theme"
)
func (n *Node) String() string {
@ -28,3 +30,32 @@ func (n *Node) String() string {
return out.String()
}
func (n *Node) PrettyPrint() string {
var out strings.Builder
it := n
for it != nil {
for ident := 0; ident < int(it.Depth); ident++ {
out.WriteString(" ")
}
if it.Key != nil {
out.Write(theme.CurrentTheme.Key(it.Key))
out.Write(theme.Colon)
}
if it.Value != nil {
out.Write(theme.Value(it.Value, false, false)(it.Value))
}
if it.Comma {
out.Write(theme.Comma)
}
out.WriteByte('\n')
if it.IsCollapsed() {
it = it.Collapsed
} else {
it = it.Next
}
}
return out.String()
}

@ -1,4 +1,4 @@
package main
package theme
import (
"encoding/json"
@ -10,42 +10,44 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/muesli/termenv"
"github.com/antonmedv/fx/internal/utils"
)
type theme struct {
Cursor color
Syntax color
Preview color
StatusBar color
Search color
Key color
String color
Null color
Boolean color
Number color
Size color
type Theme struct {
Cursor Color
Syntax Color
Preview Color
StatusBar Color
Search Color
Key Color
String Color
Null Color
Boolean Color
Number Color
Size Color
}
type color func(s []byte) []byte
type Color func(s []byte) []byte
func valueStyle(b []byte, selected, chunk bool) color {
func Value(b []byte, selected, chunk bool) Color {
if selected {
return currentTheme.Cursor
return CurrentTheme.Cursor
} else if chunk {
return currentTheme.String
return CurrentTheme.String
} else {
switch b[0] {
case '"':
return currentTheme.String
return CurrentTheme.String
case 't', 'f':
return currentTheme.Boolean
return CurrentTheme.Boolean
case 'n':
return currentTheme.Null
return CurrentTheme.Null
case '{', '[', '}', ']':
return currentTheme.Syntax
return CurrentTheme.Syntax
default:
if isDigit(b[0]) || b[0] == '-' {
return currentTheme.Number
if utils.IsDigit(b[0]) || b[0] == '-' {
return CurrentTheme.Number
}
return noColor
}
@ -53,7 +55,7 @@ func valueStyle(b []byte, selected, chunk bool) color {
}
var (
termOutput = termenv.NewOutput(os.Stderr)
TermOutput = termenv.NewOutput(os.Stderr)
)
func init() {
@ -71,51 +73,51 @@ func init() {
showSizesValue, ok := os.LookupEnv("FX_SHOW_SIZE")
if ok {
showSizesValue := strings.ToLower(showSizesValue)
showSizes = showSizesValue == "true" || showSizesValue == "yes" || showSizesValue == "on" || showSizesValue == "1"
ShowSizes = showSizesValue == "true" || showSizesValue == "yes" || showSizesValue == "on" || showSizesValue == "1"
}
currentTheme, ok = themes[themeId]
CurrentTheme, ok = themes[themeId]
if !ok {
_, _ = fmt.Fprintf(os.Stderr, "fx: unknown theme %q, available themes: %v\n", themeId, themeNames)
os.Exit(1)
}
if termOutput.ColorProfile() == termenv.Ascii {
currentTheme = themes["0"]
if TermOutput.ColorProfile() == termenv.Ascii {
CurrentTheme = themes["0"]
}
colon = currentTheme.Syntax([]byte{':', ' '})
colonPreview = currentTheme.Preview([]byte{':'})
comma = currentTheme.Syntax([]byte{','})
empty = currentTheme.Preview([]byte{'~'})
dot3 = currentTheme.Preview([]byte("…"))
closeCurlyBracket = currentTheme.Syntax([]byte{'}'})
closeSquareBracket = currentTheme.Syntax([]byte{']'})
Colon = CurrentTheme.Syntax([]byte{':', ' '})
ColonPreview = CurrentTheme.Preview([]byte{':'})
Comma = CurrentTheme.Syntax([]byte{','})
Empty = CurrentTheme.Preview([]byte{'~'})
Dot3 = CurrentTheme.Preview([]byte("…"))
CloseCurlyBracket = CurrentTheme.Syntax([]byte{'}'})
CloseSquareBracket = CurrentTheme.Syntax([]byte{']'})
}
var (
themeNames []string
currentTheme theme
CurrentTheme Theme
defaultCursor = toColor(lipgloss.NewStyle().Reverse(true).Render)
defaultPreview = toColor(lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render)
defaultStatusBar = toColor(lipgloss.NewStyle().Background(lipgloss.Color("7")).Foreground(lipgloss.Color("0")).Render)
defaultSearch = toColor(lipgloss.NewStyle().Background(lipgloss.Color("11")).Foreground(lipgloss.Color("16")).Render)
defaultNull = fg("243")
defaultSize = toColor(lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render)
showSizes = false
ShowSizes = false
)
var (
colon []byte
colonPreview []byte
comma []byte
empty []byte
dot3 []byte
closeCurlyBracket []byte
closeSquareBracket []byte
Colon []byte
ColonPreview []byte
Comma []byte
Empty []byte
Dot3 []byte
CloseCurlyBracket []byte
CloseSquareBracket []byte
)
var themes = map[string]theme{
var themes = map[string]Theme{
"0": {
Cursor: defaultCursor,
Syntax: noColor,
@ -268,21 +270,21 @@ func noColor(s []byte) []byte {
return s
}
func toColor(f func(s ...string) string) color {
func toColor(f func(s ...string) string) Color {
return func(s []byte) []byte {
return []byte(f(string(s)))
}
}
func fg(color string) color {
func fg(color string) Color {
return toColor(lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render)
}
func boldFg(color string) color {
func boldFg(color string) Color {
return toColor(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render)
}
func themeTester() {
func ThemeTester() {
title := lipgloss.NewStyle().Bold(true)
for _, name := range themeNames {
t := themes[name]
@ -319,7 +321,7 @@ func themeTester() {
}
}
func exportThemes() {
func ExportThemes() {
lipgloss.SetColorProfile(termenv.ANSI256) // Export in Terminal.app compatible colors
placeholder := []byte{'_'}
extract := func(b []byte) string {

@ -1,9 +1,9 @@
package jsonx
package utils
func isHexDigit(ch byte) bool {
func IsHexDigit(ch byte) bool {
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
}
func isDigit(ch byte) bool {
func IsDigit(ch byte) bool {
return ch >= '0' && ch <= '9'
}

@ -27,6 +27,7 @@ import (
"github.com/antonmedv/fx/internal/complete"
. "github.com/antonmedv/fx/internal/jsonx"
"github.com/antonmedv/fx/internal/theme"
jsonpath "github.com/antonmedv/fx/path"
)
@ -72,10 +73,10 @@ func main() {
fmt.Println(version)
return
case "--themes":
themeTester()
theme.ThemeTester()
return
case "--export-themes":
exportThemes()
theme.ExportThemes()
return
default:
args = append(args, arg)
@ -177,7 +178,7 @@ func main() {
search: newSearch(),
}
lipgloss.SetColorProfile(termOutput.ColorProfile())
lipgloss.SetColorProfile(theme.TermOutput.ColorProfile())
withMouse := tea.WithMouseCellMotion()
if _, ok := os.LookupEnv("FX_NO_MOUSE"); ok {
@ -680,12 +681,12 @@ func (m *model) scrollIntoView() {
func (m *model) View() string {
if m.showHelp {
statusBar := flex(m.termWidth, ": press q or ? to close help", "")
return m.help.View() + "\n" + string(currentTheme.StatusBar([]byte(statusBar)))
return m.help.View() + "\n" + string(theme.CurrentTheme.StatusBar([]byte(statusBar)))
}
if m.showPreview {
statusBar := flex(m.termWidth, m.cursorPath(), m.fileName)
return m.preview.View() + "\n" + string(currentTheme.StatusBar([]byte(statusBar)))
return m.preview.View() + "\n" + string(theme.CurrentTheme.StatusBar([]byte(statusBar)))
}
var screen []byte
@ -707,7 +708,7 @@ func (m *model) View() string {
if n.Key != nil {
screen = append(screen, m.prettyKey(n, isSelected)...)
screen = append(screen, colon...)
screen = append(screen, theme.Colon...)
isSelected = false // don't highlight the key's value
}
@ -716,26 +717,26 @@ func (m *model) View() string {
if n.IsCollapsed() {
if n.Value[0] == '{' {
if n.Collapsed.Key != nil {
screen = append(screen, currentTheme.Preview(n.Collapsed.Key)...)
screen = append(screen, colonPreview...)
screen = append(screen, theme.CurrentTheme.Preview(n.Collapsed.Key)...)
screen = append(screen, theme.ColonPreview...)
}
screen = append(screen, dot3...)
screen = append(screen, closeCurlyBracket...)
screen = append(screen, theme.Dot3...)
screen = append(screen, theme.CloseCurlyBracket...)
} else if n.Value[0] == '[' {
screen = append(screen, dot3...)
screen = append(screen, closeSquareBracket...)
screen = append(screen, theme.Dot3...)
screen = append(screen, theme.CloseSquareBracket...)
}
if n.End != nil && n.End.Comma {
screen = append(screen, comma...)
screen = append(screen, theme.Comma...)
}
}
if n.Comma {
screen = append(screen, comma...)
screen = append(screen, theme.Comma...)
}
if showSizes && len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') {
if theme.ShowSizes && len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') {
if n.IsCollapsed() || n.Size > 1 {
screen = append(screen, currentTheme.Size([]byte(fmt.Sprintf(" // %d", n.Size)))...)
screen = append(screen, theme.CurrentTheme.Size([]byte(fmt.Sprintf(" // %d", n.Size)))...)
}
}
@ -745,7 +746,7 @@ func (m *model) View() string {
}
for i := printedLines; i < m.viewHeight(); i++ {
screen = append(screen, empty...)
screen = append(screen, theme.Empty...)
screen = append(screen, '\n')
}
@ -753,7 +754,7 @@ func (m *model) View() string {
screen = append(screen, m.digInput.View()...)
} else {
statusBar := flex(m.termWidth, m.cursorPath(), m.fileName)
screen = append(screen, currentTheme.StatusBar([]byte(statusBar))...)
screen = append(screen, theme.CurrentTheme.StatusBar([]byte(statusBar))...)
}
if m.yank {
@ -785,9 +786,9 @@ func (m *model) View() string {
func (m *model) prettyKey(node *Node, selected bool) []byte {
b := node.Key
style := currentTheme.Key
style := theme.CurrentTheme.Key
if selected {
style = currentTheme.Cursor
style = theme.CurrentTheme.Cursor
}
if indexes, ok := m.search.keys[node]; ok {
@ -796,9 +797,9 @@ func (m *model) prettyKey(node *Node, selected bool) []byte {
if i%2 == 0 {
out = append(out, style(p.b)...)
} else if p.index == m.search.cursor {
out = append(out, currentTheme.Cursor(p.b)...)
out = append(out, theme.CurrentTheme.Cursor(p.b)...)
} else {
out = append(out, currentTheme.Search(p.b)...)
out = append(out, theme.CurrentTheme.Search(p.b)...)
}
}
return out
@ -819,7 +820,7 @@ func (m *model) prettyPrint(node *Node, selected bool) []byte {
return b
}
style := valueStyle(b, selected, node.Chunk != nil)
style := theme.Value(b, selected, node.Chunk != nil)
if indexes, ok := m.search.values[node]; ok {
var out []byte
@ -827,9 +828,9 @@ func (m *model) prettyPrint(node *Node, selected bool) []byte {
if i%2 == 0 {
out = append(out, style(p.b)...)
} else if p.index == m.search.cursor {
out = append(out, currentTheme.Cursor(p.b)...)
out = append(out, theme.CurrentTheme.Cursor(p.b)...)
} else {
out = append(out, currentTheme.Search(p.b)...)
out = append(out, theme.CurrentTheme.Search(p.b)...)
}
}
return out

@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/antonmedv/fx/internal/jsonx"
"github.com/antonmedv/fx/internal/theme"
)
func init() {
@ -105,8 +106,8 @@ func TestCollapseRecursive(t *testing.T) {
}
func TestCollapseRecursiveWithSizes(t *testing.T) {
showSizes = true
defer func() { showSizes = true }()
theme.showSizes = true
defer func() { theme.showSizes = true }()
tm := prepare(t)

@ -4,10 +4,6 @@ import (
"strings"
)
func isDigit(ch byte) bool {
return ch >= '0' && ch <= '9'
}
func regexCase(code string) (string, bool) {
if strings.HasSuffix(code, "/i") {
return code[:len(code)-2], true

Loading…
Cancel
Save