├── VimFlavor ├── .gitignore ├── addon-info.json ├── t ├── fixtures │ ├── simple.py │ ├── paired.c │ └── Inline.java ├── plugin.vim ├── util │ └── helpers.vim ├── leaders.vim ├── simple.vim ├── inline.vim └── paired.vim ├── plugin └── textobj │ └── comment.vim ├── Makefile ├── README.markdown ├── doc └── textobj-comment.txt └── autoload └── textobj └── comment.vim /VimFlavor: -------------------------------------------------------------------------------- 1 | flavor 'kana/vim-textobj-user', '~> 0.4' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/tags 2 | /textobj-comment.zip 3 | /textobj-comment.vmb* 4 | /textobj-comment.vba* 5 | -------------------------------------------------------------------------------- /addon-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textobj-comment", 3 | "description": "Text objects for comments", 4 | "author": "glts <676c7473@gmail.com>", 5 | "version": "1.0.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/glts/vim-textobj-comment.git" 9 | }, 10 | "dependencies": { 11 | "textobj-user": {} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /t/fixtures/simple.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | 3 | pygame.init() 4 | size = [640, 480] 5 | # a simple comment 6 | 7 | screen = pygame.display.set_mode(size) 8 | 9 | while True: 10 | 11 | # another simple comment 12 | 13 | for event in pygame.event.get(): # end-of-line comment 14 | 15 | # multi-line comment 16 | # continues here and on 17 | # a third line 18 | if event.type == pygame.QUIT: 19 | # 20 | # 21 | done=True 22 | 23 | # 24 | 25 | #No space 26 | 27 | pygame.quit() 28 | -------------------------------------------------------------------------------- /t/fixtures/paired.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* this is a comment 4 | * spanning not two 5 | * but three lines */ 6 | tEntry *find(tEntry **ht, uint32_t val) 7 | { 8 | /*a short comment*/ 9 | tEntry *te; 10 | unsigned int hi; /* end-of-line comment */ 11 | 12 | /* this */ is invalid */ 13 | hi = UCHASH(val, SIZE); 14 | 15 | /* this is a one-line */ 16 | /* style comment */ 17 | for (te = ht[hi]; te != NULL; te = te->nextEntry) 18 | if (te->val == val) 19 | /* */ 20 | return te; 21 | 22 | /* this comment /* has /* 23 | * many start /* leaders */ 24 | 25 | return NULL; 26 | /**/ 27 | 28 | } 29 | -------------------------------------------------------------------------------- /t/fixtures/Inline.java: -------------------------------------------------------------------------------- 1 | package com.example.inline.core; 2 | 3 | import /* inline comment */ com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import /*inline*/ com.google.gson.JsonParser; // end-of-line comment 6 | 7 | public class Inline { /* paired end-of-line comment */ 8 | 9 | private Gson gson; 10 | private JsonParser /* many /* start /* leaders */ parser; 11 | 12 | // a full-line comment 13 | public Inline() { 14 | 15 | GsonBuilder/* invalid inline comment */builder = new GsonBuilder() 16 | .registerTypeAdapter(long.class, new LongDeserializer()); 17 | 18 | builder;//.setPrettyPrinting(//TODO); 19 | 20 | Gson /* comment */ /* */gson /* com//ment */ = builder.create(); 21 | 22 | parser = new JsonParser(); 23 | 24 | } // another end-of-line comment 25 | 26 | } 27 | -------------------------------------------------------------------------------- /plugin/textobj/comment.vim: -------------------------------------------------------------------------------- 1 | " textobj-comment - Text objects for comments 2 | " Author: glts <676c7473@gmail.com> 3 | " Date: 2013-11-15 4 | " Version: 1.0.0 5 | " GetLatestVimScripts: 2100 1 textobj-user 6 | " GetLatestVimScripts: 4570 1 :AutoInstall: textobj-comment 7 | 8 | if exists('g:loaded_textobj_comment') 9 | finish 10 | endif 11 | 12 | if exists(':NeoBundleDepends') == 2 13 | NeoBundleDepends 'kana/vim-textobj-user' 14 | endif 15 | 16 | call textobj#user#plugin('comment', { 17 | \ '-': { 18 | \ 'select-a-function': 'textobj#comment#select_a', 19 | \ 'select-a': 'ac', 20 | \ 'select-i-function': 'textobj#comment#select_i', 21 | \ 'select-i': 'ic', 22 | \ }, 23 | \ 'big': { 24 | \ 'select-a-function': 'textobj#comment#select_big_a', 25 | \ 'select-a': 'aC', 26 | \ } 27 | \ }) 28 | 29 | let g:loaded_textobj_comment = 1 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Test and dist helpers 2 | 3 | # make test Runs the test suite 4 | # make t/simple.vim Runs the test "t/simple.vim" 5 | # make dist Creates the zip archive for distribution on vim.org 6 | # make vimball Creates a Vimball archive 7 | # make clean Removes all archives 8 | 9 | VSPEC = ~/.vim/bundle/vspec/bin/vspec 10 | RTPDIRS = ~/.vim/bundle/{vspec,textobj-user,textobj-comment} 11 | 12 | FILES = plugin/textobj/comment.vim autoload/textobj/comment.vim \ 13 | doc/textobj-comment.txt 14 | TESTS = t/plugin.vim t/leaders.vim t/simple.vim t/paired.vim t/inline.vim 15 | 16 | test: 17 | @for t in $(TESTS); do \ 18 | echo "$${t}"; \ 19 | $(VSPEC) $(RTPDIRS) "$${t}"; \ 20 | done 21 | 22 | $(TESTS): 23 | $(VSPEC) $(RTPDIRS) $@ 24 | 25 | dist: textobj-comment.zip 26 | textobj-comment.zip: $(FILES) 27 | zip -r textobj-comment $(FILES) 28 | 29 | VMBDEPS = $(shell if [ -f textobj-comment.vba ]; \ 30 | then echo textobj-comment.vba; \ 31 | else echo textobj-comment.vmb; fi) 32 | vimball: $(VMBDEPS) 33 | textobj-comment.vmb textobj-comment.vba: $(FILES) 34 | @cat <(for f in $(FILES); do echo "$${f}"; done) \ 35 | | vim -c 'silent exe "%MkVimball! textobj-comment ." | q!' - 36 | 37 | clean: 38 | -rm -f textobj-comment.zip 39 | -rm -f textobj-comment.{vmb,vba}* 40 | 41 | SHELL = /bin/bash 42 | .PHONY: test $(TESTS) dist vimball clean 43 | -------------------------------------------------------------------------------- /t/plugin.vim: -------------------------------------------------------------------------------- 1 | runtime! plugin/textobj/comment.vim 2 | 3 | let g:maps = { 'ac': '(textobj-comment-a)', 4 | \ 'ic': '(textobj-comment-i)', 5 | \ 'aC': '(textobj-comment-big-a)' } 6 | 7 | describe 'plugin' 8 | 9 | it 'loaded' 10 | Expect exists('g:loaded_textobj_comment') == 1 11 | end 12 | 13 | end 14 | 15 | describe ' mappings' 16 | 17 | it 'defined in proper modes' 18 | for pm in values(g:maps) 19 | Expect maparg(pm, 'n') == '' 20 | Expect maparg(pm, 'v') not == '' 21 | Expect maparg(pm, 'o') not == '' 22 | Expect maparg(pm, 'i') == '' 23 | Expect maparg(pm, 'c') == '' 24 | endfor 25 | end 26 | 27 | end 28 | 29 | describe 'default key mappings' 30 | 31 | it 'defined in proper modes' 32 | for [km, pm] in items(g:maps) 33 | Expect maparg(km, 'n') ==# '' 34 | Expect maparg(km, 'v') ==# pm 35 | Expect maparg(km, 'o') ==# pm 36 | Expect maparg(km, 'i') ==# '' 37 | Expect maparg(km, 'c') ==# '' 38 | endfor 39 | end 40 | 41 | end 42 | 43 | describe ':TextobjCommentDefaultKeyMappings' 44 | 45 | it 'defined' 46 | Expect exists(':TextobjCommentDefaultKeyMappings') == 2 47 | end 48 | 49 | it 'restores default key mappings' 50 | for km in keys(g:maps) 51 | exe 'unmap' km 52 | endfor 53 | Expect map(keys(g:maps), 'maparg(v:val, "ov")') == ['', '', ''] 54 | TextobjCommentDefaultKeyMappings 55 | for [km, pm] in items(g:maps) 56 | Expect maparg(km, 'n') ==# '' 57 | Expect maparg(km, 'v') ==# pm 58 | Expect maparg(km, 'o') ==# pm 59 | Expect maparg(km, 'i') ==# '' 60 | Expect maparg(km, 'c') ==# '' 61 | endfor 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /t/util/helpers.vim: -------------------------------------------------------------------------------- 1 | " Selection helpers 2 | 3 | function! DoSelect(cmd) 4 | exe 'normal' a:cmd 5 | let [_b, lnum1, col1, _o] = getpos("'<") 6 | let [_b, lnum2, col2, _o] = getpos("'>") 7 | return [[lnum1, col1], [lnum2, col2]] 8 | endfunction 9 | 10 | function! SelectAComment(...) 11 | return DoSelect(a:0 ? a:1 : "v\(textobj-comment-a)\") 12 | endfunction 13 | 14 | function! SelectInnerComment(...) 15 | return DoSelect(a:0 ? a:1 : "v\(textobj-comment-i)\") 16 | endfunction 17 | 18 | function! SelectABigComment(...) 19 | return DoSelect(a:0 ? a:1 : "v\(textobj-comment-big-a)\") 20 | endfunction 21 | 22 | " Custom matchers 23 | 24 | function! ToHavePositions(actual, pos1, pos2) 25 | return a:actual[0] == a:pos1 && a:actual[1] == a:pos2 26 | endfunction 27 | 28 | function! ToHaveLineNumbers(actual, lnum1, lnum2) 29 | return a:actual[0][0] == a:lnum1 && a:actual[1][0] == a:lnum2 30 | endfunction 31 | 32 | function! ToHaveColumns(actual, col1, col2) 33 | return a:actual[0][1] == a:col1 && a:actual[1][1] == a:col2 34 | endfunction 35 | 36 | function! LineNumbersFailureMessage(actual, lnum1, lnum2) 37 | return FailureMessage(a:actual, a:lnum1, a:lnum2, 0) 38 | endfunction 39 | 40 | function! ColumnsFailureMessage(actual, col1, col2) 41 | return FailureMessage(a:actual, a:col1, a:col2, 1) 42 | endfunction 43 | 44 | function! FailureMessage(actual, arg1, arg2, posidx) 45 | return [ ' Actual value: ' . string(map(copy(a:actual), 'v:val['.a:posidx.']')), 46 | \ 'Expected value: ' . string([a:arg1, a:arg2]) ] 47 | endfunction 48 | 49 | call vspec#customize_matcher('to_have_pos', {'match': function('ToHavePositions')}) 50 | call vspec#customize_matcher('to_have_lnums', { 51 | \ 'match': function('ToHaveLineNumbers'), 52 | \ 'failure_message_for_should': function('LineNumbersFailureMessage'), 53 | \ 'failure_message_for_should_not': function('LineNumbersFailureMessage') 54 | \ }) 55 | call vspec#customize_matcher('to_have_cols', { 56 | \ 'match': function('ToHaveColumns'), 57 | \ 'failure_message_for_should': function('ColumnsFailureMessage'), 58 | \ 'failure_message_for_should_not': function('ColumnsFailureMessage') 59 | \ }) 60 | -------------------------------------------------------------------------------- /t/leaders.vim: -------------------------------------------------------------------------------- 1 | runtime! plugin/textobj/comment.vim 2 | runtime! autoload/textobj/comment.vim 3 | 4 | silent filetype plugin indent on 5 | syntax enable 6 | 7 | function! SID() 8 | redir => scripts 9 | silent scriptnames 10 | redir END 11 | for line in split(scripts, '\n') 12 | let [_0, sid, path; _] = matchlist(line, '^\s*\(\d\+\):\s*\(.*\)$') 13 | if path =~# 'autoload/textobj/comment\.vim$' 14 | return '' . sid . '_' 15 | endif 16 | endfor 17 | endfunction 18 | 19 | call vspec#hint({'sid': 'SID()'}) 20 | 21 | describe '''comments'' and ''commentstring''' 22 | 23 | it 'default settings' 24 | Expect &comments ==# 's1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-' 25 | Expect &commentstring ==# '/*%s*/' 26 | end 27 | 28 | it 'set for filetype' 29 | silent tabedit Abc.java 30 | Expect &comments ==# 'sO:* -,mO:* ,exO:*/,s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-' 31 | Expect &commentstring ==# '//%s' 32 | bwipeout 33 | end 34 | 35 | end 36 | 37 | describe 's:GetLeaders()' 38 | 39 | before 40 | silent tabedit abc.unknown 41 | end 42 | 43 | after 44 | bwipeout 45 | end 46 | 47 | it 'parses default ''comments'' setting' 48 | Expect len(Call('s:GetLeaders')) == 2 49 | Expect Call('s:GetLeaders')[0] == ['//', '#', '%', 'XCOMM', '>'] 50 | Expect Call('s:GetLeaders')[1] == [['/*', '*/']] 51 | Expect filter(Call('s:GetLeaders')[1], 'len(v:val)!=2') == [] 52 | end 53 | 54 | it 'falls back on ''commentstring'' setting' 55 | set comments= 56 | Expect Call('s:GetLeaders') == [[], [['/*', '*/']]] 57 | set commentstring=;;\ %s 58 | Expect Call('s:GetLeaders') == [[';;'], []] 59 | end 60 | 61 | it 'handles empty ''comments'' and ''commentstring'' settings' 62 | set comments= 63 | set commentstring= 64 | Expect Call('s:GetLeaders') == [[], []] 65 | end 66 | 67 | end 68 | 69 | describe 's:GetSimpleLeaders()' 70 | 71 | it 'returns the simple leaders' 72 | let leaders = [['bO', '; '], ['s', '{-'], ['e', '-}'], ['', '--']] 73 | Expect Call('s:GetSimpleLeaders', leaders) == ['; ', '--'] 74 | end 75 | 76 | it 'accepts empty list' 77 | Expect Call('s:GetSimpleLeaders', []) == [] 78 | end 79 | 80 | end 81 | 82 | describe 's:GetPairedLeaders()' 83 | 84 | it 'returns the paired leaders' 85 | let leaders = [['bO', '; '], ['s1', '{-'], ['e', '-}'], ['', '--'], ['sO', '<'], ['ex', '>']] 86 | Expect Call('s:GetPairedLeaders', leaders) == [['{-', '-}'], ['<', '>']] 87 | end 88 | 89 | it 'ignores invalid pairs' 90 | let leaders = [['s', '{{'], ['s', '[['], ['e', ']]'], ['e', '>']] 91 | Expect Call('s:GetPairedLeaders', leaders) == [['[[', ']]']] 92 | end 93 | 94 | it 'accepts empty list' 95 | Expect Call('s:GetPairedLeaders', []) == [] 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | textobj-comment 2 | =============== 3 | 4 | This Vim plugin provides text objects for comments. 5 | 6 | `ac` selects a comment including the comment delimiters and `ic` selects 7 | just the comment content. (There's also a third text object, `aC`, which 8 | selects a comment including trailing or leading whitespace.) These 9 | mappings are available in Visual and Operator-pending mode. 10 | 11 | This plugin uses the `'comments'` and `'commentstring'` settings to 12 | determine what a comment looks like for a given filetype. It works with 13 | both `/* paired */` and `// simple` comment delimiters. 14 | 15 | This plugin depends on the [textobj-user][1] plugin. 16 | 17 | [1]: https://github.com/kana/vim-textobj-user 18 | 19 | Usage 20 | ----- 21 | 22 | Comprehensive on-line documentation is included and available at 23 | `:h textobj-comment`. 24 | 25 | Below is a quick demo of the 'a comment' text object. The command used 26 | is `vac`. The targeted area is the same for analogous commands using an 27 | operator, such as `dac`, `cac`, and `gqac`. 28 | 29 | ![demo](https://raw.github.com/glts/vim-textobj-comment/gh-pages/images/demo-vac.gif) 30 | 31 | The 'inner comment' text object targets the inside of a comment. Here I 32 | use `cic`: 33 | 34 | ![demo](https://raw.github.com/glts/vim-textobj-comment/gh-pages/images/demo-cic.gif) 35 | 36 | Requirements 37 | ------------ 38 | 39 | * Vim 7.3 or later 40 | * [textobj-user][2] Vim plugin, at least version 0.4.0 41 | 42 | [2]: https://github.com/kana/vim-textobj-user 43 | 44 | Installation 45 | ------------ 46 | 47 | Move the files into their respective directories inside your `~/.vim` 48 | directory (or your `$HOME\vimfiles` directory if you're on Windows). 49 | 50 | With [pathogen.vim][3] the installation is as simple as: 51 | 52 | git clone git://github.com/glts/vim-textobj-comment.git ~/.vim/bundle/textobj-comment 53 | 54 | This plugin also plays well with other plugin managers. 55 | 56 | Don't forget to install textobj-user, too, if your setup doesn't take 57 | care of dependencies automatically. 58 | 59 | [3]: http://www.vim.org/scripts/script.php?script_id=2332 60 | 61 | Development 62 | ----------- 63 | 64 | The code isn't pretty. If you look closely you'll notice a lot of effort 65 | being made to cover corner cases and make the behaviour of the text 66 | objects as similar to the built-ins as possible. That said, if you are 67 | interested in working on it, use the test suite to make sure you don't 68 | break anything. 69 | 70 | The test suite is written using [vspec][4]. 71 | 72 | `make test` runs the whole suite and `make t/.vim` runs a specific 73 | test. You may need to adapt the paths to the vspec executable and to the 74 | runtime path directories at the top of the Makefile. 75 | 76 | [4]: https://github.com/kana/vim-vspec 77 | -------------------------------------------------------------------------------- /t/simple.vim: -------------------------------------------------------------------------------- 1 | runtime! plugin/textobj/comment.vim 2 | 3 | silent filetype plugin indent on 4 | syntax enable 5 | 6 | source t/util/helpers.vim 7 | 8 | describe '(textobj-comment-a)' 9 | 10 | before 11 | silent tabedit t/fixtures/simple.py 12 | end 13 | 14 | after 15 | bwipeout! 16 | end 17 | 18 | it 'selects a comment' 19 | 11 20 | Expect SelectAComment() to_have_lnums 11, 11 21 | 16 22 | Expect SelectAComment() to_have_lnums 15, 17 23 | 19 24 | Expect SelectAComment() to_have_lnums 19, 20 25 | end 26 | 27 | it 'selects linewise' 28 | 5 29 | call SelectAComment() 30 | Expect visualmode() ==# 'V' 31 | end 32 | 33 | it 'sets proper start and end column' 34 | let command = "v\(textobj-comment-a)v\" 35 | 5 36 | Expect SelectAComment(command) to_have_cols 1, 18 37 | 17 38 | Expect SelectAComment(command) to_have_cols 9, 25 39 | 23 40 | Expect SelectAComment(command) to_have_cols 1, 1 41 | end 42 | 43 | end 44 | 45 | describe '(textobj-comment-i)' 46 | 47 | before 48 | silent tabedit t/fixtures/simple.py 49 | end 50 | 51 | after 52 | bwipeout! 53 | end 54 | 55 | it 'selects inner comment' 56 | 11 57 | Expect SelectInnerComment() to_have_pos [11, 7], [11, 28] 58 | 25 59 | Expect SelectInnerComment() to_have_pos [25, 2], [25, 9] 60 | 15 61 | Expect SelectInnerComment() to_have_pos [15, 11], [17, 25] 62 | end 63 | 64 | it 'selects inner whitespace comment' 65 | 23 66 | call setline(23, '# ') 67 | Expect SelectInnerComment() to_have_pos [23, 2], [23, 4] 68 | 20 69 | Expect SelectInnerComment() to_have_pos [19, 14], [20, 16] 70 | end 71 | 72 | it 'selects characterwise' 73 | 5 74 | call SelectInnerComment() 75 | Expect visualmode() ==# 'v' 76 | end 77 | 78 | it 'doesn''t select inside empty comment' 79 | 23 80 | let @@ = '' 81 | exe "normal y\(textobj-comment-i)" 82 | Expect @@ == '' 83 | end 84 | 85 | end 86 | 87 | describe '(textobj-comment-big-a)' 88 | 89 | before 90 | silent tabedit t/fixtures/simple.py 91 | end 92 | 93 | after 94 | bwipeout! 95 | end 96 | 97 | it 'selects a big comment with trailing whitespace' 98 | 5 99 | Expect SelectABigComment() to_have_lnums 5, 6 100 | 11 101 | Expect SelectABigComment() to_have_lnums 11, 12 102 | end 103 | 104 | it 'selects a big comment with leading whitespace' 105 | 26d 106 | normal! k 107 | Expect SelectABigComment() to_have_lnums 24, 25 108 | 16 109 | Expect SelectABigComment() to_have_lnums 14, 17 110 | end 111 | 112 | it 'selects linewise' 113 | 5 114 | call SelectABigComment() 115 | Expect visualmode() ==# 'V' 116 | end 117 | 118 | it 'sets proper start and end column' 119 | let command = "v\(textobj-comment-big-a)v\" 120 | 11 121 | Expect SelectABigComment(command) to_have_cols 5, 1 122 | 17 123 | Expect SelectABigComment(command) to_have_cols 1, 25 124 | end 125 | 126 | end 127 | 128 | describe 'simple leader search' 129 | 130 | before 131 | silent tabedit t/fixtures/simple.py 132 | end 133 | 134 | after 135 | bwipeout! 136 | end 137 | 138 | it 'proceeds upwards' 139 | 14 140 | Expect SelectAComment() to_have_lnums 11, 11 141 | 13 142 | Expect SelectAComment() not to_have_lnums 11, 11 143 | end 144 | 145 | end 146 | -------------------------------------------------------------------------------- /t/inline.vim: -------------------------------------------------------------------------------- 1 | runtime! plugin/textobj/comment.vim 2 | 3 | silent filetype plugin indent on 4 | syntax enable 5 | 6 | source t/util/helpers.vim 7 | 8 | describe '(textobj-comment-a)' 9 | 10 | before 11 | silent tabedit t/fixtures/Inline.java 12 | end 13 | 14 | after 15 | bwipeout! 16 | end 17 | 18 | it 'selects a comment' 19 | 3 20 | Expect SelectAComment() to_have_cols 8, 27 21 | 5 22 | normal! WWW 23 | Expect SelectAComment() to_have_cols 47, 68 24 | 7 25 | Expect SelectAComment() to_have_cols 23, 54 26 | exe "normal! A\\\B" 27 | Expect SelectAComment() to_have_cols 23, 54 28 | end 29 | 30 | it 'selects characterwise' 31 | 20 32 | call SelectAComment() 33 | Expect visualmode() ==# 'v' 34 | end 35 | 36 | end 37 | 38 | describe '(textobj-comment-i)' 39 | 40 | before 41 | silent tabedit t/fixtures/Inline.java 42 | end 43 | 44 | after 45 | bwipeout! 46 | end 47 | 48 | it 'selects inner comment' 49 | 20 50 | normal! WWW 51 | Expect SelectInnerComment() to_have_cols 17, 23 52 | 5 53 | Expect SelectInnerComment() to_have_cols 10, 15 54 | end 55 | 56 | it 'selects inner whitespace comment' 57 | 20 58 | normal! WWWW 59 | Expect SelectInnerComment() to_have_cols 30, 31 60 | 24 61 | exe "normal c\(textobj-comment-i) \\" 62 | Expect SelectInnerComment() to_have_cols 9, 15 63 | end 64 | 65 | it 'selects characterwise' 66 | 7 67 | call SelectInnerComment() 68 | Expect visualmode() ==# 'v' 69 | end 70 | 71 | it 'doesn''t select inside empty comment' 72 | 22 73 | call setline(line("."), 'parser = /**/ new JsonParser();') 74 | let @@ = '' 75 | exe "normal y\(textobj-comment-i)" 76 | Expect @@ == '' 77 | end 78 | 79 | end 80 | 81 | describe '(textobj-comment-big-a)' 82 | 83 | before 84 | silent tabedit t/fixtures/Inline.java 85 | end 86 | 87 | after 88 | bwipeout! 89 | end 90 | 91 | it 'selects a big comment with trailing whitespace' 92 | 3 93 | normal! ww 94 | Expect SelectABigComment() to_have_cols 8, 28 95 | 7 96 | exe "normal! A \ \b" 97 | Expect SelectABigComment() to_have_cols 23, 58 98 | 24 99 | normal! ww 100 | Expect SelectABigComment() to_have_cols 7, 39 101 | end 102 | 103 | it 'selects a big comment with leading whitespace' 104 | 5 105 | normal! WWhr_h 106 | Expect SelectABigComment() to_have_cols 7, 17 107 | 5 108 | normal! $ 109 | Expect SelectABigComment() to_have_cols 46, 68 110 | 7 111 | Expect SelectABigComment() to_have_cols 22, 54 112 | end 113 | 114 | it 'selects a big comment without whitespace' 115 | 15 116 | normal! ww 117 | Expect SelectABigComment() to_have_cols 20, 47 118 | 18 119 | Expect SelectABigComment() to_have_cols 17, 45 120 | end 121 | 122 | it 'selects characterwise' 123 | 18 124 | call SelectABigComment() 125 | Expect visualmode() ==# 'v' 126 | end 127 | 128 | end 129 | 130 | describe 'inline leader search' 131 | 132 | before 133 | silent tabedit t/fixtures/Inline.java 134 | end 135 | 136 | after 137 | bwipeout! 138 | end 139 | 140 | it 'proceeds towards the right' 141 | 20 142 | Expect SelectABigComment() to_have_cols 14, 27 143 | Expect SelectABigComment() to_have_cols 27, 33 144 | normal! w 145 | Expect SelectInnerComment() to_have_cols 42, 50 146 | Expect SelectInnerComment() to_have_cols 47, 73 147 | end 148 | 149 | it 'doesn''t proceed towards the left' 150 | 15 151 | Expect SelectAComment() to_have_lnums 15, 15 152 | normal! $ 153 | Expect SelectAComment() not to_have_lnums 15, 15 154 | end 155 | 156 | end 157 | -------------------------------------------------------------------------------- /t/paired.vim: -------------------------------------------------------------------------------- 1 | runtime! plugin/textobj/comment.vim 2 | 3 | silent filetype plugin indent on 4 | syntax enable 5 | 6 | source t/util/helpers.vim 7 | 8 | describe '(textobj-comment-a)' 9 | 10 | before 11 | silent tabedit t/fixtures/paired.c 12 | end 13 | 14 | after 15 | bwipeout! 16 | end 17 | 18 | it 'selects a comment' 19 | 4 20 | Expect SelectAComment() to_have_lnums 3, 5 21 | 8 22 | Expect SelectAComment() to_have_lnums 8, 8 23 | 26 24 | Expect SelectAComment() to_have_lnums 26, 26 25 | 22 26 | Expect SelectAComment() to_have_lnums 22, 23 27 | 12 28 | Expect SelectAComment() to_have_lnums 12, 12 29 | normal! WWW 30 | Expect SelectAComment() not to_have_lnums 12, 12 31 | end 32 | 33 | it 'selects linewise' 34 | 4 35 | call SelectAComment() 36 | Expect visualmode() ==# 'V' 37 | end 38 | 39 | it 'sets proper start and end column' 40 | let command = "v\(textobj-comment-a)v\" 41 | 3 42 | Expect SelectAComment(command) to_have_pos [3, 1], [5, 21] 43 | 8 44 | Expect SelectAComment(command) to_have_cols 5, 23 45 | 26 46 | Expect SelectAComment(command) to_have_cols 5, 8 47 | end 48 | 49 | end 50 | 51 | describe '(textobj-comment-i)' 52 | 53 | before 54 | silent tabedit t/fixtures/paired.c 55 | end 56 | 57 | after 58 | bwipeout! 59 | end 60 | 61 | it 'selects inner comment' 62 | 5 63 | Expect SelectInnerComment() to_have_pos [3, 4], [5, 18] 64 | 8 65 | Expect SelectInnerComment() to_have_pos [8, 7], [8, 21] 66 | end 67 | 68 | it 'selects inner whitespace comment' 69 | 19 70 | Expect SelectInnerComment() to_have_cols 15, 18 71 | normal! D 72 | call append(line("."), [" \t", "", "\t */"]) 73 | Expect SelectInnerComment() to_have_pos [19, 15], [22, 2] 74 | end 75 | 76 | it 'selects characterwise' 77 | 8 78 | call SelectInnerComment() 79 | Expect visualmode() ==# 'v' 80 | end 81 | 82 | it 'doesn''t select inside empty comment' 83 | 26 84 | let @@ = '' 85 | exe "normal y\(textobj-comment-i)" 86 | Expect @@ == '' 87 | end 88 | 89 | end 90 | 91 | describe '(textobj-comment-big-a)' 92 | 93 | before 94 | silent tabedit t/fixtures/paired.c 95 | end 96 | 97 | after 98 | bwipeout! 99 | end 100 | 101 | it 'selects a big comment with trailing whitespace' 102 | 26 103 | Expect SelectABigComment() to_have_lnums 26, 27 104 | call append(5, ["", "\t", ""]) 105 | 5 106 | Expect SelectABigComment() to_have_lnums 3, 8 107 | end 108 | 109 | it 'selects a big comment with leading whitespace' 110 | 4 111 | Expect SelectABigComment() to_have_lnums 2, 5 112 | call append(7, "\t\t") 113 | 9 114 | Expect SelectABigComment() to_have_lnums 8, 9 115 | end 116 | 117 | it 'selects a big comment without whitespace' 118 | 19 119 | Expect SelectABigComment() to_have_lnums 19, 19 120 | 2delete 121 | Expect SelectABigComment() to_have_lnums 2, 4 122 | end 123 | 124 | it 'selects linewise' 125 | 4 126 | call SelectABigComment() 127 | Expect visualmode() ==# 'V' 128 | end 129 | 130 | it 'sets proper start and end column' 131 | let command = "v\(textobj-comment-big-a)v\" 132 | 3 133 | Expect SelectABigComment(command) to_have_pos [2, 1], [5, 21] 134 | 8 135 | Expect SelectABigComment(command) to_have_cols 5, 23 136 | end 137 | 138 | end 139 | 140 | describe 'paired leader search' 141 | 142 | before 143 | silent tabedit t/fixtures/paired.c 144 | end 145 | 146 | after 147 | bwipeout! 148 | end 149 | 150 | it 'proceeds upwards' 151 | 9 152 | Expect SelectABigComment() to_have_lnums 8, 8 153 | 10 154 | Expect SelectABigComment() not to_have_lnums 8, 8 155 | end 156 | 157 | end 158 | -------------------------------------------------------------------------------- /doc/textobj-comment.txt: -------------------------------------------------------------------------------- 1 | *textobj-comment.txt* Text objects for comments 2 | 3 | Author: glts <676c7473@gmail.com> 4 | License: Same terms as Vim itself (see |license|) 5 | 6 | 7 | DESCRIPTION *textobj-comment* 8 | 9 | This plugin provides |text-objects| to select comments. 10 | 11 | "ac" selects a comment including the comment delimiters, "ic" selects the 12 | comment content without the delimiters. This behaviour is modeled after the 13 | built-in delimited text objects such as |a[| and |it|. A third text object, 14 | "aC", selects a "big" comment, including trailing or leading whitespace. 15 | 16 | |textobj-comment| uses the filetype-specific 'comments' setting to determine 17 | what a comment looks like for a given language. Make sure you have enabled 18 | filetype plugins in your configuration to make this work. |vimrc-filetype| > 19 | 20 | filetype plugin indent on 21 | 22 | In order to find a target, textobj-comment looks for a comment under the 23 | cursor, in or at the end of the cursor line, and above the cursor line. 24 | 25 | You can see the text objects and the heuristic in action in the following 26 | section, |textobj-comment-usage|. 27 | 28 | Note This plugin depends on the |textobj-user| plugin, version 0.4.0. 29 | https://github.com/kana/vim-textobj-user 30 | 31 | 32 | USAGE *textobj-comment-usage* 33 | 34 | The text objects "ac", "ic", and "aC" are defined in |Visual| and 35 | |Operator-pending| mode. When the user enters any one of these text objects, 36 | textobj-comment searches for a comment by using this procedure: 37 | 38 | 1. Use any comment spanning the full line under the cursor. 39 | 2. Use any inline comment (if the language has them) or end-of-line comment 40 | under or to the right of the cursor. 41 | 3. Use the nearest comment spanning the full line above the cursor. 42 | 43 | Note that step 3 is essentially an upwards search. This is where a comment 44 | relevant to the current cursor position is most likely to be found. 45 | 46 | We'll now go on an illustrated tour of the text objects and the three steps of 47 | the heuristic. See also |textobj-comment-interface| below for more details. 48 | 49 | 50 | 1. First, look for a full-line comment under the cursor. In full-line 51 | context "ac" selects a comment |linewise| (the vertical bar indicates 52 | selected lines). > 53 | 54 | | # We should probably inline 55 | | # this con[s]tant 56 | DOZEN = 12 57 | < 58 | "ic" selects just what's inside the comment and always operates 59 | |characterwise| (the overline indicates selected characters). > 60 | _________________________ 61 | % See [t]he TeXbook, p. 352 62 | \def\thinspace{\kern .16667em } 63 | < 64 | The "aC" text object selects "a big comment", which is the same as "ac" 65 | but includes whitespace after the comment, or if there isn't any, 66 | whitespace before the comment. > 67 | 68 | | -- Baby steps ... 69 | | -- Cust[o]m "asInt'" function 70 | | 71 | asInt' :: String -> Either ErrorMessage Int 72 | 73 | 74 | 2. Second, look for an inline or an end-of-line comment. Inline and 75 | end-of-line comments are selected characterwise. Again, "ac" selects a 76 | comment including the delimiter. > 77 | __________________ 78 | *parse_syllab[l]e = \&parse_syllables; # deprecated alias 79 | < 80 | "aC" selects the same but with trailing or leading whitespace included. 81 | This is especially useful when you want to delete an end-of-line comment 82 | and not leave any whitespace behind. > 83 | __________________________ 84 | int tem[p] = 12; /* core temperature */ 85 | < 86 | For inline comments, a match under the cursor is preferred, else one to 87 | the right of the cursor is selected. With "ic": > 88 | ______ 89 | int /* tem[p] */ temperature = 12; // renamed variable 90 | 91 | 92 | 3. Third, if there hasn't been any success so far, search upwards for the 93 | nearest full-line comment. Using "ac": > 94 | 95 | | (* Let us make 96 | | a singleton. *) 97 | let s = SS.singleton "umb[r]ella";; 98 | < 99 | Of course, the three steps always apply to all three text objects. Here's 100 | a final example featuring "ic" searching upwards to find its target. > 101 | _________________ 102 | 105 | 106 | 107 | 108 | After this the comment search fails and nothing is selected. 109 | 110 | Note that in order to support two styles of commenting with opening/closing 111 | delimiters, textobj-comment tries to be smart and handles them differently. 112 | Consider these comments in C and XML source code: > 113 | 114 | /* These functions should be 115 | * moved to a separate file. */ 116 | /* See also fileio.c and 117 | * sockets.c for examples. */ 118 | 119 | 120 | 121 | 122 | 123 | 124 | In the first example, two separate comments are recognized, but in the second 125 | example, which has only single-line comments with opening/closing delimiters, 126 | the whole commented block is treated as one big comment. 127 | 128 | Note that even for linewise selections a sensible start and end column is set, 129 | so that you can use "v" to switch to a useful characterwise selection. |v_v| 130 | 131 | 132 | INTERFACE *textobj-comment-interface* 133 | 134 | By default, textobj-comment defines the key mappings "ac", "ic", and "aC" for 135 | the text objects it provides. These map to named mappings and can be 136 | customized easily. For example, to use "ax" instead of "ac": > 137 | 138 | let g:textobj_comment_no_default_key_mappings = 1 139 | xmap ax (textobj-comment-a) 140 | omap ax (textobj-comment-a) 141 | 142 | The mappings are defined in Visual and Operator-pending mode. See the 143 | |textobj-user| documentation for more info. 144 | 145 | *(textobj-comment-a)* 146 | ac (textobj-comment-a) 147 | "a comment", select a comment with delimiter. 148 | Select linewise for full-line comments, characterwise 149 | for inline and end-of-line comments. 150 | 151 | *(textobj-comment-i)* 152 | ic (textobj-comment-i) 153 | "inner comment", select the content inside the comment 154 | delimiter. 155 | Always characterwise. 156 | 157 | *(textobj-comment-big-a)* 158 | aC (textobj-comment-big-a) 159 | "a big comment", select a comment with delimiter and 160 | whitespace after the comment, or if there isn't any, 161 | whitespace before the comment. 162 | Select linewise for full-line comments, characterwise 163 | for inline and end-of-line comments. 164 | 165 | All text objects use the same simple heuristic. See |textobj-comment-usage|. 166 | 167 | A user command and a global variable are also installed with this plugin. 168 | 169 | *:TextobjCommentDefaultKeyMappings* 170 | :TextobjCommentDefaultKeyMappings[!] 171 | Restore the default key mappings. 172 | With the [!] overrides existing key mappings. 173 | 174 | *g:textobj_comment_no_default_key_mappings* 175 | g:textobj_comment_no_default_key_mappings 176 | Whether or not to define the default key mappings. 177 | Set this to "1" if you would like to disable the 178 | default key mappings. 179 | 180 | 181 | CHANGELOG *textobj-comment-changelog* 182 | 183 | 1.0.0 2013-05-04 184 | Initial release. 185 | 186 | 187 | vim:tw=78:ts=8:ft=help:norl: 188 | -------------------------------------------------------------------------------- /autoload/textobj/comment.vim: -------------------------------------------------------------------------------- 1 | " textobj-comment - Text objects for comments 2 | " Author: glts <676c7473@gmail.com> 3 | " Date: 2013-05-03 4 | " Version: 1.0.0 5 | 6 | " Select() {{{1 7 | 8 | " The Select() function contains the main algorithm for finding comments. 9 | " First we look for a full-line comment with simple leader or with paired 10 | " leader under the cursor, then for inline and end-of-line comments at the 11 | " cursor position, and finally for the nearest full-line comment above. 12 | function! s:Select(inside, whitespace) abort 13 | let [simple_leaders, paired_leaders] = s:GetLeaders() 14 | if empty(simple_leaders + paired_leaders) 15 | return 0 16 | endif 17 | 18 | let pos = getpos(".")[1:2] 19 | 20 | " Search for simple leader first to avoid being caught up in strange paired 21 | " leaders 22 | let comment = s:FindSimpleLineComment(pos, simple_leaders, 0) 23 | if !empty(comment) 24 | return s:AdjustLineEnds(comment, a:whitespace, a:inside) 25 | endif 26 | let comment = s:FindPairedLineComment(pos, paired_leaders, 0) 27 | if !empty(comment) 28 | return s:AdjustLineEnds(comment, a:whitespace, a:inside) 29 | endif 30 | 31 | let comment = s:FindInlineComment(pos, simple_leaders, paired_leaders) 32 | if !empty(comment) 33 | return s:AdjustInlineEnds(comment, a:whitespace, a:inside) 34 | endif 35 | 36 | let scomment = s:FindSimpleLineComment(pos, simple_leaders, 1) 37 | let pcomment = s:FindPairedLineComment(pos, paired_leaders, 1) 38 | if !empty(scomment) || !empty(pcomment) 39 | if empty(scomment) 40 | let comment = pcomment 41 | elseif empty(pcomment) 42 | let comment = scomment 43 | else 44 | let comment = pcomment[2][0] > scomment[2][0] ? pcomment : scomment 45 | endif 46 | return s:AdjustLineEnds(comment, a:whitespace, a:inside) 47 | endif 48 | 49 | return 0 50 | endfunction 51 | 52 | " Comment leaders {{{1 53 | 54 | " Comment delimiters are called "leaders" in the help. We extract them from 55 | " the current 'comments' setting into two types, simple and paired comment 56 | " leaders. The 'commentstring' setting is used as fallback and verification. 57 | 58 | function! s:GetLeaders() abort 59 | let leaders = map(split(&comments, '\\\@= 2 80 | let [s, e; seleaders] = seleaders 81 | if s[0] !~# "s" || e[0] !~# "e" 82 | call insert(seleaders, e) 83 | continue 84 | elseif &commentstring =~# '\V\^\s\*'.s:escape(s[1]).'\s\*%s\s\*'.s:escape(e[1]).'\s\*\$' 85 | call insert(pairedleaders, [s[1], e[1]]) 86 | else 87 | call add(pairedleaders, [s[1], e[1]]) 88 | endif 89 | endwhile 90 | return pairedleaders " e.g. [['/*','*/'], ['* -','*/']] 91 | endfunction 92 | 93 | " Comment search {{{1 94 | 95 | " The comment search functions all return a list [leader, start, end], where 96 | " leader is the comment character or pair of characters, and start and end are 97 | " the byte positions of the opening and closing leaders (for simple leaders 98 | " the end is on the last byte position in the line). Empty when no match. 99 | 100 | " s:FindSimpleLineComment() {{{2 101 | function! s:FindSimpleLineComment(pos, simple_leaders, upwards) abort 102 | let cursor_line = a:pos[0] 103 | 104 | if a:upwards && !empty(a:simple_leaders) 105 | let allre = '\V\^\s\*\%(' . join(map(copy(a:simple_leaders),'s:escape(v:val)'),'\|') . '\)' 106 | while cursor_line > 1 107 | let cursor_line -= 1 108 | if getline(cursor_line) =~# allre 109 | break 110 | endif 111 | endwhile 112 | endif 113 | 114 | for simple in a:simple_leaders 115 | let simplere = '\V\^\s\*' . s:escape(simple) 116 | if getline(cursor_line) =~# simplere 117 | let startline = cursor_line 118 | let ln = cursor_line - 1 119 | while ln > 0 120 | if getline(ln) !~# simplere 121 | break 122 | endif 123 | let startline = ln 124 | let ln -= 1 125 | endwhile 126 | let endline = cursor_line 127 | let ln = cursor_line + 1 128 | while ln <= line("$") 129 | if getline(ln) !~# simplere 130 | break 131 | endif 132 | let endline = ln 133 | let ln += 1 134 | endwhile 135 | let startcol = match(getline(startline) ,'\V\^\s\*\zs'.s:escape(simple)) + 1 136 | let endcol = match(getline(endline), '.$') + 1 137 | return [simple, [startline, startcol], [endline, endcol]] 138 | endif 139 | endfor 140 | 141 | return [] 142 | endfunction 143 | 144 | " s:FindPairedLineComment() {{{2 145 | function! s:FindPairedLineComment(pos, paired_leaders, upwards) abort 146 | let found = [] 147 | for pair in a:paired_leaders 148 | let pairpos = s:FindNearestPair(a:pos, pair, a:upwards) 149 | if pairpos != [] 150 | if !a:upwards 151 | let found = [pair, pairpos[0], pairpos[1]] 152 | break 153 | else 154 | if found == [] || s:compare(found[2], pairpos[1]) < 0 155 | let found = [pair, pairpos[0], pairpos[1]] 156 | endif 157 | endif 158 | endif 159 | endfor 160 | return found 161 | endfunction 162 | 163 | " s:FindNearestPair() {{{2 164 | function! s:FindNearestPair(pos, pair, upwards) abort 165 | let [open, close] = a:pair 166 | let start = [] 167 | let end = [] 168 | 169 | " We will often use variations of the regexp idiom "open((close)\@!.)*close" 170 | let endre = '\V\^\%(\%('.s:escape(close).'\)\@!\.\)\*\zs'.s:escape(close).'\s\*\$' 171 | let startre = '\V\^\s\*\zs'.s:escape(open) 172 | let ere = '\V'.s:escape(close) 173 | 174 | " Search for a full-line comment either on the cursor line or above it 175 | if !a:upwards 176 | let ln = a:pos[0] 177 | while ln <= line("$") 178 | if getline(ln) =~# ere 179 | let col = match(getline(ln), endre) 180 | if col >= 0 181 | let end = [ln, col + 1] 182 | endif 183 | break 184 | endif 185 | let ln += 1 186 | endwhile 187 | if end != [] 188 | let ln = end[0] 189 | " Start line can be the same as end line (one-line comment) or above 190 | let col = match(getline(ln), startre) 191 | if col >= 0 && ln == a:pos[0] 192 | let start = [ln, col + 1] 193 | else 194 | let ln -= 1 195 | while ln > 0 196 | if getline(ln) =~# ere 197 | break 198 | endif 199 | let col = match(getline(ln), startre) 200 | if col >= 0 && ln <= a:pos[0] 201 | let start = [ln, col + 1] 202 | " Don't break, find better start candidates still 203 | endif 204 | let ln -= 1 205 | endwhile 206 | endif 207 | endif 208 | else 209 | let ln = a:pos[0] - 1 210 | while ln > 0 && empty(start) 211 | let col = match(getline(ln), endre) 212 | if col >= 0 213 | let end = [ln, col + 1] 214 | " Found possible end, look upwards for a matching start 215 | let col = match(getline(ln), startre) 216 | if col >= 0 217 | let start = [ln, col + 1] 218 | else 219 | let nextln = ln - 1 220 | while nextln > 0 221 | if getline(nextln) =~# ere 222 | break 223 | endif 224 | let col = match(getline(nextln), startre) 225 | if col >= 0 226 | let start = [nextln, col + 1] 227 | " Don't break, find better start candidates still 228 | endif 229 | let nextln -= 1 230 | endwhile 231 | endif 232 | endif 233 | let ln -= 1 234 | endwhile 235 | endif 236 | 237 | " Expand multiple one-line comments to one big comment 238 | if start != [] && end != [] && start[0] == end[0] 239 | let startre = '\V\^\s\*\zs'.s:escape(open).'\%(\%('.s:escape(close).'\)\@!\.\)\*'.s:escape(close).'\s\*\$' 240 | let endre = '\V\^\s\*'.s:escape(open).'\%(\%('.s:escape(close).'\)\@!\.\)\*\zs'.s:escape(close).'\s\*\$' 241 | 242 | let ln = start[0] - 1 243 | while ln > 0 244 | let col = match(getline(ln), startre) 245 | if col < 0 246 | break 247 | endif 248 | let [start[0], start[1]] = [ln, col+1] 249 | let ln -= 1 250 | endwhile 251 | 252 | let ln = end[0] + 1 253 | while ln <= line("$") 254 | let col = match(getline(ln), endre) 255 | if col < 0 256 | break 257 | endif 258 | let [end[0], end[1]] = [ln, col+1] 259 | let ln += 1 260 | endwhile 261 | endif 262 | 263 | return start == [] || end == [] ? [] : [start, end] 264 | endfunction 265 | 266 | " s:FindInlineComment() {{{2 267 | function! s:FindInlineComment(pos, simple_leaders, paired_leaders) abort 268 | " Since we are working with searchpos() to search for inline comments, it is 269 | " important to always restore the cursor position 270 | let save_pos = getpos(".") 271 | 272 | " Find the first simple comment in the line. If it is to the left of the 273 | " cursor return it, else remember it for later. We must do this first to 274 | " avoid getting caught up in strange paired leaders, such as in Vim script. 275 | let simple = [] 276 | if !empty(a:simple_leaders) 277 | call cursor(a:pos[0], 1) 278 | let simplere = '\V' . join(map(copy(a:simple_leaders),'"\\(".s:escape(v:val)."\\)"'),'\|') 279 | let [lnum, col, submatch] = searchpos(simplere, 'npW', line(".")) 280 | if lnum != 0 && col != 0 281 | let endcol = match(getline(lnum), '.$') + 1 282 | let simple = [a:simple_leaders[submatch-2], [lnum, col], [lnum, endcol]] 283 | if col <= a:pos[1] 284 | call setpos(".", save_pos) 285 | return simple 286 | endif 287 | endif 288 | call setpos(".", save_pos) 289 | endif 290 | 291 | " Find a paired comment surrounding the cursor position 292 | for [open, close] in a:paired_leaders 293 | let sre = '\V'.s:escape(open) 294 | let ere = '\V'.s:escape(close) 295 | 296 | let start = [] 297 | call cursor(a:pos[0], a:pos[1]) 298 | let end = searchpos(ere, 'cen', line(".")) 299 | if end != [0, 0] 300 | " We have found an end but must avoid matching or similar 301 | let end[1] -= strlen(close) - 1 302 | call cursor(0, end[1]) 303 | let nextend = searchpos(ere, 'ben', line(".")) 304 | let nextstart = searchpos(sre, 'be', line(".")) 305 | while nextstart != [0, 0] && (nextend == [0, 0] || s:compare(nextstart, nextend) > 0) 306 | let start = nextstart 307 | let nextstart = searchpos(sre, 'be', line(".")) 308 | endwhile 309 | if start != [] 310 | let start[1] -= strlen(open) - 1 311 | if a:pos[1] >= start[1] 312 | call setpos(".", save_pos) 313 | return [[open, close], start, end] 314 | endif 315 | endif 316 | endif 317 | call setpos(".", save_pos) 318 | endfor 319 | 320 | " Find the next paired or simple comment towards the right 321 | for [open, close] in a:paired_leaders 322 | let sre = '\V'.s:escape(open) 323 | let ere = '\V'.s:escape(close) 324 | 325 | " Start searching for comment start after cursor position 326 | call cursor(a:pos[0], a:pos[1]) 327 | let start = searchpos(sre, 'n', line(".")) 328 | let end = searchpos(ere, 'n', line(".")) 329 | if start != [0, 0] && end != [0, 0] && s:compare(start, end) < 0 330 | " Search again for the proper end, we must avoid matching 331 | call cursor(0, start[1] + strlen(open)-1) 332 | let end = searchpos(ere, 'n', line(".")) 333 | if end != [0, 0] 334 | if empty(simple) || !empty(simple) && s:compare(start, simple[1]) < 0 335 | call setpos(".", save_pos) 336 | return [[open, close], start, end] 337 | endif 338 | endif 339 | endif 340 | call setpos(".", save_pos) 341 | endfor 342 | 343 | call setpos(".", save_pos) 344 | return simple 345 | endfunction 346 | 347 | " Selection adjustment {{{1 348 | 349 | " These functions adjust the ends of the selection and return the list 350 | " required by textobj-user, or 0 on failure. Adjustment is generally 351 | " multibyte-safe. Multibyte space characters are not handled properly. 352 | 353 | " s:AdjustLineEnds() {{{2 354 | function! s:AdjustLineEnds(comment, whitespace, inside) abort 355 | if a:inside 356 | return s:AdjustInsideEnds(a:comment) 357 | endif 358 | 359 | let [leader, start, end] = a:comment 360 | 361 | " For paired leaders, move the end over the end leader 362 | if type(leader) == type([]) 363 | let end[1] += strlen(leader[1]) - 1 364 | endif 365 | 366 | " For "aC", move the end over trailing blank lines, if there aren't any move 367 | " the start over leading blank lines 368 | if a:whitespace 369 | if end[0] + 1 <= line("$") && s:isblank(end[0] + 1) 370 | let ln = end[0] + 1 371 | while ln <= line("$") && s:isblank(ln) 372 | let [end[0], end[1]] = [ln, 1] 373 | let ln += 1 374 | endwhile 375 | else 376 | let ln = start[0] - 1 377 | while ln > 0 && s:isblank(ln) 378 | let [start[0], start[1]] = [ln, 1] 379 | let ln -= 1 380 | endwhile 381 | endif 382 | endif 383 | 384 | return [ "V", [0, start[0], start[1], 0], [0, end[0], end[1], 0] ] 385 | endfunction 386 | 387 | " s:AdjustInlineEnds() {{{2 388 | function! s:AdjustInlineEnds(comment, whitespace, inside) abort 389 | if a:inside 390 | return s:AdjustInsideEnds(a:comment) 391 | endif 392 | 393 | let [leader, start, end] = a:comment 394 | 395 | if type(leader) == type([]) 396 | " For "ac" and "aC", move the end over the end leader 397 | let end[1] += strlen(leader[1]) - 1 398 | " For "aC", move the end over trailing whitespace, if there isn't any move 399 | " the start over leading whitespace 400 | if a:whitespace 401 | call cursor(end[0], end[1]) 402 | let newend = searchpos('\S', 'n', line(".")) 403 | let lastcol = match(getline(line(".")), '.$') + 1 404 | if newend == [0, 0] && end[1] != lastcol 405 | let end[1] = lastcol 406 | elseif end[1] < newend[1] - 1 407 | let end[1] = newend[1] - 1 408 | else 409 | call cursor(0, start[1]) 410 | let newstart = searchpos('\S', 'bn', line(".")) 411 | if newstart != [0, 0] 412 | let start[1] = s:nextcol(0, newstart[1]) 413 | endif 414 | endif 415 | endif 416 | else 417 | if a:whitespace 418 | " For "aC", move the end over trailing whitespace, if there isn't any 419 | " move the start over leading whitespace 420 | call cursor(end[0], end[1]) 421 | let newend = searchpos('\s$', 'cn', line(".")) 422 | if newend == [0, 0] 423 | call cursor(0, start[1]) 424 | let newstart = searchpos('\S', 'bn', line(".")) 425 | let start[1] = s:nextcol(0, newstart[1]) 426 | endif 427 | else 428 | " For "ac", move the end to the last non-whitespace character 429 | let end[1] = start[1] + strlen(leader) - 1 430 | call cursor(end[0], end[1]) 431 | let newend = searchpos('\S\s*$', 'n', line(".")) 432 | if newend != [0, 0] 433 | let [end[0], end[1]] = [newend[0], newend[1]] 434 | endif 435 | endif 436 | endif 437 | 438 | return [ "v", [0, start[0], start[1], 0], [0, end[0], end[1], 0] ] 439 | endfunction 440 | 441 | " s:AdjustInsideEnds() {{{2 442 | function! s:AdjustInsideEnds(comment) abort 443 | let [leader, start, end] = a:comment 444 | 445 | if type(leader) == type([]) 446 | " Move the start over the start leader 447 | let start[1] += strlen(leader[0]) - 1 448 | " Either select non-whitespace content, but if there is none select 449 | " whitespace or nothing 450 | call cursor(start[0], start[1]) 451 | let newstart = searchpos('\V'.s:escape(leader[1]).'\|\S', 'nW') 452 | if s:compare(newstart, end) < 0 453 | let [start[0], start[1]] = [newstart[0], newstart[1]] 454 | call cursor(end[0], end[1]) 455 | let [end[0], end[1]] = searchpos('\S', 'bnW') 456 | else 457 | if s:compare([start[0], start[1] + 1], end) < 0 458 | let start[1] += 1 459 | call cursor(end[0], end[1]) 460 | " Special treatment for cursor at start of line 461 | let [end[0], end[1]] = searchpos(end[1]==1 ? '$' : '.', 'bnW') 462 | else 463 | return 0 464 | endif 465 | endif 466 | else 467 | let start[1] += strlen(leader) - 1 468 | call cursor(start[0], start[1]) 469 | if start[0] == end[0] 470 | " For one-line comments with simple leader select non-whitespace 471 | " content, but if there is none select whitespace or nothing 472 | let newstart = searchpos('\S', 'n', line(".")) 473 | let newend = searchpos('\S\s*$', 'n', line(".")) 474 | if newstart != [0, 0] && newend != [0, 0] 475 | let [start[0], start[1]] = [newstart[0], newstart[1]] 476 | let [end[0], end[1]] = [newend[0], newend[1]] 477 | else 478 | let newstart = searchpos('\s', 'n', line(".")) 479 | let newend = searchpos('\s$', 'n', line(".")) 480 | if newstart != [0, 0] && newend != [0, 0] 481 | let [start[0], start[1]] = [newstart[0], newstart[1]] 482 | let [end[0], end[1]] = [newend[0], newend[1]] 483 | else 484 | return 0 485 | endif 486 | endif 487 | else 488 | " It isn't clear what the appropriate behaviour for multi-line comments 489 | " with simple leader should be, we try to move the start to the first \S 490 | let newstart = searchpos('\S', 'n', line(".")) 491 | let start[1] = newstart[1] ? newstart[1] : start[1] + 1 492 | endif 493 | endif 494 | 495 | return [ "v", [0, start[0], start[1], 0], [0, end[0], end[1], 0] ] 496 | endfunction 497 | 498 | " Utilities {{{1 499 | 500 | function! s:partition(str, delim) abort 501 | let idx = stridx(a:str, a:delim) 502 | return idx < 0 ? [a:str, ''] : [strpart(a:str,0,idx), strpart(a:str,idx+strlen(a:delim))] 503 | endfunction 504 | 505 | function! s:nextcol(lnum, col) abort 506 | let col = match(strpart(getline(a:lnum > 0 ? a:lnum : line(".")), a:col-1), '.\zs.') 507 | return col < 0 ? -1 : col + a:col 508 | endfunction 509 | 510 | function! s:escape(str) abort 511 | return escape(a:str, '\') 512 | endfunction 513 | 514 | function! s:isblank(lnum) abort 515 | return getline(a:lnum) =~ '^\s*$' 516 | endfunction 517 | 518 | function! s:compare(pos1, pos2) abort 519 | if a:pos1[0] < a:pos2[0] 520 | return -1 521 | elseif a:pos1[0] > a:pos2[0] 522 | return 1 523 | elseif a:pos1[1] < a:pos2[1] 524 | return -1 525 | elseif a:pos1[1] > a:pos2[1] 526 | return 1 527 | endif 528 | return 0 529 | endfunction 530 | 531 | " Public interface {{{1 532 | 533 | function! textobj#comment#select_a() abort 534 | return s:Select(0, 0) 535 | endfunction 536 | 537 | function! textobj#comment#select_i() abort 538 | return s:Select(1, 0) 539 | endfunction 540 | 541 | function! textobj#comment#select_big_a() abort 542 | return s:Select(0, 1) 543 | endfunction 544 | --------------------------------------------------------------------------------