Compare commits

..

1 commit

Author SHA1 Message Date
Vitaliy Sh
7bc00d6891
feat: ToggleFoldAll (#18) 2026-02-17 09:50:37 +00:00
4 changed files with 85 additions and 30 deletions

View file

@ -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 |

View file

@ -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":
@ -562,6 +569,7 @@ func (c *Config) GetAllKeybindings() map[string][]string {
"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,

View file

@ -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,

View file

@ -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) {