From 3b144dd9dcb82b1067a2cc75ca213ce1ea017856 Mon Sep 17 00:00:00 2001 From: Keenan Johns Date: Thu, 12 Mar 2026 21:05:33 -0500 Subject: [PATCH] fix: skip extractor loading in setup for infer motions Infer motions (d, y, c) don't define an extractor field because it gets populated later by infer.run() from the composable motion (e.g. w, b, e). setup.run() was trying to load a "default" extractor that doesn't exist in the registry, producing the error: "extractor 'default' not found in registry" Skip extractor resolution during setup for infer motions and let infer.run() handle it. Fixes #151 --- lua/smart-motion/core/engine/setup.lua | 8 ++- tests/test_engine_setup.lua | 68 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/lua/smart-motion/core/engine/setup.lua b/lua/smart-motion/core/engine/setup.lua index b40f02a..7f1b35d 100644 --- a/lua/smart-motion/core/engine/setup.lua +++ b/lua/smart-motion/core/engine/setup.lua @@ -26,7 +26,13 @@ function M.run(trigger_key) -- For operator motions (infer=true), infer.run will override this with the composed key. motion_state.motion_key = trigger_key - local modules = module_loader.get_modules(ctx, cfg, motion_state) + -- For infer motions (d, y, c), skip loading the extractor here — infer.run() + -- will determine the correct extractor from the composable motion (e.g. w, b, e). + local load_keys = nil + if motion_state.motion.infer then + load_keys = { "collector", "modifier", "filter", "visualizer", "action" } + end + local modules = module_loader.get_modules(ctx, cfg, motion_state, load_keys) -- The modules might have motion_state they would like to set -- Use motion_state.motion (the shallow copy, possibly modified by filetype_dispatch) diff --git a/tests/test_engine_setup.lua b/tests/test_engine_setup.lua index ec06b29..1d879c4 100644 --- a/tests/test_engine_setup.lua +++ b/tests/test_engine_setup.lua @@ -9,6 +9,9 @@ local T = MiniTest.new_set({ presets = { words = true, lines = true, + delete = true, + yank = true, + change = true, }, }) end, @@ -136,4 +139,69 @@ T["setup.run"]["applies per-mode motion state overrides"] = function() expect.no_equality(ms, nil) end +T["setup.run"]["does not error for infer motions without an extractor"] = function() + helpers.create_buf({ "hello world test" }) + helpers.set_cursor(1, 0) + + local setup = require("smart-motion.core.engine.setup") + local exit = require("smart-motion.core.events.exit") + + -- Capture vim.notify calls to detect error messages + local notifications = {} + local orig_notify = vim.notify + vim.notify = function(msg, level) + table.insert(notifications, { msg = msg, level = level }) + end + + local ctx, cfg, ms + local exit_type = exit.wrap(function() + ctx, cfg, ms = setup.run("d") + end) + + vim.notify = orig_notify + + -- setup.run should succeed without errors + expect.equality(exit_type, nil) + expect.no_equality(ctx, nil) + expect.no_equality(ms, nil) + expect.equality(ms.motion.infer, true) + + -- No error notifications about missing extractor + for _, n in ipairs(notifications) do + if n.level == vim.log.levels.ERROR and n.msg:find("extractor") then + error("setup.run emitted extractor error for infer motion: " .. n.msg) + end + end +end + +T["setup.run"]["does not error for yank and change infer motions"] = function() + helpers.create_buf({ "hello world test" }) + helpers.set_cursor(1, 0) + + local setup = require("smart-motion.core.engine.setup") + local exit = require("smart-motion.core.events.exit") + + for _, key in ipairs({ "y", "c" }) do + local notifications = {} + local orig_notify = vim.notify + vim.notify = function(msg, level) + table.insert(notifications, { msg = msg, level = level }) + end + + local exit_type = exit.wrap(function() + setup.run(key) + end) + + vim.notify = orig_notify + + expect.equality(exit_type, nil) + + for _, n in ipairs(notifications) do + if n.level == vim.log.levels.ERROR and n.msg:find("extractor") then + error("setup.run emitted extractor error for '" .. key .. "' motion: " .. n.msg) + end + end + end +end + return T