Extended glob and fancy path patterns #13

Closed
opened 2026-09-21 18:54:10 +00:00 by barrettruth · 0 comments
Owner

Original issue: barrettruth/canola.nvim#36
Original author: barrettruth
Original date: 2026-02-23T21:49:42Z

Filter the entries visible in an oil buffer by a glob pattern — e.g. show only *.lua files, or only files matching test_*. The filter is per-buffer, toggled by the user, and does not affect mutations on entries that remain visible.

What "extended glob" means here

This is not glob expansion in rename targets (that is covered by the brace-expansion logic already present in create_actions_from_diffs). The feature here is view filtering: narrowing which entries are rendered in the buffer based on a pattern the user supplies at runtime.

Example use cases:

  • In a large directory, show only *.rs source files.
  • Temporarily hide everything except files matching a prefix.
  • Filter to recently-modified files (combined with a sort).

Relationship to view_options

The existing visibility pipeline in view.lua:should_display calls config.view_options.is_always_hidden and config.view_options.is_hidden_file, then gates on show_hidden. A per-buffer glob filter is a third, independent gate: an entry passes only if the active filter matches its name (or if no filter is active).

The filter must be per-buffer rather than global, because the user may have multiple oil buffers open simultaneously and filtering one should not affect the others.

Implementation approach

Filter state

A module-level table in view.lua (keyed by bufnr) holds the active pattern for each buffer. When the table has no entry for a buffer, no filtering is applied.

should_display integration

After the existing is_always_hidden / is_hidden_file checks, apply the per-buffer filter. If a filter is active for the buffer and the entry name does not match it, return false (not displayed, not hidden in the oil-hidden sense — the entry simply does not appear).

UI: setting and clearing the filter

Two mechanisms:

  1. Action (filter): prompts the user for a pattern via vim.ui.input. Accepts a glob pattern (converted to a Lua pattern via a small helper, or matched with vim.fn.glob semantics). Calling the action with an empty string clears the filter.
  2. Command flag: :Oil --filter=*.lua could pass a filter at open time, narrowing the view before the first render.

After setting or clearing the filter, call view.rerender_all_oil_buffers({ refetch = false }) (same as toggle_hidden) so the buffer updates immediately without a round-trip to the filesystem.

A statusline component or winbar integration point should expose whether a filter is active, so the user has a visible indicator. The exact mechanism is left to the user (an API function that returns the current filter for a buffer would suffice).

Mutations and filtered-out entries

Entries hidden by the glob filter are not removed from the cache. The parse function in parser.lua only sees lines present in the buffer; filtered-out entries are absent from the buffer and therefore appear as deletes in the diff. This is the same behaviour as show_hidden = false: hidden entries are not protected from deletion if the user toggles visibility off, edits, and saves.

Two approaches to address the deletion risk:

Option A — treat filtered-out entries as protected. Before computing diffs, parser.parse checks whether each original_entries entry is hidden by an active filter. If it is, remove it from original_entries so it is never emitted as a delete diff. This is analogous to how should_display gates which entries are included in original_entries today.

Option B — document the behaviour. Filtered-out entries behave like hidden files with show_hidden = false. The user is responsible for clearing the filter before saving if they do not want to delete filtered entries. This is the simpler implementation.

Option A is recommended for the same reason that is_always_hidden entries are excluded from original_entries: a user who filtered to *.lua should not accidentally delete every non-Lua file in the directory on :w.

Pattern semantics

Accept standard glob patterns (*, ?, [abc]). Use vim.fn.fnamemodify matching or convert globs to Lua patterns with a simple substitution (* → .*, ? → ., escape magic chars). Do not support ** (recursive glob) — the filter applies only to names in the current directory, not to subtrees.

Scope

  • Per-buffer filter state stored in view.lua
  • should_display extended with a filter gate
  • original_entries in parser.parse excludes filtered-out entries (Option A)
  • filter action that prompts for a pattern and rerenders
  • API function to read the current filter for statusline integration
  • Glob-to-Lua-pattern conversion utility
> Original issue: barrettruth/canola.nvim#36 > Original author: `barrettruth` > Original date: 2026-02-23T21:49:42Z Filter the entries visible in an oil buffer by a glob pattern — e.g. show only `*.lua` files, or only files matching `test_*`. The filter is per-buffer, toggled by the user, and does not affect mutations on entries that remain visible. ## What "extended glob" means here This is not glob expansion in rename targets (that is covered by the brace-expansion logic already present in `create_actions_from_diffs`). The feature here is **view filtering**: narrowing which entries are rendered in the buffer based on a pattern the user supplies at runtime. Example use cases: - In a large directory, show only `*.rs` source files. - Temporarily hide everything except files matching a prefix. - Filter to recently-modified files (combined with a sort). ## Relationship to `view_options` The existing visibility pipeline in `view.lua:should_display` calls `config.view_options.is_always_hidden` and `config.view_options.is_hidden_file`, then gates on `show_hidden`. A per-buffer glob filter is a third, independent gate: an entry passes only if the active filter matches its name (or if no filter is active). The filter must be per-buffer rather than global, because the user may have multiple oil buffers open simultaneously and filtering one should not affect the others. ## Implementation approach ### Filter state A module-level table in `view.lua` (keyed by `bufnr`) holds the active pattern for each buffer. When the table has no entry for a buffer, no filtering is applied. ### `should_display` integration After the existing `is_always_hidden` / `is_hidden_file` checks, apply the per-buffer filter. If a filter is active for the buffer and the entry name does not match it, return `false` (not displayed, not hidden in the oil-hidden sense — the entry simply does not appear). ### UI: setting and clearing the filter Two mechanisms: 1. **Action** (`filter`): prompts the user for a pattern via `vim.ui.input`. Accepts a glob pattern (converted to a Lua pattern via a small helper, or matched with `vim.fn.glob` semantics). Calling the action with an empty string clears the filter. 2. **Command flag**: `:Oil --filter=*.lua` could pass a filter at open time, narrowing the view before the first render. After setting or clearing the filter, call `view.rerender_all_oil_buffers({ refetch = false })` (same as `toggle_hidden`) so the buffer updates immediately without a round-trip to the filesystem. A statusline component or winbar integration point should expose whether a filter is active, so the user has a visible indicator. The exact mechanism is left to the user (an API function that returns the current filter for a buffer would suffice). ### Mutations and filtered-out entries Entries hidden by the glob filter are **not** removed from the cache. The `parse` function in `parser.lua` only sees lines present in the buffer; filtered-out entries are absent from the buffer and therefore appear as deletes in the diff. This is the same behaviour as `show_hidden = false`: hidden entries are not protected from deletion if the user toggles visibility off, edits, and saves. Two approaches to address the deletion risk: **Option A — treat filtered-out entries as protected.** Before computing diffs, `parser.parse` checks whether each `original_entries` entry is hidden by an active filter. If it is, remove it from `original_entries` so it is never emitted as a delete diff. This is analogous to how `should_display` gates which entries are included in `original_entries` today. **Option B — document the behaviour.** Filtered-out entries behave like hidden files with `show_hidden = false`. The user is responsible for clearing the filter before saving if they do not want to delete filtered entries. This is the simpler implementation. Option A is recommended for the same reason that `is_always_hidden` entries are excluded from `original_entries`: a user who filtered to `*.lua` should not accidentally delete every non-Lua file in the directory on `:w`. ### Pattern semantics Accept standard glob patterns (`*`, `?`, `[abc]`). Use `vim.fn.fnamemodify` matching or convert globs to Lua patterns with a simple substitution (`*` → `.*`, `?` → `.`, escape magic chars). Do not support `**` (recursive glob) — the filter applies only to names in the current directory, not to subtrees. ## Scope - Per-buffer filter state stored in `view.lua` - `should_display` extended with a filter gate - `original_entries` in `parser.parse` excludes filtered-out entries (Option A) - `filter` action that prompts for a pattern and rerenders - API function to read the current filter for statusline integration - Glob-to-Lua-pattern conversion utility
barrettruth added this to the v1.1 milestone 2026-09-21 18:54:10 +00:00
Sign in to join this conversation.
No description provided.