jerry-rig a help toggle cmd

This commit is contained in:
2025-07-13 20:56:16 -07:00
parent 96b10e7b00
commit 5543a9a8e0
10 changed files with 147 additions and 55 deletions

View File

@@ -28,8 +28,9 @@ type blinkMsg struct{}
// AppKeyMap defines the keybindings for the application // AppKeyMap defines the keybindings for the application
type AppKeyMap struct { type AppKeyMap struct {
Quit key.Binding Quit key.Binding
Suspend key.Binding Suspend key.Binding
ToggleHelp key.Binding
} }
// DefaultAppKeyMap returns the default keybindings // DefaultAppKeyMap returns the default keybindings
@@ -43,6 +44,10 @@ func DefaultAppKeyMap() AppKeyMap {
key.WithKeys("ctrl+z"), key.WithKeys("ctrl+z"),
key.WithHelp("ctrl+z", "suspend"), key.WithHelp("ctrl+z", "suspend"),
), ),
ToggleHelp: key.NewBinding(
key.WithKeys("ctrl+g"),
key.WithHelp("ctrl+g", "toggle help"),
),
} }
} }
@@ -57,6 +62,7 @@ type (
SwitchToQueryMsg struct{} SwitchToQueryMsg struct{}
ReturnToQueryMsg struct{} // Return to query mode from row detail ReturnToQueryMsg struct{} // Return to query mode from row detail
RefreshDataMsg struct{} RefreshDataMsg struct{}
ToggleHelpMsg struct{} // Toggle between short and full help
UpdateCellMsg struct { UpdateCellMsg struct {
RowIndex, ColIndex int RowIndex, ColIndex int
Value string Value string
@@ -594,6 +600,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Quit return m, tea.Quit
case key.Matches(msg, m.keyMap.Suspend): case key.Matches(msg, m.keyMap.Suspend):
return m, tea.Suspend return m, tea.Suspend
case key.Matches(msg, m.keyMap.ToggleHelp):
return m, func() tea.Msg { return ToggleHelpMsg{} }
} }
case SwitchToTableListMsg: case SwitchToTableListMsg:
@@ -667,6 +675,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
queryModel.handleQueryCompletion(msg) queryModel.handleQueryCompletion(msg)
} }
return m, nil return m, nil
case ToggleHelpMsg:
// Forward the help toggle to the current view
var cmd tea.Cmd
m.currentView, cmd = m.currentView.Update(msg)
return m, cmd
} }
if m.err != nil { if m.err != nil {

View File

@@ -11,15 +11,16 @@ import (
) )
type EditCellModel struct { type EditCellModel struct {
Shared *SharedData Shared *SharedData
rowIndex int rowIndex int
colIndex int colIndex int
input textinput.Model input textinput.Model
blinkState bool blinkState bool
keyMap EditCellKeyMap keyMap EditCellKeyMap
help help.Model help help.Model
focused bool showFullHelp bool
id int focused bool
id int
} }
// EditCellOption is a functional option for configuring EditCellModel // EditCellOption is a functional option for configuring EditCellModel
@@ -106,6 +107,10 @@ func (m *EditCellModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case ToggleHelpMsg:
m.showFullHelp = !m.showFullHelp
return m, nil
case blinkMsg: case blinkMsg:
m.blinkState = !m.blinkState m.blinkState = !m.blinkState
cmds = append(cmds, blinkCmd()) cmds = append(cmds, blinkCmd())
@@ -146,7 +151,16 @@ func (m *EditCellModel) View() string {
content := fmt.Sprintf("%s\n\n", TitleStyle.Render(fmt.Sprintf("Edit Cell: %s", columnName))) content := fmt.Sprintf("%s\n\n", TitleStyle.Render(fmt.Sprintf("Edit Cell: %s", columnName)))
content += fmt.Sprintf("Value: %s\n\n", m.input.View()) content += fmt.Sprintf("Value: %s\n\n", m.input.View())
content += m.help.View(m.keyMap)
var helpText string
if m.showFullHelp {
helpText = m.help.FullHelpView(m.keyMap.FullHelp())
} else {
helpText = m.help.ShortHelpView(m.keyMap.ShortHelp())
// Add ctrl+g to short help
helpText += " • " + HelpStyle.Render("ctrl+g: toggle help")
}
content += helpText
return content return content
} }

View File

@@ -23,6 +23,7 @@ type QueryModel struct {
gPressed bool gPressed bool
keyMap QueryKeyMap keyMap QueryKeyMap
help help.Model help help.Model
showFullHelp bool
focused bool focused bool
id int id int
} }
@@ -105,6 +106,10 @@ func (m *QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case ToggleHelpMsg:
m.showFullHelp = !m.showFullHelp
return m, nil
case blinkMsg: case blinkMsg:
m.blinkState = !m.blinkState m.blinkState = !m.blinkState
cmds = append(cmds, tea.Tick(time.Millisecond*500, func(t time.Time) tea.Msg { cmds = append(cmds, tea.Tick(time.Millisecond*500, func(t time.Time) tea.Msg {
@@ -156,17 +161,17 @@ func (m *QueryModel) handleResultsNavigation(msg tea.KeyMsg) (tea.Model, tea.Cmd
case key.Matches(msg, m.keyMap.GoToStart): case key.Matches(msg, m.keyMap.GoToStart):
if m.gPressed { if m.gPressed {
// Second g - go to beginning // Second g - go to beginning (gg pattern like vim)
m.selectedRow = 0 m.selectedRow = 0
m.gPressed = false m.gPressed = false
} else { } else {
// First g - wait for second g // First g - wait for second g to complete gg sequence
m.gPressed = true m.gPressed = true
} }
return m, nil return m, nil
case key.Matches(msg, m.keyMap.GoToEnd): case key.Matches(msg, m.keyMap.GoToEnd):
// Go to end // Go to end (G pattern like vim)
if len(m.results) > 0 { if len(m.results) > 0 {
m.selectedRow = len(m.results) - 1 m.selectedRow = len(m.results) - 1
} }
@@ -449,9 +454,17 @@ func (m *QueryModel) View() string {
content.WriteString("\n") content.WriteString("\n")
if m.FocusOnInput { if m.FocusOnInput {
content.WriteString(HelpStyle.Render("enter: execute • esc: back")) content.WriteString(HelpStyle.Render("enter: execute • esc: back • ctrl+g: toggle help"))
} else { } else {
content.WriteString(m.help.View(m.keyMap)) var helpText string
if m.showFullHelp {
helpText = m.help.FullHelpView(m.keyMap.FullHelp())
} else {
helpText = m.help.ShortHelpView(m.keyMap.ShortHelp())
// Add ctrl+g to short help
helpText += " • " + HelpStyle.Render("ctrl+g: toggle help")
}
content.WriteString(helpText)
} }
return content.String() return content.String()

View File

@@ -2,7 +2,10 @@ package app
import "github.com/charmbracelet/bubbles/key" import "github.com/charmbracelet/bubbles/key"
// QueryKeyMap defines keybindings for the query view // QueryKeyMap defines keybindings for the query view.
// Navigation follows vim-like patterns:
// - gg: go to start (requires two 'g' presses)
// - G: go to end (single 'G' press)
type QueryKeyMap struct { type QueryKeyMap struct {
// Input mode keys // Input mode keys
Execute key.Binding Execute key.Binding
@@ -100,7 +103,7 @@ func DefaultQueryKeyMap() QueryKeyMap {
// ShortHelp returns keybindings to be shown in the mini help view // ShortHelp returns keybindings to be shown in the mini help view
func (k QueryKeyMap) ShortHelp() []key.Binding { func (k QueryKeyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Execute, k.Up, k.Down, k.Enter} return []key.Binding{k.Execute, k.Up, k.Down, k.GoToStart, k.GoToEnd, k.EditQuery}
} }
// FullHelp returns keybindings for the expanded help view // FullHelp returns keybindings for the expanded help view

View File

@@ -10,15 +10,16 @@ import (
) )
type RowDetailModel struct { type RowDetailModel struct {
Shared *SharedData Shared *SharedData
rowIndex int rowIndex int
selectedCol int selectedCol int
FromQuery bool FromQuery bool
gPressed bool gPressed bool
keyMap RowDetailKeyMap keyMap RowDetailKeyMap
help help.Model help help.Model
focused bool showFullHelp bool
id int focused bool
id int
} }
// RowDetailOption is a functional option for configuring RowDetailModel // RowDetailOption is a functional option for configuring RowDetailModel
@@ -81,6 +82,10 @@ func (m *RowDetailModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
switch msg := msg.(type) { switch msg := msg.(type) {
case ToggleHelpMsg:
m.showFullHelp = !m.showFullHelp
return m, nil
case tea.KeyMsg: case tea.KeyMsg:
return m.handleNavigation(msg) return m.handleNavigation(msg)
} }
@@ -98,17 +103,17 @@ func (m *RowDetailModel) handleNavigation(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keyMap.GoToStart): case key.Matches(msg, m.keyMap.GoToStart):
if m.gPressed { if m.gPressed {
// Second g - go to beginning // Second g - go to beginning (gg pattern like vim)
m.selectedCol = 0 m.selectedCol = 0
m.gPressed = false m.gPressed = false
} else { } else {
// First g - wait for second g // First g - wait for second g to complete gg sequence
m.gPressed = true m.gPressed = true
} }
return m, nil return m, nil
case key.Matches(msg, m.keyMap.GoToEnd): case key.Matches(msg, m.keyMap.GoToEnd):
// Go to end // Go to end (G pattern like vim)
if len(m.Shared.Columns) > 0 { if len(m.Shared.Columns) > 0 {
m.selectedCol = len(m.Shared.Columns) - 1 m.selectedCol = len(m.Shared.Columns) - 1
} }
@@ -176,7 +181,15 @@ func (m *RowDetailModel) View() string {
} }
content.WriteString("\n") content.WriteString("\n")
content.WriteString(m.help.View(m.keyMap)) var helpText string
if m.showFullHelp {
helpText = m.help.FullHelpView(m.keyMap.FullHelp())
} else {
helpText = m.help.ShortHelpView(m.keyMap.ShortHelp())
// Add ctrl+g to short help
helpText += " • " + HelpStyle.Render("ctrl+g: toggle help")
}
content.WriteString(helpText)
return content.String() return content.String()
} }

View File

@@ -2,7 +2,10 @@ package app
import "github.com/charmbracelet/bubbles/key" import "github.com/charmbracelet/bubbles/key"
// RowDetailKeyMap defines keybindings for the row detail view // RowDetailKeyMap defines keybindings for the row detail view.
// Navigation follows vim-like patterns:
// - gg: go to start (requires two 'g' presses)
// - G: go to end (single 'G' press)
type RowDetailKeyMap struct { type RowDetailKeyMap struct {
Up key.Binding Up key.Binding
Down key.Binding Down key.Binding
@@ -49,7 +52,7 @@ func DefaultRowDetailKeyMap() RowDetailKeyMap {
// ShortHelp returns keybindings to be shown in the mini help view // ShortHelp returns keybindings to be shown in the mini help view
func (k RowDetailKeyMap) ShortHelp() []key.Binding { func (k RowDetailKeyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Up, k.Down, k.Enter, k.Back} return []key.Binding{k.Up, k.Down, k.Enter, k.GoToStart, k.GoToEnd, k.Back}
} }
// FullHelp returns keybindings for the expanded help view // FullHelp returns keybindings for the expanded help view

View File

@@ -12,15 +12,16 @@ import (
) )
type TableDataModel struct { type TableDataModel struct {
Shared *SharedData Shared *SharedData
searchInput textinput.Model searchInput textinput.Model
searching bool searching bool
selectedRow int selectedRow int
gPressed bool gPressed bool
keyMap TableDataKeyMap keyMap TableDataKeyMap
help help.Model help help.Model
focused bool showFullHelp bool
id int focused bool
id int
} }
// TableDataOption is a functional option for configuring TableDataModel // TableDataOption is a functional option for configuring TableDataModel
@@ -93,6 +94,10 @@ func (m *TableDataModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case ToggleHelpMsg:
m.showFullHelp = !m.showFullHelp
return m, nil
case tea.KeyMsg: case tea.KeyMsg:
if m.searching { if m.searching {
return m.handleSearchInput(msg) return m.handleSearchInput(msg)
@@ -151,20 +156,20 @@ func (m *TableDataModel) handleNavigation(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keyMap.GoToStart): case key.Matches(msg, m.keyMap.GoToStart):
if m.gPressed { if m.gPressed {
// Second g - go to absolute beginning // Second g - go to absolute beginning (gg pattern like vim)
m.Shared.CurrentPage = 0 m.Shared.CurrentPage = 0
m.Shared.LoadTableData() m.Shared.LoadTableData()
m.filterData() m.filterData()
m.selectedRow = 0 m.selectedRow = 0
m.gPressed = false m.gPressed = false
} else { } else {
// First g - wait for second g // First g - wait for second g to complete gg sequence
m.gPressed = true m.gPressed = true
} }
return m, nil return m, nil
case key.Matches(msg, m.keyMap.GoToEnd): case key.Matches(msg, m.keyMap.GoToEnd):
// Go to absolute end // Go to absolute end (G pattern like vim)
maxPage := (m.Shared.TotalRows - 1) / PageSize maxPage := (m.Shared.TotalRows - 1) / PageSize
m.Shared.CurrentPage = maxPage m.Shared.CurrentPage = maxPage
m.Shared.LoadTableData() m.Shared.LoadTableData()
@@ -440,7 +445,15 @@ func (m *TableDataModel) View() string {
if m.searching { if m.searching {
content.WriteString(HelpStyle.Render("Type to search • enter/esc: finish search")) content.WriteString(HelpStyle.Render("Type to search • enter/esc: finish search"))
} else { } else {
content.WriteString(m.help.View(m.keyMap)) var helpText string
if m.showFullHelp {
helpText = m.help.FullHelpView(m.keyMap.FullHelp())
} else {
helpText = m.help.ShortHelpView(m.keyMap.ShortHelp())
// Add ctrl+g to short help
helpText += " • " + HelpStyle.Render("ctrl+g: toggle help")
}
content.WriteString(helpText)
} }
return content.String() return content.String()

View File

@@ -2,7 +2,10 @@ package app
import "github.com/charmbracelet/bubbles/key" import "github.com/charmbracelet/bubbles/key"
// TableDataKeyMap defines keybindings for the table data view // TableDataKeyMap defines keybindings for the table data view.
// Navigation follows vim-like patterns:
// - gg: go to start (requires two 'g' presses)
// - G: go to end (single 'G' press)
type TableDataKeyMap struct { type TableDataKeyMap struct {
Up key.Binding Up key.Binding
Down key.Binding Down key.Binding
@@ -74,7 +77,7 @@ func DefaultTableDataKeyMap() TableDataKeyMap {
// ShortHelp returns keybindings to be shown in the mini help view // ShortHelp returns keybindings to be shown in the mini help view
func (k TableDataKeyMap) ShortHelp() []key.Binding { func (k TableDataKeyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Up, k.Down, k.Enter, k.Search} return []key.Binding{k.Up, k.Down, k.Enter, k.GoToStart, k.GoToEnd, k.Search}
} }
// FullHelp returns keybindings for the expanded help view // FullHelp returns keybindings for the expanded help view

View File

@@ -20,6 +20,7 @@ type TableListModel struct {
gPressed bool gPressed bool
keyMap TableListKeyMap keyMap TableListKeyMap
help help.Model help help.Model
showFullHelp bool
focused bool focused bool
id int id int
} }
@@ -95,6 +96,10 @@ func (m *TableListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case ToggleHelpMsg:
m.showFullHelp = !m.showFullHelp
return m, nil
case tea.KeyMsg: case tea.KeyMsg:
if m.searching { if m.searching {
return m.handleSearchInput(msg) return m.handleSearchInput(msg)
@@ -162,18 +167,18 @@ func (m *TableListModel) handleNavigation(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keyMap.GoToStart): case key.Matches(msg, m.keyMap.GoToStart):
if m.gPressed { if m.gPressed {
// Second g - go to beginning // Second g - go to beginning (gg pattern like vim)
m.selectedTable = 0 m.selectedTable = 0
m.currentPage = 0 m.currentPage = 0
m.gPressed = false m.gPressed = false
} else { } else {
// First g - wait for second g // First g - wait for second g to complete gg sequence
m.gPressed = true m.gPressed = true
} }
return m, nil return m, nil
case key.Matches(msg, m.keyMap.GoToEnd): case key.Matches(msg, m.keyMap.GoToEnd):
// Go to end // Go to end (G pattern like vim)
if len(m.Shared.FilteredTables) > 0 { if len(m.Shared.FilteredTables) > 0 {
m.selectedTable = len(m.Shared.FilteredTables) - 1 m.selectedTable = len(m.Shared.FilteredTables) - 1
m.adjustPage() m.adjustPage()
@@ -406,7 +411,15 @@ func (m *TableListModel) View() string {
if m.searching { if m.searching {
content.WriteString(HelpStyle.Render("Type to search • enter/esc: finish search")) content.WriteString(HelpStyle.Render("Type to search • enter/esc: finish search"))
} else { } else {
content.WriteString(m.help.View(m.keyMap)) var helpText string
if m.showFullHelp {
helpText = m.help.FullHelpView(m.keyMap.FullHelp())
} else {
helpText = m.help.ShortHelpView(m.keyMap.ShortHelp())
// Add ctrl+g to short help
helpText += " • " + HelpStyle.Render("ctrl+g: toggle help")
}
content.WriteString(helpText)
} }
return content.String() return content.String()

View File

@@ -2,7 +2,10 @@ package app
import "github.com/charmbracelet/bubbles/key" import "github.com/charmbracelet/bubbles/key"
// TableListKeyMap defines keybindings for the table list view // TableListKeyMap defines keybindings for the table list view.
// Navigation follows vim-like patterns:
// - gg: go to start (requires two 'g' presses)
// - G: go to end (single 'G' press)
type TableListKeyMap struct { type TableListKeyMap struct {
Up key.Binding Up key.Binding
Down key.Binding Down key.Binding
@@ -69,7 +72,7 @@ func DefaultTableListKeyMap() TableListKeyMap {
// ShortHelp returns keybindings to be shown in the mini help view // ShortHelp returns keybindings to be shown in the mini help view
func (k TableListKeyMap) ShortHelp() []key.Binding { func (k TableListKeyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Up, k.Down, k.Enter, k.Search} return []key.Binding{k.Up, k.Down, k.Enter, k.GoToStart, k.GoToEnd, k.Search}
} }
// FullHelp returns keybindings for the expanded help view // FullHelp returns keybindings for the expanded help view