The set of operator and textobject plugins to search/select/edit sandwiched textobjects.
Clone or download


Build Status Build status

sandwich.vim is a set of operator and textobject plugins to add/delete/replace surroundings of a sandwiched textobject, like (foo), "bar".

Quick start


Press sa{motion/textobject}{addition}. For example, a key sequence saiw( makes foo to (foo).


Press sdb or sd{deletion}. For example, key sequences sdb or sd( makes (foo) to foo. sdb searches a set of surrounding automatically.


Press srb{addition} or sr{deletion}{addition}. For example, key sequences srb" or sr(" makes (foo) to "foo".

That's all. Now you already know enough about sandwich.vim. If you are using vim-surround, you can use a preset keymappings similar as it. See here

sandwich.vim has some functional input for {addition}/{deletion}. Check here!


This plugin provides functions to add/delete/replace surroundings of a sandwiched text. These functions are implemented genuinely by utilizing operator/textobject framework. Thus their action can be repeated by . command without any dependency. It consists of two parts, operator-sandwich and textobj-sandwich.


A sandwiched text could be resolved into two parts, {surrounding} and {surrounded text}.

  • Add surroundings: mapped to the key sequence sa

    • {surrounded text} ---> {surrounding}{surrounded text}{surrounding}
  • Delete surroundings: mapped to the key sequence sd

    • {surrounding}{surrounded text}{surrounding} ---> {surrounded text}
  • Replace surroundings: mapped to the key sequence sr

    • {surrounding}{surrounded text}{surrounding} ---> {new surrounding}{surrounded text}{new surrounding}


  • Search and select a sandwiched text automatically: mapped to the key sequence ib and ab
  • Search and select a sandwiched text with query: mapped to the key sequence is and as

ib and is selects {surrounded text}. ab and as selects {surrounded text} including {surrounding}s.

{surrounding}{surrounded text}{surrounding}


The point is that it would be nice to be shared the definitions of {surrounding}s pairs in all kinds of operations. User can freely add new settings to extend the functionality. If g:sandwich#recipes was defined, this plugin works with the settings inside. As a first step, it would be better to copy the default settings in g:sandwich#default_recipes.

let g:sandwich#recipes = deepcopy(g:sandwich#default_recipes)

Each setting, it is called recipe, is a set of a definition of {surrounding}s pair and options. The key named buns is used for the definition of {surrounding}.

let g:sandwich#recipes += [{'buns': [{surrounding}, {surrounding}], 'option-name1': {value1}, 'option-name2': {value2} ...}]

For example: {'buns': ['(', ')']}
    foo   --->   (foo)

Or there is a different way, use external textobjects to define {surrounding}s from the difference of two textobjects.

let g:sandwich#recipes += [{'external': [{textobj-i}, {textobj-a}], 'option-name1': {value1}, 'option-name2': {value} ...}]

For example: {'external': ['it', 'at']}
    <title>foo</title>   --->   foo


Unique count handling

As for the default operators, the possible key input in normal mode is like this.


Default operators do not distinguish [count1] and [count2] but operator-sandwich does. [count1] is given for {operators} and [count2] is given for {textobject}.

Linewise and blockwise operations

Operator-sandwich works linewise with the linewise-visual selection and linewise motions.

" press Vsa(
    foo  --->  (

Using command option, user can execute vim Ex-commands after an action. For example it can be used to adjust indent automatically.

let g:sandwich#recipes += [
      \   {
      \     'buns'        : ['{', '}'],
      \     'motionwise'  : ['line'],
      \     'kind'        : ['add'],
      \     'linewise'    : 1,
      \     'command'     : ["'[+1,']-1normal! >>"],
      \   },
      \   {
      \     'buns'        : ['{', '}'],
      \     'motionwise'  : ['line'],
      \     'kind'        : ['delete'],
      \     'linewise'    : 1,
      \     'command'     : ["'[,']normal! <<"],
      \   }
      \ ]

" press Vsa{
    foo    --->  {

" press V2jsd
    {      --->  foo

Operator-sandwich also can work blockwise with the blockwise-visual selection and blockwise motions.

" press <C-v>2j2lsa(
    foo        (foo)
    bar  --->  (bar)
    baz        (baz)

There is an option to skip white space skip_space, it is valid in default. Empty line is ignored.

" press <C-v>3j$sa(
    fooooooo            (fooooooo)
      baaaar   --->       (baaaar)

    baaaz               (baaaz)

Expression surroundings and regular expression matching

The option expr enables to evaluate surroundings (buns) before adding/deleting/replacing surroundings. The following recipe is an simple example to wrap texts by html style tags.

let g:sandwich#recipes += [
      \   {
      \     'buns'    : ['TagInput(1)', 'TagInput(0)'],
      \     'expr'    : 1,
      \     'filetype': ['html'],
      \     'kind'    : ['add', 'replace'],
      \     'action'  : ['add'],
      \     'input'   : ['t'],
      \   },
      \ ]

function! TagInput(is_head) abort
  if a:is_head
    let s:TagLast = input('Tag: ')
    if s:TagLast !=# ''
      let tag = printf('<%s>', s:TagLast)
      throw 'OperatorSandwichCancel'
    let tag = printf('</%s>', matchstr(s:TagLast, '^\a[^[:blank:]>/]*'))
  return tag

The option regex is to regard surroundings (buns) as regular expressions to match and delete/replace. The following recipe is an simple example to delete both ends of html tag.

let g:sandwich#recipes += [
      \   {
      \     'buns'    : ['<\a[^[:blank:]>/]*.\{-}>',
      \                  '</\a[^[:blank:]>/]*>'],
      \     'regex'   : 1,
      \     'filetype': ['html'],
      \     'nesting' : 1,
      \     'input'   : ['t'],
      \   },
      \ ]

However the above example is not so accurate. Instead of the example, there are excellent built-in textobjects it and at, these external textobjects also can be utilized through external.

let g:sandwich#recipes += [
      \   {
      \     'external': ['it', 'at'],
      \     'noremap' : 1,
      \     'filetype': ['html'],
      \     'input'   : ['t'],
      \   },
      \ ]