readme and keybindings improvements

This commit is contained in:
Rasmus Wejlgaard 2025-11-08 14:47:51 +00:00
parent 097703beda
commit 8f6ec4a79f
5 changed files with 180 additions and 17 deletions

BIN
.imgs/priority_prompt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -70,7 +70,7 @@ org # Opens ./todo.org by default
| `p` | Set priority |
| `e` | Set effort |
| `r` | Toggle reorder mode |
| `shift+↑/↓` | Move item up/down (in reorder mode) |
| `shift+↑/↓` | Move item up/down |
| `ctrl+s` | Save |
| `?` | Toggle help |
| `q` or `ctrl+c` | Quit |
@ -89,6 +89,7 @@ Changes are automatically saved when you quit the application.
### Prompts
![capture](./.imgs/capture_prompt.png)
![delete](./.imgs/delete_prompt.png)
![priority](./.imgs/priority_prompt.png)
## File Format

View file

@ -22,24 +22,26 @@ const (
modeSetDeadline
modeSetPriority
modeSetEffort
modeHelp
)
type uiModel struct {
orgFile *model.OrgFile
cursor int
scrollOffset int // Track the scroll position
mode viewMode
help help.Model
keys keyMap
width int
height int
statusMsg string
statusExpiry time.Time
editingItem *model.Item
textarea textarea.Model
textinput textinput.Model
itemToDelete *model.Item
reorderMode bool
orgFile *model.OrgFile
cursor int
scrollOffset int // Track the scroll position
helpScroll int // Track scroll position in help mode
mode viewMode
help help.Model
keys keyMap
width int
height int
statusMsg string
statusExpiry time.Time
editingItem *model.Item
textarea textarea.Model
textinput textinput.Model
itemToDelete *model.Item
reorderMode bool
}
func initialModel(orgFile *model.OrgFile) uiModel {

View file

@ -30,6 +30,8 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.updateSetPriority(msg)
case modeSetEffort:
return m.updateSetEffort(msg)
case modeHelp:
return m.updateHelp(msg)
}
switch msg := msg.(type) {
@ -48,7 +50,8 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Quit
case key.Matches(msg, m.keys.Help):
m.help.ShowAll = !m.help.ShowAll
m.mode = modeHelp
m.helpScroll = 0 // Reset scroll when entering help
return m, nil
case key.Matches(msg, m.keys.Up):
@ -733,3 +736,41 @@ func (m *uiModel) swapItems(item1, item2 *model.Item) {
}
swapInList(m.orgFile.Items)
}
func (m uiModel) updateHelp(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
case tea.KeyMsg:
switch msg.String() {
case "?", "esc", "q":
m.mode = modeList
m.helpScroll = 0 // Reset scroll when exiting
return m, nil
case "up", "k":
if m.helpScroll > 0 {
m.helpScroll--
}
return m, nil
case "down", "j":
m.helpScroll++
// The view will handle clamping to max scroll
return m, nil
case "pageup":
m.helpScroll -= 10
if m.helpScroll < 0 {
m.helpScroll = 0
}
return m, nil
case "pagedown":
m.helpScroll += 10
return m, nil
case "home", "g":
m.helpScroll = 0
return m, nil
}
}
return m, nil
}

View file

@ -85,6 +85,8 @@ func (m uiModel) View() string {
return m.viewSetPriority()
case modeSetEffort:
return m.viewSetEffort()
case modeHelp:
return m.viewHelp()
}
// Build footer (status + help)
@ -421,6 +423,123 @@ func (m uiModel) viewSetEffort() string {
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, dialog)
}
func (m uiModel) viewHelp() string {
// Build the full help content first
var lines []string
// Title
lines = append(lines, titleStyle.Render("Keybindings Help"))
lines = append(lines, "")
// Group bindings by category
navigationBindings := []key.Binding{m.keys.Up, m.keys.Down, m.keys.Left, m.keys.Right}
itemBindings := []key.Binding{m.keys.ToggleFold, m.keys.EditNotes, m.keys.CycleState}
taskBindings := []key.Binding{m.keys.Capture, m.keys.AddSubTask, m.keys.Delete}
timeBindings := []key.Binding{m.keys.ClockIn, m.keys.ClockOut, m.keys.SetDeadline, m.keys.SetEffort}
organizationBindings := []key.Binding{m.keys.SetPriority, m.keys.ShiftUp, m.keys.ShiftDown, m.keys.ToggleReorder}
viewBindings := []key.Binding{m.keys.ToggleView, m.keys.Save, m.keys.Help, m.keys.Quit}
// Helper function to render a binding
renderBinding := func(b key.Binding) string {
keyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("99")).Bold(true)
descStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
help := b.Help()
return fmt.Sprintf(" %s %s", keyStyle.Render(help.Key), descStyle.Render(help.Desc))
}
// Render categories
categoryStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("214")).Bold(true)
lines = append(lines, categoryStyle.Render("Navigation"))
for _, binding := range navigationBindings {
lines = append(lines, renderBinding(binding))
}
lines = append(lines, "")
lines = append(lines, categoryStyle.Render("Item Actions"))
for _, binding := range itemBindings {
lines = append(lines, renderBinding(binding))
}
lines = append(lines, "")
lines = append(lines, categoryStyle.Render("Task Management"))
for _, binding := range taskBindings {
lines = append(lines, renderBinding(binding))
}
lines = append(lines, "")
lines = append(lines, categoryStyle.Render("Time Tracking"))
for _, binding := range timeBindings {
lines = append(lines, renderBinding(binding))
}
lines = append(lines, "")
lines = append(lines, categoryStyle.Render("Organization"))
for _, binding := range organizationBindings {
lines = append(lines, renderBinding(binding))
}
lines = append(lines, "")
lines = append(lines, categoryStyle.Render("View & System"))
for _, binding := range viewBindings {
lines = append(lines, renderBinding(binding))
}
lines = append(lines, "")
// Calculate visible area
footerLines := 2 // Footer text
availableHeight := m.height - footerLines
if availableHeight < 5 {
availableHeight = 5
}
totalLines := len(lines)
// Determine which lines to show based on scroll offset
startLine := m.helpScroll
endLine := startLine + availableHeight
if endLine > totalLines {
endLine = totalLines
}
if startLine >= totalLines {
startLine = totalLines - 1
if startLine < 0 {
startLine = 0
}
}
// Build visible content
var content strings.Builder
for i := startLine; i < endLine && i < len(lines); i++ {
content.WriteString(lines[i])
content.WriteString("\n")
}
// Add scroll indicators and footer
var footer strings.Builder
if startLine > 0 || endLine < totalLines {
scrollInfo := fmt.Sprintf("(Scroll: %d-%d of %d lines)", startLine+1, endLine, totalLines)
footer.WriteString(statusStyle.Render(scrollInfo))
footer.WriteString(" ")
}
footer.WriteString(statusStyle.Render("↑/↓ scroll • ? or ESC to close"))
// Combine content and footer
var result strings.Builder
result.WriteString(content.String())
// Add padding if needed
currentHeight := lipgloss.Height(content.String())
paddingNeeded := availableHeight - currentHeight
if paddingNeeded > 0 {
result.WriteString(strings.Repeat("\n", paddingNeeded))
}
result.WriteString(footer.String())
return result.String()
}
func (m uiModel) viewEditMode() string {
var b strings.Builder