Skip to content

Commit

Permalink
🚧 Add section option foundation
Browse files Browse the repository at this point in the history
Add interface to select an option for configuration. A setting is
assigned to a category and all settings in that category will be
displayed in the area next to the section component.
  • Loading branch information
mikelorant committed Mar 1, 2023
1 parent f891b8e commit c875c16
Show file tree
Hide file tree
Showing 18 changed files with 784 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
Expand Down
29 changes: 29 additions & 0 deletions internal/ui/colour/colour.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ type message struct {

type option struct{}

type optionSection struct {
Category lipgloss.TerminalColor
CategorySelected lipgloss.TerminalColor
CategorySpacer lipgloss.TerminalColor
CategoryPrompt lipgloss.TerminalColor
Setting lipgloss.TerminalColor
SettingSelected lipgloss.TerminalColor
SettingSpacer lipgloss.TerminalColor
SettingPrompt lipgloss.TerminalColor
SettingJoiner lipgloss.TerminalColor
}

type shortcut struct {
Key lipgloss.TerminalColor
Label lipgloss.TerminalColor
Expand Down Expand Up @@ -229,6 +241,23 @@ func (c *Colour) Option() option {
return option{}
}

//nolint:revive
func (c *Colour) OptionSection() optionSection {
clr := c.registry

return optionSection{
Category: clr.Fg(),
CategorySelected: ToAdaptive(clr.BrightWhite()),
CategorySpacer: clr.Fg(),
CategoryPrompt: clr.Cyan(),
Setting: clr.Fg(),
SettingSelected: ToAdaptive(clr.BrightWhite()),
SettingSpacer: clr.Fg(),
SettingPrompt: clr.Fg(),
SettingJoiner: clr.Fg(),
}
}

//nolint:revive
func (c *Colour) Shortcut() shortcut {
clr := c.registry
Expand Down
56 changes: 56 additions & 0 deletions internal/ui/colour/colour_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ type message struct {

type option struct{}

type optionSection struct {
Category Colour
CategorySelected Colour
CategorySpacer Colour
CategoryPrompt Colour
Setting Colour
SettingSelected Colour
SettingSpacer Colour
SettingPrompt Colour
SettingJoiner Colour
}

type shortcut struct {
Key Colour
Label Colour
Expand Down Expand Up @@ -416,6 +428,50 @@ func TestOption(t *testing.T) {
}
}

func TestOptionSection(t *testing.T) {
t.Parallel()

tests := []struct {
name string
optionSection optionSection
}{
{
name: "OptionSection",
optionSection: optionSection{
Category: Colour{Dark: "#bbbbbb"},
CategorySelected: Colour{Dark: "#ffffff", Light: "#ffffff"},
CategorySpacer: Colour{Dark: "#bbbbbb"},
CategoryPrompt: Colour{Dark: "#00bbbb"},
Setting: Colour{Dark: "#bbbbbb"},
SettingSelected: Colour{Dark: "#ffffff", Light: "#ffffff"},
SettingSpacer: Colour{Dark: "#bbbbbb"},
SettingPrompt: Colour{Dark: "#bbbbbb"},
SettingJoiner: Colour{Dark: "#bbbbbb"},
},
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

clr := colour.New(theme.New(config.ColourAdaptive)).OptionSection()

assert.Equal(t, tt.optionSection.Category, toColour(clr.Category), "Category")
assert.Equal(t, tt.optionSection.CategorySelected, toColour(clr.CategorySelected), "CategorySelected")
assert.Equal(t, tt.optionSection.CategorySpacer, toColour(clr.CategorySpacer), "CategorySpacer")
assert.Equal(t, tt.optionSection.CategoryPrompt, toColour(clr.CategoryPrompt), "CategoryPrompt")
assert.Equal(t, tt.optionSection.Setting, toColour(clr.Setting), "Setting")
assert.Equal(t, tt.optionSection.SettingSelected, toColour(clr.SettingSelected), "SettingSelected")
assert.Equal(t, tt.optionSection.SettingSpacer, toColour(clr.SettingSpacer), "SettingSpacer")
assert.Equal(t, tt.optionSection.SettingPrompt, toColour(clr.SettingPrompt), "SettingPrompt")
assert.Equal(t, tt.optionSection.SettingJoiner, toColour(clr.SettingJoiner), "SettingJoiner")
})
}
}

func TestShortcut(t *testing.T) {
t.Parallel()

Expand Down
226 changes: 226 additions & 0 deletions internal/ui/option/section/section.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package section

import (
"fmt"
"strings"

"github.com/mikelorant/committed/internal/commit"
"github.com/mikelorant/committed/internal/ui/colour"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"golang.org/x/exp/slices"
)

type Model struct {
Width int
Height int
Settings []Setting
CatIndex int
SetIndex int

state *commit.State
styles Styles
}

type Setting struct {
Name string
Category string
}

const (
defaultWidth = 20
defaultHeight = 20

defaultCatSpacer = " "
defaultCatPrompt = "❯"

defaultSetSpacer = " "
defaultSetJoiner = "│ "
defaultSetPrompt = "└▸"
)

func New(state *commit.State) Model {
return Model{
Width: defaultWidth,
Height: defaultHeight,
styles: defaultStyles(state.Theme),
state: state,
}
}

func (m Model) Init() tea.Cmd {
return nil
}

//nolint:ireturn
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
//nolint:gocritic
switch msg.(type) {
case colour.Msg:
m.styles = defaultStyles(m.state.Theme)
}

return m, nil
}

func (m Model) View() string {
return lipgloss.NewStyle().
MaxWidth(m.Width).
MaxHeight(m.Height).
Render(m.renderSection())
}

func (m Model) SelectedCategory() string {
if m.CatIndex >= len(Categories(m.Settings)) {
return ""
}

return Categories(m.Settings)[m.CatIndex]
}

func (m Model) SelectedSetting() string {
ss := Settings(m.SelectedCategory(), m.Settings)

if len(ss) <= 1 {
return ""
}

return ss[m.SetIndex]
}

func (m *Model) Reset() {
m.CatIndex = 0
m.SetIndex = 0
}

func (m *Model) Next() {
// List of categories
cat := Categories(m.Settings)
// List of current category settings
set := Settings(cat[m.CatIndex], m.Settings)

lastCategory := len(cat) - 1
lastSetting := len(set) - 1

switch {
// Index at end of setting but not on the last category
case m.SetIndex >= lastSetting && !(m.CatIndex >= lastCategory):
m.CatIndex++
m.SetIndex = 0

// Index at last category and last setting
case m.SetIndex >= lastSetting && m.CatIndex >= lastCategory:
default:
m.SetIndex++
}
}

func (m *Model) Previous() {
// List of categories
cat := Categories(m.Settings)

switch {
// Index at first category and first setting
case m.CatIndex <= 0 && m.SetIndex <= 0:

// Index at beginning of setting but not on the firat category
case m.CatIndex != 0 && m.SetIndex <= 0:
m.CatIndex--
prevSet := Settings(cat[m.CatIndex], m.Settings)
m.SetIndex = len(prevSet) - 1

default:
m.SetIndex--
}
}

func (m Model) renderSection() string {
var str []string

for idx, c := range Categories(m.Settings) {
selected := idx == m.CatIndex

cp := m.styles.categorySpacer
var cat string

switch selected {
case true:
cp = m.styles.categoryPrompt
cat = fmt.Sprintf("%v %v", cp, m.styles.categorySelected.Render(c))
default:
cat = fmt.Sprintf("%v %v", cp, m.styles.setting.Render(c))
}

str = append(str, cat)

rc := m.renderCategory(c, selected)
if len(rc) <= 1 {
str = append(str, "")
continue
}

str = append(str, rc)
}

return strings.Join(str, "\n")
}

func (m Model) renderCategory(cat string, selected bool) string {
var str []string

for idx, s := range Settings(cat, m.Settings) {
var ss string

sp := m.styles.settingSpacer

switch {
case idx < m.SetIndex && selected:
sp = m.styles.settingJoiner
ss = m.styles.setting.Render(s)
case idx == m.SetIndex && selected:
sp = m.styles.settingPrompt
ss = m.styles.settingSelected.Render(s)
default:
ss = m.styles.setting.Render(s)
}

str = append(str, fmt.Sprintf(" %v%v", sp, ss))
}

return strings.Join(str, "\n") + "\n"
}

func Categories(sets []Setting) []string {
var cat []string

for _, v := range sets {
if !slices.Contains(cat, v.Category) {
cat = append(cat, v.Category)
}
}

return cat
}

func Settings(cat string, sets []Setting) []string {
var set []string

for _, v := range sets {
if cat != v.Category {
continue
}

set = append(set, v.Name)
}

if len(set) <= 1 {
return nil
}

return set
}

func ToModel(m tea.Model, c tea.Cmd) (Model, tea.Cmd) {
return m.(Model), c
}
Loading

0 comments on commit c875c16

Please sign in to comment.