From 1525ad8beed0b54876481b248619a2410b491f01 Mon Sep 17 00:00:00 2001 From: Sasha Gurevich Date: Wed, 11 Mar 2026 20:57:41 +0200 Subject: [PATCH 1/6] feat: initial work on visual replacer --- lua/toggle/init.lua | 3 ++- lua/toggle/replacer.lua | 22 ++++++++++++++++++++++ test/test_files/test_1.txt | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lua/toggle/init.lua b/lua/toggle/init.lua index d801d9a..1b637d3 100644 --- a/lua/toggle/init.lua +++ b/lua/toggle/init.lua @@ -37,10 +37,11 @@ end function M.toggle() local replacers = { + replacer.__visual_mode_replacer, replacer.__get_cWORD_replacer, replacer.__get_cword_replacer, replacer.__get_end_of_word_replacer, - replacer.__character_replacer, + replacer.__get_character_replacer, } local current_cursor_position = vim.fn.getcurpos() diff --git a/lua/toggle/replacer.lua b/lua/toggle/replacer.lua index 7b03b79..105d493 100644 --- a/lua/toggle/replacer.lua +++ b/lua/toggle/replacer.lua @@ -66,4 +66,26 @@ M.__get_end_of_word_replacer = function() } end +M.__visual_mode_replacer = function() + + local mode = vim.api.nvim_get_mode().mode + local selected_text = '' + if mode == 'v' or mode == 'V' then + local vstart = vim.fn.getpos(".") + local vend = vim.fn.getpos("v") + local lines = vim.fn.getregion(vstart, vend) + selected_text = lines[1] + end + + return { + can_handle = function() + return (mode == 'v' or mode == 'V') and mapping.__has_mapping(selected_text) + end, + + replace = function() + vim.api.nvim_command('norm! c' .. mapping.__get_mapping(selected_text)) + end, + } +end + return M diff --git a/test/test_files/test_1.txt b/test/test_files/test_1.txt index 50b2a23..e831355 100644 --- a/test/test_files/test_1.txt +++ b/test/test_files/test_1.txt @@ -2,3 +2,4 @@ false false, < goto_prev +thisisalongwordwithpublicanotherwordinside From 52b46fbf2a60100dc94bf6aea1cd63b5707f1f31 Mon Sep 17 00:00:00 2001 From: Sasha Gurevich Date: Thu, 12 Mar 2026 15:59:22 +0200 Subject: [PATCH 2/6] feat(defaults): added in - out mapping --- lua/toggle/defaults.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/toggle/defaults.lua b/lua/toggle/defaults.lua index eee4e1e..3ca35fe 100644 --- a/lua/toggle/defaults.lua +++ b/lua/toggle/defaults.lua @@ -11,6 +11,7 @@ return { -- short words { 'or', 'and' }, { 'on', 'off' }, + { 'in', 'out' }, { 'up', 'down' }, { 'get', 'set' }, { 'yes', 'no' }, From 39fc205b0279e942f4554c41841593432d0cdeda Mon Sep 17 00:00:00 2001 From: Sasha Gurevich Date: Thu, 12 Mar 2026 15:59:45 +0200 Subject: [PATCH 3/6] feat(replacer): added visual mode toggle logic --- lua/toggle/replacer.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lua/toggle/replacer.lua b/lua/toggle/replacer.lua index 105d493..5d843e4 100644 --- a/lua/toggle/replacer.lua +++ b/lua/toggle/replacer.lua @@ -67,10 +67,13 @@ M.__get_end_of_word_replacer = function() end M.__visual_mode_replacer = function() + local function is_visual_mode() + local mode = vim.api.nvim_get_mode().mode + return (mode == 'v') or (mode == 'V') + end - local mode = vim.api.nvim_get_mode().mode local selected_text = '' - if mode == 'v' or mode == 'V' then + if is_visual_mode() then local vstart = vim.fn.getpos(".") local vend = vim.fn.getpos("v") local lines = vim.fn.getregion(vstart, vend) @@ -79,7 +82,7 @@ M.__visual_mode_replacer = function() return { can_handle = function() - return (mode == 'v' or mode == 'V') and mapping.__has_mapping(selected_text) + return is_visual_mode() and mapping.__has_mapping(selected_text) end, replace = function() From 5ccad46ef916fb2cac70e626470decec1037409b Mon Sep 17 00:00:00 2001 From: Sasha Gurevich Date: Thu, 12 Mar 2026 20:29:36 +0200 Subject: [PATCH 4/6] test(visual): added tests for visual replacing --- test/test_files/test_1.txt | 3 ++- test/toggle_spec.lua | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/test/test_files/test_1.txt b/test/test_files/test_1.txt index e831355..230d2d3 100644 --- a/test/test_files/test_1.txt +++ b/test/test_files/test_1.txt @@ -2,4 +2,5 @@ false false, < goto_prev -thisisalongwordwithpublicanotherwordinside +thisisalongwordwithprotectedanotherwordinside +kaboomza diff --git a/test/toggle_spec.lua b/test/toggle_spec.lua index ba1a413..c230cd9 100644 --- a/test/toggle_spec.lua +++ b/test/toggle_spec.lua @@ -50,4 +50,23 @@ describe('toggle', function() assert(vim.fn.expand('') == 'goto_prev') end) end) + + it('toggle - no match', function() + open_file_at('test/test_files/test_1.txt', 6, 1, function() + toggle.toggle() + assert(vim.fn.expand('') == 'kaboomza') + toggle.toggle() + assert(vim.fn.expand('') == 'kaboomza') + end) + end) + + -- TODO + it('toggle - visual mode', function() + open_file_at('test/test_files/test_1.txt', 4, 5, function() + toggle.toggle() + assert(vim.fn.expand('') == 'goto_next') + toggle.toggle() + assert(vim.fn.expand('') == 'goto_prev') + end) + end) end) From 2a8dfb8609532db68c524bae2301ad1f5719c0b9 Mon Sep 17 00:00:00 2001 From: Sasha Gurevich Date: Thu, 12 Mar 2026 20:30:26 +0200 Subject: [PATCH 5/6] fix(stylua): formatting --- lua/toggle/replacer.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/toggle/replacer.lua b/lua/toggle/replacer.lua index 5d843e4..f3cce87 100644 --- a/lua/toggle/replacer.lua +++ b/lua/toggle/replacer.lua @@ -74,8 +74,8 @@ M.__visual_mode_replacer = function() local selected_text = '' if is_visual_mode() then - local vstart = vim.fn.getpos(".") - local vend = vim.fn.getpos("v") + local vstart = vim.fn.getpos('.') + local vend = vim.fn.getpos('v') local lines = vim.fn.getregion(vstart, vend) selected_text = lines[1] end From 11d51bd55a40d8d4b3cf3b9dfce53c890bc44574 Mon Sep 17 00:00:00 2001 From: Sasha Gurevich Date: Thu, 12 Mar 2026 20:35:26 +0200 Subject: [PATCH 6/6] feat(README): doc update --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02c0d9f..e3922be 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,16 @@ require('toggle').setup({ ### Keymap example ```lua -vim.keymap.set('n', 't', require('toggle').toggle, { desc = 'Toggle word' }) +-- v for visual mode, if needed +vim.keymap.set({ 'n', 'v' }, 't', require('toggle').toggle, { desc = 'Toggle word' }) ``` ## How it works -1. Gets the **word** under the cursor and checks for a mapping → replaces with `ciw` +1. Gets the **word** under the cursor and checks for a mapping → replaces with `ciw` / `ciW` 2. Falls back to the single **character** under the cursor → replaces with `r` +3. Also works for visual mode, in such case selection is being checked +4. Also checks if end of word matches anything in mappings and toggles it Mappings are circular: for a group like `{ 'public', 'protected', 'private' }`, each value cycles to the next, and the last wraps to the first.