*textobj-sandwich.txt* The textobject plugin to select sandwiched texts. Last change:28-Nov-2021. Author : machakann License : NYSL license Japanese English (Unofficial) Requirement: Vim 7.4 or higher |+reltime| feature (optional) |+float| feature (optional) ============================================================================== CONTENTS *textobj-sandwich-contents* TL;DR INTRODUCTION |textobj-sandwich-introduction| KEYMAPPINGS |textobj-sandwich-keymappings| CONFIGURATION |textobj-sandwich-configuration| REQUISITE buns external FILTERS filetype kind mode operator LOCAL OPTIONS nesting expand_range regex skip_regex skip_regex_head skip_regex_tail quoteescape expr noremap syntax inner_syntax match_syntax skip_breaking skip_expr GLOBAL OPTIONS timeout |g:textobj#sandwich#timeout| timeoutlen |g:textobj#sandwich#timeoutlen| stimeoutlen |g:textobj#sandwich#stimeoutlen| latest_jump |g:textobj#sandwich#latest_jump| MISCELLANEOUS |textobj-sandwich-miscellaneous| It is not what I expect with key sequence dib. Timed out so frequently! My cursor moved a lot because of unintended selection! ============================================================================== TL;DR See |sandwich-quick-start|. ============================================================================== INTRODUCTION *textobj-sandwich-introduction* *textobj-sandwich* is an textobject plugin to search and select sandwiched text, like (foo) or "bar". It consists of four independent textobject keymappings, |(textobj-sandwich-auto-i)|, |(textobj-sandwich-auto-a)|, |(textobj-sandwich-query-i)| and |(textobj-sandwich-query-a)|. |(textobj-sandwich-auto-i)| and |(textobj-sandwich-auto-a)| searches a sandwiched text automatically. The sets of surroundings called "recipe"s are listed in advance. It is described in detail in |textobj-sandwich-configuration|. |(textobj-sandwich-auto-i)| selects a surrounded region without surroundings. |(textobj-sandwich-auto-a)| selects a surrounded region including surroundings. These are mapped to key sequences `ib` and `ab` in default. |(textobj-sandwich-query-i)| and |(textobj-sandwich-query-a)| searches a sandwiched text based on a user input. If the user input is matched with a recipe registered, then it selects the text determined by the matched recipe. Otherwise it selects the region surrounded by a same character input. |(textobj-sandwich-query-i)| selects a surrounded region without surroundings. |(textobj-sandwich-query-a)| selects a surrounded region including surroundings. These are mapped to key sequences `is` and `as` in default. ------------------------------------------------------------------------------ In visual mode, these textobjects expands the selection area by successive key sequence as similar to |v|iw|iw|iw|.... Assume that the following text and the cursor is on foo. > {baz[bar(foo)bar]baz} < Press `vib` > <-> : selected region {baz[bar(foo)bar]baz} < Press `ib` again > <---------> : selected region {baz[bar(foo)bar]baz} < Press `ib` again > <-----------------> : selected region {baz[bar(foo)bar]baz} < ------------------------------------------------------------------------------ In block-wise visual mode, a target text is searched within the cursor line and the other end follows. For example, assume the 5 by 5 text. > (foo) (bar) (baz) < When the cursor is on the first line, press `2jibd` > () () () < Next assume that the lines like this: > foooo baaar (baz) < When the cursor is on the first line, press `2jibd` > fo br () < ============================================================================== KEYMAPPINGS *textobj-sandwich-keymappings* This plugin defines following keymappings. They are valid in operator-pending mode and visual mode. keymappings default keymappings ----------------------------------------------------------- (textobj-sandwich-auto-i) ib (textobj-sandwich-auto-a) ab (textobj-sandwich-query-i) is (textobj-sandwich-query-a) as ----------------------------------------------------------- If you do not need default keymappings, define a variable named g:textobj_sandwich_no_default_key_mappings in your vimrc. > let g:textobj_sandwich_no_default_key_mappings = 1 < Then default mappings are never applied. And map them again as you like. > omap ia (textobj-sandwich-auto-i) xmap ia (textobj-sandwich-auto-i) omap aa (textobj-sandwich-auto-a) xmap aa (textobj-sandwich-auto-a) < ------------------------------------------------------------------------------ keymappings~ (textobj-sandwich-auto-i) *(textobj-sandwich-auto-i)* The textobject mapping to search and select a sandwiched text automatically. This keymapping selects inside surroundings. The surroundings are not included. This keymapping is mapped to `ib` of operator-pending mode and visual mode in default. This keymapping could be mapped to operator-pending mode |:omap|, to visual mode |:xmap|, and also to normal mode |:nmap|. (textobj-sandwich-auto-a) *(textobj-sandwich-auto-a)* The textobject mapping to search and select a sandwiched text automatically. This keymapping selects sandwiched text including surroundings. This keymapping is mapped to `ab` of operator-pending mode and visual mode in default. This keymapping could be mapped to operator-pending mode |:omap|, to visual mode |:xmap|, and also to normal mode |:nmap|. (textobj-sandwich-query-i) *(textobj-sandwich-query-i)* The textobject mapping to search and select a sandwiched text depending on a user input. This keymapping selects inside surroundings. The surroundings are not included. This keymapping is mapped to `is` of operator-pending mode and visual mode in default. This keymapping could be mapped to operator-pending mode |:omap|, to visual mode |:xmap|, and also to normal mode |:nmap|. (textobj-sandwich-query-a) *(textobj-sandwich-query-a)* The textobject mapping to search and select a sandwiched text depending on a user input. This keymapping selects sandwiched text including surroundings. This keymapping is mapped to `as` of operator-pending mode and visual mode in default. This keymapping could be mapped to operator-pending mode |:omap|, to visual mode |:xmap|, and also to normal mode |:nmap|. *(textobj-sandwich-literal-query-i)* (textobj-sandwich-literal-query-i) This textobject mapping is similar as |(textobj-sandwich-query-i)| but it always ignores user settings, |g:sandwich#recipes| and |g:textobj#sandwich#recipes|. The user input is interpreted as-is, thus the mapping selects a text surrounded by the same characters. This keymapping could be mapped to operator-pending mode |:omap|, to visual mode |:xmap|, and also to normal mode |:nmap|. > omap if (textobj-sandwich-literal-query-i) xmap if (textobj-sandwich-literal-query-i) " press difa to delete a text surrounded by 'a'. " abcba -> aa < *(textobj-sandwich-literal-query-a)* (textobj-sandwich-literal-query-a) This textobject mapping is similar as |(textobj-sandwich-query-a)| but it always ignores user settings, |g:sandwich#recipes| and |g:textobj#sandwich#recipes|. The user input is interpreted as-is, thus the mapping selects a text surrounded by the same characters. This keymapping could be mapped to operator-pending mode |:omap|, to visual mode |:xmap|, and also to normal mode |:nmap|. > omap if (textobj-sandwich-literal-query-a) xmap if (textobj-sandwich-literal-query-a) " press dafb to delete a text surrounded by 'b' including 'b'. " abcba -> aa < KEY MAPPING FUNCTIONS~ User can make new mappings by using function interfaces natively. *textobj#sandwich#auto()* textobj#sandwich#auto(mode, a_or_i[, options[, recipes]]) This function is used to make a operator key-mapping as following. > onoremap ib textobj#sandwich#auto('o', 'i') xnoremap ib textobj#sandwich#auto('x', 'i') onoremap ab textobj#sandwich#auto('o', 'a') xnoremap ab textobj#sandwich#auto('x', 'a') < If a not-empty dictionary is given to the optional third argument of this function, the local options inside the dictionary override default option values. > " example 1 onoremap ib \ textobj#sandwich#auto('o', 'i', {'expand_range': 0}) " example 2 let g:sandwich_alt_options = {'expand_range': 0} onoremap ib \ textobj#sandwich#auto('o', 'i', g:sandwich_alt_options) < If a list of recipes is given to the optional fourth argument of this function, the key mapping use the list instead of |g:sandwich#recipes| (|g:sandwich#default_recipes|) and |g:textobj#sandwich#recipes| (|g:textobj#sandwich#default_recipes|). > " example 1 onoremap ib \ textobj#sandwich#auto('o', 'i', {}, [{'buns': ['(', ')']}]) " example 2 let g:sandwich_alt_recipes = [{'buns': ['(', ')']}] onoremap ib \ textobj#sandwich#auto('o', 'i', {}, g:sandwich_alt_recipes) < *textobj#sandwich#query()* textobj#sandwich#query(mode, a_or_i[, options[, recipes]]) This function is similar as |textobj#sandwich#auto()|, but it is used for declaring new query series textobjects. > onoremap is textobj#sandwich#query('o', 'i') xnoremap is textobj#sandwich#query('x', 'i') onoremap as textobj#sandwich#query('o', 'a') xnoremap as textobj#sandwich#query('x', 'a') < ============================================================================== CONFIGURATION *textobj-sandwich-configuration* A set of surroundings and options for it is called "recipe". Each recipe is a dictionary and the |list|s of recipes determines the textobject's behavior. |g:sandwich#default_recipes| is one of the |list|s of recipes. This is shared to be used with |operator-sandwich| since it is convenient in many cases. If |g:sandwich#recipes| is defined by user, it is employed alternatively. The default recipes |g:sandwich#default_recipes| can be checked by |:echo| command. > :echo g:sandwich#default_recipes < This variable is locked usually, but it can be copied when you declare |g:sandwich#recipes| if you need. > :let g:sandwich#recipes = deepcopy(g:sandwich#default_recipes) < The set of "(" and ")", "[" and "]", "{" and "}", "<" and ">" are registered in default. The detailed description is in |g:sandwich#default_recipes|. |g:textobj#sandwich#default_recipes| is another list of recipes. This is used only by |textobj-sandwich|. If |g:textobj#sandwich#recipes| is defined, it is employed alternatively. |g:textobj#sandwich#default_recipes| is locked as same as |g:sandwich#default_recipes|. g:textobj#sandwich#recipes *g:textobj#sandwich#recipes* This is one of the lists of recipes which is referred only from |textobj-sandwich|. If this list does not exist, |g:textobj#sandwich#default_recipes| is used. *b:textobj_sandwich_recipes* If |b:textobj_sandwich_recipes| exists, it would be used instead of |g:textobj#sandwich#recipes|. This is buffer local, thus it might be convenient to manage too many filetype-specific recipes. g:textobj#sandwich#default_recipes *g:textobj#sandwich#default_recipes* This is one of the lists of recipes which is prepared in default. If |g:textobj#sandwich#recipes| exists, it will be used alternatively. This variable is locked usually, but it can be copied when you declare |g:textobj#sandwich#recipes| if you need. > :let g:textobj#sandwich#recipes \ = deepcopy(g:textobj#sandwich#default_recipes) < NOTE: If recipes are conflicted in some reason, |g:textobj#sandwich#default_recipes| and |g:textobj#sandwich#recipes| is prior to |g:sandwich#default_recipes| and |g:sandwich#recipes|. In a same list, a latter item is prior to a former item. ------------------------------------------------------------------------------ A recipe is a |Dictionary| variable and it can have four kinds of information. Requisite, input, filters and local options. The requisite is essential for all recipe, it defines a set of surroundings. The input is a option to assign a recipe for an action. The filters is the option to filter recipes depending on the situation in use. The local option is utilized to tune the behavior for each recipe. In addition to them, several global options are employed to control fundamental behavior of the textobjects. As a first step, define your list of recipes. > let g:sandwich#recipes = [] < Or just copy the default one if you need. > let g:sandwich#recipes = deepcopy(g:sandwich#default_recipes) < Requisite~ There are two kinds of requisite, buns and external. All recipes should have any one of the two. buns This is the key to assign the surroundings. Its value is a list including two strings which will be the surroundings searched in text. If "regex" option is true, it is regarded as regular expression. If a recipe do not have "input" option, this is used as the assignment input for |(textobj-sandwich-query-i)| and |(textobj-sandwich-query-a)|. > let g:sandwich#recipes += [ \ {'buns': ['(', ')']} \ ] " Press dis( or dis) " (foo) ---> () let g:sandwich#recipes += [ \ {'buns': ['ab', 'cd']} \ ] " Press disab or discd " abfoocd ---> abcd let g:sandwich#recipes += [ \ {'buns': ['ab', 'cd'], 'input': ['a']} \ ] " Press disa " abfoocd ---> abcd < external This is supplementary requisite. This is a list including two textobjects as strings. A narrower textobject is the first item and wider textobject is the second item. This is used when a user want to use external textobjects as a recipe. Many local options are not valid. > let g:sandwich#recipes += [ \ {'external': ['it', 'at']} \ ] " "it" selects the text inside tags, "at" selects including tags. " <---it---> " title here " <----------at-----------> " Press dib " title here ---> " Press dab " title here ---> < "noremap" option is applied for this. Since visual selection is employed to check its region intrinsically, only the mappings in visual mode are related to the option. > let g:sandwich#recipes += [ \ {'external': ['i[', 'a['], 'noremap': 0} \ ] " Press dib " [foo] ---> [] xnoremap i[ i{ xnoremap a[ a{ " Press dib " {foo} ---> {} < Combined with "noremap" option, user defined textobjects can be used in the same way. > " "noremap" option should be false. let g:sandwich#recipes += [ \ { \ 'external': ["\(textobj-sandwich-auto-i)", \ "\(textobj-sandwich-auto-a)"], \ 'noremap': 0, \ 'kind': ['query'], \ 'input': ['b'] \ } \ ] < NOTE: Registered textobjects should work correctly in visual mode. NOTE: Not all the user defined textobjects are not guaranteed to work. Input~ input This is the key to assign a recipe for an searching. |(textobj-sandwich-query-i)| and |(textobj-sandwich-query-a)| ask user to determine surroundings in an searching. At that moment, users are asked an input to assign a recipe. This option makes the input. If a recipe does not have the key, items in "buns" are used for the assignment. > let g:sandwich#recipes += [ \ {'buns': ['"""', '"""']} \ ] " Press dis""" " """foo""" ---> """""" < If the recipe has input key, it will be used alternatively. > let g:sandwich#recipes += [ \ {'buns': ['"""', '"""'], 'input': ['"']} \ ] " Press dis" " """foo""" ---> """""" < This value should be a |list|, and multiple assignment is valid. Filter~ filetype This filter filters recipes by filetypes in use. It is a list of filetypes as strings. If a recipe does not have filetype key or has a value "all", the recipe is valid on any filetype. > " The following recipes are valid on any filetype. let g:sandwich#recipes += [ \ {'buns': ['(', ')']} \ {'buns': ['[', ']'], 'filetype': ['all']} \ ] " The textobj "it" and "at" is not versatile and might be heavy on " large files, thus it would be better to activate only on specific " filetypes. let g:sandwich#recipes += [ \ {'external': ['it', 'at'], 'filetype': ['html']} \ ] < kind This filter filters recipes by kinds of operator actions. It is a list of names of kinds. "auto", "query", "textobj" and "all" can be used. "textobj" is same as both "auto" and "query". The difference between "textobj" and "all" is that "all" might include operator kinds in |g:sandwich#recipes|. See |operator-sandwich-configuration|. If a recipe does not have kind key, the recipe is valid on any kind. > " The following recipe is valid only with " (textobj-sandwich-auto-i) and (textobj-sandwich-auto-a) let g:sandwich#recipes += [ \ {'buns': ['"""', '"""'], 'kind': ['auto']} \ ] < mode This filter filters recipes by modes. It is a list of characters representing modes. "o" or "x" can be used. "o" represents a use in operator-pending mode, and "x" represents a use in visual mode. If a recipe does not have mode key, the recipe is valid on any mode. > " These recipes are switch behaviors on modes with the same input. let g:sandwich#recipes += [ \ {'buns': ['"', '"'], 'mode': ['o']} \ {'buns': ['"""', '"""'], 'mode': ['x'], 'input': ['"']} \ ] < action This key is used to filter the recipes by the kinds of action. "add" and "delete" is reserved for |operator-sandwich|, "all" includes the those two kinds of action. "add" prohibits to take effect for |textobj-sandwich| and "delete" and "all" allows it, because usually a delete action uses those textobjects. > " This recipe does not take effect for textobjects let g:sandwich#recipes += [ \ {'buns': ['"""', '"""'], 'action': ['add'], 'input': ['"']} \ ] " This recipe is valid for delete actions and textobjects let g:sandwich#recipes += [ \ {'buns': ['"""', '"""'], 'action': ['delete'], 'input': ['"']} \ ] < expr_filter A user can define filters by oneself. The items of the list are evaluated as |expression|, and if the value is true (1) then the recipe is valid, if the value is false (0) then the recipe is invalid. > " A filter should be defined in somewhere, for example in your vimrc. function! FilterValid() return 1 endfunction function! FilterInvalid() return 0 endfunction " This recipe is valid let g:sandwich#recipes += [ \ {'buns': ['(', ')'], 'expr_filter': ['FilterValid()']} \ ] " This recipe is invalid let g:sandwich#recipes += [ \ {'buns': ['(', ')'], 'expr_filter': ['FilterInvalid()']} \ ] < For example, the following filter makes recipes possible to be valid only with a specific operator. > function! FilterOperator(operator) return a:operator ==# v:operator ? 1 : 0 endfunction " This recipe is valid only with d operator. let g:sandwich#recipes += [ \ {'buns': ['(', ')'], 'expr_filter': ['FilterOperator("d")']} \ ] < Local option~ Local options are used to optimize the behavior for each recipe. If any option is set, the default value depending on kinds is used. These default values are changed by |g:textobj#sandwich#options|. If you want to change the default value of skip_break option of |(textobj-sandwich-query-i)| and |(textobj-sandwich-query-a)| > let g:textobj#sandwich#options.query.skip_break = 1 < Or use the function |textobj#sandwich#set()|. > call textobj#sandwich#set('query', 'skip_break', 1) < g:textobj#sandwich#options *g:textobj#sandwich#options* The dictionary includes default local option values. > " let textobj#sandwich#options[kind][option] = {value} " For example let g:textobj#sandwich#options['query']['skip_break'] = 1 " or let g:textobj#sandwich#options.query.skip_break = 1 < *b:textobj_sandwich_options* If |b:textobj_sandwich_options| exists, it will be used instead of |g:textobj#sandwich#options|. It would be useful when a user wants to use buffer-local option settings. Available keys are listed below. * kind - query - auto * option - nesting - expand_range - regex - skip_regex - skip_regex_head - skip_regex_tail - quoteescape - expr - noremap - syntax - inner_syntax - match_syntax - skip_break - skip_expr *textobj#sandwich#set()* textobj#sandwich#set(kind, option, value) The function to change default values of local options easily and safely. If the combination of arguments is not appropriate, this function shows error messages. The available arguments are listed in |g:textobj#sandwich#options|. In addition to that, "all" is available for kind. *textobj#sandwich#setlocal()* textobj#sandwich#setlocal(kind, option, value) The function to change buffer-local default values of local options easily and safely. If the combination of arguments is not appropriate, this function shows error messages. The available arguments are listed in |g:textobj#sandwich#options|. In addition to that, "all" is available for kind. textobj#sandwich#set_default() *textobj#sandwich#set_default()* The function initializes all default values of local options. nesting This option switches the searching algorithm. If a set of "buns" makes nested structure, this option should be 1. Otherwise it should be 0. For example, ( and ) makes nest but " is not generally. Assume that the cursor is on foo of the text, expected results are different depending on whether it makes nest or not. Yank a part of text by a key sequence `yib`. > # : cursor case1: (foo(bar)baz) : foo(bar)baz is the expected result case2: "foo"bar"baz" : foo is the expected result < This option is desirable to set on every recipes. Default values * query: 0 * auto : 0 expand_range If this option is 0, surroundings are searched within the cursor line. If this option is a positive integer, the searched range is expanded by the number of lines. If this option is a negative integer, whole buffer is taken as the searched range. NOTE: Intrinsically, it is not searched a wide range at a time. The searching lines are expanded step-by-step with monitoring the elapsed time and a certain time |g:textobj#sandwich#stimeoutlen| is elapsed without finding [count] number of candidate, the searching process would time out. In many cases, 0 or a negative number would be a good choice. Default values * query: -1 * auto : -1 regex If this option is true, requisite "buns" is regarded as regular expressions. > let g:sandwich#recipes += [ \ {'buns': ['\d\+', '\d\+'], 'regex': 1} \ ] " Press dib " 123foo456 ---> 123456 < NOTE: If this option is true, the recipe requires "input" to use with query-textobjects. "buns" never be regarded as key inputs to trigger. Default values * query: 0 * auto : 0 skip_regex skip_regex_head skip_regex_tail These are lists of regular expressions to skip candidate positions in searching. When the textobjects searches a target text, even though a text matched with "buns", if the text also matched with an item in these lists then it will be skipped. The regular expression have to matched with the head of a skipped text. > let g:sandwich#recipes += [ \ {'buns': ['b', 'a'], 'skip_regex': ['a\zea']} \ ] " Press dib " baaaaaaaar ---> bar < Items in "skip_regex" are checked both in searching head and tail buns. Usually this is enough helpful, but if head, tail and escape character are all same, it might be a problem. For instance, successive two quotes, '', inside a quote wrapped string literal is regarded as a quote in Vim script. > :echo '''foo bar''' " -> 'foo bar' < To skip the successive quotes, searching head and tail are required to be distinguished. Use "skip_regex_head", "skip_regex_tail". > let g:sandwich#recipes += [ \ { \ 'buns': ["'", "'"], \ 'filetype': ['vim'], \ 'expand_range': 0, \ 'skip_regex_head': \ ['\%(\%#\zs''\|''\%#\zs\)''\%(''''\)*[^'']'], \ 'skip_regex_tail': \ ['[^'']\%(''''\)*\%(\%#\zs''\|''\%#\zs\)'''], \ 'nesting': 0 \ 'match_syntax': 2, \ } \ ] < NOTE: This is still not perfect. If cursor is on ended successive single quotes, it does not work as expected. > ### ### : Cursor should not be on '''foo '' bar''' : the positions pointed by #. < Cursor is moving in searching positions, thus user can use the pattern \%# |/\%#|. Default values * query: [] * auto : [] quoteescape If this option is true, skip candidate positions escaped by characters included in 'quoteescape' option. Practically this option is specific for ', " and `. > let g:sandwich#recipes += [ \ {'buns': ['"', '"'], 'quoteescape': 1} \ ] " Press dib " "foo\"bar" ---> foo\"bar < Default values * query: 0 * auto : 0 expr If this option is 1 or 2, requisite "buns" is evaluated as expression. If this option is 1, "buns" are evaluated once and repeat it by |.| command. If this option is 2, "buns" are evaluated every times in |.| repeating. For example, the following recipe searches the text in the unnamed register. (ref. |:let-@|, |quotequote|) > let g:sandwich#recipes += [ \ {'buns': ['@@', '@@'], 'expr': 1, 'input': ['@']} \ ] < NOTE: If this option is true, the recipe requires "input" to use with query-textobjects. "buns" never be regarded as key inputs to trigger. NOTE: If "buns" include empty string resulting of the evaluation, |textobj-sandwich| cancels the selection. NOTE: If the expressions query user something by |getchar()| or |input()|, the recipe may cause problem on the |operator-sandwich| delete operator and |textobj-sandwich| textobjects, |(textobj-sandwich-auto-i)| and |(textobj-sandwich-auto-a)|. To avoid this problem, it is recommended to use "kind" filter. For example, the following recipe asks two characters to select a text between the two characters. > let g:textobj#sandwich#recipes += [ \ { \ 'buns': ['GetChar()', 'GetChar()'], \ 'kind': ['query'], \ 'expr': 1, \ 'input': ['f'] \ }, \ ] function! GetChar() abort let c = getchar() let c = type(c) == type(0) ? nr2char(c) : c return c ==# "\" || c ==# "\" ? '' : c endfunction < Default values * query: 0 * auto : 0 noremap This option is referred only with "external" textobjects. In case that a "external" textobject has been remapped, if this option is true it would not be remapped. If this option is false, the nest mapping would be expanded. Also see the description about "external" in requisite. NOTE: Since visual selection is employed to check its region intrinsically, only the mappings in visual mode are related. Default values * query: 1 * auto : 1 syntax This is a list of syntax groups expected to be applied to the surroundings. If this list is not empty, the textobjects check the syntax to skip candidates. If syntax highlighting is not active, this option is simply ignored. > " check the position pointed by # " # # " "foo" < For example, " is expected to be highlighted by several limited highlight groups. > let g:sandwich#recipes += [ \ { \ 'buns': ['"', '"'], \ 'quoteescape': 1, \ 'expand_range': 0, \ 'nesting': 0, \ 'match_syntax': 1, \ 'syntax': \ ['Constant', 'Statement', 'Special', 'String', 'Comment'], \ 'inner_syntax': \ ['Constant', 'PreProc', 'Special', 'String', 'Comment'], \ } \ ] < However the recipe is not useful for some filetype, for example markdown, while it really goes well with some filetypes to recognize strings surrounded by "" even in the following situation. > key press vas" selects as following. # means cursor positions. <-#-> list = ["foo", "bar"] <-----#------> < Therefore, if possible, it would be better to prepare recipes for each filetypes. > let g:sandwich#recipes += [ \ { \ 'buns': ['"', '"'], \ 'filetype': ['ruby'], \ 'quoteescape': 1, \ 'expand_range': 0, \ 'nesting': 0, \ 'match_syntax': 1, \ 'syntax': \ ['Constant', 'Special', 'String', 'Comment'], \ 'inner_syntax': \ ['Constant', 'Special', 'String', 'Comment'], \ } \ ] let g:sandwich#recipes += [ \ { \ 'buns': ['"', '"'], \ 'filetype': ['sh'], \ 'quoteescape': 1, \ 'expand_range': 0, \ 'nesting': 0, \ 'match_syntax': 1, \ 'syntax': \ ['Constant', 'Statement', 'String', 'Comment'], \ 'inner_syntax': \ ['Constant', 'Special', 'PreProc', 'String', 'Comment'], \ } \ ] < In many colorscheme, frequently used highlight group name is shared. See |group-name|. NOTE: If an item in the list exists in the syntax stack at the candidate, it is regarded as the matched. The syntax stack can be checked by using following keymapping on a text. > nnoremap s :echo map(synstack(line('.'), col('.')), \ 'synIDattr(synIDtrans(v:val), "name")') < Default values * query: [] * auto : [] inner_syntax This is a list of syntax groups expected to be applied to the both edges of a surrounded text. If this list is not empty, the textobjects check the syntax to skip candidates. If syntax highlighting is not active, this option is simply ignored. In addition, also when "match_syntax" option is 2, this option is ignored. > let g:sandwich#recipes += [ \ { \ 'buns': ['"', '"'], \ 'quoteescape': 1, \ 'inner_syntax': ['Constant', 'String'] \ } \ ] " check the position pointed by # " # # " "foo" < There are practical examples in the above, local option "syntax", section. Please see also them. In many colorscheme, frequently used highlight group name is shared. See |group-name|. NOTE: If an item in the list exists in the syntax stack at the candidate, it is regarded as the matched. The syntax stack can be checked by using following keymapping on a text. > nnoremap s :echo map(synstack(line('.'), col('.')), \ 'synIDattr(synIDtrans(v:val), "name")') < Default values * query: [] * auto : [] match_syntax If this option is 1, the textobjects check the both surroundings whether their syntaxes are matched or not. If not, the candidate is skipped. In addition to that, if this option is 2, the textobjects also check the both edge of the surrounded text whether their colorings are same as those of surroundings. If this option is 3, almost same as that is 3 but textobjects check the inside text independently. That is to say, both surroundings should be same colorings and both edge of surrounded text should be same colorings, but it is not required to be same the colorings of surroundings and that of the surrounded text. > AB CD (foo) match_syntax is 1: A == D is required. match_syntax is 2: A == D == B == C is required. match_syntax is 3: A == D and B == C is required. < If a surrounding is not a character, textobjects check its head only. In many cases, both sides of surroundings have same syntaxes. For example, '' is frequently used for the string literal, "" itself would be applied Constant or String syntax and the inside text also applied the same. > let g:sandwich#recipes += [ \ { \ 'buns': ["'", "'"], \ 'quoteescape' : 1, \ 'syntax' : ['Constant', 'String'], \ 'match_syntax': 2 \ } \ ] < There are also other examples in the section of local option "syntax". NOTE: The applied (displayed) syntax can be checked by using the following keymapping on a text. > nnoremap S \ :echo synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'name') < The displayed syntaxes of both surroundings should be matched to be valid. However, as for the case of insides the surroundings the matched condition is somehow relaxed, only have to be included in syntax stacks. Because, for instance, output conversion specifiers in string literal like "%s" is highlighted with different color. Even though the fact, "String" is expect to included in its syntax stack, if it is true it works same as other string literals. A syntax stack can be checked by using the following key mapping on a text. > nnoremap s :echo map(synstack(line('.'), col('.')), \ 'synIDattr(synIDtrans(v:val), "name")') < Default values * query: 0 * auto : 0 skip_break Assume that the following text. > { foo } < The strings surrounded by { and } is "\n foo\n", thus key sequence dib should simply give: > {} < However sometimes it is more useful to make like this. > { } < If this option is true, the textobjects skip the breakings at the both ends to make the latter situation. In addition, skip following spaces together to keep indentation. That is to say, skipped string would be a string matched with the pattern "\n\\s*". This option is valid only for |(textobj-sandwich-query-i)| and |(textobj-sandwich-auto-i)|. Default values * query: 0 * auto : 0 skip_expr The value is a list of |expression|s. Searching the selection area, every time when a text matched with buns is found the expressions are evalueated. If the expression returns true (not 0), then the candidate is skipped and textobject searches next. > " skip 'a's other than head and tail of a line. let g:sandwich#recipes += [ \ { \ 'buns': ['a', 'a'], \ 'skip_expr': [ \ 'getpos(".")[2] != 1 && getpos(".")[2] != col([getpos(".")[1], "$"])-1' \ ] \ } \ ] " Press dib " aaaaa ---> aa < Note that if the item is |Funcref|, it should be possible to accept two arguments. > :function {func}(is_head, pos) < If a:is_head is 1, the textobject is searching a head surrounding matched with buns. Otherwise the textobject is searching a tail surrounding matched with buns. a:pos is a list pointing the head of matched position. It has four items which is same format with the argument of |getpos()|. Not necessarily to use them, use if needed. In future, the arguments are possible to append. Thus it might be better to assign variable-length argument. > " skip 'a's other than head and tail of a line. function! SkipIntermediates(is_head, pos, ...) if a:is_head return a:pos[2] != 1 else return a:pos[2] != col([a:pos[1], '$'])-1 endif endfunction let g:sandwich#recipes += [ \ { \ 'buns': ['a', 'a'], \ 'skip_expr': [function('SkipIntermediates')] \ } \ ] " Press dib " aaaaa ---> aa < Default values * query: [] * auto : [] Global option~ There are several options to control fundamental behavior of the operators. g:textobj#sandwich#timeout *g:textobj#sandwich#timeout* If this option is a falsy value, the query-textobject will wait for subsequent inputs until the complete key sequence has been received to specify a recipe. For example, with the following recipes, > let g:sandwich#recipes = [ \ {'buns': ['for {', '}'], 'nesting': 1, 'input': ['bf']} \ {'buns': ['if {', '}'], 'nesting': 1, 'input': ['bi']} \ {'buns': ['else {', '}'], 'nesting': 1, 'input': ['be']} \ ] < type `visb` and a while later the textobject eagerly select a text wrapped in `b` if this option is true. The textobject wait next input until a recipe is specified if this option is false. If this has not been defined, |g:sandwich#timeout| is used instead. Unless there is any particular reason, use |g:sandwich#timeout|. See |g:textobj#sandwich#timeoutlen| also. g:textobj#sandwich#timeoutlen *g:textobj#sandwich#timeoutlen* The time in milli seconds that waits for a key code or mapped key sequence to complete. If there are recipes overlapped, this option is used. Assume that the following recipes are prepared: > let g:sandwich#recipes = [ \ {'buns': ['(', ')']} \ {'buns': ['((', '))']} \ ] < after pressing vis(, the textobject waits in the time. If you press one more ( in the time, then a recipe for '((' and '))' is decided to use. No keypress has come through the time a recipe for '(' and ')' is settled. If this has not been defined, |g:sandwich#timeoutlen| is used instead. Unless there is any particular reason, use |g:sandwich#timeoutlen|. When 'timeout' is off or |g:textobj#sandwich#timeout| is false, this option is ignored. g:textobj#sandwich#stimeoutlen *g:textobj#sandwich#stimeoutlen* This is the time in milli second to give up searching candidates. After passing over the time without finding enough number of candidates, then the textobjects give up searching and selection. The default value is 500. g:textobj#sandwich#latest_jump *g:textobj#sandwich#latest_jump* If the value is true, the textobjects record the original cursor position to the latest jump marker |``|. If the cursor moved unexpectedly, |``| command will restore the cursor position quickly. The default value is 1. ============================================================================== MISCELLANEOUS *textobj-sandwich-miscellaneous* It is not what I expect with key sequence dib.~ The key sequence `dib` will make this: > ( foo ) < to this: > () < Because the surrouded text is not ' foo' but '\n foo\n'. If you want a result like this: > ( ) < then you can control by local option "skip_break". > call textobj#sandwich#set('all', 'skip_break', 1) < Timed out so frequently!~ Set the |g:textobj#sandwich#stimeoutlen| larger. The default value is 500. My cursor moved a lot because of unintended selection!~ Don't panic. You can use undo |u| command, and you can go back to the original position by |``| command. See |g:textobj#sandwich#latest_jump|. ============================================================================== vim:tw=78:ts=8:ft=help:norl:noet: