-
Notifications
You must be signed in to change notification settings - Fork 1
Extractors
Extractors are the second stage in the SmartMotion pipeline. They receive data yielded from a collector and generate targets — positional objects describing where actions can apply.
Tip
If collectors define where to look, extractors decide what to act on.
Extractors operate on each yielded item from the collector (often a line object) and generate one or more SmartMotionTarget entries. These targets include position info, visual text, and metadata that can be used for labeling, filtering, and execution.
Extractors are also written as coroutines, allowing for:
- Early-exit target detection (e.g. flow state or quick jump)
- Scalable, memory-efficient target generation
Important
Extractors should not apply logic for direction, cursor filtering, or context sensitivity. That logic belongs to filters. Extractors should only describe all possible targets.
Like collectors, extractors are implemented as coroutines.
- The collector is resumed to yield an item
- The extractor processes the item and can
coroutine.yield()one or more targets - If the pipeline needs only one match, it stops early
This system minimizes overhead and makes SmartMotion fast, even on large files.
Important
One coroutine loop pulls from the collector, and one loop extracts all targets — and that’s it. No extra passes are needed.
| Name | Description |
|---|---|
lines |
Extracts whole-line targets |
words |
Extracts words using regex-based word boundaries |
text_search |
Extracts fixed-length search targets (1-2 chars) |
Note
All built-in extractors are registered using register_many() in the extractors registry. You can override or extend them.
Used inside a pipeline:
pipeline = {
collector = "lines",
extractor = "words",
visualizer = "hint_start",
}This example extracts visible words from all lines in the buffer.
Here’s a simplified version of a custom words extractor that finds word matches using regex and yields them as targets. This version omits cursor and direction logic entirely — such filtering should be handled in the filters module.
---@type SmartMotionExtractorModuleEntry
local M = {}
function M.run(collector, opts)
return coroutine.create(function(ctx, cfg, motion_state)
while true do
local ok, data_or_error = coroutine.resume(collector, ctx, cfg, motion_state)
if not ok then
log.error("Collector Coroutine Error: " .. tostring(data_or_error))
break
end
if data_or_error == nil then
break
end
local row = line_data.line_number
local line = line_data.text
for match_text, start_pos in line:gmatch("()%w+") do
local end_pos = start_pos + #match_text - 1
coroutine.yield({
row = row,
col = start_pos,
text = match_text,
start_pos = { row = row, col = start_pos },
end_pos = { row = row, col = end_pos },
type = "words",
})
end
end
end)
end
return Mlocal extractors = require("smart-motion.core.registries"):get().extractors
extractors.register("my_extractor", MyModule)You can also provide metadata and keys to link extractors to motion keys if using infer = true.
The built-in words extractor:
- Uses
vim.fn.matchstrposto find regex-based words - Applies cursor logic and direction-specific filtering
- Reorders results for BEFORE_CURSOR
Note
This behavior will be moved into filters in the future to better separate concerns.
Now that you're generating targets, it's time to:
- Visualize them with
visualizers.md - Define what happens when selected using
actions.md