A Neovim plugin to quickly swap (switch, change)
a word (string) under the cursor or a pattern in the current line.
For example, if the cursor is on enable
it will switch to disable
and vice
versa (see Features).
[!NOTE] This plugin is based on my personal needs. Work in progress. ๐
Other similar or better plugins are:
[!CAUTION] BREAKING CHANGES (2025-07-03): The name has changed.
- The repo name has changed from
nvim-opposites
toswap.nvim
.- The plugin module name has changed from
opposites
toswap
.More information and older notes can be found in the Breaking Changes section.
Table of Contents:
true
-> false
true
, True
, tRUe
, TRUE
-> false
, False
, fALse
, FALSE
.foo
-> bar
-> baz
-> foo
foo_bar
-> fooBar
-> FooBar
-> foo_bar
- [ ] foo
-> - [x] foo
If several results are found, the user is asked which result to switch to.
return {
'tigion/swap.nvim',
keys = {
{ '<Leader>i', function() require('swap').switch() end, desc = 'Swap word' },
-- { '<Leader>I', function() require('swap').opposites.switch() end, desc = 'Swap to opposite word' },
-- { '<Leader>I', function() require('swap').chains.switch() end, desc = 'Swap to next word' },
-- { '<Leader>I', function() require('swap').cases.switch() end, desc = 'Swap naming convention' },
-- { '<Leader>I', function() require('swap').cases.switch('pascal') end, desc = 'Swap to PascalCase' },
-- { '<Leader>I', function() require('swap').todos.switch() end, desc = 'Swap todo state' },
},
---@module 'swap'
---@type swap.Config
opts = {},
}
Function | Description | Module |
---|---|---|
require('swap').switch() |
Uses all allowed modules (config) | |
require('swap').opposites.switch() |
Switches between opposite words | opposites |
require('swap').chains.switch() |
Switches through word chains | chains |
require('swap').cases.switch() |
Switches between naming conventions | cases |
require('swap').cases.switch('<case_id>') |
Switches to the given naming convention | cases |
require('swap').todos.switch() |
Switches through todo states | todos |
Call the functions directly or use them in a key mapping.
vim.keymap.set('n', '<Leader>i', require('swap').switch, { desc = 'Swap word' })
See the configuration section for the available default options and the modules section for configuration examples.
Call require(โswapโ).switch()
to change the word (string) under the cursor or
the pattern in the current line to one of the allowed modules in
the all.modules
table.
Example:
opts = {
all = {
-- modules = { 'opposites', 'todos' }, -- defaults
modules = { 'opposites', 'chains', 'cases', 'todos' },
},
}
Module | Description |
---|---|
opposites | Switches between opposite words |
chains | Switches through word chains |
cases | Switches between naming conventions |
todos | Switches through todo states |
Call require('swap').opposites.switch()
to switch to the opposite word
or string under the cursor. The found string can also be a part of a word.
For more own defined words, add them to the words
or words_by_ft
table in
the opposites
part of the swap.Config
table.
If use_default_words
and use_default_words_by_ft
is set to false
, only
the user defined words will be used.
Example:
opts = {
opposites = {
words = { -- Default opposite words.
['angel'] = 'devil', -- Adds a new one.
['yes'] = 'ja', -- Replaces the default `['yes'] = 'no'`.
['min'] = nil, -- Removes a default.
},
words_by_ft = { -- File type specific opposite words.
['lua'] = {
['=='] = '~=', -- Replaces the default `['=='] = '!='` for lua files.
},
['sql'] = {
['asc'] = 'desc', -- Adds a new for SQL files.
},
},
},
}
[!NOTE] Flexible word recognition can be used to avoid having to configure every variant of capitalization. Activated by default. See Case Sensitive Mask.
[!TIP] It doesn't have to be opposites words that are exchanged (e.g.
['Vim'] = 'Neovim'
).
Call require(โoppositesโ).chains.switch()
to switch to the next word or
string in a word chain under the cursor. The found string can also be a part of
a word.
Examples:
Monday
-> Tuesday
-> Wednesday
-> ... -> Sunday
-> Monday
foo
-> bar
-> baz
-> qux
-> foo
The word chains are defined in the words
and words_by_ft
tables in
the chains
part of the swap.Config
table.
Example:
opts = {
chains = {
words = { -- Default word chains.
{ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' },
{ 'foo', 'bar', 'baz', 'qux' },
},
words_by_ft = { -- File type specific word chains.
asciidoc = {
{ '[NOTE]', '[TIP]', '[IMPORTANT]', '[WARNING]', '[CAUTION]' }, -- AsciiDoc admonitions (block)
{ 'NOTE:', 'TIP:', 'IMPORTANT:', 'WARNING:', 'CAUTION:' }, -- AsciiDoc admonitions (line)
},
markdown = {
{ '[!NOTE]', '[!TIP]', '[!IMPORTANT]', '[!WARNING]', '[!CAUTION]' }, -- Markdown (GitHub) alerts
},
},
},
}
Rules:
[!NOTE] Flexible word recognition can be used to avoid having to configure every variant of capitalization. Activated by default. See Case Sensitive Mask.
[!WARNING] This feature is experimental and work in progress. The word identification is very limited (see Limits).
Call require('swap').cases.switch()
to switch to the next case type of the
word under the cursor.
Example:
foo_bar
โ FOO_BAR
โ foo-bar
โ FOO-BAR
โ fooBar
โ FooBar
โ foo_bar
Supported case types are:
snake_case
, SCREAMING_SNAKE_CASE
kebab-case
, SCREAMING-KEBAB-CASE
camelCase
PascalCase
The allowed case types and the switch order can be configured in the types
table in the cases
part of the swap.Config
table.
Example:
opts = {
cases = {
types = {
'snake', -- snake_case
'screaming_snake', -- SCREAMING_SNAKE_CASE
'kebab', -- kebab-case
'screaming_kebab', -- SCREAMING-KEBAB-CASE
'camel', -- camelCase
'pascal', -- PascalCase
},
},
}
[!TIP] With a given case type id in
require('swap').cases.switch('<case_id>')
you can also directly switch to an case type. The supported case type ids are:snake
,screaming_snake
,kebab
,screaming_kebab
,camel
andpascal
.Example with
require('swap').cases.switch('pascal')
:
- foo_bar -> FooBar
a-zA-Z0-9_-
).fooJson
, โ fooJSON
, โ
userId
, โ userID
).Examples:
foo_bar
, foo_bar1
, foo_bar_baz
foo
, foo_1bar
, _foo_bar
, foo_bar_
, foo_bar-baz
, foo_bar_Baz
Call require('swap').todos.switch()
to switch through the todo states.
Supported default todo syntax:
- [ ] foo
with the states [ ]
, [x]
Supported filetype specific todo syntax:
- [ ] foo
with the states [ ]
, [x]
* [ ] foo
with the states [ ]
, [x]
([*]
)- [ ] foo
with the states [ ]
, [-]
, [X]
([x]
)Rules:
In lazy.nvim, use the table opts = {}
for your own configuration. For other
plugin manager, call the setup function require('swap').setup({})
with the
provided options in {}
directly.
---@alias swap.ConfigModule
--- | 'opposites'
--- | 'cases'
--- | 'chains'
--- | 'todos'
---@alias swap.ConfigOppositesWords table<string, string>
---@alias swap.ConfigOppositesWordsByFt table<string, swap.ConfigOppositesWords>
---@alias swap.ConfigChainsWords string[][]
---@alias swap.ConfigChainsWordsByFt table<string, swap.ConfigChainsWords>
---@alias swap.ConfigCasesId
--- | 'snake' snake_case
--- | 'screaming_snake' SCREAMING_SNAKE_CASE
--- | 'kebab' kebab-case
--- | 'screaming_kebab' SCREAMING-KEBAB-CASE
--- | 'camel' camelCase
--- | 'pascal' PascalCase
---@alias swap.ConfigCasesTypes swap.ConfigCasesId[]
---@class swap.ConfigAll
---@field modules? swap.ConfigModule[] The default modules to use.
---@class swap.ConfigOpposites
---@field use_case_sensitive_mask? boolean Whether to use a case sensitive mask.
---@field use_default_words? boolean Whether to use the default opposites.
---@field use_default_words_by_ft? boolean Whether to use the default opposites by file type.
---@field words? swap.ConfigOppositesWords The words with their opposite words.
---@field words_by_ft? swap.ConfigOppositesWordsByFt The file type specific words with their opposite words.
---@class swap.ConfigChains
---@field use_case_sensitive_mask? boolean Whether to use a case sensitive mask.
---@field words? swap.ConfigChainsWords The word chains to search for.
---@field words_by_ft? swap.ConfigChainsWordsByFt The file type specific word chains to search for.
---@class swap.ConfigCases
---@field types? swap.ConfigCasesTypes The allowed case types to parse.
---@class swap.ConfigNotify
---@field found? boolean Whether to notify when a word is found.
---@field not_found? boolean Whether to notify when no word is found.
---@class swap.Config
---@field max_line_length? integer The maximum line length to search.
---@field ignore_overlapping_matches? boolean Whether to ignore overlapping matches.
---@field all? swap.ConfigAll The options for all modules.
---@field opposites? swap.ConfigOpposites The options for the opposites.
---@field cases? swap.ConfigCases The options for the cases.
---@field chains? swap.ConfigChains The options for the chains.
---@field notify? swap.ConfigNotify The notifications to show.
---@type swap.Config
local defaults = {
max_line_length = 1000,
ignore_overlapping_matches = true,
all = {
modules = { 'opposites', 'todos' },
},
opposites = {
use_case_sensitive_mask = true,
use_default_words = true,
use_default_words_by_ft = true,
words = {
['enable'] = 'disable',
['true'] = 'false',
['yes'] = 'no',
['on'] = 'off',
['and'] = 'or',
['left'] = 'right',
['up'] = 'down',
['min'] = 'max',
['=='] = '!=',
['<='] = '>=',
['<'] = '>',
},
words_by_ft = {
['lua'] = {
['=='] = '~=',
},
['sql'] = {
['asc'] = 'desc',
},
},
},
chains = {
use_case_sensitive_mask = true,
words = {},
words_by_ft = {},
},
cases = {
types = {
'snake',
'screaming_snake',
'kebab',
'screaming_kebab',
'camel',
'pascal',
},
},
notify = {
found = false,
not_found = true,
},
}
Flexible word recognition can be used to avoid having to configure every variant of capitalization. This means that variants with capital letters are also found for configured lower-case words and the replaced opposite word adapts the capitalization.
Rules:
Deactivate this behavior by setting use_case_sensitive_mask = false
in the
module options.
[!IMPORTANT] If a configured word or his opposite word contains capital letters, then for this words no mask is used.
Example with ['enable'] = 'disable'
:
enable
, Enable
, EnAbLe
and ENABLE
disable
, Disable
, diSAble
and DISABLE
Example with ['enable'] = 'Disable'
:
enable
Disable
By default, overlapping matches are ignored. This means that for the word
foofoo
, if the cursor is in the middle foo
of the word foofoofoo
, only
the first foofoo
is found and the second foofoo
is ignored.
If you want to not ignore overlapping matches, set the option
opts.ignore_overlapping_matches
to false
(default is true
).
2025-07-03: The name has changed.
nvim-opposites
to swap.nvim
.opposites
to swap
.2025-06-24: The functions have changed.
require('opposites').switch()
is now to switch to
a supported variant.require('opposites').opposites.switch()
is now only for switching to the
opposite word.require('opposites').cases.next()
is now require('opposites').cases.switch()
2025-06-19: The configuration has changed.
opposites
table.opposites
and opposites_by_ft
tables are now renamed to words
and
words_by_ft
.
swap.nvim
.{ 'foo', 'bar', 'baz' }
.opposites
and cases
.vim.ui.select
instead of vim.fn.inputlist
.true
,
True
, tRUe
and TRUE
.