├── screencast.gif ├── README.md ├── LICENSE └── plugin └── flow_outline.vim /screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreypopp/vim-flow-outline/HEAD/screencast.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-flow-outline 2 | 3 | Outline view for a JS module based on CtrlP and FlowType 4 | 5 | ![screencast][screencast] 6 | 7 | ## Installation 8 | 9 | If you use [vim-plug][]: 10 | 11 | Plug 'ctrlpvim/ctrlp.vim' 12 | Plug 'flowtype/vim-flow' 13 | Plug 'andreypopp/vim-flow-outline' 14 | 15 | Then add a mapping: 16 | 17 | au FileType javascript nnoremap :FlowOutline 18 | 19 | [vim-plug]: https://github.com/junegunn/vim-plug 20 | [screencast]: ./screencast.gif 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For vim-flow software 4 | 5 | Copyright (c) 2013-2016, Facebook, Inc. All rights reserved. 6 | Copyright (c) 2016-present, Andrey Popp <8mayday@gmail.com> 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | * Neither the name Facebook nor the names of its contributors may be used to 19 | endorse or promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /plugin/flow_outline.vim: -------------------------------------------------------------------------------- 1 | let s:fzf_loaded = exists('g:fzf#vim#default_layout') 2 | let s:ctrlp_loaded = exists('g:loaded_ctrlp') 3 | 4 | if !s:ctrlp_loaded && !s:fzf_loaded 5 | finish 6 | endif 7 | 8 | if s:fzf_loaded 9 | 10 | " ------------------------------------------------------------------ 11 | " FZF harness 12 | " ------------------------------------------------------------------ 13 | 14 | let s:TYPE = {'dict': type({}), 'funcref': type(function('call'))} 15 | 16 | function! s:get_color(attr, ...) 17 | for group in a:000 18 | let code = synIDattr(synIDtrans(hlID(group)), a:attr, 'cterm') 19 | if code =~ '^[0-9]\+$' 20 | return code 21 | endif 22 | endfor 23 | return '' 24 | endfunction 25 | 26 | function! s:defaults() 27 | let rules = copy(get(g:, 'fzf_colors', {})) 28 | let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',') 29 | return empty(colors) ? '' : ('--color='.colors) 30 | endfunction 31 | 32 | function! s:wrap(name, opts, bang) 33 | " fzf#wrap does not append --expect if sink or sink* is found 34 | let opts = copy(a:opts) 35 | if get(opts, 'options', '') !~ '--expect' && has_key(opts, 'sink*') 36 | let Sink = remove(opts, 'sink*') 37 | let wrapped = fzf#wrap(a:name, opts, a:bang) 38 | let wrapped['sink*'] = Sink 39 | else 40 | let wrapped = fzf#wrap(a:name, opts, a:bang) 41 | endif 42 | return wrapped 43 | endfunction 44 | 45 | function! s:fzf(name, opts, extra) 46 | let [extra, bang] = [{}, 0] 47 | if len(a:extra) <= 1 48 | let first = get(a:extra, 0, 0) 49 | if type(first) == s:TYPE.dict 50 | let extra = first 51 | else 52 | let bang = first 53 | endif 54 | elseif len(a:extra) == 2 55 | let [extra, bang] = a:extra 56 | else 57 | throw 'invalid number of arguments' 58 | endif 59 | 60 | let eopts = has_key(extra, 'options') ? remove(extra, 'options') : '' 61 | let merged = extend(copy(a:opts), extra) 62 | let merged.options = join(filter([s:defaults(), get(merged, 'options', ''), eopts], '!empty(v:val)')) 63 | return fzf#run(s:wrap(a:name, merged, bang)) 64 | endfunction 65 | endif 66 | 67 | python << EOF 68 | import collections 69 | import re 70 | 71 | vim_flow_outline_item = collections.namedtuple('vim_flow_outline_item', [ 72 | 'line', 'loc', 'prefix', 'kind' 73 | ]) 74 | 75 | vim_flow_outline_find_loc = re.compile(r'\((\d+):(\d+)\)$') 76 | 77 | def vim_flow_outline_process(node): 78 | 79 | def add(kind, loc, prefix, *line): 80 | outline.append(vim_flow_outline_item(list(line), loc, prefix, kind)) 81 | 82 | def process(node, prefix=[]): 83 | if node['type'] == 'ImportDeclaration': 84 | kind = 'import type' if node['importKind'] == 'type' else 'import' 85 | for spec in node['specifiers']: 86 | if spec['type'] == 'ImportDefaultSpecifier': 87 | name = '%s from ...' % spec['local']['name'] 88 | add('import', spec['loc'], prefix, kind, name) 89 | elif spec['type'] == 'ImportSpecifier': 90 | name = '{%s} from ...' % spec['local']['name'] 91 | add('import', spec['loc'], prefix, kind, name) 92 | elif node['type'] == 'TypeAlias': 93 | add('type alias', node['loc'], prefix, 'type', node['id']['name']) 94 | elif node['type'] == 'ClassDeclaration': 95 | kind = 'class' 96 | name = node['id']['name'] 97 | add('class', node['loc'], prefix, 'class', name + ' {...}') 98 | for item in node['body']['body']: 99 | process(item, prefix=prefix + ['class', name]) 100 | elif node['type'] == 'MethodDefinition': 101 | name = node['key']['name'] + '(...)' 102 | if node['static']: 103 | name = 'static ' + name 104 | add('method', node['key']['loc'], prefix, name) 105 | elif node['type'] == 'FunctionDeclaration': 106 | if 'name' in node['id']: 107 | add('function', node['loc'], prefix, 'function', node['id']['name'] + '(...)') 108 | elif node['type'] == 'VariableDeclaration': 109 | for dec in node['declarations']: 110 | if 'name' in dec['id']: 111 | add('binding', node['loc'], prefix, node['kind'], dec['id']['name'] + ' = ...') 112 | elif node['type'] == 'ExportNamedDeclaration': 113 | if node['declaration']: 114 | process(node['declaration'], prefix=prefix + ['export']) 115 | 116 | outline = [] 117 | 118 | for item in node['body']: 119 | process(item) 120 | 121 | return outline 122 | 123 | def vim_flow_outline_fortmat_loc(loc): 124 | return '(%d:%d)' % ( 125 | loc['start']['line'], 126 | loc['start']['column'] + 1) 127 | 128 | EOF 129 | 130 | let s:flow_from = '--from vim' 131 | 132 | " Call wrapper for flow. 133 | " Borrowed from flowtype/vim-flow. 134 | function! FlowClientCall(cmd, suffix) 135 | " Invoke typechecker. 136 | " We also concatenate with the empty string because otherwise 137 | " cgetexpr complains about not having a String argument, even though 138 | " type(flow_result) == 1. 139 | let command = g:flow#flowpath.' '.a:cmd.' '.s:flow_from.' '.a:suffix 140 | 141 | let flow_result = system(command) 142 | 143 | " Handle the server still initializing 144 | if v:shell_error == 1 145 | echohl WarningMsg 146 | echomsg 'Flow server is still initializing...' 147 | echohl None 148 | cclose 149 | return 0 150 | endif 151 | 152 | " Handle timeout 153 | if v:shell_error == 3 154 | echohl WarningMsg 155 | echomsg 'Flow timed out, please try again!' 156 | echohl None 157 | cclose 158 | return 0 159 | endif 160 | 161 | return flow_result 162 | endfunction 163 | 164 | function! flow_outline#get_outline(filename) 165 | let l:outline = [] 166 | let l:winwidth = winwidth(0) 167 | let l:res = FlowClientCall('ast ' . a:filename, '2> /dev/null') 168 | python << EOF 169 | import vim 170 | import json 171 | 172 | width = int(vim.eval('winwidth'), 10) 173 | node = json.loads(vim.eval('res')) 174 | 175 | for item in vim_flow_outline_process(node): 176 | line = ' '.join(item.prefix + item.line) 177 | loc = vim_flow_outline_fortmat_loc(item.loc) 178 | space = ''.join(' ' for s in range(width - len(line) - len(loc) - 4)) 179 | vim.command("call add(l:outline, '%s%s%s')" % (line, space, loc)) 180 | 181 | EOF 182 | return l:outline 183 | endfunction 184 | 185 | function! flow_outline#accept(value) 186 | python << EOF 187 | import vim 188 | import re 189 | value = vim.eval('a:value') 190 | match = vim_flow_outline_find_loc.search(value) 191 | if match is not None: 192 | [line, column] = match.groups() 193 | vim.command('normal %sG%s|' % (line, column)) 194 | vim.command('normal zz') 195 | EOF 196 | endfunction 197 | 198 | if s:ctrlp_loaded 199 | 200 | function! flow_outline#ctrlp_accept(mode, value) 201 | call ctrlp#exit() 202 | call flow_outline#accept(a:value) 203 | endfunction 204 | 205 | function! flow_outline#ctrlp_id() 206 | retu s:id 207 | endfunction 208 | 209 | "" CtrlP outline 210 | cal add(g:ctrlp_ext_vars, { 211 | \ 'init': 'flow_outline#get_outline(s:crfile)', 212 | \ 'accept': 'flow_outline#accept', 213 | \ 'lname': 'outline', 214 | \ 'sname': 'ml', 215 | \ 'type': 'tabs', 216 | \ 'sort': 0, 217 | \ 'nolim': 1, 218 | \ }) 219 | 220 | let s:id = g:ctrlp_builtins + len(g:ctrlp_ext_vars) 221 | 222 | elseif s:fzf_loaded 223 | 224 | function! flow_outline#fzf_accept(value) 225 | call flow_outline#accept(a:value) 226 | endfunction 227 | 228 | endif 229 | 230 | function! flow_outline#Outline(...) 231 | if s:ctrlp_loaded 232 | call ctrlp#init(flow_outline#ctrlp_id()) 233 | elseif s:fzf_loaded 234 | let l:outline = flow_outline#get_outline(bufname('%')) 235 | return s:fzf('fzf_merlin_outline', { 236 | \ 'source': l:outline, 237 | \ 'sink': function('g:flow_outline#fzf_accept'), 238 | \ 'options': '+m -x --tiebreak=index --header-lines=0 --ansi -d "\t" -n 2,1..2 --prompt="FlowOutline> "', 239 | \}, a:000) 240 | endif 241 | endfunction 242 | 243 | " Commands 244 | command! FlowOutline call flow_outline#Outline() 245 | --------------------------------------------------------------------------------