package style import ( "git.gophernest.net/azpect/TextEditor/internal/core" "github.com/alecthomas/chroma/v2" "github.com/charmbracelet/lipgloss" ) // Styles holds all the visual styling for the editor. type Styles struct { // Cursor styles by mode CursorNormal lipgloss.Style CursorInsert lipgloss.Style CursorCommand lipgloss.Style // Gutter (line numbers) Gutter lipgloss.Style GutterCurrentLine lipgloss.Style // Visual mode VisualHighlight lipgloss.Style VisualAnchor lipgloss.Style // debugging // Status bar StatusBar lipgloss.Style StatusBarActive lipgloss.Style // Command line CommandError lipgloss.Style CommandOutputBorder lipgloss.Style CommandContinueMessage lipgloss.Style // General Styles LineStyle lipgloss.Style // This is a simple background with no text coloring BackgroundStyle lipgloss.Style // This is just the background // Chroma data chromaStyle *chroma.Style } // DefaultStyles: Returns the default editor color scheme. func DefaultStyles() Styles { return Styles{ CursorNormal: lipgloss.NewStyle().Reverse(true), CursorInsert: lipgloss.NewStyle().Underline(true), CursorCommand: lipgloss.NewStyle().Reverse(true), Gutter: lipgloss.NewStyle(). Background(lipgloss.Color("236")). Foreground(lipgloss.Color("243")), GutterCurrentLine: lipgloss.NewStyle(). Background(lipgloss.Color("236")). Foreground(lipgloss.Color("#d69d00")), VisualHighlight: lipgloss.NewStyle(). Background(lipgloss.Color("#7a6a00")), VisualAnchor: lipgloss.NewStyle(). Background(lipgloss.Color("#a89020")), StatusBar: lipgloss.NewStyle(). Background(lipgloss.Color("236")). Foreground(lipgloss.Color("243")), StatusBarActive: lipgloss.NewStyle(). Background(lipgloss.Color("62")). Foreground(lipgloss.Color("230")), CommandError: lipgloss.NewStyle(). Foreground(lipgloss.Color("#e3203a")), CommandOutputBorder: lipgloss.NewStyle(). Background(lipgloss.Color("#000000")), CommandContinueMessage: lipgloss.NewStyle(). Foreground(lipgloss.Color("#546fba")), chromaStyle: nil, } } func ChromaStyles(chromaStyle *chroma.Style) Styles { bgString := chromaStyle.Get(chroma.Background).Background.String() lineNumbers := chromaStyle.Get(chroma.LineTableTD) lineHighlight := chromaStyle.Get(chroma.LineHighlight) return Styles{ CursorNormal: lipgloss.NewStyle(). Background(lipgloss.Color(bgString)). Reverse(true), CursorInsert: lipgloss.NewStyle(). Background(lipgloss.Color(bgString)). Underline(true), CursorCommand: lipgloss.NewStyle(). Background(lipgloss.Color(bgString)). Reverse(true), Gutter: lipgloss.NewStyle(). Background(lipgloss.Color( darkenColor(lineNumbers.Background, 0.9).String()), ). Foreground(lipgloss.Color(lineNumbers.Colour.String())), GutterCurrentLine: lipgloss.NewStyle(). Background(lipgloss.Color( darkenColor(lineNumbers.Background, 0.9).String()), ). Foreground(lipgloss.Color(lineNumbers.Colour.String())), VisualHighlight: lipgloss.NewStyle(). Background(lipgloss.Color(lineHighlight.Background.String())). Foreground(lipgloss.Color(lineHighlight.Colour.String())), VisualAnchor: lipgloss.NewStyle(). Background(lipgloss.Color(lineHighlight.Background.String())). Foreground(lipgloss.Color(lineHighlight.Colour.String())), StatusBar: lipgloss.NewStyle(). Background(lipgloss.Color(bgString)). Foreground(lipgloss.Color("243")), StatusBarActive: lipgloss.NewStyle(). Background(lipgloss.Color(bgString)). Foreground(lipgloss.Color("230")), CommandError: lipgloss.NewStyle(). Background(lipgloss.Color(bgString)). Foreground(lipgloss.Color("#e3203a")), CommandOutputBorder: lipgloss.NewStyle(). Background( lipgloss.Color( darkenColor( chromaStyle.Get(chroma.Background).Background, 0.5). String(), ), ), CommandContinueMessage: lipgloss.NewStyle(). Background(lipgloss.Color(bgString)). Foreground(lipgloss.Color("#546fba")), LineStyle: lipgloss.NewStyle(). Foreground(lipgloss.Color(chromaStyle.Get(chroma.Line).Colour.String())). Background(lipgloss.Color(bgString)), BackgroundStyle: lipgloss.NewStyle().Background(lipgloss.Color(bgString)), chromaStyle: chromaStyle, } } // Styles.DefaultCursorStyle: Returns the appropriate cursor style for the given mode. func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style { switch mode { case core.InsertMode: return s.CursorInsert case core.CommandMode: return s.CursorCommand default: return s.CursorNormal } } // Styles.CursorStyle: Returns a cursor style derived from a chroma style. This function should preferred // over the DefaultCursorStyle, but in cases where there is no style to apply, the DefaultCursorStyle // will always work. func (s Styles) CursorStyle(mode core.Mode, style lipgloss.Style) lipgloss.Style { switch mode { case core.NormalMode, core.VisualLineMode, core.VisualBlockMode, core.VisualMode: return lipgloss.NewStyle(). Background(style.GetForeground()). Foreground(style.GetBackground()) default: return lipgloss.NewStyle(). Background(s.BackgroundStyle.GetBackground()). Foreground(style.GetForeground()). Underline(true) } } // Styles.VisualHighlightWithTextColor: Works analogously to CursorStyle vs DefaultCursorStyle. When a // style is available, this function should be used, so the text color will be rendered in front // of the background. Otherwise, the VisualHighlight property will always work. func (s Styles) VisualHighlightWithTextColor(style lipgloss.Style) lipgloss.Style { return lipgloss.NewStyle(). Background(s.VisualHighlight.GetBackground()). Foreground(style.GetForeground()) } // Styles.MakeStyleMap: Generates a style map for a single line. A style map is a mapping from // column a lipgloss style. Cursor styles are not handled by this map, but they can be derived // by inverting the background and foreground (and rolling back to the default). func (s Styles) MakeStyleMap(lexer chroma.Lexer, line string) []lipgloss.Style { m := make([]lipgloss.Style, len(line)) iter, err := lexer.Tokenise(nil, line) if err != nil { panic(err) } col := 0 for _, token := range iter.Tokens() { entry := s.chromaStyle.Get(token.Type) s := lipgloss.NewStyle(). Background(lipgloss.Color(entry.Background.String())). Foreground(lipgloss.Color(entry.Colour.String())) for _, char := range token.Value { if char == '\n' { continue } if col < len(m) { m[col] = s } col++ } } return m } // darkenColor: Uses a factor (0.0 to 1.0) to darken a color using its opacity. func darkenColor(c chroma.Colour, factor float64) chroma.Colour { r := uint8(float64(c.Red()) * factor) g := uint8(float64(c.Green()) * factor) b := uint8(float64(c.Blue()) * factor) return chroma.NewColour(r, g, b) }