Skip to content

Conversation

@olad5
Copy link
Contributor

@olad5 olad5 commented Nov 17, 2025

📝 Summary

🔍 Description of Changes

Fixes #2873,
Add button to be toggle hidden files/directory in file explorer.

I added a Eye-off Button to toggle the hidden files on/off.

Filters Nodes based on if they are hidden or not, then pass that to the Tree to display them.

📋 Checklist

  • I have read the contributor guidelines.
  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • I have added tests for the changes made.
  • I have run the code and verified that it works as expected.
@vercel
Copy link

vercel bot commented Nov 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
marimo-docs Ready Ready Preview Comment Nov 17, 2025 9:40pm
@olad5
Copy link
Contributor Author

olad5 commented Nov 17, 2025

I have read the CLA Document and I hereby sign the CLA

const [openFile, setOpenFile] = useState<FileInfo | null>(null);

const [hideHiddenFilesOrDirectory, setHideHiddenFilesOrDirectory] = useState(
() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use atomWithStorage (you can search the repo for examples)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the requested changes

<RefreshCcwIcon size={16} />
</Button>
</Tooltip>
<Tooltip content="Hidden-Files">
Copy link
Contributor

@mscolnick mscolnick Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe Toggle hidden files for the content

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the requested changes

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds functionality to toggle the visibility of hidden files and directories in the file explorer by introducing a new button with an eye-off icon in the toolbar.

  • Implements persistent storage for the "show hidden files" preference using Jotai atoms
  • Adds filtering logic to recursively hide/show files and directories that start with a dot (.)
  • Introduces a new toolbar button to toggle the visibility state

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const handleHiddenFilesToggle = useEvent(() => {
const newValue = !showHiddenFiles;
setShowHiddenFiles(newValue);
return;
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return statement is unnecessary here since the function doesn't need to return anything. Simply remove it or remove the entire line as the function already ends after the assignment.

Suggested change
return;
Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the requested changes

}
let next = item;
if (item.children && item.children.length) {
const kids = filterHiddenTree(item.children, false);
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recursive call passes false as the showHidden parameter instead of passing through the original parameter value. This means once you enter the recursion, it will always hide hidden files regardless of the parent's showHidden value.

Change line 731 to: const kids = filterHiddenTree(item.children, showHidden);

Suggested change
const kids = filterHiddenTree(item.children, false);
const kids = filterHiddenTree(item.children, showHidden);
Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the requested changes

const [data, setData] = useState<FileInfo[]>([]);
const [openFile, setOpenFile] = useState<FileInfo | null>(null);
const showHiddenFiles = useAtomValue<boolean>(hiddenFilesState);
const setShowHiddenFiles = useSetAtom(hiddenFilesState);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be simplified to

const [showHiddenFiles, setShowHiddenFiles] = useAtom(hiddenFilesState);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the requested changes

import { useFileExplorerUpload } from "./upload";

const hiddenFilesState = atomWithStorage(
"showHiddenFiles",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"showHiddenFiles",
"marimo:showHiddenFiles",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the requested changes

return true;
}
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we add some tests for this?

import { describe, it, expect } from 'vitest';

interface FileInfo {
  name: string;
  children?: FileInfo[];
}

describe('isDirectoryOrFileHidden', () => {
  it('should return true for files starting with dot', () => {
    expect(isDirectoryOrFileHidden('.git')).toBe(true);
    expect(isDirectoryOrFileHidden('.env')).toBe(true);
    expect(isDirectoryOrFileHidden('.gitignore')).toBe(true);
  });

  it('should return false for normal files', () => {
    expect(isDirectoryOrFileHidden('README.md')).toBe(false);
    expect(isDirectoryOrFileHidden('package.json')).toBe(false);
    expect(isDirectoryOrFileHidden('index.ts')).toBe(false);
  });

  it('should return false for files with dots in the middle', () => {
    expect(isDirectoryOrFileHidden('file.test.ts')).toBe(false);
    expect(isDirectoryOrFileHidden('my.config.js')).toBe(false);
  });
});

describe('filterHiddenTree', () => {
  it('should return all items when showHidden is true', () => {
    const list: FileInfo[] = [
      { name: '.git' },
      { name: 'README.md' },
      { name: '.env' },
    ];
    
    const result = filterHiddenTree(list, true);
    
    expect(result).toBe(list); // should be the exact same reference
    expect(result).toHaveLength(3);
  });

  it('should filter out hidden files when showHidden is false', () => {
    const list: FileInfo[] = [
      { name: '.git' },
      { name: 'README.md' },
      { name: '.env' },
      { name: 'package.json' },
    ];
    
    const result = filterHiddenTree(list, false);
    
    expect(result).toHaveLength(2);
    expect(result[0].name).toBe('README.md');
    expect(result[1].name).toBe('package.json');
  });

  it('should filter hidden directories recursively', () => {
    const list: FileInfo[] = [
      {
        name: 'src',
        children: [
          { name: 'index.ts' },
          { name: '.DS_Store' },
          { name: 'utils.ts' },
        ],
      },
      {
        name: '.git',
        children: [
          { name: 'config' },
        ],
      },
    ];
    
    const result = filterHiddenTree(list, false);
    
    expect(result).toHaveLength(1);
    expect(result[0].name).toBe('src');
    expect(result[0].children).toHaveLength(2);
    expect(result[0].children?.[0].name).toBe('index.ts');
    expect(result[0].children?.[1].name).toBe('utils.ts');
  });

  it('should handle nested hidden files', () => {
    const list: FileInfo[] = [
      {
        name: 'project',
        children: [
          {
            name: 'src',
            children: [
              { name: 'index.ts' },
              { name: '.backup' },
            ],
          },
          { name: '.env' },
        ],
      },
    ];
    
    const result = filterHiddenTree(list, false);
    
    expect(result).toHaveLength(1);
    expect(result[0].children).toHaveLength(1);
    expect(result[0].children?.[0].name).toBe('src');
    expect(result[0].children?.[0].children).toHaveLength(1);
    expect(result[0].children?.[0].children?.[0].name).toBe('index.ts');
  });

  it('should preserve directory structure when no children are filtered', () => {
    const list: FileInfo[] = [
      {
        name: 'src',
        children: [
          { name: 'index.ts' },
          { name: 'utils.ts' },
        ],
      },
    ];
    
    const result = filterHiddenTree(list, false);
    
    // Should return the same reference since nothing changed
    expect(result[0]).toBe(list[0]);
  });

  it('should create new object only when children are filtered', () => {
    const list: FileInfo[] = [
      {
        name: 'src',
        children: [
          { name: 'index.ts' },
          { name: '.hidden' },
        ],
      },
    ];
    
    const result = filterHiddenTree(list, false);
    
    // Should be a new object since children changed
    expect(result[0]).not.toBe(list[0]);
    expect(result[0].children).not.toBe(list[0].children);
  });

  it('should handle empty list', () => {
    const result = filterHiddenTree([], false);
    expect(result).toEqual([]);
  });

  it('should handle empty children arrays', () => {
    const list: FileInfo[] = [
      {
        name: 'empty-dir',
        children: [],
      },
    ];
    
    const result = filterHiddenTree(list, false);
    
    expect(result).toHaveLength(1);
    expect(result[0].children).toEqual([]);
  });

  it('should handle deeply nested structures', () => {
    const list: FileInfo[] = [
      {
        name: 'level1',
        children: [
          {
            name: 'level2',
            children: [
              {
                name: 'level3',
                children: [
                  { name: 'file.ts' },
                  { name: '.hidden' },
                ],
              },
            ],
          },
          { name: '.ignore' },
        ],
      },
    ];
    
    const result = filterHiddenTree(list, false);
    
    expect(result[0].children?.[0].children?.[0].children).toHaveLength(1);
    expect(result[0].children?.[0].children?.[0].children?.[0].name).toBe('file.ts');
  });
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the requested changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the requested changes

@olad5 olad5 force-pushed the hidden-files-file-explorer branch from c0ef425 to 50fe5e6 Compare November 18, 2025 18:42
@vercel
Copy link

vercel bot commented Nov 18, 2025

Deployment failed with the following error:

The provided GitHub repository does not contain the requested branch or commit reference. Please ensure the repository is not empty.
@olad5 olad5 requested a review from Copilot November 18, 2025 18:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@olad5
Copy link
Contributor Author

olad5 commented Nov 20, 2025

Hi @mscolnick ,

I see you have approved the changes but you have not merged.

Is there something else you want me to add to this PR?

@mscolnick mscolnick merged commit c081cc6 into marimo-team:main Nov 20, 2025
3 of 5 checks passed
@dmadisetti dmadisetti added the enhancement New feature or request label Nov 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

3 participants