diff --git a/README.md b/README.md index e170f6c..d0bee16 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,10 @@ return { dependencies = { "mikavilpas/blink-ripgrep.nvim", -- 👆🏻👆🏻 add the dependency here + + -- optional dependency used for toggling features on/off + -- https://github.com/folke/snacks.nvim + "folke/snacks.nvim", }, ---@module 'blink.cmp' ---@type blink.cmp.Config @@ -125,6 +129,20 @@ return { -- root. additional_paths = {}, + -- Features that are not yet stable and might change in the future. + -- You can enable these to try them out beforehand, but be aware + -- that they might change. Nothing is enabled by default. + future_features = { + -- Keymaps to toggle features on/off. This can be used to alter + -- the behavior of the plugin without restarting Neovim. Nothing + -- is enabled by default. + toggles = { + -- The keymap to toggle the plugin on and off from blink + -- completion results. Example: "tg" + on_off = "tg", + }, + }, + -- Show debug information in `:messages` that can help in -- diagnosing issues with the plugin. debug = false, diff --git a/integration-tests/MyTestDirectory.ts b/integration-tests/MyTestDirectory.ts index e65ab7a..cb0d25b 100644 --- a/integration-tests/MyTestDirectory.ts +++ b/integration-tests/MyTestDirectory.ts @@ -56,6 +56,10 @@ export const MyTestDirectorySchema = z.object({ name: z.literal("don't_use_debug_mode.lua"), type: z.literal("file"), }), + "enable_toggling.lua": z.object({ + name: z.literal("enable_toggling.lua"), + type: z.literal("file"), + }), "set_ignore_paths.lua": z.object({ name: z.literal("set_ignore_paths.lua"), type: z.literal("file"), @@ -156,6 +160,7 @@ export const testDirectoryFiles = z.enum([ "additional-words-dir", "config-modifications/disable_project_root_fallback.lua", "config-modifications/don't_use_debug_mode.lua", + "config-modifications/enable_toggling.lua", "config-modifications/set_ignore_paths.lua", "config-modifications/use_additional_paths.lua", "config-modifications/use_case_sensitive_search.lua", diff --git a/integration-tests/cypress/e2e/blink-ripgrep/toggling.cy.ts b/integration-tests/cypress/e2e/blink-ripgrep/toggling.cy.ts new file mode 100644 index 0000000..15337cd --- /dev/null +++ b/integration-tests/cypress/e2e/blink-ripgrep/toggling.cy.ts @@ -0,0 +1,67 @@ +import { flavors } from "@catppuccin/palette" +import { rgbify } from "@tui-sandbox/library/dist/src/client/color-utilities" +import { createFakeGitDirectoriesToLimitRipgrepScope } from "./createFakeGitDirectoriesToLimitRipgrepScope" + +describe("toggling features on/off", () => { + // Some features can be toggled on/off without restarting Neovim. This can be + // useful to combat performance issues, for example. + it("can toggle the plugin on/off in blink completions", () => { + cy.visit("/") + cy.startNeovim({ + filename: "limited/main-project-file.lua", + startupScriptModifications: ["enable_toggling.lua"], + }).then((nvim) => { + // when completing from a file in a superproject, the search may descend + // to subprojects + cy.contains("this text is from main-project-file") + createFakeGitDirectoriesToLimitRipgrepScope() + + // first verify that the plugin is enabled + cy.typeIntoTerminal("o") + cy.typeIntoTerminal("some") + + cy.contains("here").should( + "have.css", + "color", + rgbify(flavors.macchiato.colors.green.rgb), + ) + + cy.typeIntoTerminal("{esc}") + + // toggle the plugin off and wait for confirmation + cy.typeIntoTerminal("{esc}") + cy.typeIntoTerminal(" tg") + cy.contains("Disabled **blink-ripgrep**") + + // try to complete again + cy.typeIntoTerminal("ciw") + cy.typeIntoTerminal("some") + + nvim + .runLuaCode({ + luaCode: `return _G.blink_ripgrep_invocations`, + }) + .should((result) => { + // ripgrep should only have been invoked once + expect(result.value).to.be.an("array") + const invocations = JSON.stringify(result.value) + expect(invocations).to.contain("ignored-because-mode-is-off") + }) + + // toggle it back on + cy.typeIntoTerminal("{esc}") + cy.typeIntoTerminal(" tg") + cy.contains("Enabled **blink-ripgrep**") + + // try to complete again and verify that the completion is there + cy.typeIntoTerminal("ciw") + cy.typeIntoTerminal("some") + + cy.contains("here").should( + "have.css", + "color", + rgbify(flavors.macchiato.colors.green.rgb), + ) + }) + }) +}) diff --git a/integration-tests/test-environment/.config/nvim/init.lua b/integration-tests/test-environment/.config/nvim/init.lua index f14c86e..8c76952 100644 --- a/integration-tests/test-environment/.config/nvim/init.lua +++ b/integration-tests/test-environment/.config/nvim/init.lua @@ -126,6 +126,7 @@ local plugins = { end, }, + { "folke/snacks.nvim" }, { "catppuccin/nvim", name = "catppuccin", priority = 1000 }, } require("lazy").setup({ spec = plugins }) diff --git a/integration-tests/test-environment/config-modifications/enable_toggling.lua b/integration-tests/test-environment/config-modifications/enable_toggling.lua new file mode 100644 index 0000000..9cb08ac --- /dev/null +++ b/integration-tests/test-environment/config-modifications/enable_toggling.lua @@ -0,0 +1,8 @@ +require("blink-ripgrep").setup({ + future_features = { + toggles = { + -- mnemonic: toggle grep + on_off = "tg", + }, + }, +}) diff --git a/lua/blink-ripgrep/init.lua b/lua/blink-ripgrep/init.lua index c959197..5b42dd1 100644 --- a/lua/blink-ripgrep/init.lua +++ b/lua/blink-ripgrep/init.lua @@ -14,6 +14,18 @@ ---@field debug? boolean # Show debug information in `:messages` that can help in diagnosing issues with the plugin. ---@field ignore_paths? string[] # Absolute root paths where the rg command will not be executed. Usually you want to exclude paths using gitignore files or ripgrep specific ignore files, but this can be used to only ignore the paths in blink-ripgrep.nvim, maintaining the ability to use ripgrep for those paths on the command line. If you need to find out where the searches are executed, enable `debug` and look at `:messages`. ---@field additional_paths? string[] # Any additional paths to search in, in addition to the project root. This can be useful if you want to include dictionary files (/usr/share/dict/words), framework documentation, or any other reference material that is not available within the project root. +---@field mode? blink-ripgrep.Mode # The mode to use for showing completions. Defaults to automatically showing suggestions. +---@field future_features? blink-ripgrep.FutureFeatures # Features that are not yet stable and might change in the future. You can enable these to try them out beforehand, but be aware that they might change. Nothing is enabled by default. + +---@class blink-ripgrep.FutureFeatures +---@field toggles? blink-ripgrep.ToggleKeymaps # Keymaps to toggle features on/off. This can be used to alter the behavior of the plugin without restarting Neovim. Nothing is enabled by default. + +---@class blink-ripgrep.ToggleKeymaps +---@field on_off? string # The keymap to toggle the plugin on and off from blink completion results. Example: "tg" + +---@alias blink-ripgrep.Mode +---| "on" # Show completions when triggered by blink +---| "off" # Don't show completions at all ---@class blink-ripgrep.RgSource : blink.cmp.Source ---@field get_command fun(context: blink.cmp.Context, prefix: string): blink-ripgrep.RipgrepCommand | nil @@ -40,12 +52,44 @@ RgSource.config = { ignore_paths = {}, project_root_fallback = true, additional_paths = {}, + mode = "on", + future_features = { toggles = nil }, } -- set up default options so that they are used by the next search ---@param options? blink-ripgrep.Options function RgSource.setup(options) RgSource.config = vim.tbl_deep_extend("force", RgSource.config, options or {}) + + if not RgSource.config.future_features.toggles then + if RgSource.config.debug then + require("blink-ripgrep.debug").add_debug_message( + "not enabling toggles because the feature is not enabled" + ) + end + + return + end + + local on_off = RgSource.config.future_features.toggles.on_off + if on_off then + require("snacks.toggle") + .new({ + id = "blink-ripgrep-manual-mode", + name = "blink-ripgrep", + get = function() + return RgSource.config.mode == "on" + end, + set = function(state) + if state then + RgSource.config.mode = "on" + else + RgSource.config.mode = "off" + end + end, + }) + :map(on_off, { mode = { "n" } }) + end end ---@param input_opts blink-ripgrep.Options @@ -126,6 +170,16 @@ local function render_item_documentation(opts, file, match) end function RgSource:get_completions(context, resolve) + if RgSource.config.mode ~= "on" then + if RgSource.config.debug then + local debug = require("blink-ripgrep.debug") + debug.add_debug_message("mode is off, skipping the search") + debug.add_debug_invocation({ "ignored-because-mode-is-off" }) + end + resolve() + return + end + local prefix = self.get_prefix(context) if string.len(prefix) < RgSource.config.prefix_min_len then