# switch.vim: toggling the token under the cursor with one keystroke

> switch.vim is a Vim plugin that replaces the text under the cursor with the next item in a predefined list, so true becomes false and a markdown checkbox becomes checked. Its three matching rules decide what happens when several patterns overlap, and that is where the surprises live.

**AndrewRadev/switch.vim** — A simple Vim plugin to switch segments of text with predefined replacements

- Repository: https://github.com/AndrewRadev/switch.vim
- Website: http://www.vim.org/scripts/script.php?script_id=4172
- Stars: 763 · Forks: 31
- Language: Ruby
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/andrewradev-switch-vim

## One command, one token, one substitution

switch.vim is a simple Vim plugin that switches segments of text with predefined replacements. The purpose of the plugin is to switch some text under the cursor based on regex patterns, and the main entry point is a single command, :Switch.

When the command runs, the plugin looks for one of a few specific patterns under the cursor and performs a substitution depending on which one it found. The canonical example is a boolean literal, where the cursor is on the true in a line like flag = true and :Switch turns it into false. There is a demo screencast linked from the README for the visual version of that.

The default mapping to trigger the command is gs, and the README is upfront that this is already a Vim built-in but does not seem particularly useful. If you want a different key, you set g:switch_mapping in your .vimrc:

```vim
let g:switch_mapping = "-"
```

And to remove the mapping entirely, set it to an empty string:

```vim
let g:switch_mapping = ""
```

There is a second command, :SwitchReverse, and the mapping that drives it is g:switch_reverse_mapping, which is unset by default so no key is bound until you ask for one:

```vim
let g:switch_reverse_mapping = '+'
```

The plugin is MIT licensed, the default branch is main, the last push was on 2026-10-18, and the repository publishes no GitHub releases, so you install it the way you install any Vim plugin from a repository.

## Lists cycle forward, and the reverse command cycles back

The core idea is a list of replacements. A definition built as a list, for example ["one", "two", "three"], means the plugin substitutes whichever item it finds with the next one, wrapping around. For that definition, running :Switch on two gives three, and running :SwitchReverse on two gives one.

For dictionary switches, the reverse command works exactly like :Switch does. That is a meaningful difference in behaviour between the two definition styles, and it is worth knowing before you write a dictionary rule that you expect to be reversible.

The plugin also ships Plug mappings, which is the idiomatic Vim way to let a user rebind a plugin without hardcoding it:

```vim
nmap - <Plug>(Switch)
nmap + <Plug>(SwitchReverse)
```

The stated benefit of using mappings is that the commands become repeatable with the dot command. That is a real advantage over a command that edits the buffer in a way the dot command cannot capture, and it is the reason to bind through Plug rather than calling the command directly.

The example files in the repository show the breadth of what gets switched. There are examples for Clojure, CoffeeScript, C++, ERB, PHP, Ruby and Scala, plus dedicated examples for multibyte text and for a name with separators, and an example_spec.rb that exercises the definitions from a test harness rather than from an editor.

## Three matching rules, and the shortest match wins

The substitution follows three principles, and the second one is the one that changes how you use the plugin.

First, the cursor needs to be on the match. Regardless of the pattern, the plugin only performs the substitution if the cursor is positioned in the matched text. That is a deliberate design choice rather than a limitation, and it is what lets several patterns coexist on one line.

Second, when several patterns match, the shortest match is performed. The README uses a markdown checkbox as the example: the plugin switches the pair of brackets in a task list line, so an unchecked box becomes a checked one. The subtlety is that the true to false substitution is also active on that same line, and because that match is shorter, it wins whenever the cursor sits on the words true or false. The instruction is to move the cursor away from those words if you want the larger substitution instead.

Third, when several patterns of the same size match, the order of the definitions is respected. The example given is ERB, where a block that could either be commented out or could have its condition rewritten to a constant ends up taking the second form, because that pattern was placed higher in the definition list. If you want a different priority, you redefine the list.

So the cursor position is the control surface, and the outcome depends on where you put it. That is a genuinely different interaction model from a search and replace, and it is the thing to try before deciding the plugin suits you.

## Definitions live in variables, and the built-ins are a fallback

There are two variables holding the definition lists, a global one and a buffer-local one, named g:switch_definitions and b:switch_definitions. The built-in definitions for the plugin live there. If you want to add your own switches, the instruction is to override g:switch_custom_definitions and b:switch_custom_definitions instead, so your definitions sit alongside the built-ins rather than replacing them.

The format is a plain List, and each item is either a List or a Dict. A list definition looks like this:

```vim
let g:switch_custom_definitions =
    \ [
    \   ['foo', 'bar', 'baz']
    \ ]
```

With that list, foo under the cursor becomes bar, bar becomes baz, and baz becomes foo, cycling round. The documentation notes that this simple case is implemented, in a slightly different way, by the toggle.vim plugin, which is the closest thing here to a direct alternative.

The dict form is described as the more complicated and more powerful way to define a switch pattern, and the README section on it is cut off before the details in the copy available here, so the reference to read is the doc/ directory in the repository, which contains the plugin's own help file, including the switch-internals topic that explains the underlying data format.

There is also a wiki linked from the README for more switches from the community, which is where the interesting definitions live once you start writing your own.

## Modifier functions for case and word boundaries

A plain list definition matches literally, so switching one and Two does not work. The plugin solves that with modifier functions that wrap a list, and there are three of them.

switch#NormalizedCase makes the switch work across capitalizations, so one, One and ONE are treated as the same item and switch to each other. switch#Words restricts the match to word boundaries, behaving as if the patterns had the \< and \> modifiers added. switch#NormalizedCaseWords combines both.

They are used like this:

```vim
let g:switch_custom_definitions =
    \ [
    \   switch#NormalizedCase(['one', 'two']),
    \   switch#Words(['three', 'four']),
    \   switch#NormalizedCaseWords(['five', 'six']),
    \ ]
```

The result is that the first definition switches between one and two in any capitalization, the second switches between three and four only at word boundaries, and the third does both at once for five and six.

The underlying data format is documented in the switch-internals help topic, and the README points at it explicitly for anyone who wants to generate definitions another way, for instance by loading them from JSON. That escape hatch matters if you plan to build a large definition set, since the Vimscript literal form gets unwieldy quickly.

## The function form, and the return value to branch on

For anything beyond the two commands, the plugin exposes an autoloaded function, switch#Switch. Calling it with no arguments is the same as calling the:Switch command:

```vim
:call switch#Switch()
" equivalent to:
:Switch
```

It also accepts a dict of options, and the reverse behaviour is one of them:

```vim
:call switch#Switch({'reverse': 1})
" or,
:call switch#Switch({'reverse': v:true})
" equivalent to:
:SwitchReverse
```

The other option is definitions, which sets an explicit list of definitions different from the built-ins for that one call:

```vim
:call switch#Switch({'definitions': list_of_definitions})
```

And there is a return value to work with. The switch#Switch() function returns 1 if it succeeded and 0 if it failed, and the intended use is to decide whether to apply some other mapping, which is how you chain a plugin action onto the result of a substitution.

The repository is worth a look for one more reason. The primary language of the repository is Ruby, and the top level of the tree carries a Gemfile, a Gemfile.lock, a Rakefile, an.rspec file and a spec/ directory, so the test suite is written in Ruby with RSpec, alongside a.travis.yml and a.gitmodules file. The plugin/ directory holds the plugin entry point, autoload/ holds the functions, ftplugin/ holds filetype-specific behaviour, and doc/ holds the help file.

## Conclusion

Adopt switch.vim if you spend the day flipping boolean literals, markdown checkboxes and language keywords, since one keystroke at the cursor is faster and more reliable than a search and replace, and the reverse command covers the way back. Keep it out of your setup if you want a single mapping that does everything, because the plugin is opinionated about match length and the shorter match always wins. Before you install it, read the doc/ directory in the repository and the switch-internals help topic, and note that the plugin defines a gs mapping by default, which is a Vim built-in you will be overriding.

## FAQ

### What does switch.vim do in Vim?

It replaces the text under the cursor with the next item in a predefined list, so true becomes false and a markdown checkbox becomes checked. The entry point is the :Switch command, triggered by default with the gs mapping, and the :SwitchReverse command works in the opposite direction.

### How do I change or remove the default gs mapping in switch.vim?

Set g:switch_mapping in your .vimrc to the key you want, for example let g:switch_mapping = "-", or set it to an empty string to remove the mapping completely. The reverse command has its own variable, g:switch_reverse_mapping, which is unset by default.

### Which switch wins when several patterns match under the cursor?

The shortest match is performed. If several patterns match with the same length, the order of the definitions decides, so a pattern placed higher in the list takes priority. Moving the cursor away from a shorter match changes which substitution runs.

### How do I add my own switch definitions to switch.vim?

Override g:switch_custom_definitions or b:switch_custom_definitions rather than the built-in lists, so your definitions are added to the ones the plugin ships. Each item is either a List or a Dict, and the modifier functions switch#NormalizedCase, switch#Words and switch#NormalizedCaseWords adjust case handling and word boundary matching.

## Sources

- [AndrewRadev/switch.vim on GitHub](https://github.com/AndrewRadev/switch.vim)
- [Issues](https://github.com/AndrewRadev/switch.vim/issues)
- [License: MIT](https://github.com/AndrewRadev/switch.vim/blob/main/LICENSE)
- [Project website](http://www.vim.org/scripts/script.php?script_id=4172)
- [README](https://github.com/AndrewRadev/switch.vim/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/andrewradev-switch-vim
