diff --git a/README.md b/README.md index fd53cc3..bc2759d 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ Feel free to fork and create a pull request if there's any features missing for - **Syntax Highlighting**: Code blocks are automatically highlighted (supports both ```lang and #+BEGIN_SRC formats) - **Markdown Support**: Use markdown-style code blocks in your notes - **Drawer Management**: LOGBOOK and PROPERTIES drawers are automatically filtered in list view +- **Fold/Unfold All**: Fold/Unfold all items with shift+tab ### Keybindings @@ -118,6 +119,7 @@ Feel free to fork and create a pull request if there's any features missing for | `←/h`, `→/l` | Cycle state backward/forward | | `t` or `space` | Cycle TODO state | | `tab` | Fold/unfold item | +| `shift+tab` | Fold/Unfold all items | | `enter` | Edit notes | | `c` | Capture new TODO | | `s` | Add sub-task | diff --git a/internal/config/config.go b/internal/config/config.go index 67b1030..c9b3d93 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -31,6 +31,7 @@ type KeybindingsConfig struct { Rename []string `toml:"rename"` CycleState []string `toml:"cycle_state"` ToggleFold []string `toml:"toggle_fold"` + ToggleFoldAll []string `toml:"toggle_fold_all"` EditNotes []string `toml:"edit_notes"` ToggleView []string `toml:"toggle_view"` Capture []string `toml:"capture"` @@ -115,6 +116,7 @@ func DefaultConfig() *Config { Rename: []string{"R"}, CycleState: []string{"t", " "}, ToggleFold: []string{"tab"}, + ToggleFoldAll: []string{"shift+tab", "backtab"}, EditNotes: []string{"enter"}, ToggleView: []string{"a"}, Capture: []string{"c"}, @@ -284,6 +286,9 @@ func (c *Config) fillDefaults() { if len(c.Keybindings.ToggleFold) == 0 { c.Keybindings.ToggleFold = defaults.Keybindings.ToggleFold } + if len(c.Keybindings.ToggleFoldAll) == 0 { + c.Keybindings.ToggleFoldAll = defaults.Keybindings.ToggleFoldAll + } if len(c.Keybindings.EditNotes) == 0 { c.Keybindings.EditNotes = defaults.Keybindings.EditNotes } @@ -522,6 +527,8 @@ func (c *Config) UpdateKeybinding(action string, keys []string) error { c.Keybindings.CycleState = keys case "toggle_fold": c.Keybindings.ToggleFold = keys + case "toggle_fold_all": + c.Keybindings.ToggleFoldAll = keys case "edit_notes": c.Keybindings.EditNotes = keys case "capture": @@ -551,34 +558,35 @@ func (c *Config) UpdateKeybinding(action string, keys []string) error { // GetAllKeybindings returns a map of all keybindings func (c *Config) GetAllKeybindings() map[string][]string { return map[string][]string{ - "up": c.Keybindings.Up, - "down": c.Keybindings.Down, - "left": c.Keybindings.Left, - "right": c.Keybindings.Right, - "shift_up": c.Keybindings.ShiftUp, - "shift_down": c.Keybindings.ShiftDown, - "shift_left": c.Keybindings.ShiftLeft, - "shift_right": c.Keybindings.ShiftRight, - "rename": c.Keybindings.Rename, - "cycle_state": c.Keybindings.CycleState, - "toggle_fold": c.Keybindings.ToggleFold, - "edit_notes": c.Keybindings.EditNotes, - "toggle_view": c.Keybindings.ToggleView, - "capture": c.Keybindings.Capture, - "add_subtask": c.Keybindings.AddSubTask, - "delete": c.Keybindings.Delete, - "save": c.Keybindings.Save, - "toggle_reorder": c.Keybindings.ToggleReorder, - "clock_in": c.Keybindings.ClockIn, - "clock_out": c.Keybindings.ClockOut, - "set_deadline": c.Keybindings.SetDeadline, - "set_scheduled": c.Keybindings.SetScheduled, - "set_priority": c.Keybindings.SetPriority, - "set_effort": c.Keybindings.SetEffort, - "help": c.Keybindings.Help, - "quit": c.Keybindings.Quit, - "settings": c.Keybindings.Settings, - "tag_item": c.Keybindings.TagItem, + "up": c.Keybindings.Up, + "down": c.Keybindings.Down, + "left": c.Keybindings.Left, + "right": c.Keybindings.Right, + "shift_up": c.Keybindings.ShiftUp, + "shift_down": c.Keybindings.ShiftDown, + "shift_left": c.Keybindings.ShiftLeft, + "shift_right": c.Keybindings.ShiftRight, + "rename": c.Keybindings.Rename, + "cycle_state": c.Keybindings.CycleState, + "toggle_fold": c.Keybindings.ToggleFold, + "toggle_fold_all": c.Keybindings.ToggleFoldAll, + "edit_notes": c.Keybindings.EditNotes, + "toggle_view": c.Keybindings.ToggleView, + "capture": c.Keybindings.Capture, + "add_subtask": c.Keybindings.AddSubTask, + "delete": c.Keybindings.Delete, + "save": c.Keybindings.Save, + "toggle_reorder": c.Keybindings.ToggleReorder, + "clock_in": c.Keybindings.ClockIn, + "clock_out": c.Keybindings.ClockOut, + "set_deadline": c.Keybindings.SetDeadline, + "set_scheduled": c.Keybindings.SetScheduled, + "set_priority": c.Keybindings.SetPriority, + "set_effort": c.Keybindings.SetEffort, + "help": c.Keybindings.Help, + "quit": c.Keybindings.Quit, + "settings": c.Keybindings.Settings, + "tag_item": c.Keybindings.TagItem, } } diff --git a/internal/ui/keybindings.go b/internal/ui/keybindings.go index 9a5cef0..aba4e95 100644 --- a/internal/ui/keybindings.go +++ b/internal/ui/keybindings.go @@ -26,6 +26,7 @@ type keyMap struct { Delete key.Binding Save key.Binding ToggleFold key.Binding + ToggleFoldAll key.Binding EditNotes key.Binding ToggleReorder key.Binding ClockIn key.Binding @@ -87,6 +88,10 @@ func newKeyMapFromConfig(cfg *config.Config) keyMap { key.WithKeys(kb.ToggleFold...), key.WithHelp(formatKeyHelp(kb.ToggleFold), "fold/unfold"), ), + ToggleFoldAll: key.NewBinding( + key.WithKeys(kb.ToggleFoldAll...), + key.WithHelp(formatKeyHelp(kb.ToggleFoldAll), "fold/unfold all"), + ), EditNotes: key.NewBinding( key.WithKeys(kb.EditNotes...), key.WithHelp(formatKeyHelp(kb.EditNotes), "edit notes"), @@ -188,7 +193,7 @@ func (k keyMap) FullHelp() [][]key.Binding { // This will be overridden by custom rendering in viewFullHelp return [][]key.Binding{ {k.Up, k.Down, k.Left, k.Right}, - {k.ToggleFold, k.EditNotes, k.ToggleReorder}, + {k.ToggleFold, k.ToggleFoldAll, k.EditNotes, k.ToggleReorder}, {k.Capture, k.AddSubTask, k.Delete, k.Save}, {k.ToggleView, k.Help, k.Quit}, } @@ -198,7 +203,7 @@ func (k keyMap) FullHelp() [][]key.Binding { func (k keyMap) getAllBindings() []key.Binding { return []key.Binding{ k.Up, k.Down, k.Left, k.Right, - k.ToggleFold, k.EditNotes, k.ToggleReorder, + k.ToggleFold, k.ToggleFoldAll, k.EditNotes, k.ToggleReorder, k.Capture, k.AddSubTask, k.Delete, k.Save, k.ClockIn, k.ClockOut, k.SetDeadline, k.SetScheduled, k.SetPriority, k.SetEffort, k.TagItem, k.Settings, k.ToggleView, k.Help, k.Quit, diff --git a/internal/ui/modes.go b/internal/ui/modes.go index 89a5e33..96300f8 100644 --- a/internal/ui/modes.go +++ b/internal/ui/modes.go @@ -155,6 +155,46 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + case key.Matches(msg, m.keys.ToggleFoldAll): + if len(m.orgFile.Items) > 0 { + // Check if any top-level item is not folded + anyUnfolded := false + for _, item := range m.orgFile.Items { + if !item.Folded { + anyUnfolded = true + break + } + } + + if anyUnfolded { + // Fold all items recursively (collapse all) + var foldAll func([]*model.Item) + foldAll = func(items []*model.Item) { + for _, item := range items { + item.Folded = true + if len(item.Children) > 0 { + foldAll(item.Children) + } + } + } + foldAll(m.orgFile.Items) + m.setStatus("All items folded") + } else { + // Unfold everything recursively + var unfoldAll func([]*model.Item) + unfoldAll = func(items []*model.Item) { + for _, item := range items { + item.Folded = false + if len(item.Children) > 0 { + unfoldAll(item.Children) + } + } + } + unfoldAll(m.orgFile.Items) + m.setStatus("All items unfolded") + } + } + case key.Matches(msg, m.keys.EditNotes): items := m.getVisibleItems() if len(items) > 0 && m.cursor < len(items) {