From 5fd2372cd5b42689c305e82421832af5f8a42f10 Mon Sep 17 00:00:00 2001 From: eltrac Date: Thu, 5 Mar 2026 12:24:01 +0800 Subject: feat: use viewport to show long list --- model_list.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 10 deletions(-) (limited to 'model_list.go') diff --git a/model_list.go b/model_list.go index e916a03..f7a39db 100644 --- a/model_list.go +++ b/model_list.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" ) @@ -15,6 +16,8 @@ type model struct { choices [][]string cursor int selected map[int]struct{} + ready bool + viewport viewport.Model } func listModel(arg string) model { @@ -48,19 +51,28 @@ func (m model) Init() tea.Cmd { } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + switch msg := msg.(type) { case tea.KeyPressMsg: switch msg.String() { - case "ctrl+c", "q": + case "ctrl+c", "q", "esc": return m, tea.Quit - case "up", "k": + case "k": if m.cursor > 0 { m.cursor-- + m.viewport.SetContent(m.buildContent()) } - case "down", "j": + return m, nil + case "j": if m.cursor < len(m.choices)-1 { m.cursor++ + m.viewport.SetContent(m.buildContent()) } + return m, nil case "enter": choice := m.choices[m.cursor] path := choice[0] @@ -72,15 +84,44 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } else { m.selected[m.cursor] = struct{}{} } + m.viewport.SetContent(m.buildContent()) + return m, nil + } + + case tea.WindowSizeMsg: + headerHeight := 1 + footerHeight := 1 + verticalMarginHeight := headerHeight + footerHeight + + if !m.ready { + m.viewport = viewport.New(viewport.WithWidth(msg.Width), viewport.WithHeight(msg.Height-verticalMarginHeight)) + m.viewport.YPosition = headerHeight + m.viewport.SetContent(m.buildContent()) + m.ready = true + } else { + m.viewport.SetWidth(msg.Width) + m.viewport.SetHeight(msg.Height - verticalMarginHeight) } + case editorFinishedMsg: return m, tea.Quit } - return m, nil + + m.viewport, cmd = m.viewport.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) } -func (m model) View() tea.View { - s := "Search: press '/' to filter items (not implemented yet)\n\n" +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func (m model) buildContent() string { + var s strings.Builder for i, choice := range m.choices { cursor := " " // no cursor @@ -96,14 +137,31 @@ func (m model) View() tea.View { title := choice[2] t, err := time.Parse(time.RFC3339, choice[3]) if err != nil { - + continue } datetime := t.Format("2006-01-02 15:04") - s += fmt.Sprintf("%s [%s] %s \t %s\n", cursor, checked, datetime, title) + s.WriteString(fmt.Sprintf("%s [%s] %s \t %s\n", cursor, checked, datetime, title)) } - s += "\n[q] quit | [Enter] edit | [SPACE] select | [↑/k] up | [↓/j] down\n" + return s.String() +} + +func (m model) headerView() string { + title := fmt.Sprintf("Hugo Content Manager (%d items)", len(m.choices)) + line := strings.Repeat("─", max(0, m.viewport.Width()-len(title))) + return title + line +} - return tea.NewView(s) +func (m model) footerView() string { + help := "[q] quit | [Enter] edit | [SPACE] select | [j/k] cursor | [↑/↓] scroll" + line := strings.Repeat("─", max(0, m.viewport.Width()-len(help))) + return line + help +} + +func (m model) View() tea.View { + if !m.ready { + return tea.NewView("\n Initializing...") + } + return tea.NewView(fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView())) } -- cgit v1.2.3