- Extract preset definitions from templates.lua into per-framework JSON files under lua/project_search/templates/ (common, react, vue, nest) - templates.lua now loads from JSON via M.load(name), keeping the default_rules() API unchanged - Expand common: add console.log, deprecated, live search presets - Expand React: add React Query hooks, context providers, Zustand stores, shadcn imports, service files/content presets - Expand Vue: add watch/watchEffect, Pinia stores, provide/inject, composables, page components presets - Expand NestJS: add DTOs, guards, interceptors, decorators, entity/module files presets - Split preset guide into separate en/ and zh/ directories Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8.5 KiB
lazy-project-search.nvim Implementation Notes And Roadmap
This document tracks the current implementation and future roadmap. Historical single-file migration notes have been removed because the plugin architecture is now complete.
1. Current Architecture
lua/project_search/
├── init.lua
├── config.lua
├── util.lua
├── detector.lua
├── storage.lua
├── templates.lua
├── runner.lua
├── picker.lua
├── commands.lua
└── health.lua
| Module | Responsibility |
|---|---|
init.lua |
Public plugin API and setup() |
config.lua |
Defaults and user options |
util.lua |
Root detection, path helpers, JSON helpers, command helpers |
detector.lua |
React / Vue / Nest project detection |
storage.lua |
Rule path, load/save/init/reset/edit |
templates.lua |
Built-in common / React / Vue / Nest presets |
runner.lua |
Execute files, grep, and files_regex |
picker.lua |
Main Snacks Picker UI, formatting, preview |
commands.lua |
User command registration |
health.lua |
:checkhealth project_search implementation |
2. Implemented Features
| Feature | Status | Notes |
|---|---|---|
| Standard plugin shape | Done | Uses lua/project_search/init.lua as main |
| Lazy loading | Done | Recommended via lazy.nvim cmd and keys |
| Configurable keymap | Done | Internal keymap still supported, but lazy.nvim keys is recommended |
| Recommended keymap | Done | <C-p> in normal mode |
| Plain Neovim support | Done | Requires snacks.nvim picker; root fallback uses markers |
| External project rule storage | Done | stdpath("data")/project-search/rules |
| Auto init | Done | Opens generated JSON on first use |
| Rule editing | Done | :ProjectSearchEdit |
| Rule reset | Done | :ProjectSearchReset / :ProjectSearchInit! |
| Rule path copy | Done | Manage action in picker |
| Rule previews | Done | Generated on demand |
| Search/manage grouping | Done | Search rules first, manage actions at bottom |
files runner |
Done | Uses Snacks files picker |
grep runner |
Done | Uses Snacks grep picker, supports exclude |
files_regex runner |
Done | Uses fd / fdfind |
files_regex error reporting |
Done | Reports fd stderr for invalid regex |
| Template detection | Done | common / React / Vue / Nest |
| Health check | Done | Checks Snacks, fd/fdfind, rg, rules path |
| English docs | Done | README.md |
| Chinese docs | Done | README.zh-CN.md |
| Example screenshots | Done | docs/assets/ |
3. Performance Decisions
Startup
The plugin should not eagerly load picker/storage/templates during Neovim startup.
Current strategy:
- lazy.nvim registers command and key triggers
setup()registers lightweight commands only- heavy modules are required on command execution
- recommended user config sets
keymap = false
Root Detection
The plugin supports both LazyVim and plain Neovim. Root detection priority is:
opts.rootfunction or stringLazyVim.root()when LazyVim is availablevim.fs.root(source, opts.root_markers)- current working directory fallback
This avoids hard-coding a LazyVim dependency while preserving LazyVim's project-root behavior.
Picker Open Latency
Earlier versions generated every preview before showing the picker. That caused visible latency when many presets existed.
Current strategy:
- picker items are lightweight
- Markdown previews are generated only when Snacks requests the current item preview
- JSON pretty printing is implemented in Lua, avoiding repeated external process startup
matcher.sort_empty = falsepreserves grouping order
4. Keymap Policy
Recommended:
<C-p>
Reasoning:
- fast single chord
- mnemonic for Project Search / Project Picker
- does not conflict with LazyVim's common
<leader>pYank History mapping - does not affect insert-mode completion navigation
Fallbacks:
| Keymap | Notes |
|---|---|
<leader>sP |
conservative LazyVim search namespace fallback |
<leader>fP |
file/find namespace fallback |
Avoid:
| Keymap | Reason |
|---|---|
<leader>p |
commonly Yank History |
<leader><space> |
LazyVim Find Files |
<leader>fp |
LazyVim Projects |
<leader>sp |
LazyVim Search Plugin Spec |
5. Rule Semantics
files
Input:
{
"type": "files",
"cwd": "src"
}
Execution:
- resolve
cwdrelative to project root - open
Snacks.picker.files
grep
Input:
{
"type": "grep",
"search": "queryKey",
"dirs": ["src"],
"glob": ["*.ts", "*.tsx"]
}
Execution:
- expand
dirsto absolute paths - merge global default excludes and preset excludes
- convert exclude patterns to ripgrep
--glob '!...'args - open
Snacks.picker.grep - pass through
regex,live,glob,args,hidden, andignored
The absolute-dir expansion is important because Snacks grep treats configured dirs as explicit search paths.
files_regex
Input:
{
"type": "files_regex",
"regex": "service/[^/]+\\.(ts|tsx)$",
"dirs": ["src"],
"exclude": ["*.test.ts", "*.spec.ts"]
}
Execution:
- use
fd --type f --full-path - merge global default excludes and preset excludes
- expand
dirsrelative to project root - report
fdstderr when regex parsing fails - open custom file picker from matched items
Important:
fd uses Rust regex and does not support look-around.
Negative filters should use exclude instead.
6. Current Template Coverage
common
Files: srcSearch: TODO / FIXMESearch: env usageSearch: console.logSearch: deprecatedSearch: live in src
react
Hooks: use-* kebab filesHooks: claw hooksHooks: panel hooksReact: classNameReact: queryKeyReact: custom hook definitionsReact Query: hooksReact: context providersZustand: store definitionsUI: shadcn component importsService: API layer filesSearch: service content
vue
Vue: definePropsVue: defineEmitsVue: ref / computedVue: watch / watchEffectPinia: store definitionsVue: provide / injectComposables: use-* filesVue: page components
nest
Nest: ControllersNest: ServicesNest: ModulesNest: DTOsNest: GuardsNest: InterceptorsNest: custom decoratorsNest: entity filesNest: module files
7. Documentation Status
Current docs:
README.md
README.zh-CN.md
CLAUDE.md
docs/rule/en/PRESET_GUIDE.md
docs/rule/zh/PRESET_GUIDE.md
docs/rule/en/react-rule.json
docs/assets/react-project-search.png
docs/assets/react-service-dir-search.png
Docs cover:
- product pain point
- LazyVim install
- plain Neovim install
- root detection and
root_markers - keymap recommendations
- how to adapt rules to a project
- rule types
- React examples
- files_regex limitations
- troubleshooting
8. Roadmap
v0.3 Rule Authoring
| Feature | Reason |
|---|---|
| Rule variables | Avoid repeating dirs and glob lists |
| Rule inheritance | Allow generated templates plus project-specific additions |
| Rule enable/disable flag | Let users keep experimental rules without deleting them |
| Rule validation command | Validate JSON schema, unsupported regex, missing dirs |
Example variable design:
{
"vars": {
"sourceDirs": ["src", "app", "packages"],
"tsFiles": ["*.ts", "*.tsx"]
},
"presets": [
{
"name": "React: queryKey",
"type": "grep",
"search": "queryKey",
"dirs": "$sourceDirs",
"glob": "$tsFiles"
}
]
}
v0.4 Project Index
Maintain:
~/.local/share/nvim/project-search/index.json
Potential command:
:ProjectSearchProjects
Use cases:
- list initialized projects
- jump to a project's rule file
- delete stale rules
- inspect last-used timestamp
v0.5 Rule Management UI
Potential picker actions:
Enter run rule
Ctrl-e edit rules file
Ctrl-r reset rules
Ctrl-y copy current rule JSON
Ctrl-d disable selected rule
v0.6 Save Current Search As Rule
Potential commands:
:ProjectSearchSaveGrep
:ProjectSearchSaveFilesRegex
Goal:
manual search -> name it -> save into current project JSON
9. Non-Goals For Now
- No repository-local rules by default, because avoiding source pollution is a core product decision.
- No dependency on
jq; picker previews should not spawn external formatters. - No mandatory Telescope support; Snacks Picker is the target UI.
- No project index until rule-file workflows are stable.