{"id":51020848,"url":"https://github.com/beyondlex/finder","last_synced_at":"2026-06-21T16:01:32.708Z","repository":{"id":364974551,"uuid":"1269959999","full_name":"beyondlex/finder","owner":"beyondlex","description":"macOS Finder-style \"Go to Folder\" path browser for Neovim. Provides an interactive floating-window UI for selecting file or directory paths via fuzzy matching.","archived":false,"fork":false,"pushed_at":"2026-06-15T10:03:36.000Z","size":31,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-15T11:45:07.038Z","etag":null,"topics":["file-browser","file-picker","finder","fuzzy-matching","neovim","path","save-dir"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/beyondlex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-15T08:58:10.000Z","updated_at":"2026-06-15T10:03:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/beyondlex/finder","commit_stats":null,"previous_names":["beyondlex/finder"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/beyondlex/finder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyondlex%2Ffinder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyondlex%2Ffinder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyondlex%2Ffinder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyondlex%2Ffinder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beyondlex","download_url":"https://codeload.github.com/beyondlex/finder/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyondlex%2Ffinder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34616512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["file-browser","file-picker","finder","fuzzy-matching","neovim","path","save-dir"],"created_at":"2026-06-21T16:01:30.992Z","updated_at":"2026-06-21T16:01:32.692Z","avatar_url":"https://github.com/beyondlex.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# finder.nvim\n\n[English](./README.md) | [中文](./README.zh-CN.md)\n\n---\n\nmacOS Finder-style \"Go to Folder\" path browser for Neovim. Provides an interactive\nfloating-window UI for selecting file or directory paths via fuzzy matching.\n\n## Features\n\n- **Three modes** — `dir` / `file` / `both` (filter by type)\n- **Extension filter** — in `file` mode, restrict results to specific extensions (e.g. only `csv`, `tsv`, `json`)\n- **Interactive input** — as you type, the result list updates in real-time\n- **Smart mode switching** — trailing `/` lists directory contents; otherwise\n  fuzzy-matches items under the parent directory\n- **Auto-listing** — paths without any `/` (e.g. `~`, `foo`) that resolve to a\n  directory auto-list its contents\n- **Fuzzy matching** — first character prefix filter (case-insensitive) +\n  `matchfuzzypos` with lowercased query for subsequent characters\n- **`*` prefix substring match** — prefix with `*` to match anywhere in the name\n  (e.g. `*abc` finds items containing `abc` anywhere). Results are still\n  fuzzy-ordered and highlighted.\n- **Match highlighting** — matched characters highlighted only in the item name,\n  not in the parent path prefix\n- **Tab completion** — completes the selected item's name into the input;\n  virtual text shows what Tab will complete\n- **Parent navigation** — `\u003cC-w\u003e` / `Cmd+↑` strips the last path segment\n- **Isolated test environment** — `test/run.sh` launches via `NVIM_APPNAME`\n  without touching your system Neovim\n\n## Installation\n\n```lua\n-- lazy.nvim\n{\n  \"beyondlex/finder\",\n  config = function()\n    -- :Finder commands auto-registered\n  end,\n}\n```\n\n## Commands\n\n| Command | Mode | Description |\n|---------|------|-------------|\n| `:Finder ~/Downloads` | dir | Browse directories |\n| `:FinderDir ~/Downloads` | dir | Alias for `:Finder` |\n| `:FinderFile ~/Downloads` | file | Browse files |\n| `:FinderBoth ~/Downloads` | both | Browse files + directories |\n\n`--ext \u003clist\u003e` limits results to the given extensions (comma-separated, no dots):\n\n| Example | Description |\n|---------|-------------|\n| `:FinderFile --ext csv,tsv,json ~/data` | Only `.csv`, `.tsv`, `.json` files |\n| `:FinderFile --ext lua` | Only `.lua` files |\n\nArguments are optional. `\u003cTab\u003e` completion for paths (`complete=dir/file`).\n\n## Lua API\n\n```lua\nlocal finder = require(\"finder\")\n\nfinder.open({\n  mode = \"dir\",          -- \"dir\" | \"file\" | \"both\"\n  initial_path = \"~\",    -- starting path\n  extensions = {\"csv\", \"tsv\", \"json\"},  -- optional: only show files with these extensions (file mode)\n  on_confirm = function(path)\n    print(\"Selected: \" .. path)\n  end,\n  on_cancel = function()\n    print(\"Cancelled\")\n  end,\n})\n```\n\n## Keymaps\n\n| Key | Mode | Action |\n|-----|------|--------|\n| `\u003cTab\u003e` | Insert / Normal | Complete selected item into path |\n| `\u003cCR\u003e` | Insert / Normal | Confirm selection |\n| `\u003cEsc\u003e` / `\u003cC-c\u003e` | Insert / Normal | Cancel |\n| `\u003cUp\u003e` / `\u003cDown\u003e` | Insert | Select previous / next item |\n| `k` / `j` | Normal | Select previous / next item |\n| `\u003cC-w\u003e` / `Cmd+↑` | Insert | Go to parent directory |\n\n## How It Works\n\n### Path Mode Switching\n\nThe UI operates in two modes, determined by the current input:\n\n- **Listing mode** — activated when the input ends with `/` (e.g. `~/ai/`) or\n  has no `/` at all and resolves to a directory (e.g. `~`, `foo`). Lists all\n  items in the expanded directory. The first result is always a **self-item**\n  representing the directory itself.\n\n- **Matching mode** — activated when the input contains `/` but does not end\n  with it (e.g. `~/ai`, `~/do`). Extracts the parent directory (everything\n  before the last `/`) and fuzzy-matches items under it with the trailing text.\n\n\u003e `~/ai` keeps matching mode even when `~/ai` is a directory — it only lists\n\u003e if you add the trailing `/` to get `~/ai/`.\n\n### Fuzzy Matching\n\n1. **First-character filter** — items must start with the same character as the\n   query (case-insensitive). `Ai` only matches names starting with `a` or `A`.\n2. **Subsequent fuzzy** — uses `vim.fn.matchfuzzypos` with the query lowered\n   to avoid case-sensitivity issues.\n3. **Single char** — skips `matchfuzzypos` entirely, just returns the\n   first-char filtered results.\n\nEach result carries `match_positions` (0-indexed byte offsets from\n`matchfuzzypos`) for highlighting.\n\n### Display \u0026 Match Highlighting\n\nResults are displayed as `parent_display + item_name`. The `parent_display`\nvaries by mode:\n\n| Mode | parent_display | Example |\n|------|---------------|---------|\n| Listing | effective path (trailing `/`) | `~/ai/` |\n| Matching | everything before last `/` | `~/` |\n| Auto-listing (no slash) | path with `/` appended | `~/` |\n\nA `display_offset` (equal to `#parent_display`) is stored per item so that\nmatch highlights are applied only to the item name portion, not the prefix.\n\n### Tab Completion\n\nUses `self._parent_display` (computed and cached during `refresh()`) as the\nprefix. Builds the new path as `prefix .. item.name`, appending `/` for\ndirectories.\n\n| Input | Result | Mode |\n|-------|--------|------|\n| `~/ai` + Tab | `~/ai/` | matching |\n| `~` + Tab | `~/ai/` | auto-listing |\n| `~/ai/` + Tab | `~/ai/projects/` | listing |\n| `ai` + Tab | `some_dir/` | no-slash matching |\n\n### Go to Parent (`\u003cC-w\u003e` / `Cmd+↑`)\n\nStrips the last path segment and ensures a trailing `/`:\n\n| Input | Result |\n|-------|--------|\n| `~/ai/projects/` | `~/ai/` |\n| `~/` | `/` |\n| `~` | `/` |\n| `/` | no-op |\n\n### Confirm \u0026 Cancel\n\n- **Enter** — passes the selected item's display path (without trailing `/`)\n  to `on_confirm(path)` and closes the UI. Does nothing when the result list\n  is empty.\n- **Esc** / **Ctrl-C** — calls `on_cancel()` and closes the UI.\n\n### UI Layout\n\n```\n┌─ Go to Path ────────────────┐\n│ ~/ai/projects/cu            │  ← Input (width × 1)\n├─────────────────────────────┤\n│ ~/ai/projects               │  ← Self-item (selected)\n│ ~/ai/projects/cursor/       │\n│ ~/ai/projects/curl/         │  ← Results (width × ≤12)\n│ ~/ai/projects/custom/       │\n└─────────────────────────────┘\n```\n\nCentered in the editor, `minimal` style with rounded borders.\n\n## File Structure\n\n```\nlua/finder/\n├── init.lua      # Entry point + Finder class\n├── ui.lua        # Floating window UI (input, results, virtual text)\n├── fs.lua        # Filesystem operations (expand, parent, list, ...)\n└── matcher.lua   # Fuzzy matching (first-char filter + matchfuzzypos)\nplugin/\n└── finder.lua    # :Finder/:FinderDir/:FinderFile/:FinderBoth commands\ntest/\n├── run.sh        # Isolated test environment launcher\n├── clean.sh      # Delete all test data\n├── init.lua      # Test config (edit to add/remove plugins)\n├── spec.md       # Detailed mechanics documentation\n├── spec.lua      # Automated test suite (85+ tests)\n└── README.md     # Test environment instructions\n```\n\n## Example\n\nSee [example/](./example/) for a runnable Neovim config with demo keymaps:\n\n```bash\nnvim -u example/init.lua\n```\n\n## Development\n\n```bash\n# Launch isolated test environment\n./test/run.sh\n\n# With plugins (edit test/init.lua first)\n./test/run.sh\n\n# Clean and launch\n./test/run.sh --clean\n\n# Run automated tests\n./test/run.sh --clean -- --headless -l test/spec.lua -c 'qa!'\n\n# Clean all test data\n./test/clean.sh\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeyondlex%2Ffinder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeyondlex%2Ffinder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeyondlex%2Ffinder/lists"}