├── LICENSE ├── README.md ├── autoload ├── fin.vim ├── fin │ ├── internal │ │ ├── args.vim │ │ ├── command │ │ │ └── fin.vim │ │ ├── complete.vim │ │ ├── context.vim │ │ └── filter.vim │ ├── matcher │ │ ├── all.vim │ │ └── fuzzy.vim │ └── util.vim └── vital │ ├── _fin.vim │ ├── _fin │ ├── App │ │ └── Args.vim │ └── Config.vim │ ├── fin.vim │ └── fin.vital ├── doc ├── .gitignore └── fin.txt ├── ftplugin └── fin.vim ├── plugin └── fin.vim └── syntax └── fin.vim /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Alisue, hashnote.net 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🐟 fin.vim 2 | 3 | ![Support Vim 8.1 or above](https://img.shields.io/badge/support-Vim%208.1%20or%20above-yellowgreen.svg) 4 | ![Support Neovim 0.4 or above](https://img.shields.io/badge/support-Neovim%200.4%20or%20above-yellowgreen.svg) 5 | [![Powered by vital.vim](https://img.shields.io/badge/powered%20by-vital.vim-80273f.svg)](https://github.com/vim-jp/vital.vim) 6 | [![Powered by vital-Whisky](https://img.shields.io/badge/powered%20by-vital--Whisky-80273f.svg)](https://github.com/lambdalisue/vital-Whisky) 7 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 8 | [![Doc](https://img.shields.io/badge/doc-%3Ah%20fin-orange.svg)](doc/fin.txt) 9 | 10 | Fin is a plugin to filter buffer content in-place without modification. 11 | It's written in pure Vim script. 12 | 13 | **UNDER DEVELOPMENT: It is in alpha stage. Any changes will be applied without any announcement.** 14 | 15 | ## Installation 16 | 17 | fin.vim has no extra dependencies so use your favorite Vim plugin manager. 18 | 19 | ## Usage 20 | 21 | ``` 22 | :Fin 23 | ``` 24 | 25 | Or use with `{after}` like 26 | 27 | ``` 28 | :botright copen | Fin -after=\ | cclose 29 | ``` 30 | 31 | ## License 32 | 33 | The code in fin.vim follows MIT license texted in [LICENSE](./LICENSE). 34 | Contributors need to agree that any modifications sent in this repository follow the license. 35 | -------------------------------------------------------------------------------- /autoload/fin.vim: -------------------------------------------------------------------------------- 1 | let s:root = expand(':p:h') 2 | let s:Config = vital#fin#import('Config') 3 | 4 | " Define Public variables 5 | call s:Config.config(expand(':p'), { 6 | \ 'prompt': '> ', 7 | \ 'interval': 50, 8 | \ 'wrap_around': 1, 9 | \ 'matcher': 'all', 10 | \ 'matchers': ['all', 'fuzzy'], 11 | \}) 12 | 13 | function! fin#version() abort 14 | if !executable('git') 15 | echohl ErrorMsg 16 | echo '[fin] "git" is not executable' 17 | echohl None 18 | return 19 | endif 20 | let r = system(printf('git -C %s describe --tags --always --dirty', s:root)) 21 | echo printf('[fin] %s', r) 22 | endfunction 23 | -------------------------------------------------------------------------------- /autoload/fin/internal/args.vim: -------------------------------------------------------------------------------- 1 | let s:Args = vital#fin#import('App.Args') 2 | 3 | function! fin#internal#args#set(args, name, value) abort 4 | return s:Args.set(a:args, a:name, a:value) 5 | endfunction 6 | 7 | function! fin#internal#args#pop(args, name, default) abort 8 | return s:Args.pop(a:args, a:name, a:default) 9 | endfunction 10 | 11 | function! fin#internal#args#throw_if_dirty(args) abort 12 | return s:Args.throw_if_dirty(a:args, '[fin] ') 13 | endfunction 14 | -------------------------------------------------------------------------------- /autoload/fin/internal/command/fin.vim: -------------------------------------------------------------------------------- 1 | function! fin#internal#command#fin#command(mods, fargs) abort 2 | try 3 | let after = fin#internal#args#pop(a:fargs, 'after', '') 4 | let after_noremap = fin#internal#args#pop(a:fargs, 'After', '') 5 | let cancel = fin#internal#args#pop(a:fargs, 'cancel', '') 6 | let cancel_noremap = fin#internal#args#pop(a:fargs, 'Cancel', '') 7 | let matcher = fin#internal#args#pop(a:fargs, 'matcher', g:fin#matcher) 8 | 9 | if len(a:fargs) > 1 10 | \ || type(after) isnot# v:t_string 11 | \ || type(after_noremap) isnot# v:t_string 12 | \ || type(cancel) isnot# v:t_string 13 | \ || type(cancel_noremap) isnot# v:t_string 14 | \ || type(matcher) isnot# v:t_string 15 | \ || (!empty(after) && !empty(after_noremap)) 16 | \ || (!empty(cancel) && !empty(cancel_noremap)) 17 | throw 'Usage: Fin [-after|-After={after}] [-cancel|-Cancel={cancel}] [-matcher={matcher}] [{query}]' 18 | endif 19 | 20 | " Does all options are handled? 21 | call fin#internal#args#throw_if_dirty(a:fargs) 22 | 23 | let query = get(a:fargs, 0, '') 24 | let ctx = fin#internal#context#new({ 25 | \ 'query': query, 26 | \ 'matcher': matcher, 27 | \}) 28 | let index = fin#internal#filter#start(ctx) 29 | if index isnot# -1 30 | call cursor(index + 1, 1, 0) 31 | if !empty(after) 32 | call feedkeys(eval(printf('"%s"', after)), 'x') 33 | elseif !empty(after_noremap) 34 | call feedkeys(eval(printf('"%s"', after_noremap)), 'nx') 35 | else 36 | call feedkeys('zvzz', 'nx') 37 | endif 38 | else 39 | if !empty(cancel) 40 | call feedkeys(eval(printf('"%s"', cancel)), 'x') 41 | elseif !empty(cancel_noremap) 42 | call feedkeys(eval(printf('"%s"', cancel_noremap)), 'nx') 43 | endif 44 | endif 45 | catch 46 | echohl ErrorMsg 47 | echomsg v:exception 48 | echohl None 49 | endtry 50 | endfunction 51 | 52 | function! fin#internal#command#fin#complete(arglead, cmdline, cursorpos) abort 53 | let pattern = '^' . a:arglead 54 | if a:arglead =~# '^-matcher=' 55 | let candidates = map(copy(g:fin#matchers), { _, v -> printf('-matcher=%s', v) }) 56 | return filter(candidates, { _, v -> v =~# pattern }) 57 | elseif a:arglead =~# '^-' 58 | let candidates = ['-after=', '-After=', '-cancel=', '-Cancel=', '-matcher='] 59 | return filter(candidates, { _, v -> v =~# pattern }) 60 | endif 61 | return [] 62 | endfunction 63 | -------------------------------------------------------------------------------- /autoload/fin/internal/complete.vim: -------------------------------------------------------------------------------- 1 | let s:modes = [ 2 | \ 'm', 3 | \ 'n', 4 | \ 't', 5 | \ 'i', 6 | \ 'x', 7 | \ '!', 8 | \] 9 | 10 | let s:options = { 11 | \ 'Fern': [ 12 | \ '-matcher=', 13 | \ '-matchers=', 14 | \ '-feedkeys=', 15 | \ ], 16 | \ 'FernFocus': [ 17 | \ '-mode', 18 | \ ] 19 | \} 20 | 21 | function! fin#internal#complete#modes(arglead, cmdline, cursorpos) abort 22 | let pattern = '^' . a:arglead 23 | let candidates = map(copy(s:modes), { _, v -> printf('-mode=%s', v) }) 24 | return filter(candidates, { _, v -> v =~# pattern }) 25 | endfunction 26 | 27 | function! fern#internal#complete#options(arglead, cmdline, cursorpos) abort 28 | let pattern = '^' . a:arglead 29 | let command = matchstr(a:cmdline, '^\w\+') 30 | let candidates = get(s:options, command, []) 31 | return filter(copy(candidates), { _, v -> v =~# pattern }) 32 | endfunction 33 | 34 | function! fern#internal#complete#url(arglead, cmdline, cursorpos) abort 35 | let scheme = matchstr(a:arglead, '^[^:]\+\ze://') 36 | if empty(scheme) 37 | return map(getcompletion(a:arglead, 'dir'), { -> escape(v:val, ' ') }) 38 | endif 39 | let rs = fern#internal#scheme#complete_url(scheme, a:arglead, a:cmdline, a:cursorpos) 40 | return rs is# v:null ? [printf('%s:///', scheme)] : rs 41 | endfunction 42 | 43 | function! fern#internal#complete#reveal(arglead, cmdline, cursorpos) abort 44 | let scheme = matchstr(a:cmdline, '\<[^ :]\+\ze://') 45 | if empty(scheme) 46 | let rs = getcompletion(matchstr(a:arglead, '^-reveal=\zs.*'), 'file') 47 | call map(rs, { _, v -> matchstr(v, '.\{-}\ze[/\\]\?$') }) 48 | return map(rs, { _, v -> printf('-reveal=%s', escape(v, ' ')) }) 49 | endif 50 | let rs = fern#internal#scheme#complete_reveal(scheme, a:arglead, a:cmdline, a:cursorpos) 51 | return rs is# v:null ? [] : rs 52 | endfunction 53 | -------------------------------------------------------------------------------- /autoload/fin/internal/context.vim: -------------------------------------------------------------------------------- 1 | function! fin#internal#context#new(...) abort 2 | let ctx = extend({ 3 | \ 'query': '', 4 | \ 'matcher': g:fin#matcher, 5 | \ 'number': &number, 6 | \ 'cursor': line('.'), 7 | \ 'bufname': bufname('%'), 8 | \ 'content': getline(1, '$'), 9 | \}, a:0 ? a:1 : {}, 10 | \) 11 | let ctx.size = len(ctx.content) 12 | let ctx.indices = range(0, ctx.size - 1) 13 | return ctx 14 | endfunction 15 | 16 | function! fin#internal#context#update(ctx, ignore) abort 17 | let indices = fin#matcher#{a:ctx.matcher}#filter( 18 | \ a:ctx.content, 19 | \ a:ctx.query, 20 | \ a:ignore, 21 | \) 22 | let a:ctx.indices = indices 23 | let a:ctx.cursor = max([min([a:ctx.cursor, len(indices)]), 1]) 24 | endfunction 25 | -------------------------------------------------------------------------------- /autoload/fin/internal/filter.vim: -------------------------------------------------------------------------------- 1 | function! fin#internal#filter#start(ctx) abort 2 | let bufnr = bufnr('%') 3 | let bufhidden = &bufhidden 4 | try 5 | set bufhidden=hide 6 | if s:start(a:ctx) && a:ctx.cursor <= len(a:ctx.indices) 7 | return a:ctx.indices[a:ctx.cursor - 1] 8 | else 9 | return -1 10 | endif 11 | finally 12 | execute 'keepalt keepjumps buffer' bufnr 13 | let &bufhidden = bufhidden 14 | redraw | echo 15 | endtry 16 | endfunction 17 | 18 | function! s:start(ctx) abort 19 | let bufname = printf('fin://%s', expand('%:p')) 20 | execute printf('keepalt keepjumps edit %s', fnameescape(bufname)) 21 | 22 | cnoremap (fin-line-prev) m_line_prev() 23 | cnoremap (fin-line-next) m_line_next() 24 | cnoremap (fin-matcher-prev) m_matcher_prev() 25 | cnoremap (fin-matcher-next) m_matcher_next() 26 | 27 | setlocal buftype=nofile bufhidden=wipe undolevels=-1 28 | setlocal noswapfile nobackup nobuflisted 29 | setlocal nowrap nofoldenable nonumber 30 | setlocal filetype=fin 31 | 32 | let b:fin_context = a:ctx 33 | let bufnr = bufnr('%') 34 | call timer_start(0, { -> s:consumer(a:ctx, bufnr) }) 35 | call s:update(a:ctx) 36 | return fin#util#prompt(g:fin#prompt, a:ctx.query) isnot# v:null 37 | endfunction 38 | 39 | function! s:consumer(ctx, bufnr) abort 40 | if getcmdtype() !=# '@' || bufnr('%') isnot# a:bufnr 41 | return 42 | endif 43 | let query = getcmdline() 44 | if query !=# a:ctx.query 45 | let a:ctx.query = query 46 | call s:update(a:ctx) 47 | endif 48 | call timer_start(g:fin#interval, { -> s:consumer(a:ctx, a:bufnr) }) 49 | endfunction 50 | 51 | function! s:update(ctx) abort 52 | let ignore = a:ctx.query ==# tolower(a:ctx.query) 53 | call fin#internal#context#update(a:ctx, ignore) 54 | call s:update_statusline(a:ctx) 55 | call s:update_content(a:ctx) 56 | call fin#matcher#{a:ctx.matcher}#highlight(a:ctx.query, ignore) 57 | call cursor(a:ctx.cursor, 1, 0) 58 | redraw 59 | endfunction 60 | 61 | function! s:update_statusline(ctx) abort 62 | let statusline = [ 63 | \ '%%#FinStatuslineFile#%s ', 64 | \ '%%#FinStatuslineMiddle#%%=', 65 | \ '%%#FinStatuslineMatcher# Matcher: %s ', 66 | \ '%%#FinStatuslineIndicator# %d/%d', 67 | \] 68 | let &l:statusline = printf( 69 | \ join(statusline, ''), 70 | \ a:ctx.bufname, 71 | \ a:ctx.matcher, 72 | \ len(a:ctx.indices), 73 | \ a:ctx.size, 74 | \) 75 | endfunction 76 | 77 | function! s:update_content(ctx) abort 78 | if empty(a:ctx.indices) 79 | silent! keepjumps %delete _ 80 | return 81 | endif 82 | let content = a:ctx.content 83 | let indices = copy(a:ctx.indices) 84 | if a:ctx.number 85 | let digit = len(a:ctx.size . ' ') 86 | let format = printf('%%%dd %%s', digit) 87 | let body = map( 88 | \ indices, 89 | \ 'printf(format, v:val + 1, content[v:val])' 90 | \) 91 | else 92 | let body = map(indices, 'content[v:val]') 93 | endif 94 | silent! call setline(1, body) 95 | execute printf('silent! keepjumps %d,$delete _', len(indices) + 1) 96 | endfunction 97 | 98 | function! s:m_line_prev() abort 99 | let ctx = b:fin_context 100 | let size = max([len(ctx.indices), 1]) 101 | if ctx.cursor is# 1 102 | let ctx.cursor = g:fin#wrap_around ? size : 1 103 | else 104 | let ctx.cursor -= 1 105 | endif 106 | call cursor(ctx.cursor, 1, 0) 107 | redraw 108 | call feedkeys(" \", 'n') " Stay TERM cursor on cmdline 109 | return '' 110 | endfunction 111 | 112 | function! s:m_line_next() abort 113 | let ctx = b:fin_context 114 | let size = max([len(ctx.indices), 1]) 115 | if ctx.cursor is# size 116 | let ctx.cursor = g:fin#wrap_around ? 1 : size 117 | else 118 | let ctx.cursor += 1 119 | endif 120 | call cursor(ctx.cursor, 1, 0) 121 | redraw 122 | call feedkeys(" \", 'n') " Stay TERM cursor on cmdline 123 | return '' 124 | endfunction 125 | 126 | function! s:m_matcher_prev() abort 127 | let ctx = b:fin_context 128 | let size = len(g:fin#matchers) 129 | let index = max([index(g:fin#matchers, ctx.matcher), 0]) 130 | if index is# 0 131 | let index = size - 1 132 | else 133 | let index -= 1 134 | endif 135 | let ctx.matcher = g:fin#matchers[index] 136 | call timer_start(0, { -> s:update(ctx) }) 137 | call feedkeys(" \", 'n') " Stay TERM cursor on cmdline 138 | return '' 139 | endfunction 140 | 141 | function! s:m_matcher_next() abort 142 | let ctx = b:fin_context 143 | let size = len(g:fin#matchers) 144 | let index = max([index(g:fin#matchers, ctx.matcher), 0]) 145 | if index is# size - 1 146 | let index = 0 147 | else 148 | let index += 1 149 | endif 150 | let ctx.matcher = g:fin#matchers[index] 151 | call timer_start(0, { -> s:update(ctx) }) 152 | call feedkeys(" \", 'n') " Stay TERM cursor on cmdline 153 | return '' 154 | endfunction 155 | 156 | function! s:define_highlights() abort 157 | highlight default link FinStatuslineFile Comment 158 | highlight default link FinStatuslineMiddle None 159 | highlight default link FinStatuslineMatcher Statement 160 | highlight default link FinStatuslineIndicator Tag 161 | highlight default link FinBase Comment 162 | highlight default link FinLineNr LineNr 163 | augroup fin_internal_filter_define_highlight 164 | autocmd! 165 | autocmd ColorScheme * ++once call s:define_highlights() 166 | augroup END 167 | endfunction 168 | 169 | call s:define_highlights() 170 | -------------------------------------------------------------------------------- /autoload/fin/matcher/all.vim: -------------------------------------------------------------------------------- 1 | function! fin#matcher#all#filter(items, query, ignore) abort 2 | if len(a:items) is# 0 3 | return [] 4 | endif 5 | let indices = range(0, len(a:items) - 1) 6 | let l:Wrap = a:ignore ? function('tolower') : { v -> v } 7 | for term in split(a:query, ' ') 8 | call filter( 9 | \ indices, 10 | \ 'stridx(Wrap(a:items[v:val]), term) isnot# -1', 11 | \) 12 | endfor 13 | return indices 14 | endfunction 15 | 16 | function! fin#matcher#all#highlight(query, ignore) abort 17 | let pat = join(map(split(a:query, ' '), 'fin#util#escape_pattern(v:val)'), '\|') 18 | if empty(pat) 19 | silent nohlsearch 20 | else 21 | silent! execute printf( 22 | \ '/%s\%%(%s\)/', 23 | \ a:ignore ? '\c' : '\C', 24 | \ pat 25 | \) 26 | endif 27 | endfunction 28 | -------------------------------------------------------------------------------- /autoload/fin/matcher/fuzzy.vim: -------------------------------------------------------------------------------- 1 | function! fin#matcher#fuzzy#filter(items, query, ignore) abort 2 | if len(a:items) is# 0 3 | return [] 4 | endif 5 | let pattern = (a:ignore ? '\c' : '\C') . s:pattern(a:query) 6 | let indices = range(0, len(a:items) - 1) 7 | return filter( 8 | \ indices, 9 | \ 'a:items[v:val] =~# pattern', 10 | \) 11 | endfunction 12 | 13 | function! fin#matcher#fuzzy#highlight(query, ignore) abort 14 | let pat = s:pattern(a:query) 15 | if empty(pat) 16 | silent nohlsearch 17 | else 18 | silent! execute printf('/%s%s/', a:ignore ? '\c' : '\C', pat) 19 | endif 20 | endfunction 21 | 22 | function! s:pattern(query) abort 23 | let chars = map( 24 | \ split(a:query, '\zs'), 25 | \ 'fin#util#escape_pattern(v:val)' 26 | \) 27 | let patterns = map(chars, { _, v -> printf('%s[^%s]\{-}', v, v)}) 28 | return join(patterns, '') 29 | endfunction 30 | -------------------------------------------------------------------------------- /autoload/fin/util.vim: -------------------------------------------------------------------------------- 1 | function! fin#util#escape_pattern(str) abort 2 | return escape(a:str, '^$~.*[]\') 3 | endfunction 4 | 5 | function! fin#util#prompt(prompt, text) abort 6 | let hash = sha256(localtime()) 7 | execute printf('cnoremap %s', hash) 8 | let result = input(a:prompt, a:text) 9 | let ok = result[-64:] ==# hash 10 | if ok 11 | call histdel('@', -1) 12 | call histadd('@', result[:-65]) 13 | endif 14 | return ok ? result[:-65] : v:null 15 | endfunction 16 | -------------------------------------------------------------------------------- /autoload/vital/_fin.vim: -------------------------------------------------------------------------------- 1 | let s:_plugin_name = expand(':t:r') 2 | 3 | function! vital#{s:_plugin_name}#new() abort 4 | return vital#{s:_plugin_name[1:]}#new() 5 | endfunction 6 | 7 | function! vital#{s:_plugin_name}#function(funcname) abort 8 | silent! return function(a:funcname) 9 | endfunction 10 | -------------------------------------------------------------------------------- /autoload/vital/_fin/App/Args.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_fin#App#Args#import() abort', printf("return map({'set': '', 'pop': '', 'get': '', 'throw_if_dirty': ''}, \"vital#_fin#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | function! s:_index(args, pattern) abort 11 | for index in range(len(a:args)) 12 | if a:args[index] =~# a:pattern 13 | return index 14 | endif 15 | endfor 16 | return -1 17 | endfunction 18 | 19 | function! s:get(args, name, default) abort 20 | let pattern = printf('^-%s\%(=.*\)\?$', a:name) 21 | let index = s:_index(a:args, pattern) 22 | if index is# -1 23 | return a:default 24 | else 25 | let value = a:args[index] 26 | return value =~# '^-[^=]\+=' 27 | \ ? matchstr(value, '=\zs.*$') 28 | \ : v:true 29 | endif 30 | endfunction 31 | 32 | function! s:pop(args, name, default) abort 33 | let pattern = printf('^-%s\%(=.*\)\?$', a:name) 34 | let index = s:_index(a:args, pattern) 35 | if index is# -1 36 | return a:default 37 | else 38 | let value = remove(a:args, index) 39 | return value =~# '^-[^=]\+=' 40 | \ ? matchstr(value, '=\zs.*$') 41 | \ : v:true 42 | endif 43 | endfunction 44 | 45 | function! s:set(args, name, value) abort 46 | let pattern = printf('^-%s\%(=.*\)\?$', a:name) 47 | let index = s:_index(a:args, pattern) 48 | let value = a:value is# v:true 49 | \ ? printf('-%s', a:name) 50 | \ : printf('-%s=%s', a:name, a:value) 51 | if index is# -1 52 | call add(a:args, value) 53 | elseif a:value is# v:false 54 | call remove(a:args, index) 55 | else 56 | let a:args[index] = value 57 | endif 58 | endfunction 59 | 60 | function! s:throw_if_dirty(args, ...) abort 61 | let prefix = a:0 ? a:1 : '' 62 | for arg in a:args 63 | if arg =~# '^-' 64 | throw printf('%sunknown option %s has specified', prefix, arg) 65 | endif 66 | endfor 67 | endfunction 68 | -------------------------------------------------------------------------------- /autoload/vital/_fin/Config.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_fin#Config#import() abort', printf("return map({'define': '', 'translate': '', 'config': ''}, \"vital#_fin#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | let s:plugin_name = expand(':p:h:t') 11 | let s:plugin_name = s:plugin_name =~# '^__.\+__$' 12 | \ ? s:plugin_name[2:-3] 13 | \ : s:plugin_name =~# '^_.\+$' 14 | \ ? s:plugin_name[1:] 15 | \ : s:plugin_name 16 | 17 | function! s:define(prefix, default) abort 18 | let prefix = a:prefix =~# '^g:' ? a:prefix : 'g:' . a:prefix 19 | for [key, Value] in items(a:default) 20 | let name = prefix . '#' . key 21 | if !exists(name) 22 | execute 'let ' . name . ' = ' . string(Value) 23 | endif 24 | unlet Value 25 | endfor 26 | endfunction 27 | 28 | function! s:config(scriptfile, default) abort 29 | let prefix = s:translate(a:scriptfile) 30 | call s:define(prefix, a:default) 31 | endfunction 32 | 33 | function! s:translate(scriptfile) abort 34 | let path = fnamemodify(a:scriptfile, ':gs?\\?/?') 35 | let name = matchstr(path, printf( 36 | \ 'autoload/\zs\%%(%s\.vim\|%s/.*\)$', 37 | \ s:plugin_name, 38 | \ s:plugin_name, 39 | \)) 40 | let name = substitute(name, '\.vim$', '', '') 41 | let name = substitute(name, '/', '#', 'g') 42 | let name = substitute(name, '\%(^#\|#$\)', '', 'g') 43 | return 'g:' . name 44 | endfunction 45 | -------------------------------------------------------------------------------- /autoload/vital/fin.vim: -------------------------------------------------------------------------------- 1 | let s:plugin_name = expand(':t:r') 2 | let s:vital_base_dir = expand(':h') 3 | let s:project_root = expand(':h:h:h') 4 | let s:is_vital_vim = s:plugin_name is# 'vital' 5 | 6 | let s:loaded = {} 7 | let s:cache_sid = {} 8 | 9 | function! vital#{s:plugin_name}#new() abort 10 | return s:new(s:plugin_name) 11 | endfunction 12 | 13 | function! vital#{s:plugin_name}#import(...) abort 14 | if !exists('s:V') 15 | let s:V = s:new(s:plugin_name) 16 | endif 17 | return call(s:V.import, a:000, s:V) 18 | endfunction 19 | 20 | let s:Vital = {} 21 | 22 | function! s:new(plugin_name) abort 23 | let base = deepcopy(s:Vital) 24 | let base._plugin_name = a:plugin_name 25 | return base 26 | endfunction 27 | 28 | function! s:vital_files() abort 29 | if !exists('s:vital_files') 30 | let s:vital_files = map( 31 | \ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(), 32 | \ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")') 33 | endif 34 | return copy(s:vital_files) 35 | endfunction 36 | let s:Vital.vital_files = function('s:vital_files') 37 | 38 | function! s:import(name, ...) abort dict 39 | let target = {} 40 | let functions = [] 41 | for a in a:000 42 | if type(a) == type({}) 43 | let target = a 44 | elseif type(a) == type([]) 45 | let functions = a 46 | endif 47 | unlet a 48 | endfor 49 | let module = self._import(a:name) 50 | if empty(functions) 51 | call extend(target, module, 'keep') 52 | else 53 | for f in functions 54 | if has_key(module, f) && !has_key(target, f) 55 | let target[f] = module[f] 56 | endif 57 | endfor 58 | endif 59 | return target 60 | endfunction 61 | let s:Vital.import = function('s:import') 62 | 63 | function! s:load(...) abort dict 64 | for arg in a:000 65 | let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] 66 | let target = split(join(as, ''), '\W\+') 67 | let dict = self 68 | let dict_type = type({}) 69 | while !empty(target) 70 | let ns = remove(target, 0) 71 | if !has_key(dict, ns) 72 | let dict[ns] = {} 73 | endif 74 | if type(dict[ns]) == dict_type 75 | let dict = dict[ns] 76 | else 77 | unlet dict 78 | break 79 | endif 80 | endwhile 81 | if exists('dict') 82 | call extend(dict, self._import(name)) 83 | endif 84 | unlet arg 85 | endfor 86 | return self 87 | endfunction 88 | let s:Vital.load = function('s:load') 89 | 90 | function! s:unload() abort dict 91 | let s:loaded = {} 92 | let s:cache_sid = {} 93 | unlet! s:vital_files 94 | endfunction 95 | let s:Vital.unload = function('s:unload') 96 | 97 | function! s:exists(name) abort dict 98 | if a:name !~# '\v^\u\w*%(\.\u\w*)*$' 99 | throw 'vital: Invalid module name: ' . a:name 100 | endif 101 | return s:_module_path(a:name) isnot# '' 102 | endfunction 103 | let s:Vital.exists = function('s:exists') 104 | 105 | function! s:search(pattern) abort dict 106 | let paths = s:_extract_files(a:pattern, self.vital_files()) 107 | let modules = sort(map(paths, 's:_file2module(v:val)')) 108 | return uniq(modules) 109 | endfunction 110 | let s:Vital.search = function('s:search') 111 | 112 | function! s:plugin_name() abort dict 113 | return self._plugin_name 114 | endfunction 115 | let s:Vital.plugin_name = function('s:plugin_name') 116 | 117 | function! s:_self_vital_files() abort 118 | let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name) 119 | let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name) 120 | let base = builtin . ',' . installed 121 | return split(globpath(base, '**/*.vim', 1), "\n") 122 | endfunction 123 | 124 | function! s:_global_vital_files() abort 125 | let pattern = 'autoload/vital/__*__/**/*.vim' 126 | return split(globpath(&runtimepath, pattern, 1), "\n") 127 | endfunction 128 | 129 | function! s:_extract_files(pattern, files) abort 130 | let tr = {'.': '/', '*': '[^/]*', '**': '.*'} 131 | let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g') 132 | let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target) 133 | return filter(a:files, 'v:val =~# regexp') 134 | endfunction 135 | 136 | function! s:_file2module(file) abort 137 | let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') 138 | let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') 139 | return join(split(tail, '[\\/]\+'), '.') 140 | endfunction 141 | 142 | " @param {string} name e.g. Data.List 143 | function! s:_import(name) abort dict 144 | if has_key(s:loaded, a:name) 145 | return copy(s:loaded[a:name]) 146 | endif 147 | let module = self._get_module(a:name) 148 | if has_key(module, '_vital_created') 149 | call module._vital_created(module) 150 | endif 151 | let export_module = filter(copy(module), 'v:key =~# "^\\a"') 152 | " Cache module before calling module._vital_loaded() to avoid cyclic 153 | " dependences but remove the cache if module._vital_loaded() fails. 154 | " let s:loaded[a:name] = export_module 155 | let s:loaded[a:name] = export_module 156 | if has_key(module, '_vital_loaded') 157 | try 158 | call module._vital_loaded(vital#{s:plugin_name}#new()) 159 | catch 160 | unlet s:loaded[a:name] 161 | throw 'vital: fail to call ._vital_loaded(): ' . v:exception . " from:\n" . s:_format_throwpoint(v:throwpoint) 162 | endtry 163 | endif 164 | return copy(s:loaded[a:name]) 165 | endfunction 166 | let s:Vital._import = function('s:_import') 167 | 168 | function! s:_format_throwpoint(throwpoint) abort 169 | let funcs = [] 170 | let stack = matchstr(a:throwpoint, '^function \zs.*, .\{-} \d\+$') 171 | for line in split(stack, '\.\.') 172 | let m = matchlist(line, '^\(.\+\)\%(\[\(\d\+\)\]\|, .\{-} \(\d\+\)\)$') 173 | if !empty(m) 174 | let [name, lnum, lnum2] = m[1:3] 175 | if empty(lnum) 176 | let lnum = lnum2 177 | endif 178 | let info = s:_get_func_info(name) 179 | if !empty(info) 180 | let attrs = empty(info.attrs) ? '' : join([''] + info.attrs) 181 | let flnum = info.lnum == 0 ? '' : printf(' Line:%d', info.lnum + lnum) 182 | call add(funcs, printf('function %s(...)%s Line:%d (%s%s)', 183 | \ info.funcname, attrs, lnum, info.filename, flnum)) 184 | continue 185 | endif 186 | endif 187 | " fallback when function information cannot be detected 188 | call add(funcs, line) 189 | endfor 190 | return join(funcs, "\n") 191 | endfunction 192 | 193 | function! s:_get_func_info(name) abort 194 | let name = a:name 195 | if a:name =~# '^\d\+$' " is anonymous-function 196 | let name = printf('{%s}', a:name) 197 | elseif a:name =~# '^\d\+$' " is lambda-function 198 | let name = printf("{'%s'}", a:name) 199 | endif 200 | if !exists('*' . name) 201 | return {} 202 | endif 203 | let body = execute(printf('verbose function %s', name)) 204 | let lines = split(body, "\n") 205 | let signature = matchstr(lines[0], '^\s*\zs.*') 206 | let [_, file, lnum; __] = matchlist(lines[1], 207 | \ '^\t\%(Last set from\|.\{-}:\)\s*\zs\(.\{-}\)\%( \S\+ \(\d\+\)\)\?$') 208 | return { 209 | \ 'filename': substitute(file, '[/\\]\+', '/', 'g'), 210 | \ 'lnum': 0 + lnum, 211 | \ 'funcname': a:name, 212 | \ 'arguments': split(matchstr(signature, '(\zs.*\ze)'), '\s*,\s*'), 213 | \ 'attrs': filter(['dict', 'abort', 'range', 'closure'], 'signature =~# (").*" . v:val)'), 214 | \ } 215 | endfunction 216 | 217 | " s:_get_module() returns module object wihch has all script local functions. 218 | function! s:_get_module(name) abort dict 219 | let funcname = s:_import_func_name(self.plugin_name(), a:name) 220 | try 221 | return call(funcname, []) 222 | catch /^Vim\%((\a\+)\)\?:E117:/ 223 | return s:_get_builtin_module(a:name) 224 | endtry 225 | endfunction 226 | 227 | function! s:_get_builtin_module(name) abort 228 | return s:sid2sfuncs(s:_module_sid(a:name)) 229 | endfunction 230 | 231 | if s:is_vital_vim 232 | " For vital.vim, we can use s:_get_builtin_module directly 233 | let s:Vital._get_module = function('s:_get_builtin_module') 234 | else 235 | let s:Vital._get_module = function('s:_get_module') 236 | endif 237 | 238 | function! s:_import_func_name(plugin_name, module_name) abort 239 | return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name)) 240 | endfunction 241 | 242 | function! s:_module_sid(name) abort 243 | let path = s:_module_path(a:name) 244 | if !filereadable(path) 245 | throw 'vital: module not found: ' . a:name 246 | endif 247 | let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name) 248 | let base = join([vital_dir, ''], '[/\\]\+') 249 | let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g') 250 | let sid = s:_sid(path, p) 251 | if !sid 252 | call s:_source(path) 253 | let sid = s:_sid(path, p) 254 | if !sid 255 | throw printf('vital: cannot get from path: %s', path) 256 | endif 257 | endif 258 | return sid 259 | endfunction 260 | 261 | function! s:_module_path(name) abort 262 | return get(s:_extract_files(a:name, s:vital_files()), 0, '') 263 | endfunction 264 | 265 | function! s:_module_sid_base_dir() abort 266 | return s:is_vital_vim ? &rtp : s:project_root 267 | endfunction 268 | 269 | function! s:_dot_to_sharp(name) abort 270 | return substitute(a:name, '\.', '#', 'g') 271 | endfunction 272 | 273 | function! s:_source(path) abort 274 | execute 'source' fnameescape(a:path) 275 | endfunction 276 | 277 | " @vimlint(EVL102, 1, l:_) 278 | " @vimlint(EVL102, 1, l:__) 279 | function! s:_sid(path, filter_pattern) abort 280 | let unified_path = s:_unify_path(a:path) 281 | if has_key(s:cache_sid, unified_path) 282 | return s:cache_sid[unified_path] 283 | endif 284 | for line in filter(split(execute(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern') 285 | let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') 286 | if s:_unify_path(path) is# unified_path 287 | let s:cache_sid[unified_path] = sid 288 | return s:cache_sid[unified_path] 289 | endif 290 | endfor 291 | return 0 292 | endfunction 293 | 294 | if filereadable(expand(':r') . '.VIM') " is case-insensitive or not 295 | let s:_unify_path_cache = {} 296 | " resolve() is slow, so we cache results. 297 | " Note: On windows, vim can't expand path names from 8.3 formats. 298 | " So if getting full path via and $HOME was set as 8.3 format, 299 | " vital load duplicated scripts. Below's :~ avoid this issue. 300 | function! s:_unify_path(path) abort 301 | if has_key(s:_unify_path_cache, a:path) 302 | return s:_unify_path_cache[a:path] 303 | endif 304 | let value = tolower(fnamemodify(resolve(fnamemodify( 305 | \ a:path, ':p')), ':~:gs?[\\/]?/?')) 306 | let s:_unify_path_cache[a:path] = value 307 | return value 308 | endfunction 309 | else 310 | function! s:_unify_path(path) abort 311 | return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) 312 | endfunction 313 | endif 314 | 315 | " copied and modified from Vim.ScriptLocal 316 | let s:SNR = join(map(range(len("\")), '"[\\x" . printf("%0x", char2nr("\"[v:val])) . "]"'), '') 317 | function! s:sid2sfuncs(sid) abort 318 | let fs = split(execute(printf(':function /^%s%s_', s:SNR, a:sid)), "\n") 319 | let r = {} 320 | let pattern = printf('\m^function\s%d_\zs\w\{-}\ze(', a:sid) 321 | for fname in map(fs, 'matchstr(v:val, pattern)') 322 | let r[fname] = function(s:_sfuncname(a:sid, fname)) 323 | endfor 324 | return r 325 | endfunction 326 | 327 | "" Return funcname of script local functions with SID 328 | function! s:_sfuncname(sid, funcname) abort 329 | return printf('%s_%s', a:sid, a:funcname) 330 | endfunction 331 | -------------------------------------------------------------------------------- /autoload/vital/fin.vital: -------------------------------------------------------------------------------- 1 | fin 2 | f8faa980c2b3e017984be9ae10bdcdb8f7bf8e84 3 | 4 | Config 5 | App.Args 6 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | -------------------------------------------------------------------------------- /doc/fin.txt: -------------------------------------------------------------------------------- 1 | *fin.txt* Filter buffer content in-place without modification 2 | 3 | Author: Alisue 4 | License: MIT license 5 | 6 | ============================================================================= 7 | CONTENTS *fin-contents* 8 | 9 | INTRODUCTION |fin-introduction| 10 | USAGE |fin-usage| 11 | INTERFACE |fin-interface| 12 | VARIABLE |fin-variable| 13 | MAPPING |fin-mapping| 14 | COMMAND |fin-command| 15 | FUNCTION |fin-function| 16 | HIGHLIGHT |fin-highlight| 17 | 18 | 19 | ============================================================================= 20 | INTRODUCTION *fin-introduction* 21 | 22 | *fin.vim* (fin) is a plugin to filter buffer content in-place without 23 | modification. It's written in a pure Vim script. 24 | 25 | 26 | ============================================================================= 27 | USAGE *fin-usage* 28 | 29 | Start filtering the buffer content by |:Fin| command 30 | > 31 | :Fin 32 | < 33 | Use the {after} argument to change the behavior after filtering. 34 | For example, the following command sequence will 1) Open |quickfix| window, 2) 35 | Filter quickfix items, 3) Jumps to the selected item, 4) Close quickfix window 36 | > 37 | :botright copen | Fin -after=\ | cclose 38 | < 39 | See |:Fin| for more details. 40 | 41 | 42 | ============================================================================= 43 | INTERFACE *fin-interface* 44 | 45 | ----------------------------------------------------------------------------- 46 | VARIABLE *fin-variable* 47 | 48 | TBW 49 | 50 | ----------------------------------------------------------------------------- 51 | MAPPING *fin-mapping* 52 | 53 | *(fin-line-prev)* 54 | Go to the previous line. 55 | 56 | *(fin-line-next)* 57 | Go to the next line. 58 | 59 | *(fin-matcher-perv)* 60 | Switch to the previous matcher. 61 | 62 | *(fin-matcher-next)* / 63 | Switch to the next matcher. 64 | 65 | ----------------------------------------------------------------------------- 66 | COMMAND *fin-command* 67 | 68 | *:Fin* 69 | :Fin [-after|-After={after}] [-cancel|-Cancel={cancel}] [-matcher={matcher}] [{query}] 70 | Start filtering the buffer content. 71 | 72 | The {query} is the initial value of the prompt. 73 | The {matcher} is the initial matcher name. 74 | 75 | Note that the command can be followed by a '|' and another command. 76 | 77 | 78 | ----------------------------------------------------------------------------- 79 | FUNCTION *fin-function* 80 | 81 | *fin#version()* 82 | fin#version() 83 | Show fin version itself. 84 | 85 | ----------------------------------------------------------------------------- 86 | HIGHLIGHT *fin-highlight* 87 | 88 | TBW 89 | 90 | 91 | ============================================================================= 92 | vim:tw=78:fo=tcq2mM:ts=8:ft=help:norl 93 | -------------------------------------------------------------------------------- /ftplugin/fin.vim: -------------------------------------------------------------------------------- 1 | if exists('b:did_ftplugin') 2 | finish 3 | endif 4 | let b:did_ftplugin = 1 5 | 6 | setlocal cursorline nolist nospell 7 | 8 | cmap (fin-line-prev) 9 | cmap (fin-line-next) 10 | cmap (fin-matcher-next) 11 | cmap (fin-matcher-next) 12 | -------------------------------------------------------------------------------- /plugin/fin.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_fin') 2 | finish 3 | endif 4 | let g:loaded_fin = 1 5 | 6 | command! -bar -nargs=* 7 | \ -complete=customlist,fin#internal#command#fin#complete 8 | \ Fin 9 | \ call fin#internal#command#fin#command(, []) 10 | -------------------------------------------------------------------------------- /syntax/fin.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | syntax clear 6 | syntax match FinBase /.*/ 7 | syntax match FinLineNr /^\s*\d\+ / 8 | 9 | let b:current_syntax = 'fin' 10 | --------------------------------------------------------------------------------