diff options
| author | ewy <ewy0@protonmail.com> | 2026-04-29 00:47:34 +0200 |
|---|---|---|
| committer | ewy <ewy0@protonmail.com> | 2026-04-29 00:47:34 +0200 |
| commit | 630d77e1962b43ee95e88a664f5e8b8993213060 (patch) | |
| tree | 8849c2a87443cd231786aed53b76dc7e4ea11aed /menu | |
| parent | 8efcf029576ad82908b4ae80b2c92022dfb857d2 (diff) | |
big stuff
* send empty screen after tui confirmation
* add scroll view / viewport
* auto enable scroll view on short terminals
* add motd tips
* add subdirs as categories
* add inline toggle hotkey (i)
Diffstat (limited to 'menu')
| -rw-r--r-- | menu/git.go | 54 | ||||
| -rw-r--r-- | menu/input.go | 17 | ||||
| -rw-r--r-- | menu/menu.go | 4 | ||||
| -rw-r--r-- | menu/model.go | 50 | ||||
| -rw-r--r-- | menu/source.go | 78 | ||||
| -rw-r--r-- | menu/state.go | 29 | ||||
| -rw-r--r-- | menu/target.go | 36 |
7 files changed, 187 insertions, 81 deletions
diff --git a/menu/git.go b/menu/git.go new file mode 100644 index 0000000..ed5a595 --- /dev/null +++ b/menu/git.go @@ -0,0 +1,54 @@ +package menu + +import ( + "github.com/charmbracelet/lipgloss" + "pik/menu/style" + "pik/model" + "strconv" +) + +var ( + GitColor = lipgloss.Color("4") + GitInfoStyle = style.New(func() lipgloss.Style { + return lipgloss.NewStyle().Background(GitColor).Border(lipgloss.OuterHalfBlockBorder(), false, false, false, true).BorderBackground(GitColor).Padding(0, 1) + }) + GitStatusStyle = style.New(func() lipgloss.Style { + return lipgloss.NewStyle().Bold(true).Background(GitColor).PaddingLeft(1) + }) + GitAddColor = lipgloss.Color("2") + GitAddStyle = style.New(func() lipgloss.Style { + return GitStatusStyle.Get().Foreground(GitAddColor) + }) + GitRemoveColor = lipgloss.Color("1") + GitRemoveStyle = style.New(func() lipgloss.Style { + return GitStatusStyle.Get().Foreground(GitRemoveColor) + }) + GitChangeColor = lipgloss.Color("5") + GitChangeStyle = style.New(func() lipgloss.Style { + return GitStatusStyle.Get().Foreground(GitChangeColor) + }) +) + +func Git(info *model.GitInfo) string { + var parts = []string{ + " ", + info.Branch, + } + + if info.Insertions > 0 { + parts = append(parts, GitAddStyle.Render("+"+strconv.Itoa(info.Insertions))) + } + if info.Deletions > 0 { + parts = append(parts, GitRemoveStyle.Render("-"+strconv.Itoa(info.Deletions))) + } + if info.Changes > 0 { + parts = append(parts, GitChangeStyle.Render("~"+strconv.Itoa(info.Changes))) + } + if info.Changes == 0 && info.Deletions == 0 && info.Insertions == 0 { + parts = append(parts, GitAddStyle.Render("clean")) + } + + return GitInfoStyle.Render(lipgloss.JoinHorizontal(lipgloss.Left, + parts..., + )) +} diff --git a/menu/input.go b/menu/input.go index 6d05749..cd07543 100644 --- a/menu/input.go +++ b/menu/input.go @@ -5,6 +5,16 @@ import tea "github.com/charmbracelet/bubbletea" func (m *Model) HandleInput(msg tea.KeyMsg) (tea.Cmd, error) { var cmd tea.Cmd switch msg.String() { + case "i", "I": + if m.Alt { + m.Alt = false + m.AutoAlt = false + return tea.ExitAltScreen, nil + } else { + m.Alt = true + m.AutoAlt = false + return tea.EnterAltScreen, nil + } case "h", "left": m.Leap(-1) case "l", "right": @@ -14,10 +24,11 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (tea.Cmd, error) { case "down", "j": m.Index++ case "q", "esc", "ctrl+c": - m.Quit = true - cmd = tea.Quit + m.Cancel = true + return tea.Quit, nil case "space", " ", "enter", "ctrl+d": - cmd = tea.Quit + m.Done = true + return tea.Quit, nil } m.Validate() diff --git a/menu/menu.go b/menu/menu.go index fdb1c6b..2677259 100644 --- a/menu/menu.go +++ b/menu/menu.go @@ -3,7 +3,6 @@ package menu import ( "errors" tea "github.com/charmbracelet/bubbletea" - "pik/flags" "pik/model" "pik/spool" ) @@ -17,9 +16,6 @@ func Show(st *model.State, hydrators []model.Modder) (*model.HydratedSource, mod } md := NewModel(st, hydrators) var opts []tea.ProgramOption - if !*flags.Inline { - opts = append(opts, tea.WithAltScreen()) - } program := tea.NewProgram(md, opts...) resultModel, err := program.Run() if err != nil { diff --git a/menu/model.go b/menu/model.go index 4eb2577..d1b9435 100644 --- a/menu/model.go +++ b/menu/model.go @@ -2,8 +2,12 @@ package menu import ( tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/term" + "github.com/spf13/pflag" "pik/model" + "pik/motd" "pik/spool" + "pik/viewport" ) type Model struct { @@ -11,24 +15,55 @@ type Model struct { Index int Indices map[int]model.HydratedTarget SourceIndices map[int]*model.HydratedSource - Quit bool + Cancel bool + Done bool + Height int + Alt bool + AutoAlt bool + Motd string } func (m *Model) Init() tea.Cmd { + _, h, err := term.GetSize(0) + if err != nil { + _, _ = spool.Warn("%v\n", err) + } + m.Height = h + wantsAlt := viewport.NeedsViewport(m.State(), m.Height) + if m.AutoAlt && wantsAlt { + return tea.EnterAltScreen + } return nil } +func (m *Model) HandleResize(msg tea.WindowSizeMsg) tea.Cmd { + if !m.AutoAlt { + return nil + } + m.Height = msg.Height + if viewport.NeedsViewport(m.State(), msg.Height) { + m.Alt = true + return tea.EnterAltScreen + } else { + m.Alt = false + return tea.ExitAltScreen + } + +} + func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var err error var result tea.Cmd switch mt := msg.(type) { + case tea.WindowSizeMsg: + result = m.HandleResize(mt) case tea.KeyMsg: result, err = m.HandleInput(mt) case tea.Cmd: result, err = m.HandleSignal(mt) } if err != nil { - spool.Warn("%v\n", err) + _, _ = spool.Warn("%v\n", err) } return m, result } @@ -38,11 +73,16 @@ func (m *Model) HandleSignal(cmd tea.Cmd) (tea.Cmd, error) { } func (m *Model) View() string { - return m.State(m.HydratedState) + if m.Cancel || m.Done { + return "" + } + result := m.State() + result = viewport.Process(result, m.Height) + return result } func (m *Model) Result() (*model.HydratedSource, model.HydratedTarget) { - if m.Quit { + if m.Cancel { return nil, nil } return m.SourceIndices[m.Index], m.Indices[m.Index] @@ -63,6 +103,8 @@ func NewModel(st *model.State, hydrators []model.Modder) *Model { Index: 0, Indices: make(map[int]model.HydratedTarget), SourceIndices: make(map[int]*model.HydratedSource), + AutoAlt: !pflag.Lookup("inline").Changed, + Motd: motd.One(), } idx := 0 for _, src := range st.Sources { diff --git a/menu/source.go b/menu/source.go index b5d462b..073c044 100644 --- a/menu/source.go +++ b/menu/source.go @@ -4,12 +4,13 @@ import ( "github.com/charmbracelet/lipgloss" "pik/menu/style" "pik/model" - "strconv" + "slices" + "strings" ) var ( SourceStyle = style.New(func() lipgloss.Style { - st := lipgloss.NewStyle().PaddingBottom(1) + st := lipgloss.NewStyle() return st }) SourceHeaderBackground = lipgloss.Color("5") @@ -35,8 +36,18 @@ var ( func (m *Model) Source(src *model.HydratedSource) string { targets := make([]string, 0, len(src.Targets)) + var sub []string for _, t := range src.HydratedTargets { - targets = append(targets, m.Target(t)) + ts := t.Sub() + header := !slices.Equal(sub, ts) + if header { + sub = ts + } + if header && strings.Join(ts, " ") != t.ShortestId() { + targets = append(targets, m.Category(strings.Join(ts, " "), "")) + header = false + } + targets = append(targets, m.Target(t, header)) } targetContent := lipgloss.JoinVertical(lipgloss.Top, targets...) @@ -56,64 +67,3 @@ func (m *Model) Source(src *model.HydratedSource) string { parts..., )) } - -var ( - StateStyle = style.New(func() lipgloss.Style { - return lipgloss.NewStyle().MarginBottom(1) - }) -) - -func (m *Model) State(st *model.HydratedState) string { - sources := make([]string, 0, len(st.Sources)) - for _, hs := range st.HydratedSources { - sources = append(sources, m.Source(hs)) - } - - return StateStyle.Render(lipgloss.JoinVertical(lipgloss.Top, sources...)) -} - -var ( - GitColor = lipgloss.Color("4") - GitInfoStyle = style.New(func() lipgloss.Style { - return lipgloss.NewStyle().Background(GitColor).Border(lipgloss.OuterHalfBlockBorder(), false, false, false, true).BorderBackground(GitColor).Padding(0, 1) - }) - GitStatusStyle = style.New(func() lipgloss.Style { - return lipgloss.NewStyle().Bold(true).Background(GitColor).PaddingLeft(1) - }) - GitAddColor = lipgloss.Color("2") - GitAddStyle = style.New(func() lipgloss.Style { - return GitStatusStyle.Get().Foreground(GitAddColor) - }) - GitRemoveColor = lipgloss.Color("1") - GitRemoveStyle = style.New(func() lipgloss.Style { - return GitStatusStyle.Get().Foreground(GitRemoveColor) - }) - GitChangeColor = lipgloss.Color("5") - GitChangeStyle = style.New(func() lipgloss.Style { - return GitStatusStyle.Get().Foreground(GitChangeColor) - }) -) - -func Git(info *model.GitInfo) string { - var parts = []string{ - " ", - info.Branch, - } - - if info.Insertions > 0 { - parts = append(parts, GitAddStyle.Render("+"+strconv.Itoa(info.Insertions))) - } - if info.Deletions > 0 { - parts = append(parts, GitRemoveStyle.Render("-"+strconv.Itoa(info.Deletions))) - } - if info.Changes > 0 { - parts = append(parts, GitChangeStyle.Render("~"+strconv.Itoa(info.Changes))) - } - if info.Changes == 0 && info.Deletions == 0 && info.Insertions == 0 { - parts = append(parts, GitAddStyle.Render("clean")) - } - - return GitInfoStyle.Render(lipgloss.JoinHorizontal(lipgloss.Left, - parts..., - )) -} diff --git a/menu/state.go b/menu/state.go new file mode 100644 index 0000000..16702a2 --- /dev/null +++ b/menu/state.go @@ -0,0 +1,29 @@ +package menu + +import ( + "github.com/charmbracelet/lipgloss" + "pik/menu/style" +) + +var ( + StateStyle = style.New(func() lipgloss.Style { + return lipgloss.NewStyle().MarginBottom(1) + }) + MotdStyle = style.New(func() lipgloss.Style { + return lipgloss.NewStyle().Faint(true).PaddingLeft(1) + }) +) + +func (m *Model) State() string { + st := m.HydratedState + sources := make([]string, 0, len(st.Sources)) + for i, hs := range st.HydratedSources { + src := m.Source(hs) + if i != len(st.HydratedSources)-1 { + src += "\n" + } + sources = append(sources, src) + } + + return StateStyle.Render(lipgloss.JoinVertical(lipgloss.Top, sources...), MotdStyle.Render("\n \U000F08B7 "+m.Motd)) +} diff --git a/menu/target.go b/menu/target.go index 04dfea1..6d6fdec 100644 --- a/menu/target.go +++ b/menu/target.go @@ -4,6 +4,7 @@ import ( "github.com/charmbracelet/lipgloss" "pik/menu/style" "pik/model" + "pik/viewport" ) var ( @@ -32,25 +33,48 @@ var ( st := lipgloss.NewStyle().PaddingLeft(1) return st }) + TargetIconSelectedStyle = style.New(func() lipgloss.Style { + return TargetIconStyle.Get().MarginLeft(1).PaddingLeft(0) + }) TargetSubStyle = style.New(func() lipgloss.Style { return lipgloss.NewStyle() }) + CategoryColor = lipgloss.Color("7") + CategoryStyle = style.New(func() lipgloss.Style { + return TargetStyle.Get().BorderForeground(CategoryColor).Background(CategoryColor).BorderBackground(CategoryColor) + }) + OrphanCategoryStyle = style.New(func() lipgloss.Style { + return TargetLabelStyle.Get().PaddingLeft(1) + }) ) -func (m *Model) Target(t model.HydratedTarget) string { - icon := TargetIconStyle.Render(PaddedIcon(t.Icon())) +func (m *Model) Target(t model.HydratedTarget, header bool) string { + _, selection := m.Result() + selected := selection != nil && selection.Target() == t.Target() + icon := "" + if selected { + icon = TargetIconSelectedStyle.Render(PaddedIcon(viewport.Caret)) + } else { + icon = TargetIconStyle.Render(PaddedIcon(t.Icon())) + } selectionStyle := TargetStyle selectionDescriptionStyle := TargetDescriptionStyle - _, sel := m.Result() - if sel != nil && sel.Target() == t.Target() { + if selected { selectionStyle = SelectedTargetStyle selectionDescriptionStyle = SelectedTargetDescriptionStyle + } else if header { + selectionStyle = CategoryStyle } var labelParts []string labelParts = append(labelParts, icon) - if t.Sub() != nil { - labelParts = append(labelParts, TargetSubStyle.Render(t.Sub()...)) + sub := t.Sub() + if sub != nil && sub[len(sub)-1] != t.ShortestId() { + labelParts = append(labelParts, TargetSubStyle.Render(sub...)) } labelParts = append(labelParts, TargetLabelStyle.Render(t.Label())) return lipgloss.JoinHorizontal(lipgloss.Left, selectionStyle.Render(labelParts...), selectionDescriptionStyle.Render(t.Description())) } + +func (m *Model) Category(input string, desc string) string { + return CategoryStyle.Render(lipgloss.JoinHorizontal(lipgloss.Left, OrphanCategoryStyle.Render(input), TargetDescriptionStyle.Render(desc))) +} |
