feat: created theme module
This means we can finally create some themes! But the treesitter mapping is not complete.
This commit is contained in:
parent
760770c564
commit
77416bc0a4
@ -4,17 +4,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/program"
|
"git.gophernest.net/azpect/TextEditor/internal/program"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/theme"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
// main: Entry point for the Gim text editor. Creates a buffer and window,
|
// main: Entry point for the Gim text editor. Creates a buffer and window,
|
||||||
// initializes the editor model, and runs the BubbleTea TUI program.
|
// initializes the editor model, and runs the BubbleTea TUI program.
|
||||||
func main() {
|
func main() {
|
||||||
if err := theme.RegisterAll(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// <exe> <filename>
|
// <exe> <filename>
|
||||||
args := os.Args[1:]
|
args := os.Args[1:]
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package action
|
|||||||
import (
|
import (
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/theme"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,6 +57,8 @@ type Model interface {
|
|||||||
SetSettings(s core.EditorSettings)
|
SetSettings(s core.EditorSettings)
|
||||||
Styles() style.Styles
|
Styles() style.Styles
|
||||||
SetStyles(s style.Styles)
|
SetStyles(s style.Styles)
|
||||||
|
Theme() theme.EditorTheme
|
||||||
|
SetTheme(t theme.EditorTheme)
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// Registers
|
// Registers
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package action
|
|||||||
import (
|
import (
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/theme"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ type MockModel struct {
|
|||||||
CommandHistoryCur int
|
CommandHistoryCur int
|
||||||
LastFindVal core.LastFindCommand
|
LastFindVal core.LastFindCommand
|
||||||
StylesVal style.Styles
|
StylesVal style.Styles
|
||||||
|
ThemeVal theme.EditorTheme
|
||||||
LastChangeKeysList []string
|
LastChangeKeysList []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,10 +93,10 @@ func (m *MockModel) SetBuffers(bufs []*core.Buffer) { m.BuffersList = bufs }
|
|||||||
func (m *MockModel) ActiveBuffer() *core.Buffer { return m.ActiveWindowVal.Buffer }
|
func (m *MockModel) ActiveBuffer() *core.Buffer { return m.ActiveWindowVal.Buffer }
|
||||||
|
|
||||||
// Insert Mode State
|
// Insert Mode State
|
||||||
func (m *MockModel) InsertKeys() []string { return m.InsertKeysList }
|
func (m *MockModel) InsertKeys() []string { return m.InsertKeysList }
|
||||||
func (m *MockModel) SetInsertKeys(keys []string) { m.InsertKeysList = keys }
|
func (m *MockModel) SetInsertKeys(keys []string) { m.InsertKeysList = keys }
|
||||||
func (m *MockModel) SetInsertRecording(count int, a Action) {}
|
func (m *MockModel) SetInsertRecording(count int, a Action) {}
|
||||||
func (m *MockModel) ExitInsertMode() {}
|
func (m *MockModel) ExitInsertMode() {}
|
||||||
func (m *MockModel) SetLastFind(char string, forward, inclusive bool) {
|
func (m *MockModel) SetLastFind(char string, forward, inclusive bool) {
|
||||||
m.LastFindVal = core.LastFindCommand{Char: char, Forward: forward, Inclusive: inclusive}
|
m.LastFindVal = core.LastFindCommand{Char: char, Forward: forward, Inclusive: inclusive}
|
||||||
}
|
}
|
||||||
@ -119,6 +121,8 @@ func (m *MockModel) Settings() core.EditorSettings { return m.SettingsVal }
|
|||||||
func (m *MockModel) SetSettings(s core.EditorSettings) { m.SettingsVal = s }
|
func (m *MockModel) SetSettings(s core.EditorSettings) { m.SettingsVal = s }
|
||||||
func (m *MockModel) Styles() style.Styles { return m.StylesVal }
|
func (m *MockModel) Styles() style.Styles { return m.StylesVal }
|
||||||
func (m *MockModel) SetStyles(s style.Styles) { m.StylesVal = s }
|
func (m *MockModel) SetStyles(s style.Styles) { m.StylesVal = s }
|
||||||
|
func (m *MockModel) Theme() theme.EditorTheme { return m.ThemeVal }
|
||||||
|
func (m *MockModel) SetTheme(t theme.EditorTheme) { m.ThemeVal = t }
|
||||||
|
|
||||||
// Registers
|
// Registers
|
||||||
func (m *MockModel) Registers() map[rune]core.Register { return m.RegistersMap }
|
func (m *MockModel) Registers() map[rune]core.Register { return m.RegistersMap }
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"git.gophernest.net/azpect/TextEditor/internal/input"
|
"git.gophernest.net/azpect/TextEditor/internal/input"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/syntax"
|
"git.gophernest.net/azpect/TextEditor/internal/syntax"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/theme"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ type Model struct {
|
|||||||
|
|
||||||
// Visual styles
|
// Visual styles
|
||||||
styles style.Styles
|
styles style.Styles
|
||||||
|
theme theme.EditorTheme
|
||||||
syntax syntax.Engine
|
syntax syntax.Engine
|
||||||
|
|
||||||
// Dot operator state
|
// Dot operator state
|
||||||
@ -351,6 +353,14 @@ func (m *Model) SetStyles(s style.Styles) {
|
|||||||
m.styles = s
|
m.styles = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) Theme() theme.EditorTheme {
|
||||||
|
return m.theme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) SetTheme(t theme.EditorTheme) {
|
||||||
|
m.theme = t
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Model) Syntax() syntax.Engine {
|
func (m *Model) Syntax() syntax.Engine {
|
||||||
return m.syntax
|
return m.syntax
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"git.gophernest.net/azpect/TextEditor/internal/input"
|
"git.gophernest.net/azpect/TextEditor/internal/input"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/syntax"
|
"git.gophernest.net/azpect/TextEditor/internal/syntax"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/theme/themes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ModelBuilder struct {
|
type ModelBuilder struct {
|
||||||
@ -34,6 +35,7 @@ func NewModelBuilder() *ModelBuilder {
|
|||||||
registers: core.DefaultRegisters(),
|
registers: core.DefaultRegisters(),
|
||||||
styles: editorStyles,
|
styles: editorStyles,
|
||||||
syntax: syntax.NewTreeSitterEngine(editorStyles),
|
syntax: syntax.NewTreeSitterEngine(editorStyles),
|
||||||
|
theme: themes.NewDefaultTheme(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/syntax"
|
"git.gophernest.net/azpect/TextEditor/internal/syntax"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/theme"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ func (m Model) View() string {
|
|||||||
// Each window has its own line numbers and gutter
|
// Each window has its own line numbers and gutter
|
||||||
// Each window has its own status bar and mode
|
// Each window has its own status bar and mode
|
||||||
|
|
||||||
styles := m.Styles()
|
t := m.Theme()
|
||||||
options := win.Options
|
options := win.Options
|
||||||
|
|
||||||
// Adjust gutter to fit line len
|
// Adjust gutter to fit line len
|
||||||
@ -29,7 +29,7 @@ func (m Model) View() string {
|
|||||||
options.GutterSize = max(options.GutterSize, maxLineLen+2)
|
options.GutterSize = max(options.GutterSize, maxLineLen+2)
|
||||||
|
|
||||||
// Draw window
|
// Draw window
|
||||||
view := viewWindow(win, styles, options, m.Mode(), m.Syntax())
|
view := viewWindow(win, t, options, m.Mode(), m.Syntax())
|
||||||
|
|
||||||
// Command bar is seperate
|
// Command bar is seperate
|
||||||
cmdBar := drawCommandBar(m)
|
cmdBar := drawCommandBar(m)
|
||||||
@ -39,7 +39,7 @@ func (m Model) View() string {
|
|||||||
// TODO: This is not idea, but it works for now
|
// TODO: This is not idea, but it works for now
|
||||||
cmd := m.CommandOutput()
|
cmd := m.CommandOutput()
|
||||||
if cmd != nil && cmd.IsActive() && !cmd.Inline && cmd.Height() > 0 {
|
if cmd != nil && cmd.IsActive() && !cmd.Inline && cmd.Height() > 0 {
|
||||||
view = overlayCommandOutputWindow(view, cmd, styles, m.termWidth, m.termHeight)
|
view = overlayCommandOutputWindow(view, cmd, t, m.termWidth, m.termHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
return view
|
return view
|
||||||
@ -47,7 +47,7 @@ func (m Model) View() string {
|
|||||||
|
|
||||||
// viewWindow: Renders a single window's content including line numbers and buffer text.
|
// viewWindow: Renders a single window's content including line numbers and buffer text.
|
||||||
// Each window has its own line numbers, gutter, and viewport dimensions.
|
// Each window has its own line numbers, gutter, and viewport dimensions.
|
||||||
func viewWindow(w *core.Window, styles style.Styles, options core.WinOptions, mode core.Mode, sx syntax.Engine) string {
|
func viewWindow(w *core.Window, t theme.EditorTheme, options core.WinOptions, mode core.Mode, sx syntax.Engine) string {
|
||||||
buf := w.Buffer
|
buf := w.Buffer
|
||||||
var view strings.Builder
|
var view strings.Builder
|
||||||
if sx != nil {
|
if sx != nil {
|
||||||
@ -65,16 +65,16 @@ func viewWindow(w *core.Window, styles style.Styles, options core.WinOptions, mo
|
|||||||
if sx != nil {
|
if sx != nil {
|
||||||
styleMap = sx.LineStyleMap(buf, lineNum)
|
styleMap = sx.LineStyleMap(buf, lineNum)
|
||||||
}
|
}
|
||||||
line := drawLine(w, styles, options, mode, buf.Line(lineNum), lineNum, styleMap)
|
line := drawLine(w, t, options, mode, buf.Line(lineNum), lineNum, styleMap)
|
||||||
view.WriteString(line)
|
view.WriteString(line)
|
||||||
} else {
|
} else {
|
||||||
view.WriteString(strings.Repeat(styles.BackgroundStyle.Render(" "), w.Width))
|
view.WriteString(strings.Repeat(t.Background.Render(" "), w.Width))
|
||||||
}
|
}
|
||||||
view.WriteRune('\n')
|
view.WriteRune('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw status line
|
// Draw status line
|
||||||
statusBar := drawStatusBar(w, mode, styles)
|
statusBar := drawStatusBar(w, mode, t)
|
||||||
view.WriteString(statusBar + "\n")
|
view.WriteString(statusBar + "\n")
|
||||||
|
|
||||||
return view.String()
|
return view.String()
|
||||||
@ -82,12 +82,12 @@ func viewWindow(w *core.Window, styles style.Styles, options core.WinOptions, mo
|
|||||||
|
|
||||||
// drawLine: Renders a single line with syntax highlighting, cursor, and visual selection.
|
// drawLine: Renders a single line with syntax highlighting, cursor, and visual selection.
|
||||||
// Handles gutter, cursor rendering, and visual mode highlighting.
|
// Handles gutter, cursor rendering, and visual mode highlighting.
|
||||||
func drawLine(w *core.Window, styles style.Styles, options core.WinOptions, mode core.Mode, line string, lineNumber int, styleMap []lipgloss.Style) string {
|
func drawLine(w *core.Window, t theme.EditorTheme, options core.WinOptions, mode core.Mode, line string, lineNumber int, styleMap []lipgloss.Style) string {
|
||||||
var view strings.Builder
|
var view strings.Builder
|
||||||
runes := []rune(line)
|
runes := []rune(line)
|
||||||
|
|
||||||
// Draw gutter first
|
// Draw gutter first
|
||||||
gutter := drawGutter(w, styles, options, lineNumber)
|
gutter := drawGutter(w, t, options, lineNumber)
|
||||||
view.WriteString(gutter)
|
view.WriteString(gutter)
|
||||||
|
|
||||||
// Now draw the line content
|
// Now draw the line content
|
||||||
@ -95,31 +95,31 @@ func drawLine(w *core.Window, styles style.Styles, options core.WinOptions, mode
|
|||||||
// Current char is cursor
|
// Current char is cursor
|
||||||
if col == w.Cursor.Col && lineNumber == w.Cursor.Line {
|
if col == w.Cursor.Col && lineNumber == w.Cursor.Line {
|
||||||
if col < len(runes) {
|
if col < len(runes) {
|
||||||
cur := styles.CursorStyle(mode, styleMap[col])
|
cur := t.Cursor(mode, styleMap[col])
|
||||||
view.WriteString(cur.Render(string(runes[col])))
|
view.WriteString(cur.Render(string(runes[col])))
|
||||||
} else {
|
} else {
|
||||||
view.WriteString(styles.DefaultCursorStyle(mode).Render(" "))
|
view.WriteString(t.DefaultCursor(mode).Render(" "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not cursor, but not end
|
// Not cursor, but not end
|
||||||
} else if col < len(runes) {
|
} else if col < len(runes) {
|
||||||
s := styleMap[col]
|
s := styleMap[col]
|
||||||
if mode.IsVisualMode() && posInsideSelection(w, mode, col, lineNumber) {
|
if mode.IsVisualMode() && posInsideSelection(w, mode, col, lineNumber) {
|
||||||
vis := styles.VisualHighlightWithTextColor(s)
|
vis := t.VisualHighlightWithTextColor(s)
|
||||||
view.WriteString(vis.Render(string(runes[col])))
|
view.WriteString(vis.Render(string(runes[col])))
|
||||||
} else {
|
} else {
|
||||||
view.WriteString(s.Render(string(runes[col])))
|
view.WriteString(s.Render(string(runes[col])))
|
||||||
}
|
}
|
||||||
// Allow highlight on blank lines or chars
|
// Allow highlight on blank lines or chars
|
||||||
} else if mode.IsVisualMode() && posInsideSelection(w, mode, col, lineNumber) {
|
} else if mode.IsVisualMode() && posInsideSelection(w, mode, col, lineNumber) {
|
||||||
view.WriteString(styles.VisualHighlight.Render(" "))
|
view.WriteString(t.VisualHightlight.Render(" "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pad remainder of line to window width with background color
|
// Pad remainder of line to window width with background color
|
||||||
dif := w.Width - lipgloss.Width(view.String())
|
dif := w.Width - lipgloss.Width(view.String())
|
||||||
if dif > 0 {
|
if dif > 0 {
|
||||||
view.WriteString(strings.Repeat(styles.BackgroundStyle.Render(" "), dif))
|
view.WriteString(strings.Repeat(t.Background.Render(" "), dif))
|
||||||
}
|
}
|
||||||
|
|
||||||
return view.String()
|
return view.String()
|
||||||
@ -127,7 +127,7 @@ func drawLine(w *core.Window, styles style.Styles, options core.WinOptions, mode
|
|||||||
|
|
||||||
// drawGutter: Renders the line number gutter with support for both absolute and
|
// drawGutter: Renders the line number gutter with support for both absolute and
|
||||||
// relative line numbers, highlighting the current line differently.
|
// relative line numbers, highlighting the current line differently.
|
||||||
func drawGutter(w *core.Window, styles style.Styles, options core.WinOptions, curLine int) string {
|
func drawGutter(w *core.Window, t theme.EditorTheme, options core.WinOptions, curLine int) string {
|
||||||
if !(options.Number || options.RelativeNumber) {
|
if !(options.Number || options.RelativeNumber) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -140,8 +140,8 @@ func drawGutter(w *core.Window, styles style.Styles, options core.WinOptions, cu
|
|||||||
lineNumber int
|
lineNumber int
|
||||||
|
|
||||||
gutter string
|
gutter string
|
||||||
gutterStyle = styles.Gutter
|
gutterStyle = t.Gutter.Default
|
||||||
gutterStyleCur = styles.GutterCurrentLine
|
gutterStyleCur = t.Gutter.CurrentLine
|
||||||
)
|
)
|
||||||
|
|
||||||
// If we have relative setting, set the numbers relatively
|
// If we have relative setting, set the numbers relatively
|
||||||
@ -173,9 +173,9 @@ func drawGutter(w *core.Window, styles style.Styles, options core.WinOptions, cu
|
|||||||
|
|
||||||
// drawStatusBar: Renders the status bar with mode and cursor position,
|
// drawStatusBar: Renders the status bar with mode and cursor position,
|
||||||
// padding the middle with spaces to fill the terminal width.
|
// padding the middle with spaces to fill the terminal width.
|
||||||
func drawStatusBar(w *core.Window, mode core.Mode, styles style.Styles) string {
|
func drawStatusBar(w *core.Window, mode core.Mode, t theme.EditorTheme) string {
|
||||||
left := leftBar(w, mode, styles)
|
left := leftBar(w, mode, t)
|
||||||
right := rightBar(w, mode, styles)
|
right := rightBar(w, mode, t)
|
||||||
|
|
||||||
diff := w.Width - (lipgloss.Width(left) + lipgloss.Width(right))
|
diff := w.Width - (lipgloss.Width(left) + lipgloss.Width(right))
|
||||||
|
|
||||||
@ -184,12 +184,12 @@ func drawStatusBar(w *core.Window, mode core.Mode, styles style.Styles) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
middle := strings.Repeat(styles.BackgroundStyle.Render(" "), diff)
|
middle := strings.Repeat(t.Background.Render(" "), diff)
|
||||||
return left + middle + right
|
return left + middle + right
|
||||||
}
|
}
|
||||||
|
|
||||||
// leftBar: Returns the left side of the status bar showing the current mode.
|
// leftBar: Returns the left side of the status bar showing the current mode.
|
||||||
func leftBar(w *core.Window, mode core.Mode, styles style.Styles) string {
|
func leftBar(w *core.Window, mode core.Mode, t theme.EditorTheme) string {
|
||||||
buf := w.Buffer
|
buf := w.Buffer
|
||||||
|
|
||||||
var flags []string
|
var flags []string
|
||||||
@ -206,12 +206,12 @@ func leftBar(w *core.Window, mode core.Mode, styles style.Styles) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bar := fmt.Sprintf(" %s %s %s", mode.ToString(), buf.Filename, flagStr)
|
bar := fmt.Sprintf(" %s %s %s", mode.ToString(), buf.Filename, flagStr)
|
||||||
return styles.LineStyle.Render(bar)
|
return t.Line.Render(bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rightBar: Returns the right side of the status bar showing cursor position
|
// rightBar: Returns the right side of the status bar showing cursor position
|
||||||
// and selection count in visual mode.
|
// and selection count in visual mode.
|
||||||
func rightBar(w *core.Window, mode core.Mode, styles style.Styles) (bar string) {
|
func rightBar(w *core.Window, mode core.Mode, t theme.EditorTheme) (bar string) {
|
||||||
if mode.IsVisualMode() {
|
if mode.IsVisualMode() {
|
||||||
lineCount := max(w.Anchor.Line, w.Cursor.Line) - min(w.Anchor.Line, w.Cursor.Line) + 1
|
lineCount := max(w.Anchor.Line, w.Cursor.Line) - min(w.Anchor.Line, w.Cursor.Line) + 1
|
||||||
bar = fmt.Sprintf("%d:%d <%d>", w.Cursor.Line+1, w.Cursor.Col+1, lineCount)
|
bar = fmt.Sprintf("%d:%d <%d>", w.Cursor.Line+1, w.Cursor.Col+1, lineCount)
|
||||||
@ -219,7 +219,7 @@ func rightBar(w *core.Window, mode core.Mode, styles style.Styles) (bar string)
|
|||||||
bar = fmt.Sprintf("%d:%d ", w.Cursor.Line+1, w.Cursor.Col+1)
|
bar = fmt.Sprintf("%d:%d ", w.Cursor.Line+1, w.Cursor.Col+1)
|
||||||
}
|
}
|
||||||
buf := w.Buffer
|
buf := w.Buffer
|
||||||
bar = styles.LineStyle.Render(fmt.Sprintf("%s %s", buf.Filetype, bar))
|
bar = t.Line.Render(fmt.Sprintf("%s %s", buf.Filetype, bar))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +321,7 @@ func posInsideSelection(w *core.Window, mode core.Mode, col, line int) bool {
|
|||||||
|
|
||||||
// overlayCommandOutputWindow: Draw the overlay of the command output window. This will override
|
// overlayCommandOutputWindow: Draw the overlay of the command output window. This will override
|
||||||
// (overlay) the displayed content, so it should be used only when needed.
|
// (overlay) the displayed content, so it should be used only when needed.
|
||||||
func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, styles style.Styles, termWidth int, termHeight int) string {
|
func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, t theme.EditorTheme, termWidth int, termHeight int) string {
|
||||||
// Safety check
|
// Safety check
|
||||||
if cmd == nil {
|
if cmd == nil {
|
||||||
return view
|
return view
|
||||||
@ -332,22 +332,22 @@ func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, styles sty
|
|||||||
|
|
||||||
// Build the overlay
|
// Build the overlay
|
||||||
var overlay []string
|
var overlay []string
|
||||||
overlay = append(overlay, styles.CommandOutputBorder.Render(strings.Repeat(" ", termWidth)))
|
overlay = append(overlay, t.CommandLine.OutputBorder.Render(strings.Repeat(" ", termWidth)))
|
||||||
|
|
||||||
if strings.TrimSpace(cmd.Title) != "" {
|
if strings.TrimSpace(cmd.Title) != "" {
|
||||||
title := styles.LineStyle.Render(cmd.Title)
|
title := t.Line.Render(cmd.Title)
|
||||||
overlay = append(overlay, title)
|
overlay = append(overlay, title)
|
||||||
}
|
}
|
||||||
viewLines := cmd.Viewport(termHeight)
|
viewLines := cmd.Viewport(termHeight)
|
||||||
for _, l := range viewLines {
|
for _, l := range viewLines {
|
||||||
content := styles.LineStyle.Render(strings.ReplaceAll(l, "\n", "\\n"))
|
content := t.Line.Render(strings.ReplaceAll(l, "\n", "\\n"))
|
||||||
overlay = append(overlay, content)
|
overlay = append(overlay, content)
|
||||||
}
|
}
|
||||||
msg := core.CommandOutputExitMessage
|
msg := core.CommandOutputExitMessage
|
||||||
if len(cmd.Lines) > len(cmd.Viewport(termHeight)) {
|
if len(cmd.Lines) > len(cmd.Viewport(termHeight)) {
|
||||||
msg += ". " + core.CommandOutputScrollMessage
|
msg += ". " + core.CommandOutputScrollMessage
|
||||||
}
|
}
|
||||||
overlay = append(overlay, styles.CommandContinueMessage.Render(msg))
|
overlay = append(overlay, t.CommandLine.ContinueMessage.Render(msg))
|
||||||
|
|
||||||
// NOTE: strings.Split on "\n" is safe as long as no style uses .Width()/.Height()/.Padding()/.Margin(),
|
// NOTE: strings.Split on "\n" is safe as long as no style uses .Width()/.Height()/.Padding()/.Margin(),
|
||||||
// which would cause Lipgloss to embed newlines internally and corrupt the line count.
|
// which would cause Lipgloss to embed newlines internally and corrupt the line count.
|
||||||
@ -356,7 +356,7 @@ func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, styles sty
|
|||||||
// Add background color to end of each line
|
// Add background color to end of each line
|
||||||
for i, l := range overlay {
|
for i, l := range overlay {
|
||||||
dif := termWidth - lipgloss.Width(l)
|
dif := termWidth - lipgloss.Width(l)
|
||||||
overlay[i] += styles.BackgroundStyle.Render(strings.Repeat(" ", dif))
|
overlay[i] += t.Background.Render(strings.Repeat(" ", dif))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove 'h' lines from back of view and append overlay
|
// Remove 'h' lines from back of view and append overlay
|
||||||
|
|||||||
@ -100,6 +100,8 @@ func DefaultStyles() Styles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Styles.DefaultCursorStyle: Returns the appropriate cursor style for the given mode.
|
// Styles.DefaultCursorStyle: Returns the appropriate cursor style for the given mode.
|
||||||
|
//
|
||||||
|
// DEPRECATED: Using EditorTheme.DefaultCursor now
|
||||||
func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style {
|
func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style {
|
||||||
switch mode {
|
switch mode {
|
||||||
case core.InsertMode:
|
case core.InsertMode:
|
||||||
@ -114,6 +116,8 @@ func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Styles.CursorStyle: Returns a cursor style derived from the text style.
|
// Styles.CursorStyle: Returns a cursor style derived from the text style.
|
||||||
|
//
|
||||||
|
// DEPRECATED: Using EditorTheme.Cursor now
|
||||||
func (s Styles) CursorStyle(mode core.Mode, textStyle lipgloss.Style) lipgloss.Style {
|
func (s Styles) CursorStyle(mode core.Mode, textStyle lipgloss.Style) lipgloss.Style {
|
||||||
switch mode {
|
switch mode {
|
||||||
case core.NormalMode, core.VisualLineMode, core.VisualBlockMode, core.VisualMode:
|
case core.NormalMode, core.VisualLineMode, core.VisualBlockMode, core.VisualMode:
|
||||||
@ -134,6 +138,8 @@ func (s Styles) CursorStyle(mode core.Mode, textStyle lipgloss.Style) lipgloss.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Styles.VisualHighlightWithTextColor: Applies visual background while preserving text color.
|
// Styles.VisualHighlightWithTextColor: Applies visual background while preserving text color.
|
||||||
|
//
|
||||||
|
// DEPRECATED: Using EditorTheme.VisualHighlightWithTextColor now
|
||||||
func (s Styles) VisualHighlightWithTextColor(textStyle lipgloss.Style) lipgloss.Style {
|
func (s Styles) VisualHighlightWithTextColor(textStyle lipgloss.Style) lipgloss.Style {
|
||||||
return lipgloss.NewStyle().
|
return lipgloss.NewStyle().
|
||||||
Background(s.VisualHighlight.GetBackground()).
|
Background(s.VisualHighlight.GetBackground()).
|
||||||
|
|||||||
@ -1,7 +1,77 @@
|
|||||||
package theme
|
package theme
|
||||||
|
|
||||||
// RegisterAll is retained as a no-op for compatibility while Chroma-based
|
import (
|
||||||
// theme loading is removed.
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
func RegisterAll() error {
|
"github.com/charmbracelet/lipgloss"
|
||||||
return nil
|
)
|
||||||
|
|
||||||
|
type EditorTheme struct {
|
||||||
|
Cursors CursorTheme
|
||||||
|
Gutter GutterTheme
|
||||||
|
VisualHightlight lipgloss.Style
|
||||||
|
StatusBar StatusBarTheme
|
||||||
|
CommandLine CommandLineTheme
|
||||||
|
Line lipgloss.Style
|
||||||
|
Background lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type CursorTheme struct {
|
||||||
|
Normal lipgloss.Style
|
||||||
|
Insert lipgloss.Style
|
||||||
|
Command lipgloss.Style
|
||||||
|
Replace lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type GutterTheme struct {
|
||||||
|
Default lipgloss.Style
|
||||||
|
CurrentLine lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusBarTheme struct {
|
||||||
|
Default lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandLineTheme struct {
|
||||||
|
Error lipgloss.Style
|
||||||
|
OutputBorder lipgloss.Style
|
||||||
|
ContinueMessage lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t EditorTheme) Cursor(mode core.Mode, textStyle lipgloss.Style) lipgloss.Style {
|
||||||
|
bg := textStyle.GetBackground()
|
||||||
|
fg := textStyle.GetForeground()
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case core.NormalMode, core.VisualLineMode, core.VisualBlockMode, core.VisualMode:
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Background(fg).
|
||||||
|
Foreground(bg)
|
||||||
|
case core.ReplaceMode, core.WaitingMode:
|
||||||
|
return textStyle.
|
||||||
|
Underline(true)
|
||||||
|
default:
|
||||||
|
return t.Background.
|
||||||
|
Foreground(fg).
|
||||||
|
Underline(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t EditorTheme) DefaultCursor(mode core.Mode) lipgloss.Style {
|
||||||
|
switch mode {
|
||||||
|
case core.InsertMode:
|
||||||
|
return t.Cursors.Insert
|
||||||
|
case core.CommandMode:
|
||||||
|
return t.Cursors.Command
|
||||||
|
case core.ReplaceMode:
|
||||||
|
return t.Cursors.Replace
|
||||||
|
default:
|
||||||
|
return t.Cursors.Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is preferred to raw access of EditorTheme.VisualHightlight since
|
||||||
|
// is has the proper foreground color applied
|
||||||
|
func (t EditorTheme) VisualHighlightWithTextColor(textStyle lipgloss.Style) lipgloss.Style {
|
||||||
|
return t.VisualHightlight.
|
||||||
|
Foreground(textStyle.GetForeground())
|
||||||
}
|
}
|
||||||
|
|||||||
83
internal/theme/themes/default.go
Normal file
83
internal/theme/themes/default.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package themes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/theme"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
)
|
||||||
|
|
||||||
|
const background = lipgloss.Color("#1f2335")
|
||||||
|
const foreground = lipgloss.Color("#dcd7ba")
|
||||||
|
|
||||||
|
func NewDefaultTheme() theme.EditorTheme {
|
||||||
|
hightlight := lipgloss.NewStyle().
|
||||||
|
Background(lipgloss.Color("#2f334d"))
|
||||||
|
|
||||||
|
line := lipgloss.NewStyle().
|
||||||
|
Foreground(foreground).
|
||||||
|
Background(background)
|
||||||
|
|
||||||
|
background := lipgloss.NewStyle().
|
||||||
|
Background(background)
|
||||||
|
|
||||||
|
return theme.EditorTheme{
|
||||||
|
Cursors: newDefaultCursorTheme(),
|
||||||
|
Gutter: newDefaultGutterTheme(),
|
||||||
|
VisualHightlight: hightlight,
|
||||||
|
StatusBar: newDefaultStatusBarTheme(),
|
||||||
|
CommandLine: newDefaultCommandLineTheme(),
|
||||||
|
Line: line,
|
||||||
|
Background: background,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is only used for the default cursors, in any other case
|
||||||
|
// the EditorTheme.Cursor() method is preferred.
|
||||||
|
func newDefaultCursorTheme() theme.CursorTheme {
|
||||||
|
base := lipgloss.NewStyle().
|
||||||
|
Foreground(foreground).
|
||||||
|
Background(background)
|
||||||
|
|
||||||
|
inv := lipgloss.NewStyle().
|
||||||
|
Foreground(background).
|
||||||
|
Background(foreground)
|
||||||
|
|
||||||
|
return theme.CursorTheme{
|
||||||
|
Normal: inv,
|
||||||
|
Insert: base.Underline(true),
|
||||||
|
Command: inv,
|
||||||
|
Replace: base.Underline(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultGutterTheme() theme.GutterTheme {
|
||||||
|
base := lipgloss.NewStyle().
|
||||||
|
Background(lipgloss.Color("#181b2a")).
|
||||||
|
Foreground(lipgloss.Color("#7e8399"))
|
||||||
|
|
||||||
|
return theme.GutterTheme{
|
||||||
|
Default: base,
|
||||||
|
CurrentLine: base.Foreground(lipgloss.Color("#f6c384")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultStatusBarTheme() theme.StatusBarTheme {
|
||||||
|
bar := lipgloss.NewStyle().
|
||||||
|
Background(lipgloss.Color("#181b2a")).
|
||||||
|
Foreground(lipgloss.Color("#8ea4a2"))
|
||||||
|
|
||||||
|
return theme.StatusBarTheme{
|
||||||
|
Default: bar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultCommandLineTheme() theme.CommandLineTheme {
|
||||||
|
base := lipgloss.NewStyle().
|
||||||
|
Foreground(foreground).
|
||||||
|
Background(background)
|
||||||
|
|
||||||
|
return theme.CommandLineTheme{
|
||||||
|
Error: base.Foreground(lipgloss.Color("#e82424")),
|
||||||
|
OutputBorder: base.Background(lipgloss.Color("#11131d")),
|
||||||
|
ContinueMessage: base.Foreground(lipgloss.Color("#7aa2f7")),
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user