Repository: marimo-team/marimo
Pull request: feat: add expand and collapse all columns to notebook actions menu
## 📝 Summary

<!--
Provide a concise summary of what this pull request is addressing.

If this PR fixes any issues, list them here by number (e.g., Fixes #123).
-->

Partially resolves #4163 
<img width="323" alt="Screenshot 2025-04-15 at 6 56 22 PM" src="https://github.com/user-attachments/assets/45a2ef2a-2351-4cff-a420-6d77b50901e3" />
Next PR will add this functionality to hotkeys.tsx

## 🔍 Description of Changes

<!--
Detail the specific changes made in this pull request. Explain the problem addressed and how it was resolved. If applicable, provide before and after comparisons, screenshots, or any relevant details to help reviewers understand the changes easily.
-->

Created collapseAllColumns and expandAllColumns hooks then added them to the notebook actions menu.

## 📋 Checklist

- [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md).
- [x] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable).
- [ ] I have added tests for the changes made.
- [x] I have run the code and verified that it works as expected.

## 📜 Reviewers

<!--
Tag potential reviewers from the community or maintainers who might be interested in reviewing this pull request.

Your PR will be reviewed more quickly if you can figure out the right person to tag with @ -->

@mscolnick


Problem statement:
New keyboard shortcuts and config file options
### Description

I am hoping to be able to:

1. Give some keyboard commands to fold/unfold all markdown headers in a notebook.
2. Create a setting in the config file, to make all headers folded by default, on opening a notebook.
3. Make a setting in the config file, so that all newly created cells are viewed as markdown by default.

### Suggested solution

Make more keyboard shortcuts and extra config file options.

Thanks for the great notebooks!

### Alternative

_No response_

### Additional context

_No response_

Hints from issue discussion:

Commit message: [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Patch to review:
diff --git a/frontend/src/components/editor/actions/useColumnExpansion.ts b/frontend/src/components/editor/actions/useColumnExpansion.ts
new file mode 100644
index 00000000000..bdf79c3759e
--- /dev/null
+++ b/frontend/src/components/editor/actions/useColumnExpansion.ts
@@ -0,0 +1,57 @@
+/* Copyright 2024 Marimo. All rights reserved. */
+import { useCellActions } from "@/core/cells/cells";
+import { getNotebook } from "@/core/cells/cells";
+import { useCallback } from "react";
+import { canCollapseOutline } from "@/core/dom/outline";
+
+/**
+ * Hooks to collapse and expand all columns in the notebook.
+ */
+
+export const useCollapseAllColumns = () => {
+  const { collapseCell } = useCellActions();
+
+  return useCallback(async () => {
+    const notebook = getNotebook();
+    const cellIds = notebook.cellIds.inOrderIds;
+
+    // Find all markdown cells that aren't already hidden
+    for (const cellId of cellIds) {
+      const outline = notebook.cellRuntime[cellId].outline;
+      // Check if the cell is a markdown cell with a TOC outline
+      if (!outline) {
+        continue;
+      }
+      // Check if the cell is a collapsible header
+      if (!canCollapseOutline(outline)) {
+        continue;
+      }
+      // Collapse the cell
+      collapseCell({ cellId });
+    }
+  }, []);
+};
+
+export const useExpandAllColumns = () => {
+  const { expandCell } = useCellActions();
+
+  return useCallback(async () => {
+    const notebook = getNotebook();
+    const cellIds = notebook.cellIds.inOrderIds;
+
+    // Find all markdown cells that aren't already hidden
+    for (const cellId of cellIds) {
+      const outline = notebook.cellRuntime[cellId].outline;
+      // Check if the cell is a markdown cell with a TOC outline
+      if (!outline) {
+        continue;
+      }
+      // Check if the cell is a collapsible header
+      if (!canCollapseOutline(outline)) {
+        continue;
+      }
+      // Collapse the cell
+      expandCell({ cellId });
+    }
+  }, []);
+};
diff --git a/frontend/src/components/editor/actions/useNotebookActions.tsx b/frontend/src/components/editor/actions/useNotebookActions.tsx
index 156f6caf21e..dcbe04c2eec 100644
--- a/frontend/src/components/editor/actions/useNotebookActions.tsx
+++ b/frontend/src/components/editor/actions/useNotebookActions.tsx
@@ -38,6 +38,8 @@ import {
   MessagesSquareIcon,
   YoutubeIcon,
   DiamondPlusIcon,
+  ChevronDownCircleIcon,
+  ChevronRightCircleIcon,
 } from "lucide-react";
 import { commandPaletteAtom } from "../controls/command-palette";
 import {
@@ -81,6 +83,10 @@ import { useRunAllCells } from "../cell/useRunCells";
 import { settingDialogAtom } from "@/components/app-config/state";
 import { AddDatabaseDialogContent } from "../database/add-database-form";
 import { useHideAllMarkdownCode } from "./useHideAllMarkdownCode";
+import {
+  useCollapseAllColumns,
+  useExpandAllColumns,
+} from "./useColumnExpansion";
 import { Constants } from "@/core/constants";
 import { getFeatureFlag } from "@/core/config/feature-flag";
 
@@ -97,6 +103,8 @@ export function useNotebookActions() {
   const [viewState] = useAtom(viewStateAtom);
   const kioskMode = useAtomValue(kioskModeAtom);
   const hideAllMarkdownCode = useHideAllMarkdownCode();
+  const collapseAllColumns = useCollapseAllColumns();
+  const expandAllColumns = useExpandAllColumns();
 
   const {
     updateCellConfig,
@@ -375,6 +383,16 @@ export function useNotebookActions() {
       label: "Hide all markdown code",
       handle: hideAllMarkdownCode,
     },
+    {
+      icon: <ChevronRightCircleIcon size={14} strokeWidth={1.5} />,
+      label: "Collapse all columns",
+      handle: collapseAllColumns,
+    },
+    {
+      icon: <ChevronDownCircleIcon size={14} strokeWidth={1.5} />,
+      label: "Expand all columns",
+      handle: expandAllColumns,
+    },
     {
       icon: <DiamondPlusIcon size={14} strokeWidth={1.5} />,
       label: "Add setup cell",


Code review comments:
diff_hunk: @@ -0,0 +1,57 @@
+/* Copyright 2024 Marimo. All rights reserved. */
+import { useCellActions } from "@/core/cells/cells";
+import { getNotebook } from "@/core/cells/cells";
+import { useCallback } from "react";
+import { canCollapseOutline } from "@/core/dom/outline";
+
+/**
+ * Hooks to collapse and expand all columns in the notebook.
+ */
+
+export const useCollapseAllColumns = () => {
+  const { collapseCell } = useCellActions();
+
+  return useCallback(async () => {
+    const notebook = getNotebook();
+    const cellIds = notebook.cellIds.inOrderIds;
+
+    // Find all markdown cells that aren't already hidden
+    for (const cellId of cellIds) {
+      const outline = notebook.cellRuntime[cellId].outline;
+      // Check if the cell is a markdown cell with a TOC outline
+      if (!outline) {
+        continue;
+      }
+      // Check if the cell is a collapsible header
+      if (!canCollapseOutline(outline)) {
+        continue;
+      }
+      // Collapse the cell
+      collapseCell({ cellId });
+    }
+  }, []);
+};
+
+export const useExpandAllColumns = () => {
+  const { expandCell } = useCellActions();
+
+  return useCallback(async () => {
path: frontend/src/components/editor/actions/useColumnExpansion.ts
text: @user1:
this `useCallback` may contain old state. to achieve what you want, we use `useEvent` - it takes no deps but is always the same function (but without stale deps)

diff_hunk: @@ -0,0 +1,57 @@
+/* Copyright 2024 Marimo. All rights reserved. */
+import { useCellActions } from "@/core/cells/cells";
+import { getNotebook } from "@/core/cells/cells";
+import { useCallback } from "react";
+import { canCollapseOutline } from "@/core/dom/outline";
+
+/**
+ * Hooks to collapse and expand all columns in the notebook.
+ */
+
+export const useCollapseAllColumns = () => {
+  const { collapseCell } = useCellActions();
+
+  return useCallback(async () => {
+    const notebook = getNotebook();
+    const cellIds = notebook.cellIds.inOrderIds;
+
+    // Find all markdown cells that aren't already hidden
+    for (const cellId of cellIds) {
+      const outline = notebook.cellRuntime[cellId].outline;
+      // Check if the cell is a markdown cell with a TOC outline
+      if (!outline) {
+        continue;
+      }
+      // Check if the cell is a collapsible header
+      if (!canCollapseOutline(outline)) {
+        continue;
+      }
+      // Collapse the cell
+      collapseCell({ cellId });
+    }
+  }, []);
+};
+
+export const useExpandAllColumns = () => {
+  const { expandCell } = useCellActions();
+
+  return useCallback(async () => {
+    const notebook = getNotebook();
+    const cellIds = notebook.cellIds.inOrderIds;
+
+    // Find all markdown cells that aren't already hidden
+    for (const cellId of cellIds) {
+      const outline = notebook.cellRuntime[cellId].outline;
+      // Check if the cell is a markdown cell with a TOC outline
+      if (!outline) {
+        continue;
+      }
+      // Check if the cell is a collapsible header
+      if (!canCollapseOutline(outline)) {
+        continue;
+      }
+      // Collapse the cell
+      expandCell({ cellId });
+    }
+  }, []);
+};
path: frontend/src/components/editor/actions/useColumnExpansion.ts
text: @user1:
could we combine these hooks, into `const {expandAllSection,collapseAllSection} = useSectionCollapse()`

diff_hunk: @@ -375,6 +383,16 @@ export function useNotebookActions() {
       label: "Hide all markdown code",
       handle: hideAllMarkdownCode,
     },
+    {
+      icon: <ChevronRightCircleIcon size={14} strokeWidth={1.5} />,
+      label: "Collapse all columns",
path: frontend/src/components/editor/actions/useNotebookActions.tsx
text: @user1:
can this be `Collapse/Expand all sections`? 

and `column` →  `section` in other places