├── LICENSE ├── README.md ├── autoload └── unite │ ├── rtags.vim │ └── sources │ ├── rtags_project.vim │ ├── rtags_references.vim │ └── rtags_symbol.vim ├── doc └── rtags.txt ├── plugin ├── rtags.vim └── vimrtags.py └── tests └── test_rtags.vim /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, lyuts 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vim Rtags 2 | 3 | Vim bindings for rtags. 4 | 5 | https://github.com/Andersbakken/rtags 6 | 7 | # Requirements 8 | 9 | # Installation 10 | ## Vundle 11 | Add the following line to ```.vimrc``` 12 | 13 | Plugin 'lyuts/vim-rtags' 14 | 15 | then while in vim run: 16 | 17 | :source % 18 | :PluginInstall 19 | 20 | ## NeoBundle 21 | Add the following line to ```.vimrc``` 22 | 23 | NeoBundle 'lyuts/vim-rtags' 24 | 25 | then while in vim run: 26 | 27 | :source % 28 | :NeoBundleInstall 29 | 30 | ## Pathogen 31 | $ cd ~/.vim/bundle 32 | $ git clone https://github.com/lyuts/vim-rtags 33 | 34 | # Configuration 35 | This plugin interacts with RTags by invoking ```rc``` commands and interpreting 36 | their results. You can override the path to ```rc``` binary by setting 37 | ```g:rtagsRcCmd``` variable. By default, it is set to ```rc```, expecting it to be 38 | found in the $PATH. 39 | 40 | Out of box this plugin provides mappings. In order to use custom mappings the 41 | default mappings can be disabled: 42 | 43 | let g:rtagsUseDefaultMappings = 0 44 | 45 | By default, search results are showed in a location list. Location lists 46 | are local to the current window. To use the vim QuickFix window, which is 47 | shared between all windows, set: 48 | 49 | let g:rtagsUseLocationList = 0 50 | 51 | To implement 'return to previous location after jump' feature, internal stack is used. 52 | It is possible to set its maximum size (number of entries), default is 100: 53 | 54 | let g:rtagsJumpStackMaxSize = 100 55 | 56 | # Usage 57 | 58 | ## Mappings 59 | | Mapping | rc flag | Description | 60 | |------------------|----------------------------------|--------------------------------------------| 61 | | <Leader>ri | -U | Symbol info | 62 | | <Leader>rj | -f | Follow location | 63 | | <Leader>rJ | -f --declaration-only | Follow declaration location | 64 | | <Leader>rS | -f | Follow location (open in horizontal split) | 65 | | <Leader>rV | -f | Follow location (open in vertical split) | 66 | | <Leader>rT | -f | Follow location open in a new tab | 67 | | <Leader>rp | -U --symbol-info-include-parents | Jump to parent | 68 | | <Leader>rc | --class-hierarchy | Find subclasses | 69 | | <Leader>rC | --class-hierarchy | Find superclasses | 70 | | <Leader>rh | --class-hierarchy | List full class hierarchy | 71 | | <Leader>rf | -e -r | Find references | 72 | | <Leader>rF | -r --containing-function-location| Call tree (o - open node, Enter - jump) | 73 | | <Leader>rn | -ae -R | Find references by name | 74 | | <Leader>rs | -a -F | Find symbols by name | 75 | | <Leader>rr | -V | Reindex current file | 76 | | <Leader>rl | -w | List all available projects | 77 | | <Leader>rw | -e -r --rename | Rename symbol under cursor | 78 | | <Leader>rv | -k -r | Find virtuals | 79 | | <Leader>rd | --diagnose | Diagnose file for warnings and errors | 80 | | <Leader>rb | N/A | Jump to previous location | 81 | 82 | ## Unite sources 83 | 84 | This plugin defines three Unite sources: 85 | * `rtags/references` - list references (i.e., <Leader>rf). 86 | * `rtags/symbol` - find symbol (i.e., <Leader>rs). Use `rtags/symbol:i` 87 | for case insensitive search. 88 | * `rtags/project` - list/switch projects. 89 | 90 | ## Code completion 91 | Code completion functionality uses ```completefunc``` (i.e. CTRL-X CTRL-U). If ```completefunc``` 92 | is set, vim-rtags will not override it with ```RtagsCompleteFunc```. This functionality is still 93 | unstable, but if you want to try it you will have to set ```completefunc``` by 94 | 95 | set completefunc=RtagsCompleteFunc 96 | 97 | Also ```RtagsCompleteFunc``` can be used as omnifunc. For example, you can use 98 | such approach with [neocomplete](https://github.com/Shougo/neocomplete.vim)(for more details read it's docs): 99 | 100 | ``` 101 | function! SetupNeocompleteForCppWithRtags() 102 | " Enable heavy omni completion. 103 | setlocal omnifunc=RtagsCompleteFunc 104 | 105 | if !exists('g:neocomplete#sources#omni#input_patterns') 106 | let g:neocomplete#sources#omni#input_patterns = {} 107 | endif 108 | let l:cpp_patterns='[^.[:digit:] *\t]\%(\.\|->\)\|\h\w*::' 109 | let g:neocomplete#sources#omni#input_patterns.cpp = l:cpp_patterns 110 | set completeopt+=longest,menuone 111 | endfunction 112 | 113 | autocmd FileType cpp,c call SetupNeocompleteForCppWithRtags() 114 | 115 | ``` 116 | Such config provides automatic calls, of omnicompletion on c and cpp entity accessors. 117 | 118 | ### Current limitations 119 | * There is no support for overridden functions and methods 120 | * There is no support for function argument completion 121 | 122 | # Notes 123 | 1. This plugin is wip. 124 | 125 | # Development 126 | Unit tests for some plugin functions can be found in ```tests``` directory. 127 | To run tests, execute: 128 | 129 | $ vim tests/test_rtags.vim +UnitTest 130 | -------------------------------------------------------------------------------- /autoload/unite/rtags.vim: -------------------------------------------------------------------------------- 1 | function! unite#rtags#get_filepath(result) 2 | return split(a:result, ':')[0] 3 | endfunction 4 | 5 | function! unite#rtags#get_fileline(result) 6 | return split(a:result, ':')[1] 7 | endfunction 8 | 9 | function! unite#rtags#get_filecol(result) 10 | return split(a:result, ':')[2] 11 | endfunction 12 | 13 | function! unite#rtags#get_filetext(result) 14 | return substitute( a:result, '^[^:]\+:[^:]\+:[^:]\+:\t', '', '') 15 | endfunction 16 | 17 | function! unite#rtags#get_word(result) 18 | let cwd = getcwd() 19 | let relpath = split(a:result, cwd.'/')[0] 20 | let relfix = '' 21 | 22 | while relpath ==# a:result 23 | " the current working directory isn't fully contained in the result's 24 | " path. E.g., result may contain /a/b/c/d and the current working 25 | " directory might be /a/b/c/e 26 | let cwd = join(split(cwd, '/')[:-2], '/') 27 | " since the join does not place a starting '/' we need to skip to the 28 | " second element 29 | let parts = split(a:result, cwd.'/') 30 | 31 | if (len(parts) == 2) 32 | let relpath = parts[1] 33 | let relfix = relfix . '../' 34 | else 35 | " no common ancestry 36 | let relpath = a:result 37 | let relfix = '' 38 | break 39 | endif 40 | endwhile 41 | 42 | return relfix . relpath 43 | endfunction 44 | -------------------------------------------------------------------------------- /autoload/unite/sources/rtags_project.vim: -------------------------------------------------------------------------------- 1 | function! unite#sources#rtags_project#define() "{{{ 2 | return s:source_rtags_project 3 | endfunction "}}} 4 | 5 | " line source. "{{{ 6 | let s:source_rtags_project = { 7 | \ 'name' : 'rtags/project', 8 | \ 'syntax' : 'uniteSource__RtagsProject', 9 | \ 'hooks' : {}, 10 | \ 'default_kind' : 'command', 11 | \ 'matchers' : 'matcher_regexp', 12 | \ 'sorters' : 'sorter_nothing', 13 | \ } 14 | " }}} 15 | 16 | function! s:source_rtags_project.gather_candidates(args, context) 17 | let args = { '-w' : '' } 18 | let result = rtags#ExecuteRC(args) 19 | return map(result, "{ 20 | \ 'word': v:val, 21 | \ 'action__command': 'call unite#sources#rtags_project#SetProject(\"'.split(v:val, ' ')[0].'\")', 22 | \ 'action__histadd': 0, 23 | \ }") 24 | endfunction 25 | 26 | function! unite#sources#rtags_project#SetProject(name) 27 | let args = { '-w' : a:name} 28 | let result = rtags#ExecuteRC(args) 29 | endfunction 30 | -------------------------------------------------------------------------------- /autoload/unite/sources/rtags_references.vim: -------------------------------------------------------------------------------- 1 | function! unite#sources#rtags_references#define() "{{{ 2 | return s:source_rtags_references 3 | endfunction "}}} 4 | 5 | " line source. "{{{ 6 | let s:source_rtags_references = { 7 | \ 'name' : 'rtags/references', 8 | \ 'syntax' : 'uniteSource__RtagsReferences', 9 | \ 'hooks' : {}, 10 | \ 'default_kind' : 'jump_list', 11 | \ 'matchers' : 'matcher_regexp', 12 | \ 'sorters' : 'sorter_nothing', 13 | \ } 14 | 15 | function! s:source_rtags_references.hooks.on_syntax(args, context) 16 | syntax match uniteSource__RtagsReferences_File /[^:]*: / contained 17 | \ containedin=uniteSource__RtagsReferences 18 | \ nextgroup=uniteSource__RtagsReferences_LineNR 19 | syntax match uniteSource__RtagsReferences_LineNR /\d\+:/ contained 20 | \ containedin=uniteSource__RtagsReferences 21 | \ nextgroup=uniteSource__RtagsReferences_Symbol 22 | execute 'syntax match uniteSource__RtagsReferences_Symbol /' 23 | \ . a:context.source__cword 24 | \ . '/ contained containedin=uniteSource__RtagsReferences' 25 | syntax match uniteSource__RtagsReferences_Separator /:/ contained 26 | \ containedin=uniteSource__RtagsReferences_File,uniteSource__RtagsReferences_LineNR 27 | highlight default link uniteSource__RtagsReferences_File Comment 28 | highlight default link uniteSource__RtagsReferences_LineNr LineNR 29 | highlight default link uniteSource__RtagsReferences_Symbol Function 30 | endfunction 31 | 32 | function! s:source_rtags_references.gather_candidates(args, context) 33 | let a:context.source__cword = expand("") 34 | let args = { 35 | \ '-e' : '', 36 | \ '-r' : rtags#getCurrentLocation() } 37 | let result = rtags#ExecuteRC(args) 38 | return map(result, "{ 39 | \ 'word': unite#rtags#get_word(v:val), 40 | \ 'action__path': unite#rtags#get_filepath(v:val), 41 | \ 'action__line': unite#rtags#get_fileline(v:val), 42 | \ 'action__col': unite#rtags#get_filecol(v:val), 43 | \ 'action__text': unite#rtags#get_filetext(v:val) 44 | \ }") 45 | endfunction 46 | -------------------------------------------------------------------------------- /autoload/unite/sources/rtags_symbol.vim: -------------------------------------------------------------------------------- 1 | function! unite#sources#rtags_symbol#define() "{{{ 2 | return s:source_rtags_symbol 3 | endfunction "}}} 4 | 5 | " line source. "{{{ 6 | let s:source_rtags_symbol = { 7 | \ 'name' : 'rtags/symbol', 8 | \ 'syntax' : 'uniteSource__RtagsSymbol', 9 | \ 'hooks' : {}, 10 | \ 'default_kind' : 'jump_list', 11 | \ 'matchers' : 'matcher_regexp', 12 | \ 'sorters' : 'sorter_nothing', 13 | \ } 14 | " }}} 15 | 16 | function! s:source_rtags_symbol.hooks.on_syntax(args, context) 17 | if (a:context.source__case ==# 'i') 18 | syntax case ignore 19 | endif 20 | 21 | syntax match uniteSource__RtagsSymbol_File /[^:]*: / contained 22 | \ containedin=uniteSource__RtagsSymbol 23 | \ nextgroup=uniteSource__RtagsSymbol_LineNR 24 | syntax match uniteSource__RtagsSymbol_LineNR /\d\+:/ contained 25 | \ containedin=uniteSource__RtagsSymbol 26 | \ nextgroup=uniteSource__RtagsSymbol_Symbol 27 | execute 'syntax match uniteSource__RtagsSymbol_Symbol /' 28 | \ . a:context.source__input 29 | \ . '/ contained containedin=uniteSource__RtagsSymbol' 30 | highlight default link uniteSource__RtagsSymbol_File Comment 31 | highlight default link uniteSource__RtagsSymbol_LineNr LineNR 32 | highlight default link uniteSource__RtagsSymbol_Symbol Function 33 | endfunction 34 | 35 | function! s:source_rtags_symbol.hooks.on_init(args, context) 36 | let a:context.source__input = get(a:args, 1, '') 37 | if (a:context.source__input ==# '') 38 | let a:context.source__input = unite#util#input('Pattern: ') 39 | endif 40 | 41 | call unite#print_source_message('Pattern: ' 42 | \ . a:context.source__input, s:source_rtags_symbol.name) 43 | 44 | let a:context.source__case = get(a:args, 0, '') 45 | endfunction 46 | 47 | function! s:source_rtags_symbol.gather_candidates(args, context) 48 | let args = { '-a' : '' } 49 | if (a:context.source__case ==# 'i') 50 | let args['-I'] = '' 51 | endif 52 | let args['-F'] = a:context.source__input 53 | let result = rtags#ExecuteRC(args) 54 | return map(result, "{ 55 | \ 'word': unite#rtags#get_word(v:val), 56 | \ 'action__path': unite#rtags#get_filepath(v:val), 57 | \ 'action__line': unite#rtags#get_fileline(v:val), 58 | \ 'action__col': unite#rtags#get_filecol(v:val), 59 | \ 'action__text': unite#rtags#get_filetext(v:val) 60 | \ }") 61 | endfunction 62 | -------------------------------------------------------------------------------- /doc/rtags.txt: -------------------------------------------------------------------------------- 1 | *vim-rtags* VIM bindings for RTags, C/C++ code indexer. 2 | 3 | CONTENTS *rtags-contents* 4 | 5 | 1. Intro |rtags-intro| 6 | 2. Installation |rtags-installation| 7 | 3. Variables |rtags-variables| 8 | 4. Mappings |rtags-mappings| 9 | 5. Commands |rtags-commands| 10 | 6. Integrations with other plugins |rtags-integrations| 11 | 7. Todo |rtags-todo| 12 | 8. License |rtags-license| 13 | 14 | ================================================================================ 15 | 16 | *rtags-intro* 17 | 1. Intro 18 | Vim bindings for rtags (https://github.com/Andersbakken/rtags). 19 | 20 | *rtags-installation* 21 | 2. Installation 22 | Follow your plugin manager installation instructions. This plugin requires 23 | vim compiled with +python option. 24 | 25 | *rtags-variable* 26 | 3. Variables 27 | 28 | *g:rtagsRcCmd* 29 | *rtags-variable-rc-cmd* 30 | g:rtagsRcCmd 31 | 32 | Default: "rc". 33 | Points to an executable of 'rc' command provided by rtags distribution. 34 | Default value expects the directory containing 'rc' to be in the PATH 35 | environment variable. Overriding |g:rtagsRcCmd| allows you specify 'rc' 36 | location if it is installed to a non standard location or the location 37 | doesn't appear in the PATH. 38 | 39 | *g:rtagsRdmCmd* 40 | *rtags-variable-rdm-cmd* 41 | g:rtagsRdmCmd 42 | 43 | Default: "rdm". 44 | Points to an executable of 'rdm' command provided by rtags distribution. 45 | Default value expects the directory containing 'rdm' to be in the PATH 46 | environment variable. Overriding |g:rtagsRdmCmd| allows you specify 'rdm' 47 | location if it is installed to a non standard location or the location 48 | doesn't appear in the PATH. 49 | 50 | *g:rtagsAutoLaunchRdm* 51 | *rtags-variable-auto-launch-rdm* 52 | g:rtagsAutoLaunchRdm 53 | 54 | Default: 0. 55 | If set to 1, rdm will be launched at startup if it is not running. 56 | 57 | *g:rtagsExcludeSysHeaders* 58 | *rtags-variable-exclude-sys-headers* 59 | g:rtagsExcludeSysHeaders 60 | 61 | Default: 0. 62 | Controls whether search should skip system headers or not. 63 | 64 | *g:rtagsUseLocationList* 65 | *rtags-variable-use-location-list* 66 | g:rtagsUseLocationList 67 | 68 | Default: 1. 69 | If set to 1, search results are showed in a location list. Location lists 70 | are local to the current window. Otherwise, QuickFix window, which is shared 71 | between all windows, is used. 72 | 73 | *g:rtagsUseDefaultMappings* 74 | *rtags-variable-use-default-mappings* 75 | g:rtagsUseDefaultMappings 76 | 77 | Default: 1. 78 | If enabled plugin defines default mappings for interacting with the plugin. 79 | Otherwise, no mappings are set up and custom mappings can be configured by 80 | a user. 81 | 82 | *g:rtagsMinCharsForCommandCompletion* 83 | *rtags-variable-min-chars-for-cmd-compl* 84 | g:rtagsMinCharsForCommandCompletion 85 | 86 | Default: 4. 87 | Minimum number of characters to be typed before argument completion is 88 | available for commands provided by the plugin or pluging mappings that 89 | require user input. 90 | 91 | *g:rtagsMaxSearchResultWindowHeight* 92 | *rtags-variable-max-search-result-window-height* 93 | g:rtagsMaxSearchResultWindowHeight 94 | 95 | Default: 10. 96 | Determines the maximum height of the search result window. When number of 97 | results is less than this parameter, the height is set to the number of 98 | results. 99 | 100 | *g:rtagsLog* 101 | *rtags-variable-rtags-log* 102 | g:rtagsLog 103 | 104 | Default: empty 105 | When set to filename, rtags will put its logs in that file. 106 | 107 | *rtags-mappings* 108 | 4. Mappings 109 | *rtags-leader-ri* 110 | *rtags-SymbolInfo* 111 | ri Display information about the symbol under the cursor. 112 | 113 | *rtags-leader-rj* 114 | *rtags-JumpTo* 115 | rj Jump to declaration/definition. 116 | 117 | *rtags-leader-rJ* 118 | *rtags-JumpTo-Decl* 119 | rJ Jump to declaration. 120 | 121 | *rtags-leader-rS* 122 | *rtags-JumpTo-Split* 123 | rS Same as rj, but opens target location in a 124 | horizontal split. 125 | 126 | *rtags-leader-rV* 127 | *rtags-JumpTo-Vert-Split* 128 | rV Same as rj, but opens target location in a vertical 129 | split. 130 | 131 | *rtags-leader-rT* 132 | *rtags-JumpTo-Tab* 133 | rT Same as rj, but opens target location in a new tab. 134 | 135 | *rtags-leader-rp* 136 | *rtags-JumpToParent* 137 | rp Jump to an entity that contains the symbol under the 138 | cursor, e.g. jump to the beginning of a function that has 139 | the local variable under a cursor, or jump to the class 140 | that defines a member function under a cursor, etc. 141 | 142 | 143 | *rtags-leader-rf* 144 | *rtags-FindRefs* 145 | rf Find references of the symbol under the cursor. 146 | 147 | *rtags-leader-rF* 148 | *rtags-FindRefsCallTree* 149 | rF Find references of the symbol under the cursor in a tree 150 | view. In this view, press 'o' to expand any reference to 151 | find callers to the function containing that reference. 152 | 153 | *rtags-leader-rn* 154 | *rtags-FindRefsByName* 155 | rn Find symbol(s) references that match the provided pattern. 156 | Pattern is typed by a user on mapping invocation. Patterns 157 | whose length is equal or greater than value of 158 | |g:rtagsMinCharsForCommandCompletion| can be autocompleted. 159 | 160 | *rtags-leader-rs* 161 | *rtags-FindSymbols* 162 | rs Find declaration/definition location for symbol(s) that 163 | match provided pattern. Pattern is typed by a user on 164 | mapping invocation. Patterns whose length is equal or 165 | greater than value of |g:rtagsMinCharsForCommandCompletion| 166 | can be autocompleted. 167 | 168 | *rtags-leader-rr* 169 | *rtags-ReindexFile* 170 | rr Trigger current file reindexing by 'rdm'. 171 | 172 | *rtags-leader-rl* 173 | *rtags-ProjectList* 174 | rl List all projects registered with rdm and optionally switch 175 | an active project to a selected one by choosing 176 | corresponding index in the resulting list. 177 | 178 | *rtags-leader-rw* 179 | *rtags-RenameSymbolUnderCursor* 180 | rw Rename symbol under cursor. 181 | 182 | *rtags-leader-rv* 183 | *rtags-FindVirtuals* 184 | rv Find other implementations of a function, such as virtual 185 | functions. 186 | 187 | *rtags-leader-rC* 188 | *rtags-FindSuperClasses* 189 | rC Find the superclasses of the class under the cursor. 190 | 191 | *rtags-leader-rc* 192 | *rtags-FindSubClasses* 193 | rc Find the subclasses of the class under the cursor. 194 | 195 | *rtags-commands* 196 | 5. Commands 197 | 198 | Helper function defined mostly for mapping convenience, but which still can 199 | be directly if needed: 200 | - RtagsFindSymbols 201 | - RtagsFindRefsByName 202 | - RtagsIFindSymbols 203 | - RtagsIFindRefsByName 204 | - RtagsLoadCompilationDb 205 | 206 | *rtags-integrations* 207 | 6. Integrations with other plugins 208 | 209 | *rtags-integration-unite* 210 | Plugin defines three Unite sources: 211 | 212 | rtags/references list references (alternative to rf). 213 | 214 | rtags/symbol find symbol (alternative to rs). 215 | 216 | rtags/symbol:i same as rtags/symbol, but for case insensitive. 217 | 218 | rtags/project list/switch projects. 219 | 220 | 221 | *rtags-todo* 222 | 7. Todo 223 | *rtags-license* 224 | 8. License 225 | -------------------------------------------------------------------------------- /plugin/rtags.vim: -------------------------------------------------------------------------------- 1 | if has('nvim') || (has('job') && has('channel')) 2 | let s:rtagsAsync = 1 3 | let s:job_cid = 0 4 | let s:jobs = {} 5 | let s:result_stdout = {} 6 | let s:result_handlers = {} 7 | else 8 | let s:rtagsAsync = 0 9 | endif 10 | 11 | if has('python') 12 | let g:rtagsPy = 'python' 13 | elseif has('python3') 14 | let g:rtagsPy = 'python3' 15 | else 16 | echohl ErrorMsg | echomsg "[vim-rtags] Vim is missing python support" | echohl None 17 | finish 18 | end 19 | 20 | 21 | 22 | if !exists("g:rtagsRcCmd") 23 | let g:rtagsRcCmd = "rc" 24 | endif 25 | 26 | if !exists("g:rtagsRdmCmd") 27 | let g:rtagsRdmCmd = "rdm" 28 | endif 29 | 30 | if !exists("g:rtagsAutoLaunchRdm") 31 | let g:rtagsAutoLaunchRdm = 0 32 | endif 33 | 34 | if !exists("g:rtagsJumpStackMaxSize") 35 | let g:rtagsJumpStackMaxSize = 100 36 | endif 37 | 38 | if !exists("g:rtagsExcludeSysHeaders") 39 | let g:rtagsExcludeSysHeaders = 0 40 | endif 41 | 42 | let g:rtagsJumpStack = [] 43 | 44 | if !exists("g:rtagsUseLocationList") 45 | let g:rtagsUseLocationList = 1 46 | endif 47 | 48 | if !exists("g:rtagsUseDefaultMappings") 49 | let g:rtagsUseDefaultMappings = 1 50 | endif 51 | 52 | if !exists("g:rtagsMinCharsForCommandCompletion") 53 | let g:rtagsMinCharsForCommandCompletion = 4 54 | endif 55 | 56 | if !exists("g:rtagsMaxSearchResultWindowHeight") 57 | let g:rtagsMaxSearchResultWindowHeight = 10 58 | endif 59 | 60 | if g:rtagsAutoLaunchRdm 61 | call system(g:rtagsRcCmd." -w") 62 | if v:shell_error != 0 63 | call system(g:rtagsRdmCmd." --daemon > /dev/null") 64 | end 65 | end 66 | 67 | let g:SAME_WINDOW = 'same_window' 68 | let g:H_SPLIT = 'hsplit' 69 | let g:V_SPLIT = 'vsplit' 70 | let g:NEW_TAB = 'tab' 71 | 72 | let s:LOC_OPEN_OPTS = { 73 | \ g:SAME_WINDOW : '', 74 | \ g:H_SPLIT : ' ', 75 | \ g:V_SPLIT : 'vert', 76 | \ g:NEW_TAB : 'tab' 77 | \ } 78 | 79 | if g:rtagsUseDefaultMappings == 1 80 | noremap ri :call rtags#SymbolInfo() 81 | noremap rj :call rtags#JumpTo(g:SAME_WINDOW) 82 | noremap rJ :call rtags#JumpTo(g:SAME_WINDOW, { '--declaration-only' : '' }) 83 | noremap rS :call rtags#JumpTo(g:H_SPLIT) 84 | noremap rV :call rtags#JumpTo(g:V_SPLIT) 85 | noremap rT :call rtags#JumpTo(g:NEW_TAB) 86 | noremap rp :call rtags#JumpToParent() 87 | noremap rf :call rtags#FindRefs() 88 | noremap rF :call rtags#FindRefsCallTree() 89 | noremap rn :call rtags#FindRefsByName(input("Pattern? ", "", "customlist,rtags#CompleteSymbols")) 90 | noremap rs :call rtags#FindSymbols(input("Pattern? ", "", "customlist,rtags#CompleteSymbols")) 91 | noremap rr :call rtags#ReindexFile() 92 | noremap rl :call rtags#ProjectList() 93 | noremap rw :call rtags#RenameSymbolUnderCursor() 94 | noremap rv :call rtags#FindVirtuals() 95 | noremap rb :call rtags#JumpBack() 96 | noremap rh :call rtags#ShowHierarchy() 97 | noremap rC :call rtags#FindSuperClasses() 98 | noremap rc :call rtags#FindSubClasses() 99 | noremap rd :call rtags#Diagnostics() 100 | endif 101 | 102 | let s:script_folder_path = escape( expand( ':p:h' ), '\' ) 103 | 104 | function! rtags#InitPython() 105 | let s:pyInitScript = " 106 | \ import vim; 107 | \ script_folder = vim.eval('s:script_folder_path'); 108 | \ sys.path.insert(0, script_folder); 109 | \ import vimrtags" 110 | 111 | exe g:rtagsPy." ".s:pyInitScript 112 | endfunction 113 | 114 | call rtags#InitPython() 115 | 116 | """ 117 | " Logging routine 118 | """ 119 | function! rtags#Log(message) 120 | if exists("g:rtagsLog") 121 | call writefile([string(a:message)], g:rtagsLog, "a") 122 | endif 123 | endfunction 124 | 125 | " 126 | " Executes rc with given arguments and returns rc output 127 | " 128 | " param[in] args - dictionary of arguments 129 | "- 130 | " return output split by newline 131 | function! rtags#ExecuteRC(args) 132 | let cmd = rtags#getRcCmd() 133 | 134 | " Give rdm unsaved file content, so that you don't have to save files 135 | " before each rc invocation. 136 | if exists('b:rtags_sent_content') 137 | let content = join(getline(1, line('$')), "\n") 138 | if b:rtags_sent_content != content 139 | let unsaved_content = content 140 | endif 141 | elseif &modified 142 | let unsaved_content = join(getline(1, line('$')), "\n") 143 | endif 144 | if exists('unsaved_content') 145 | let filename = expand("%") 146 | let output = system(printf("%s --unsaved-file=%s:%s -V %s", cmd, filename, strlen(unsaved_content), filename), unsaved_content) 147 | let b:rtags_sent_content = unsaved_content 148 | endif 149 | 150 | " prepare for the actual command invocation 151 | for [key, value] in items(a:args) 152 | let cmd .= " ".key 153 | if len(value) > 1 154 | let cmd .= " ".value 155 | endif 156 | endfor 157 | 158 | let output = system(cmd) 159 | if v:shell_error && len(output) > 0 160 | let output = substitute(output, '\n', '', '') 161 | echohl ErrorMsg | echomsg "[vim-rtags] Error: " . output | echohl None 162 | return [] 163 | endif 164 | if output =~ '^Not indexed' 165 | echohl ErrorMsg | echomsg "[vim-rtags] Current file is not indexed!" | echohl None 166 | return [] 167 | endif 168 | return split(output, '\n\+') 169 | endfunction 170 | 171 | function! rtags#CreateProject() 172 | 173 | endfunction 174 | 175 | " 176 | " param[in] results - List of found locations by rc 177 | " return locations - List of locations dict's recognizable by setloclist 178 | " 179 | function! rtags#ParseResults(results) 180 | let locations = [] 181 | let nr = 1 182 | for record in a:results 183 | let [location; rest] = split(record, '\s\+') 184 | let [file, lnum, col] = rtags#parseSourceLocation(location) 185 | 186 | let entry = {} 187 | " let entry.bufn = 0 188 | let entry.filename = substitute(file, getcwd().'/', '', 'g') 189 | let entry.filepath = file 190 | let entry.lnum = lnum 191 | " let entry.pattern = '' 192 | let entry.col = col 193 | let entry.vcol = 0 194 | " let entry.nr = nr 195 | let entry.text = join(rest, ' ') 196 | let entry.type = 'ref' 197 | 198 | call add(locations, entry) 199 | 200 | let nr = nr + 1 201 | endfor 202 | return locations 203 | endfunction 204 | 205 | function! rtags#ExtractClassHierarchyLine(line) 206 | return substitute(a:line, '\v.*\s+(\S+:[0-9]+:[0-9]+:\s)', '\1', '') 207 | endfunction 208 | 209 | " 210 | " Converts a class hierarchy of 'rc --class-hierarchy' like: 211 | " 212 | " Superclasses: 213 | " class Foo src/Foo.h:56:7: class Foo : public Bar { 214 | " class Bar src/Bar.h:46:7: class Bar : public Bas { 215 | " class Bas src/Bas.h:47:7: class Bas { 216 | " Subclasses: 217 | " class Foo src/Foo.h:56:7: class Foo : public Bar { 218 | " class Foo2 src/Foo2.h:56:7: class Foo2 : public Foo { 219 | " class Foo3 src/Foo3.h:56:7: class Foo3 : public Foo { 220 | " 221 | " into the super classes: 222 | " 223 | " src/Foo.h:56:7: class Foo : public Bar { 224 | " src/Bar.h:46:7: class Bar : public Bas { 225 | " src/Bas.h:47:7: class Bas { 226 | " 227 | function! rtags#ExtractSuperClasses(results) 228 | let extracted = [] 229 | for line in a:results 230 | if line == "Superclasses:" 231 | continue 232 | endif 233 | 234 | if line == "Subclasses:" 235 | break 236 | endif 237 | 238 | let extLine = rtags#ExtractClassHierarchyLine(line) 239 | call add(extracted, extLine) 240 | endfor 241 | return extracted 242 | endfunction 243 | 244 | " 245 | " Converts a class hierarchy of 'rc --class-hierarchy' like: 246 | " 247 | " Superclasses: 248 | " class Foo src/Foo.h:56:7: class Foo : public Bar { 249 | " class Bar src/Bar.h:46:7: class Bar : public Bas { 250 | " class Bas src/Bas.h:47:7: class Bas { 251 | " Subclasses: 252 | " class Foo src/Foo.h:56:7: class Foo : public Bar { 253 | " class Foo2 src/Foo2.h:56:7: class Foo2 : public Foo { 254 | " class Foo3 src/Foo3.h:56:7: class Foo3 : public Foo { 255 | " 256 | " into the sub classes: 257 | " 258 | " src/Foo.h:56:7: class Foo : public Bar { 259 | " src/Foo2.h:56:7: class Foo2 : public Foo { 260 | " src/Foo3.h:56:7: class Foo3 : public Foo { 261 | " 262 | function! rtags#ExtractSubClasses(results) 263 | let extracted = [] 264 | let atSubClasses = 0 265 | for line in a:results 266 | if atSubClasses == 0 267 | if line == "Subclasses:" 268 | let atSubClasses = 1 269 | endif 270 | 271 | continue 272 | endif 273 | 274 | let extLine = rtags#ExtractClassHierarchyLine(line) 275 | call add(extracted, extLine) 276 | endfor 277 | return extracted 278 | endfunction 279 | 280 | " 281 | " param[in] locations - List of locations, one per line 282 | " 283 | function! rtags#DisplayLocations(locations) 284 | let num_of_locations = len(a:locations) 285 | if g:rtagsUseLocationList == 1 286 | call setloclist(winnr(), a:locations) 287 | if num_of_locations > 0 288 | exe 'lopen '.min([g:rtagsMaxSearchResultWindowHeight, num_of_locations]) | set nowrap 289 | endif 290 | else 291 | call setqflist(a:locations) 292 | if num_of_locations > 0 293 | exe 'copen '.min([g:rtagsMaxSearchResultWindowHeight, num_of_locations]) | set nowrap 294 | endif 295 | endif 296 | endfunction 297 | 298 | " 299 | " param[in] results - List of locations, one per line 300 | " 301 | " Format of each line: ,\s 302 | function! rtags#DisplayResults(results) 303 | let locations = rtags#ParseResults(a:results) 304 | call rtags#DisplayLocations(locations) 305 | endfunction 306 | 307 | " 308 | " Creates a tree viewer for references to a symbol 309 | " 310 | " param[in] results - List of locations, one per line 311 | " 312 | " Format of each line: ,\s\sfunction: 313 | function! rtags#ViewReferences(results) 314 | let cmd = g:rtagsMaxSearchResultWindowHeight . "new References" 315 | silent execute cmd 316 | setlocal noswapfile 317 | setlocal buftype=nowrite 318 | setlocal bufhidden=delete 319 | setlocal nowrap 320 | setlocal tw=0 321 | 322 | iabc 323 | 324 | setlocal modifiable 325 | silent normal ggdG 326 | setlocal nomodifiable 327 | let b:rtagsLocations=[] 328 | call rtags#AddReferences(a:results, -1) 329 | setlocal modifiable 330 | silent normal ggdd 331 | setlocal nomodifiable 332 | 333 | let cpo_save = &cpo 334 | set cpo&vim 335 | nnoremap :call OpenReference() 336 | nnoremap o :call ExpandReferences() 337 | let &cpo = cpo_save 338 | endfunction 339 | 340 | " 341 | " Expands the callers of the reference on the current line. 342 | " 343 | function! s:ExpandReferences() " <<< 344 | let ln = line(".") 345 | 346 | " Detect expandable region 347 | if !empty(b:rtagsLocations[ln - 1].source) 348 | let location = b:rtagsLocations[ln - 1].source 349 | let rnum = b:rtagsLocations[ln - 1].rnum 350 | let b:rtagsLocations[ln - 1].source = '' 351 | let args = { 352 | \ '--containing-function-location' : '', 353 | \ '-r' : location } 354 | call rtags#ExecuteThen(args, [[function('rtags#AddReferences'), rnum]]) 355 | endif 356 | endfunction " >>> 357 | 358 | " 359 | " Opens the reference for viewing in the window below. 360 | " 361 | function! s:OpenReference() " <<< 362 | let ln = line(".") 363 | 364 | " Detect openable region 365 | if ln - 1 < len(b:rtagsLocations) 366 | let jump_file = b:rtagsLocations[ln - 1].filename 367 | let lnum = b:rtagsLocations[ln - 1].lnum 368 | let col = b:rtagsLocations[ln - 1].col 369 | wincmd j 370 | " Add location to the jumplist 371 | normal m' 372 | if rtags#jumpToLocation(jump_file, lnum, col) 373 | normal zz 374 | endif 375 | endif 376 | endfunction " >>> 377 | 378 | " 379 | " Adds the list of references below the targeted item in the reference 380 | " viewer window. 381 | " 382 | " param[in] results - List of locations, one per line 383 | " param[in] rnum - The record number the references are calling or -1 384 | " 385 | " Format of each line: ,\s\sfunction: 386 | function! rtags#AddReferences(results, rnum) 387 | let ln = line(".") 388 | let depth = 0 389 | let nr = len(b:rtagsLocations) 390 | let i = -1 391 | " If a reference number is provided, find this entry in the list and insert 392 | " after it. 393 | if a:rnum >= 0 394 | let i = 0 395 | while i < nr && b:rtagsLocations[i].rnum != a:rnum 396 | let i += 1 397 | endwhile 398 | if i == nr 399 | " We didn't find the source record, something went wrong 400 | echo "Error finding insertion point." 401 | return 402 | endif 403 | let depth = b:rtagsLocations[i].depth + 1 404 | exec (":" . (i + 1)) 405 | endif 406 | let prefix = repeat(" ", depth * 2) 407 | let new_entries = [] 408 | setlocal modifiable 409 | for record in a:results 410 | let [line; sourcefunc] = split(record, '\s\+function: ') 411 | let [location; rest] = split(line, '\s\+') 412 | let [file, lnum, col] = rtags#parseSourceLocation(location) 413 | let entry = {} 414 | let entry.filename = substitute(file, getcwd().'/', '', 'g') 415 | let entry.filepath = file 416 | let entry.lnum = lnum 417 | let entry.col = col 418 | let entry.vcol = 0 419 | let entry.text = join(rest, ' ') 420 | let entry.type = 'ref' 421 | let entry.depth = depth 422 | let entry.source = matchstr(sourcefunc, '[^\s]\+') 423 | let entry.rnum = nr 424 | silent execute "normal! A\\i".prefix . substitute(entry.filename, '.*/', '', 'g').':'.entry.lnum.' '.entry.text."\" 425 | call add(new_entries, entry) 426 | let nr = nr + 1 427 | endfor 428 | call extend(b:rtagsLocations, new_entries, i + 1) 429 | setlocal nomodifiable 430 | exec (":" . ln) 431 | endfunction 432 | 433 | " Creates a viewer for hierarachy results 434 | " 435 | " param[in] results - List of class hierarchy 436 | " 437 | " Hierarchy references have format: ::: 438 | " 439 | function! rtags#ViewHierarchy(results) 440 | let cmd = g:rtagsMaxSearchResultWindowHeight . "new Hierarchy" 441 | silent execute cmd 442 | setlocal noswapfile 443 | setlocal buftype=nowrite 444 | setlocal bufhidden=delete 445 | setlocal nowrap 446 | setlocal tw=0 447 | 448 | iabc 449 | 450 | setlocal modifiable 451 | silent normal ggdG 452 | for record in a:results 453 | silent execute "normal! A\\i".record."\" 454 | endfor 455 | silent normal ggdd 456 | setlocal nomodifiable 457 | 458 | let cpo_save = &cpo 459 | set cpo&vim 460 | nnoremap :call OpenHierarchyLocation() 461 | let &cpo = cpo_save 462 | endfunction 463 | 464 | " 465 | " Opens the location on the current line. 466 | " 467 | " Hierarchy references have format: ::: 468 | " 469 | function! s:OpenHierarchyLocation() " <<< 470 | let ln = line(".") 471 | let l = getline(ln) 472 | if l[0] == ' ' 473 | let [type, name, location; rest] = split(l, '\s\+') 474 | let [jump_file, lnum, col; rest] = split(location, ':') 475 | wincmd j 476 | " Add location to the jumplist 477 | normal m' 478 | if rtags#jumpToLocation(jump_file, lnum, col) 479 | normal zz 480 | endif 481 | endif 482 | endfunction " >>> 483 | 484 | function! rtags#getRcCmd() 485 | let cmd = g:rtagsRcCmd 486 | let cmd .= " --absolute-path " 487 | if g:rtagsExcludeSysHeaders == 1 488 | return cmd." -H " 489 | endif 490 | return cmd 491 | endfunction 492 | 493 | function! rtags#getCurrentLocation() 494 | let [lnum, col] = getpos('.')[1:2] 495 | return printf("%s:%s:%s", expand("%:p"), lnum, col) 496 | endfunction 497 | 498 | function! rtags#SymbolInfoHandler(output) 499 | echo join(a:output, "\n") 500 | endfunction 501 | 502 | function! rtags#SymbolInfo() 503 | call rtags#ExecuteThen({ '-U' : rtags#getCurrentLocation() }, [function('rtags#SymbolInfoHandler')]) 504 | endfunction 505 | 506 | function! rtags#cloneCurrentBuffer(type) 507 | if a:type == g:SAME_WINDOW 508 | return 509 | endif 510 | 511 | let [lnum, col] = getpos('.')[1:2] 512 | exec s:LOC_OPEN_OPTS[a:type]." new ".expand("%") 513 | call cursor(lnum, col) 514 | endfunction 515 | 516 | function! rtags#jumpToLocation(file, line, col) 517 | call rtags#saveLocation() 518 | return rtags#jumpToLocationInternal(a:file, a:line, a:col) 519 | endfunction 520 | 521 | function! rtags#jumpToLocationInternal(file, line, col) 522 | try 523 | if a:file != expand("%:p") 524 | exe "e ".a:file 525 | endif 526 | call cursor(a:line, a:col) 527 | return 1 528 | catch /.*/ 529 | echohl ErrorMsg 530 | echomsg v:exception 531 | echohl None 532 | return 0 533 | endtry 534 | endfunction 535 | 536 | function! rtags#JumpToHandler(results, args) 537 | let results = a:results 538 | let open_opt = a:args['open_opt'] 539 | if len(results) >= 0 && open_opt != g:SAME_WINDOW 540 | call rtags#cloneCurrentBuffer(open_opt) 541 | endif 542 | 543 | if len(results) > 1 544 | call rtags#DisplayResults(results) 545 | elseif len(results) == 1 546 | let [location; symbol_detail] = split(results[0], '\s\+') 547 | let [jump_file, lnum, col; rest] = split(location, ':') 548 | 549 | " Add location to the jumplist 550 | normal! m' 551 | if rtags#jumpToLocation(jump_file, lnum, col) 552 | normal! zz 553 | endif 554 | endif 555 | 556 | endfunction 557 | 558 | " 559 | " JumpTo(open_type, ...) 560 | " open_type - Vim command used for opening desired location. 561 | " Allowed values: 562 | " * g:SAME_WINDOW 563 | " * g:H_SPLIT 564 | " * g:V_SPLIT 565 | " * g:NEW_TAB 566 | " 567 | " a:1 - dictionary of additional arguments for 'rc' 568 | " 569 | function! rtags#JumpTo(open_opt, ...) 570 | let args = {} 571 | if a:0 > 0 572 | let args = a:1 573 | endif 574 | 575 | call extend(args, { '-f' : rtags#getCurrentLocation() }) 576 | let results = rtags#ExecuteThen(args, [[function('rtags#JumpToHandler'), { 'open_opt' : a:open_opt }]]) 577 | 578 | endfunction 579 | 580 | function! rtags#parseSourceLocation(string) 581 | let [location; symbol_detail] = split(a:string, '\s\+') 582 | let splittedLine = split(location, ':') 583 | if len(splittedLine) == 3 584 | let [jump_file, lnum, col; rest] = splittedLine 585 | " Must be a path, therefore leading / is compulsory 586 | if jump_file[0] == '/' 587 | return [jump_file, lnum, col] 588 | endif 589 | endif 590 | return ["","",""] 591 | endfunction 592 | 593 | function! rtags#saveLocation() 594 | let [lnum, col] = getpos('.')[1:2] 595 | call rtags#pushToStack([expand("%"), lnum, col]) 596 | endfunction 597 | 598 | function! rtags#pushToStack(location) 599 | let jumpListLen = len(g:rtagsJumpStack) 600 | if jumpListLen > g:rtagsJumpStackMaxSize 601 | call remove(g:rtagsJumpStack, 0) 602 | endif 603 | call add(g:rtagsJumpStack, a:location) 604 | endfunction 605 | 606 | function! rtags#JumpBack() 607 | if len(g:rtagsJumpStack) > 0 608 | let [jump_file, lnum, col] = remove(g:rtagsJumpStack, -1) 609 | call rtags#jumpToLocationInternal(jump_file, lnum, col) 610 | else 611 | echo "rtags: jump stack is empty" 612 | endif 613 | endfunction 614 | 615 | function! rtags#JumpToParentHandler(results) 616 | let results = a:results 617 | for line in results 618 | let matched = matchend(line, "^Parent: ") 619 | if matched == -1 620 | continue 621 | endif 622 | let [jump_file, lnum, col] = rtags#parseSourceLocation(line[matched:-1]) 623 | if !empty(jump_file) 624 | if a:0 > 0 625 | call rtags#cloneCurrentBuffer(a:1) 626 | endif 627 | 628 | " Add location to the jumplist 629 | normal! m' 630 | if rtags#jumpToLocation(jump_file, lnum, col) 631 | normal! zz 632 | endif 633 | return 634 | endif 635 | endfor 636 | endfunction 637 | 638 | function! rtags#JumpToParent(...) 639 | let args = { 640 | \ '-U' : rtags#getCurrentLocation(), 641 | \ '--symbol-info-include-parents' : '' } 642 | 643 | call rtags#ExecuteThen(args, [function('rtags#JumpToParentHandler')]) 644 | endfunction 645 | 646 | function! s:GetCharacterUnderCursor() 647 | return matchstr(getline('.'), '\%' . col('.') . 'c.') 648 | endfunction 649 | 650 | function! rtags#RenameSymbolUnderCursorHandler(output) 651 | let locations = rtags#ParseResults(a:output) 652 | if len(locations) > 0 653 | let newName = input("Enter new name: ") 654 | let yesToAll = 0 655 | if !empty(newName) 656 | for loc in reverse(locations) 657 | if !rtags#jumpToLocationInternal(loc.filepath, loc.lnum, loc.col) 658 | return 659 | endif 660 | normal! zv 661 | normal! zz 662 | redraw 663 | let choice = yesToAll 664 | if choice == 0 665 | let location = loc.filepath.":".loc.lnum.":".loc.col 666 | let choices = "&Yes\nYes to &All\n&No\n&Cancel" 667 | let choice = confirm("Rename symbol at ".location, choices) 668 | endif 669 | if choice == 2 670 | let choice = 1 671 | let yesToAll = 1 672 | endif 673 | if choice == 1 674 | " Special case for destructors 675 | if s:GetCharacterUnderCursor() == '~' 676 | normal! l 677 | endif 678 | exec "normal! ciw".newName."\" 679 | write! 680 | elseif choice == 4 681 | return 682 | endif 683 | endfor 684 | endif 685 | endif 686 | endfunction 687 | 688 | function! rtags#RenameSymbolUnderCursor() 689 | let args = { 690 | \ '-e' : '', 691 | \ '-r' : rtags#getCurrentLocation(), 692 | \ '--rename' : '' } 693 | 694 | call rtags#ExecuteThen(args, [function('rtags#RenameSymbolUnderCursorHandler')]) 695 | endfunction 696 | 697 | function! rtags#TempFile(job_cid) 698 | return '/tmp/neovim_async_rtags.tmp.' . getpid() . '.' . a:job_cid 699 | endfunction 700 | 701 | function! rtags#ExecuteRCAsync(args, handlers) 702 | let cmd = rtags#getRcCmd() 703 | 704 | " Give rdm unsaved file content, so that you don't have to save files 705 | " before each rc invocation. 706 | if exists('b:rtags_sent_content') 707 | let content = join(getline(1, line('$')), "\n") 708 | if b:rtags_sent_content != content 709 | let unsaved_content = content 710 | endif 711 | elseif &modified 712 | let unsaved_content = join(getline(1, line('$')), "\n") 713 | endif 714 | if exists('unsaved_content') 715 | let filename = expand("%") 716 | let output = system(printf("%s --unsaved-file=%s:%s -V %s", cmd, filename, strlen(unsaved_content), filename), unsaved_content) 717 | let b:rtags_sent_content = unsaved_content 718 | endif 719 | 720 | " prepare for the actual command invocation 721 | for [key, value] in items(a:args) 722 | let cmd .= " ".key 723 | if len(value) > 1 724 | let cmd .= " ".value 725 | endif 726 | endfor 727 | 728 | let s:callbacks = { 729 | \ 'on_exit' : function('rtags#HandleResults') 730 | \ } 731 | 732 | let s:job_cid = s:job_cid + 1 733 | " should have out+err redirection portable for various shells. 734 | if has('nvim') 735 | let cmd = cmd . ' >' . rtags#TempFile(s:job_cid) . ' 2>&1' 736 | let job = jobstart(cmd, s:callbacks) 737 | let s:jobs[job] = s:job_cid 738 | let s:result_handlers[job] = a:handlers 739 | elseif has('job') && has('channel') 740 | let l:opts = {} 741 | let l:opts.mode = 'nl' 742 | let l:opts.out_cb = {ch, data -> rtags#HandleResults(ch_info(ch).id, data, 'vim_stdout')} 743 | let l:opts.exit_cb = {ch, data -> rtags#HandleResults(ch_info(ch).id, data,'vim_exit')} 744 | let l:opts.stoponexit = 'kill' 745 | let job = job_start(cmd, l:opts) 746 | let channel = ch_info(job_getchannel(job)).id 747 | let s:result_stdout[channel] = [] 748 | let s:jobs[channel] = s:job_cid 749 | let s:result_handlers[channel] = a:handlers 750 | endif 751 | 752 | endfunction 753 | 754 | function! rtags#HandleResults(job_id, data, event) 755 | 756 | 757 | if a:event == 'vim_stdout' 758 | call add(s:result_stdout[a:job_id], a:data) 759 | elseif a:event == 'vim_exit' 760 | 761 | let job_cid = remove(s:jobs, a:job_id) 762 | let handlers = remove(s:result_handlers, a:job_id) 763 | let output = remove(s:result_stdout, a:job_id) 764 | 765 | call rtags#ExecuteHandlers(output, handlers) 766 | else 767 | let job_cid = remove(s:jobs, a:job_id) 768 | let temp_file = rtags#TempFile(job_cid) 769 | let output = readfile(temp_file) 770 | let handlers = remove(s:result_handlers, a:job_id) 771 | call rtags#ExecuteHandlers(output, handlers) 772 | execute 'silent !rm -f ' . temp_file 773 | endif 774 | 775 | endfunction 776 | 777 | function! rtags#ExecuteHandlers(output, handlers) 778 | let result = a:output 779 | for Handler in a:handlers 780 | if type(Handler) == 3 781 | let HandlerFunc = Handler[0] 782 | let args = Handler[1] 783 | call HandlerFunc(result, args) 784 | else 785 | try 786 | let result = Handler(result) 787 | catch /E706/ 788 | " If we're not returning the right type we're probably done 789 | return 790 | endtry 791 | endif 792 | endfor 793 | endfunction 794 | 795 | function! rtags#ExecuteThen(args, handlers) 796 | if s:rtagsAsync == 1 797 | call rtags#ExecuteRCAsync(a:args, a:handlers) 798 | else 799 | let result = rtags#ExecuteRC(a:args) 800 | call rtags#ExecuteHandlers(result, a:handlers) 801 | endif 802 | endfunction 803 | 804 | function! rtags#FindRefs() 805 | let args = { 806 | \ '-e' : '', 807 | \ '-r' : rtags#getCurrentLocation() } 808 | 809 | call rtags#ExecuteThen(args, [function('rtags#DisplayResults')]) 810 | endfunction 811 | 812 | function! rtags#ShowHierarchy() 813 | let args = {'--class-hierarchy' : rtags#getCurrentLocation() } 814 | 815 | call rtags#ExecuteThen(args, [function('rtags#ViewHierarchy')]) 816 | endfunction 817 | 818 | function! rtags#FindRefsCallTree() 819 | let args = { 820 | \ '--containing-function-location' : '', 821 | \ '-r' : rtags#getCurrentLocation() } 822 | 823 | call rtags#ExecuteThen(args, [function('rtags#ViewReferences')]) 824 | endfunction 825 | 826 | function! rtags#FindSuperClasses() 827 | call rtags#ExecuteThen({ '--class-hierarchy' : rtags#getCurrentLocation() }, 828 | \ [function('rtags#ExtractSuperClasses'), function('rtags#DisplayResults')]) 829 | endfunction 830 | 831 | function! rtags#FindSubClasses() 832 | let result = rtags#ExecuteThen({ '--class-hierarchy' : rtags#getCurrentLocation() }, [ 833 | \ function('rtags#ExtractSubClasses'), 834 | \ function('rtags#DisplayResults')]) 835 | endfunction 836 | 837 | function! rtags#FindVirtuals() 838 | let args = { 839 | \ '-k' : '', 840 | \ '-r' : rtags#getCurrentLocation() } 841 | 842 | call rtags#ExecuteThen(args, [function('rtags#DisplayResults')]) 843 | endfunction 844 | 845 | function! rtags#FindRefsByName(name) 846 | let args = { 847 | \ '-a' : '', 848 | \ '-e' : '', 849 | \ '-R' : a:name } 850 | 851 | call rtags#ExecuteThen(args, [function('rtags#DisplayResults')]) 852 | endfunction 853 | 854 | " case insensitive FindRefsByName 855 | function! rtags#IFindRefsByName(name) 856 | let args = { 857 | \ '-a' : '', 858 | \ '-e' : '', 859 | \ '-R' : a:name, 860 | \ '-I' : '' } 861 | 862 | call rtags#ExecuteThen(args, [function('rtags#DisplayResults')]) 863 | endfunction 864 | 865 | " Find all those references which has the name which is equal to the word 866 | " under the cursor 867 | function! rtags#FindRefsOfWordUnderCursor() 868 | let wordUnderCursor = expand("") 869 | call rtags#FindRefsByName(wordUnderCursor) 870 | endfunction 871 | 872 | """ rc -HF 873 | function! rtags#FindSymbols(pattern) 874 | let args = { 875 | \ '-a' : '', 876 | \ '-F' : a:pattern } 877 | 878 | call rtags#ExecuteThen(args, [function('rtags#DisplayResults')]) 879 | endfunction 880 | 881 | " Method for tab-completion for vim's commands 882 | function! rtags#CompleteSymbols(arg, line, pos) 883 | if len(a:arg) < g:rtagsMinCharsForCommandCompletion 884 | return [] 885 | endif 886 | call rtags#ExecuteThen({ '-S' : a:arg }, [function('filter')]) 887 | endfunction 888 | 889 | " case insensitive FindSymbol 890 | function! rtags#IFindSymbols(pattern) 891 | let args = { 892 | \ '-a' : '', 893 | \ '-I' : '', 894 | \ '-F' : a:pattern } 895 | 896 | call rtags#ExecuteThen(args, [function('rtags#DisplayResults')]) 897 | endfunction 898 | 899 | function! rtags#ProjectListHandler(output) 900 | let projects = a:output 901 | let i = 1 902 | for p in projects 903 | echo '['.i.'] '.p 904 | let i = i + 1 905 | endfor 906 | let choice = input('Choice: ') 907 | if choice > 0 && choice <= len(projects) 908 | call rtags#ProjectOpen(projects[choice-1]) 909 | endif 910 | endfunction 911 | 912 | function! rtags#ProjectList() 913 | call rtags#ExecuteThen({ '-w' : '' }, [function('rtags#ProjectListHandler')]) 914 | endfunction 915 | 916 | function! rtags#ProjectOpen(pattern) 917 | call rtags#ExecuteThen({ '-w' : a:pattern }, []) 918 | endfunction 919 | 920 | function! rtags#LoadCompilationDb(pattern) 921 | call rtags#ExecuteThen({ '-J' : a:pattern }, []) 922 | endfunction 923 | 924 | function! rtags#ProjectClose(pattern) 925 | call rtags#ExecuteThen({ '-u' : a:pattern }, []) 926 | endfunction 927 | 928 | function! rtags#PreprocessFileHandler(result) 929 | vnew 930 | call append(0, a:result) 931 | endfunction 932 | 933 | function! rtags#PreprocessFile() 934 | call rtags#ExecuteThen({ '-E' : expand("%:p") }, [function('rtags#PreprocessFileHandler')]) 935 | endfunction 936 | 937 | function! rtags#ReindexFile() 938 | call rtags#ExecuteThen({ '-V' : expand("%:p") }, []) 939 | endfunction 940 | 941 | function! rtags#FindSymbolsOfWordUnderCursor() 942 | let wordUnderCursor = expand("") 943 | call rtags#FindSymbols(wordUnderCursor) 944 | endfunction 945 | 946 | function! rtags#Diagnostics() 947 | let s:file = expand("%:p") 948 | return s:Pyeval("vimrtags.get_diagnostics()") 949 | endfunction 950 | 951 | " 952 | " This function assumes it is invoked from insert mode 953 | " 954 | function! rtags#CompleteAtCursor(wordStart, base) 955 | let flags = "--synchronous-completions -l" 956 | let file = expand("%:p") 957 | let pos = getpos('.') 958 | let line = pos[1] 959 | let col = pos[2] 960 | 961 | if index(['.', '::', '->'], a:base) != -1 962 | let col += 1 963 | endif 964 | 965 | let rcRealCmd = rtags#getRcCmd() 966 | 967 | exec "normal! \" 968 | let stdin_lines = join(getline(1, "$"), "\n").a:base 969 | let offset = len(stdin_lines) 970 | 971 | exec "startinsert!" 972 | " echomsg getline(line) 973 | " sleep 1 974 | " echomsg "DURING INVOCATION POS: ".pos[2] 975 | " sleep 1 976 | " echomsg stdin_lines 977 | " sleep 1 978 | " sed command to remove CDATA prefix and closing xml tag from rtags output 979 | let sed_cmd = "sed -e 's/.*CDATA\\[//g' | sed -e 's/.*\\/completions.*//g'" 980 | let cmd = printf("%s %s %s:%s:%s --unsaved-file=%s:%s | %s", rcRealCmd, flags, file, line, col, file, offset, sed_cmd) 981 | call rtags#Log("Command line:".cmd) 982 | 983 | let result = split(system(cmd, stdin_lines), '\n\+') 984 | " echomsg "Got ".len(result)." completions" 985 | " sleep 1 986 | call rtags#Log("-----------") 987 | "call rtags#Log(result) 988 | call rtags#Log("-----------") 989 | return result 990 | " for r in result 991 | " echo r 992 | " endfor 993 | " call rtags#DisplayResults(result) 994 | endfunction 995 | 996 | function! s:Pyeval( eval_string ) 997 | if g:rtagsPy == 'python3' 998 | return py3eval( a:eval_string ) 999 | else 1000 | return pyeval( a:eval_string ) 1001 | endif 1002 | endfunction 1003 | 1004 | function! s:RcExecuteJobCompletion() 1005 | call rtags#SetJobStateFinish() 1006 | if ! empty(b:rtags_state['stdout']) && mode() == 'i' 1007 | call feedkeys("\\", "t") 1008 | else 1009 | call RtagsCompleteFunc(0, RtagsCompleteFunc(1, 0)) 1010 | endif 1011 | endfunction 1012 | 1013 | "{{{ RcExecuteJobHandler 1014 | "Handles stdout/stderr/exit events, and stores the stdout/stderr received from the shells. 1015 | function! RcExecuteJobHandler(job_id, data, event) 1016 | if a:event == 'exit' 1017 | call s:RcExecuteJobCompletion() 1018 | else 1019 | call rtags#AddJobStandard(a:event, a:data) 1020 | endif 1021 | endf 1022 | 1023 | function! rtags#SetJobStateFinish() 1024 | let b:rtags_state['state'] = 'finish' 1025 | endfunction 1026 | 1027 | function! rtags#AddJobStandard(eventType, data) 1028 | call add(b:rtags_state[a:eventType], a:data) 1029 | endfunction 1030 | 1031 | function! rtags#SetJobStateReady() 1032 | let b:rtags_state['state'] = 'ready' 1033 | endfunction 1034 | 1035 | function! rtags#IsJobStateReady() 1036 | if b:rtags_state['state'] == 'ready' 1037 | return 1 1038 | endif 1039 | return 0 1040 | endfunction 1041 | 1042 | function! rtags#IsJobStateBusy() 1043 | if b:rtags_state['state'] == 'busy' 1044 | return 1 1045 | endif 1046 | return 0 1047 | endfunction 1048 | 1049 | function! rtags#IsJobStateFinish() 1050 | if b:rtags_state['state'] == 'finish' 1051 | return 1 1052 | endif 1053 | return 0 1054 | endfunction 1055 | 1056 | 1057 | function! rtags#SetStartJobState() 1058 | let b:rtags_state['state'] = 'busy' 1059 | let b:rtags_state['stdout'] = [] 1060 | let b:rtags_state['stderr'] = [] 1061 | endfunction 1062 | 1063 | function! rtags#GetJobStdOutput() 1064 | return b:rtags_state['stdout'] 1065 | endfunction 1066 | 1067 | function! rtags#ExistsAndCreateRtagsState() 1068 | if !exists('b:rtags_state') 1069 | let b:rtags_state = { 'state': 'ready', 'stdout': [], 'stderr': [] } 1070 | endif 1071 | endfunction 1072 | 1073 | "{{{ s:RcExecute 1074 | " Execute clang binary to generate completions and diagnostics. 1075 | " Global variable: 1076 | " Buffer vars: 1077 | " b:rtags_state => { 1078 | " 'state' : // updated to 'ready' in sync mode 1079 | " 'stdout': // updated in sync mode 1080 | " 'stderr': // updated in sync mode 1081 | " } 1082 | " 1083 | " b:clang_execute_job_id // used to stop previous job 1084 | " 1085 | " @root Clang root, project directory 1086 | " @line Line to complete 1087 | " @col Column to complete 1088 | " @return [completion, diagnostics] 1089 | function! s:RcJobExecute(offset, line, col) 1090 | 1091 | let file = expand("%:p") 1092 | let l:cmd = printf("rc --absolute-path --synchronous-completions -l %s:%s:%s --unsaved-file=%s:%s", file, a:line, a:col, file, a:offset) 1093 | 1094 | if exists('b:rc_execute_job_id') && job_status(b:rc_execute_job_id) == 'run' 1095 | try 1096 | call job_stop(b:rc_execute_job_id, 'term') 1097 | unlet b:rc_execute_job_id 1098 | catch 1099 | " Ignore 1100 | endtry 1101 | endif 1102 | 1103 | call rtags#SetStartJobState() 1104 | 1105 | let l:argv = l:cmd 1106 | let l:opts = {} 1107 | let l:opts.mode = 'nl' 1108 | let l:opts.in_io = 'buffer' 1109 | let l:opts.in_buf = bufnr('%') 1110 | let l:opts.out_cb = {ch, data -> RcExecuteJobHandler(ch, data, 'stdout')} 1111 | let l:opts.err_cb = {ch, data -> RcExecuteJobHandler(ch, data, 'stderr')} 1112 | let l:opts.exit_cb = {ch, data -> RcExecuteJobHandler(ch, data, 'exit')} 1113 | let l:opts.stoponexit = 'kill' 1114 | 1115 | let l:jobid = job_start(l:argv, l:opts) 1116 | let b:rc_execute_job_id = l:jobid 1117 | 1118 | if job_status(l:jobid) != 'run' 1119 | unlet b:rc_execute_job_id 1120 | endif 1121 | 1122 | endf 1123 | 1124 | """ 1125 | " Temporarily the way this function works is: 1126 | " - completeion invoked on 1127 | " object.meth* 1128 | " , where * is cursor position 1129 | " - find the position of a dot/arrow 1130 | " - invoke completion through rc 1131 | " - filter out options that start with meth (in this case). 1132 | " - show completion options 1133 | " 1134 | " Reason: rtags returns all options regardless of already type method name 1135 | " portion 1136 | """ 1137 | 1138 | function! RtagsCompleteFunc(findstart, base) 1139 | if s:rtagsAsync == 1 && !has('nvim') 1140 | return s:RtagsCompleteFunc(a:findstart, a:base, 1) 1141 | else 1142 | return s:RtagsCompleteFunc(a:findstart, a:base, 0) 1143 | endif 1144 | endfunction 1145 | 1146 | function! s:RtagsCompleteFunc(findstart, base, async) 1147 | call rtags#Log("RtagsCompleteFunc: [".a:findstart."], [".a:base."]") 1148 | 1149 | if a:findstart 1150 | let s:line = getline('.') 1151 | let s:start = col('.') - 2 1152 | return s:Pyeval("vimrtags.get_identifier_beginning()") 1153 | else 1154 | let pos = getpos('.') 1155 | let s:file = expand("%:p") 1156 | let s:line = str2nr(pos[1]) 1157 | let s:col = str2nr(pos[2]) + len(a:base) 1158 | let s:prefix = a:base 1159 | return s:Pyeval("vimrtags.send_completion_request()") 1160 | endif 1161 | endfunction 1162 | 1163 | if &completefunc == "" 1164 | set completefunc=RtagsCompleteFunc 1165 | endif 1166 | 1167 | " Helpers to access script locals for unit testing {{{ 1168 | function! s:get_SID() 1169 | return matchstr(expand(''), '\d\+_') 1170 | endfunction 1171 | let s:SID = s:get_SID() 1172 | delfunction s:get_SID 1173 | 1174 | function! rtags#__context__() 1175 | return { 'sid': s:SID, 'scope': s: } 1176 | endfunction 1177 | "}}} 1178 | 1179 | command! -nargs=1 -complete=customlist,rtags#CompleteSymbols RtagsFindSymbols call rtags#FindSymbols() 1180 | command! -nargs=1 -complete=customlist,rtags#CompleteSymbols RtagsFindRefsByName call rtags#FindRefsByName() 1181 | 1182 | command! -nargs=1 -complete=customlist,rtags#CompleteSymbols RtagsIFindSymbols call rtags#IFindSymbols() 1183 | command! -nargs=1 -complete=customlist,rtags#CompleteSymbols RtagsIFindRefsByName call rtags#IFindRefsByName() 1184 | 1185 | command! -nargs=1 -complete=dir RtagsLoadCompilationDb call rtags#LoadCompilationDb() 1186 | 1187 | " The most commonly used find operation 1188 | command! -nargs=1 -complete=customlist,rtags#CompleteSymbols Rtag RtagsIFindSymbols 1189 | 1190 | -------------------------------------------------------------------------------- /plugin/vimrtags.py: -------------------------------------------------------------------------------- 1 | import vim 2 | import json 3 | import subprocess 4 | import io 5 | import os 6 | import sys 7 | import tempfile 8 | 9 | import logging 10 | tempdir = tempfile.gettempdir() 11 | logging.basicConfig(filename='%s/vim-rtags-python.log' % tempdir,level=logging.DEBUG) 12 | 13 | def get_identifier_beginning(): 14 | line = vim.eval('s:line') 15 | column = int(vim.eval('s:start')) 16 | 17 | logging.debug(line) 18 | logging.debug(column) 19 | 20 | while column >= 0 and (line[column].isalnum() or line[column] == '_'): 21 | column -= 1 22 | 23 | return column + 1 24 | 25 | def run_rc_command(arguments, content = None): 26 | rc_cmd = os.path.expanduser(vim.eval('g:rtagsRcCmd')) 27 | cmdline = rc_cmd + " " + arguments 28 | 29 | encoding = 'utf-8' 30 | out = None 31 | err = None 32 | if sys.version_info.major == 3 and sys.version_info.minor >= 5: 33 | r = subprocess.run( 34 | cmdline.split(), 35 | input = content.encode(encoding), 36 | stdout = subprocess.PIPE, 37 | stderr = subprocess.PIPE, 38 | ) 39 | out, err = r.stdout, r.stderr 40 | if not out is None: 41 | out = out.decode(encoding) 42 | if not err is None: 43 | err = err.decode(encoding) 44 | 45 | elif sys.version_info.major == 3 and sys.version_info.minor < 5: 46 | r = subprocess.Popen( 47 | cmdline.split(), 48 | bufsize=0, 49 | stdout=subprocess.PIPE, 50 | stdin=subprocess.PIPE, 51 | stderr=subprocess.STDOUT 52 | ) 53 | out, err = r.communicate(input=content.encode(encoding)) 54 | if not out is None: 55 | out = out.decode(encoding) 56 | if not err is None: 57 | err = err.decode(encoding) 58 | else: 59 | r = subprocess.Popen( 60 | cmdline.split(), 61 | stdout=subprocess.PIPE, 62 | stdin=subprocess.PIPE, 63 | stderr=subprocess.STDOUT 64 | ) 65 | out, err = r.communicate(input=content) 66 | 67 | if r.returncode != 0: 68 | logging.debug(err) 69 | return None 70 | 71 | return out 72 | 73 | 74 | def get_rtags_variable(name): 75 | return vim.eval('g:rtags' + name) 76 | 77 | def parse_completion_result(data): 78 | result = json.loads(data) 79 | logging.debug(result) 80 | completions = [] 81 | 82 | for c in result['completions']: 83 | k = c['kind'] 84 | kind = '' 85 | if k == 'FunctionDecl' or k == 'FunctionTemplate': 86 | kind = 'f' 87 | elif k == 'CXXMethod' or k == 'CXXConstructor': 88 | kind = 'm' 89 | elif k == 'VarDecl': 90 | kind = 'v' 91 | elif k == 'macro definition': 92 | kind = 'd' 93 | elif k == 'EnumDecl': 94 | kind = 'e' 95 | elif k == 'TypedefDecl' or k == 'StructDecl' or k == 'EnumConstantDecl': 96 | kind = 't' 97 | 98 | match = {'menu': c['completion'], 'word': c['completion'], 'kind': kind} 99 | completions.append(match) 100 | 101 | return completions 102 | 103 | def send_completion_request(): 104 | filename = vim.eval('s:file') 105 | line = int(vim.eval('s:line')) 106 | column = int(vim.eval('s:col')) 107 | prefix = vim.eval('s:prefix') 108 | 109 | for buffer in vim.buffers: 110 | logging.debug(buffer.name) 111 | if buffer.name == filename: 112 | lines = [x for x in buffer] 113 | content = '\n'.join(lines[:line - 1] + [lines[line - 1] + prefix] + lines[line:]) 114 | 115 | cmd = ('--synchronous-completions -l %s:%d:%d --unsaved-file=%s:%d --json' 116 | % (filename, line, column, filename, len(content))) 117 | if len(prefix) > 0: 118 | cmd += ' --code-complete-prefix %s' % prefix 119 | 120 | content = run_rc_command(cmd, content) 121 | if content == None: 122 | return None 123 | 124 | return parse_completion_result(content) 125 | 126 | assert False 127 | 128 | def display_locations(errors, buffer): 129 | if len(errors) == 0: 130 | return 131 | 132 | error_data = json.dumps(errors) 133 | max_height = int(get_rtags_variable('MaxSearchResultWindowHeight')) 134 | height = min(max_height, len(errors)) 135 | 136 | if int(get_rtags_variable('UseLocationList')) == 1: 137 | vim.eval('setloclist(%d, %s)' % (buffer.number, error_data)) 138 | vim.command('lopen %d' % height) 139 | else: 140 | vim.eval('setqflist(%s)' % error_data) 141 | vim.command('copen %d' % height) 142 | 143 | def display_diagnostics_results(data, buffer): 144 | data = json.loads(data) 145 | logging.debug(data) 146 | 147 | check_style = data['checkStyle'] 148 | vim.command('sign unplace *') 149 | 150 | # There are no errors 151 | if check_style == None: 152 | return 153 | 154 | filename, errors = list(check_style.items())[0] 155 | quickfix_errors = [] 156 | 157 | vim.command('sign define fixit text=F texthl=FixIt') 158 | vim.command('sign define warning text=W texthl=Warning') 159 | vim.command('sign define error text=E texthl=Error') 160 | 161 | for i, e in enumerate(errors): 162 | if e['type'] == 'skipped': 163 | continue 164 | 165 | # strip error prefix 166 | s = ' Issue: ' 167 | index = e['message'].find(s) 168 | if index != -1: 169 | e['message'] = e['message'][index + len(s):] 170 | error_type = 'E' if e['type'] == 'error' else 'W' 171 | quickfix_errors.append({'lnum': e['line'], 'col': e['column'], 172 | 'nr': i, 'text': e['message'], 'filename': filename, 173 | 'type': error_type}) 174 | cmd = 'sign place %d line=%s name=%s file=%s' % (i + 1, e['line'], e['type'], filename) 175 | vim.command(cmd) 176 | 177 | display_locations(quickfix_errors, buffer) 178 | 179 | def get_diagnostics(): 180 | filename = vim.eval('s:file') 181 | 182 | for buffer in vim.buffers: 183 | if buffer.name == filename: 184 | is_modified = bool(int((vim.eval('getbufvar(%d, "&mod")' % buffer.number)))) 185 | cmd = '--diagnose %s --synchronous-diagnostics --json' % filename 186 | 187 | content = '' 188 | if is_modified: 189 | content = '\n'.join([x for x in buffer]) 190 | cmd += ' --unsaved-file=%s:%d' % (filename, len(content)) 191 | 192 | content = run_rc_command(cmd, content) 193 | if content == None: 194 | return None 195 | 196 | display_diagnostics_results(content, buffer) 197 | 198 | return 0 199 | -------------------------------------------------------------------------------- /tests/test_rtags.vim: -------------------------------------------------------------------------------- 1 | let s:tc = unittest#testcase#new("rtagsUnitTests", rtags#__context__()) 2 | 3 | function! s:tc.test_parsingRcOutput() 4 | let line = "/path/to/file/Src.cpp:157:5: init ();" 5 | let locations = rtags#ParseResults([line]) 6 | call self.assert_equal(1, len(locations)) 7 | 8 | let location = locations[0] 9 | call self.assert_equal("/path/to/file/Src.cpp", location.filename) 10 | call self.assert_equal(157, location.lnum) 11 | call self.assert_equal(5, location.col) 12 | call self.assert_equal(0, location.vcol) 13 | " call self.assert_equal(0, location.nr) 14 | call self.assert_equal('init ();', location.text) 15 | call self.assert_equal('ref', location.type) 16 | endfunction 17 | 18 | function! s:tc.test_parseSourceLocation() 19 | let line = "/path/to/file/Src.cpp:157:5: init ();" 20 | let [file, lnum, col] = rtags#parseSourceLocation(line) 21 | call self.assert_equal("/path/to/file/Src.cpp", file) 22 | call self.assert_equal(157, lnum) 23 | call self.assert_equal(5, col) 24 | endfunction 25 | 26 | function! s:tc.test_parseSourceLocation_should_return_empty_file_when_input_does_not_have_source_location() 27 | let line = "bad input -- no source location" 28 | let [file, lnum, col] = rtags#parseSourceLocation(line) 29 | call self.assert_equal("", file) 30 | endfunction 31 | 32 | function! s:tc.test_parseSourceLocation_should_return_empty_file_when_there_is_no_leading_slash() 33 | let line = "path/to/file/Src.cpp:157:5: init ();" 34 | let [file, lnum, col] = rtags#parseSourceLocation(line) 35 | call self.assert_equal("", file) 36 | endfunction 37 | 38 | let s:classHierarchy = [ 39 | \ "Superclasses:", 40 | \ " class Foo src/Foo.h:56:7: class Foo : public Bar {", 41 | \ " class Bar src/Bar.h:46:7: class Bar : public Bas {", 42 | \ " class Bas src/Bas.h:47:7: class Bas {", 43 | \ "Subclasses:", 44 | \ " class Foo src/Foo.h:56:7: class Foo : public Bar {", 45 | \ " class Foo2 src/Foo2.h:56:7: class Foo2 : public Foo {", 46 | \ " class Foo3 src/Foo3.h:56:7: class Foo3 : public Foo {" ] 47 | 48 | function! s:tc.test_extractSuperClasses() 49 | let lines = rtags#ExtractSuperClasses(s:classHierarchy) 50 | call self.assert_equal(len(lines), 3) 51 | call self.assert_equal(lines[0], "src/Foo.h:56:7: class Foo : public Bar {") 52 | call self.assert_equal(lines[1], "src/Bar.h:46:7: class Bar : public Bas {") 53 | call self.assert_equal(lines[2], "src/Bas.h:47:7: class Bas {") 54 | endfunction 55 | 56 | function! s:tc.test_extractSubClasses() 57 | let lines = rtags#ExtractSubClasses(s:classHierarchy) 58 | call self.assert_equal(len(lines), 3) 59 | call self.assert_equal(lines[0], "src/Foo.h:56:7: class Foo : public Bar {") 60 | call self.assert_equal(lines[1], "src/Foo2.h:56:7: class Foo2 : public Foo {") 61 | call self.assert_equal(lines[2], "src/Foo3.h:56:7: class Foo3 : public Foo {") 62 | endfunction 63 | 64 | --------------------------------------------------------------------------------