├── autoload ├── skylight │ ├── cmdline.vim │ ├── mode │ │ ├── word.vim │ │ ├── symbol.vim │ │ └── file.vim │ ├── util.vim │ ├── async.vim │ ├── search.vim │ ├── buffer.vim │ ├── path.vim │ ├── cocf.vim │ └── float.vim └── skylight.vim ├── LICENSE ├── plugin └── skylight.vim ├── .gitignore ├── README.md ├── ftplugin └── skylightmenu.vim └── doc └── skylight.txt /autoload/skylight/cmdline.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: cmdline.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | function skylight#cmdline#complete(arg_lead, cmd_line, cursor_pos) abort 8 | let candidates = ['file', 'symbol', 'word'] 9 | if a:arg_lead == '' 10 | return candidates 11 | else 12 | return filter(candidates, 'v:val[:len(a:arg_lead) - 1] == a:arg_lead') 13 | endif 14 | endfunction 15 | -------------------------------------------------------------------------------- /autoload/skylight/mode/word.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: word.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | function skylight#mode#word#search(pattern) abort 8 | let pattern = '\<' . expand('') . '\>' 9 | let filename = expand('%:p') 10 | let results = [] 11 | let curpos = getpos('.') 12 | normal! gg0 13 | let first_match_pos = searchpos(pattern, 'w') 14 | call add(results, #{ 15 | \ pattern: pattern, 16 | \ filename: filename, 17 | \ lnum: first_match_pos[0] 18 | \ }) 19 | while v:true 20 | let pos = searchpos(pattern, 'w') 21 | if pos == first_match_pos 22 | break 23 | endif 24 | call add(results, #{ 25 | \ pattern: pattern, 26 | \ filename: filename, 27 | \ lnum: pos[0] 28 | \ }) 29 | endwhile 30 | call setpos('.', curpos) 31 | call skylight#search#callback(results) 32 | endfunction 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 最上川 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugin/skylight.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: skylight.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | if !exists('g:loaded_skylight') 8 | let g:loaded_skylight = 1 9 | endif 10 | 11 | let g:skylight_width = get(g:, 'skylight_width', 0.5) 12 | let g:skylight_height = get(g:, 'skylight_height', 0.5) 13 | let g:skylight_position = get(g:, 'skylight_position', 'topright') 14 | let g:skylight_borderchars = get(g:, 'skylight_borderchars', ['─', '│', '─', '│', '╭', '╮', '╯', '╰']) 15 | let g:skylight_opener = get(g:, 'skylight_opener', 'edit') 16 | 17 | hi def link SkylightBorder Normal 18 | 19 | command! -range -bang -nargs=* -complete=customlist,skylight#cmdline#complete 20 | \ Skylight call skylight#start(, visualmode(), , 0) 21 | 22 | augroup CocSkylightLocation 23 | autocmd! 24 | let g:coc_enable_locationlist = 0 25 | autocmd User CocLocationsChange call skylight#mode#symbol#on_coclocations_change() 26 | augroup END 27 | -------------------------------------------------------------------------------- /autoload/skylight/util.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: util.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | function! s:echohl(group, msg) abort 8 | execute 'echohl ' . a:group 9 | echom '[vim-skylight] ' . a:msg 10 | echohl None 11 | endfunction 12 | 13 | function! skylight#util#show_msg(message, ...) abort 14 | if a:0 == 0 15 | let msgtype = 'info' 16 | else 17 | let msgtype = a:1 18 | endif 19 | 20 | if type(a:message) != v:t_string 21 | let message = string(a:message) 22 | else 23 | let message = a:message 24 | endif 25 | 26 | if msgtype ==# 'info' 27 | call s:echohl('MoreMsg', message) 28 | elseif msgtype ==# 'warning' 29 | call s:echohl('WarningMsg', message) 30 | elseif msgtype ==# 'error' 31 | call s:echohl('ErrorMsg', message) 32 | endif 33 | endfunction 34 | 35 | function! skylight#util#get_selected_text() abort 36 | let col1 = getpos("'<")[2] 37 | let col2 = getpos("'>")[2] 38 | let text = getline('.') 39 | if empty(text) 40 | call skylight#util#show_msg('No content', 'error') 41 | return '' 42 | endif 43 | let text = text[col1-1 : col2-1] 44 | return text 45 | endfunction 46 | -------------------------------------------------------------------------------- /autoload/skylight/async.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: skylight#async.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | function! s:do_rpc(async, id, method, args) 8 | if a:async == 0 9 | let rpc = 'rpcrequest' 10 | else 11 | let rpc = 'rpcnotify' 12 | end 13 | return call(rpc, [a:id, a:method] + a:args) 14 | endfunction 15 | 16 | function! skylight#async#new() abort 17 | let vim = {} 18 | let vim.id = jobstart([v:progpath, '--embed'], {'rpc': v:true}) 19 | let vim.async = 1 20 | 21 | function! vim.request(method, ...) dict 22 | return s:do_rpc(0, self.id, a:method, a:000) 23 | endfunction 24 | 25 | function! vim.notify(method, ...) dict 26 | return s:do_rpc(1, self.id, a:method, a:000) 27 | endfunction 28 | 29 | function! vim.cmd(cmd, async) dict 30 | if type(a:cmd) == v:t_list 31 | let cmd = join(a:cmd, ' | ') 32 | else 33 | let cmd = a:cmd 34 | endif 35 | if a:async == 1 36 | call self.notify('nvim_command', cmd) 37 | else 38 | call self.request('nvim_command', cmd) 39 | endif 40 | endfunction 41 | 42 | function! vim.isvalid() abort 43 | return jobwait([self.id], 0)[0] == -1 44 | endfunction 45 | 46 | function! vim.close() dict 47 | call jobstop(self.id) 48 | endfunction 49 | 50 | return vim 51 | endfunction 52 | 53 | function! skylight#async#close(vim) abort 54 | if a:vim.isvalid() 55 | call a:vim.close() 56 | endif 57 | endfunction 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # For current directory only 2 | # ---------------------------------------------------------------------------- 3 | 4 | # General 5 | # ---------------------------------------------------------------------------- 6 | *.o 7 | *.out 8 | 9 | # log 10 | *.log 11 | 12 | # cache 13 | *.cache 14 | cache/ 15 | 16 | # Windows 17 | # ---------------------------------------------------------------------------- 18 | Thumbs.db 19 | Desktop.ini 20 | 21 | # Tags 22 | # ----------------------------------------------------------------------------- 23 | TAGS 24 | !TAGS/ 25 | tags 26 | tags-cn 27 | !tags/ 28 | .tags 29 | .tags1 30 | tags.lock 31 | tags.temp 32 | gtags.files 33 | GTAGS 34 | GRTAGS 35 | GPATH 36 | cscope.files 37 | cscope.out 38 | cscope.in.out 39 | cscope.po.out 40 | 41 | # Vim 42 | # ------------------------------------------------------------------------------ 43 | [._]*.s[a-w][a-z] 44 | [._]s[a-w][a-z] 45 | *.un~ 46 | Session.vim 47 | .netrwhist 48 | *~ 49 | /.vim 50 | 51 | # Test % Tmp 52 | # ------------------------------------------------------------------------------- 53 | test.* 54 | tmp.* 55 | temp.* 56 | 57 | # Java 58 | # ------------------------------------------------------------------------------- 59 | *.class 60 | 61 | # JavaScript 62 | # ------------------------------------------------------------------------------- 63 | node_modules 64 | 65 | # Python 66 | # ------------------------------------------------------------------------------- 67 | *.pyc 68 | .idea/ 69 | /.idea 70 | build/ 71 | __pycache__ 72 | 73 | # Rust 74 | # ------------------------------------------------------------------------------- 75 | target/ 76 | **/*.rs.bk 77 | 78 | # C/Cpp 79 | # ------------------------------------------------------------------------------- 80 | /cmake-build-debug/ 81 | -------------------------------------------------------------------------------- /autoload/skylight.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: skylight.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | function! skylight#jumpto(location) abort 8 | if has_key(a:location, 'lnum') 9 | let cmd = a:location.lnum 10 | elseif has_key(a:location, 'cmd') 11 | let cmd = a:location.cmd 12 | else 13 | let cmd = 'normal! gg0' 14 | endif 15 | silent! execute printf('%s %s | %s', 16 | \ g:skylight_opener, 17 | \ a:location.filename, 18 | \ cmd 19 | \ ) 20 | endfunction 21 | 22 | function! skylight#preview(location) abort 23 | call skylight#float#close() 24 | let bufnr = skylight#buffer#load_buf(a:location.filename) 25 | 26 | let config = { 27 | \ 'width': g:skylight_width, 28 | \ 'height': g:skylight_height, 29 | \ 'position': g:skylight_position, 30 | \ 'borderchars': g:skylight_borderchars, 31 | \ 'title': a:location.filename, 32 | \ } 33 | if type(config.width) == v:t_float 34 | let config.width *= &columns 35 | let config.width = float2nr(config.width) 36 | endif 37 | if type(config.height) == v:t_float 38 | let config.height *= (&lines - &cmdheight - 1) 39 | let config.height = float2nr(config.height) 40 | endif 41 | let title_width = strdisplaywidth(config.title) 42 | let maxwidth = (config.width - 2) / 2 43 | if title_width > maxwidth 44 | let config.title = '...' . config.title[title_width-maxwidth+3:] 45 | endif 46 | let winid = skylight#float#open(bufnr, config) 47 | call skylight#float#locate(winid, a:location) 48 | endfunction 49 | 50 | function! skylight#start(type, visualmode, range, live_preview) abort 51 | let text = '' 52 | if a:visualmode == 'v' && a:range == 2 53 | let text = skylight#util#get_selected_text() 54 | endif 55 | call skylight#search#start(text, a:type, a:live_preview) 56 | endfunction 57 | -------------------------------------------------------------------------------- /autoload/skylight/search.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: search.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | let s:finished = 0 8 | let s:live_preview = 1 9 | 10 | function! skylight#search#get_status() abort 11 | return s:finished 12 | endfunction 13 | 14 | function! skylight#search#set_status(status) abort 15 | let s:finished = a:status 16 | endfunction 17 | 18 | function! skylight#search#start(pattern, type, live_preview) abort 19 | let s:live_preview = a:live_preview 20 | call skylight#search#set_status(0) 21 | 22 | if !empty(a:type) 23 | call skylight#mode#{a:type}#search(a:pattern) 24 | else 25 | call skylight#mode#file#search(a:pattern) 26 | call skylight#mode#symbol#search(a:pattern) 27 | endif 28 | endfunction 29 | 30 | " param `locations` is a list of dictionary, which contains: 31 | " - filename: String 32 | " - cmd?: String, used for jumping 33 | " - lnum?: Number, used for jumping 34 | " - pattern?: String, used for highlighting 35 | " - range?: Directory, used for highlighting 36 | function! skylight#search#callback(locations) abort 37 | if skylight#search#get_status() == 1 38 | return 39 | endif 40 | call skylight#search#set_status(1) 41 | 42 | let locations = filter( 43 | \ copy(a:locations), 44 | \ { _,v -> !empty(v.filename) && filereadable(v.filename) } 45 | \ ) 46 | if empty(locations) 47 | call skylight#util#show_msg('File or symbol not found', 'warning') 48 | return 49 | endif 50 | 51 | let filenames = map(copy(locations), { _,v -> v['filename'] }) 52 | let filenames = map(filenames, { _,v -> fnamemodify(v, ':~:.') }) 53 | let maxwidth = max(map(copy(filenames), { _,v -> len(v) })) 54 | if maxwidth > &columns / 2 55 | call map(filenames, { _,v -> pathshorten(v) }) 56 | endif 57 | call skylight#float#create_menu(filenames, s:live_preview, locations) 58 | call timer_start(10, { -> execute('doautocmd skylight_menu CursorMoved') }) 59 | endfunction 60 | -------------------------------------------------------------------------------- /autoload/skylight/mode/symbol.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: symbol.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | function! skylight#mode#symbol#search(pattern) abort 8 | if skylight#search#get_status() == 1 9 | return 10 | endif 11 | 12 | let pattern = empty(a:pattern) ? expand('') : a:pattern 13 | 14 | " use coc-action jumpReferences 15 | call s:coc_search() 16 | 17 | " use builtin taglist function 18 | call s:tag_search(pattern) 19 | endfunction 20 | 21 | function! s:tag_search(pattern) abort 22 | let pattern = '^' . a:pattern . '$' 23 | let vim = skylight#async#new() 24 | let cmd = [ 25 | \ 'set tags=./tags,tags,.tags,.vim/tags,.vim/.tags', 26 | \ printf('let pattern = "%s"', pattern), 27 | \ printf('let taglists = taglist("%s")', pattern), 28 | \ 'call rpcrequest(1, "vim_call_function", "skylight#mode#symbol#tag_search_callback", [pattern, taglists])', 29 | \ 'quit!' 30 | \ ] 31 | call vim.cmd(cmd, 1) 32 | call timer_start(3000, { -> s:stop_taglist(vim) }) 33 | endfunction 34 | 35 | function! s:stop_taglist(vim) abort 36 | if skylight#search#get_status() != 1 37 | call skylight#util#show_msg('Stop searching tags due to timeout', 'info') 38 | endif 39 | call skylight#async#close(a:vim) 40 | endfunction 41 | 42 | function! skylight#mode#symbol#tag_search_callback(pattern, taglists) abort 43 | let locations = [] 44 | if !empty(a:taglists) 45 | for t in a:taglists 46 | let location = { 47 | \ 'pattern': a:pattern, 48 | \ 'filename': t.filename, 49 | \ 'cmd': t.cmd, 50 | \ } 51 | if location.cmd =~ '^\d\+$' 52 | let location.lnum = str2nr(cmd) 53 | endif 54 | call add(locations, location) 55 | endfor 56 | call skylight#search#callback(locations) 57 | endif 58 | endfunction 59 | 60 | let s:override = v:false 61 | function! s:coc_search() abort 62 | if exists('g:did_coc_loaded') 63 | let s:override = v:true 64 | silent! call CocActionAsync('jumpReferences') 65 | endif 66 | endfunction 67 | 68 | function! skylight#mode#symbol#on_coclocations_change() abort 69 | if s:override 70 | let locations = [] 71 | for loc in g:coc_jump_locations 72 | call add(locations, #{ 73 | \ filename: loc.filename, 74 | \ lnum: loc.lnum, 75 | \ range: loc.range 76 | \ }) 77 | endfor 78 | call skylight#search#callback(locations) 79 | else 80 | let autocmd_count = (len(split(execute('autocmd User CocLocationsChange'), '\n')) - 1) / 3 81 | if autocmd_count == 1 82 | CocList --normal --auto-preview location 83 | endif 84 | endif 85 | let s:override = v:false 86 | endfunction 87 | -------------------------------------------------------------------------------- /autoload/skylight/mode/file.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: file.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | function! skylight#mode#file#search(pattern) abort 8 | if skylight#search#get_status() == 1 9 | return 10 | endif 11 | 12 | let lnumpat = '\(:\|(\||\|\s\+\)\zs\d\+\ze' 13 | if !empty(a:pattern) 14 | let pattern = a:pattern 15 | let lnumstr = matchstr(pattern, lnumpat) 16 | if empty(lnumstr) 17 | let lnumstr = matchstr(getline('.'), pattern . lnumpat) 18 | endif 19 | else 20 | let pattern = expand('') 21 | let lnumstr = matchstr(getline('.'), pattern . lnumpat) 22 | endif 23 | let filename = substitute(pattern, '^\zs\(\~\|\$HOME\)', $HOME, '') 24 | 25 | if filereadable(filename) 26 | let result = #{filename: filename} 27 | if !empty(lnumstr) 28 | let result.lnum = str2nr(lnumstr) 29 | endif 30 | call skylight#search#callback([result]) 31 | return 32 | elseif isdirectory(filename) 33 | call skylight#util#show_msg('Can not preview directory', 'warning') 34 | call skylight#search#set_status(1) 35 | return 36 | elseif filename =~ '^/' 37 | let filename = substitute(filename, '^\zs/\ze', '', '') 38 | else 39 | " pass 40 | endif 41 | 42 | if filereadable(filename) 43 | let result = #{filename: filename} 44 | if !empty(lnumstr) 45 | let result.lnum = str2nr(lnumstr) 46 | endif 47 | call skylight#search#callback([result]) 48 | return 49 | elseif isdirectory(filename) 50 | call skylight#util#show_msg('Can not preview directory', 'warning') 51 | call skylight#search#set_status(1) 52 | return 53 | elseif filename =~ '^\(../\|./\)' 54 | " remove `./` and `../` 55 | while filename =~ '^\(../\|./\)' 56 | let filename = substitute(filename, '^\(../\|./\)', '', 'g') 57 | endwhile 58 | else 59 | " pass 60 | endif 61 | 62 | let path = '.,**5' 63 | let root = skylight#path#get_root() 64 | if !empty(root) 65 | let path .= ';' . root 66 | endif 67 | 68 | let vim = skylight#async#new() 69 | let cmd = [ 70 | \ printf('let F = findfile("%s", "%s", -1)', filename, path), 71 | \ printf('let results = map(F, { _,v -> #{filename: v} })'), 72 | \ printf('if !empty("%s")', lnumstr), 73 | \ printf('call map(results, { _,v -> #{filename: v.filename, lnum: %s} })', str2nr(lnumstr)), 74 | \ printf('endif'), 75 | \ printf('call rpcrequest(1, "vim_call_function", "skylight#search#callback", [results])'), 76 | \ printf('quit!') 77 | \ ] 78 | call vim.cmd(cmd, 1) 79 | call timer_start(3000, { -> s:stop_findfile(vim) }) 80 | endfunction 81 | 82 | function! s:stop_findfile(vim) abort 83 | if skylight#search#get_status() != 1 84 | call skylight#util#show_msg('Stop searching files due to timeout', 'info') 85 | endif 86 | call skylight#async#close(a:vim) 87 | endfunction 88 | -------------------------------------------------------------------------------- /autoload/skylight/buffer.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: buffer.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | let s:bufnr = -1 8 | function! skylight#buffer#load_buf(filename) abort 9 | if bufloaded(a:filename) 10 | let s:bufnr = bufnr(a:filename) 11 | else 12 | let s:bufnr = bufadd(a:filename) 13 | call nvim_buf_set_option(s:bufnr, 'bufhidden', 'wipe') 14 | call nvim_buf_set_option(s:bufnr, 'buflisted', v:false) 15 | endif 16 | return s:bufnr 17 | endfunction 18 | 19 | let s:ns_id = -1 20 | function! skylight#buffer#add_highlight(location) abort 21 | let s:ns_id = nvim_create_namespace('skylight') 22 | let lnum = line('.') - 1 23 | let start = 0 24 | let end = -1 25 | if has_key(a:location, 'pattern') 26 | let [_, start, end] = matchstrpos(getline('.'), a:location.pattern) 27 | elseif has_key(a:location, 'range') 28 | let start = a:location.range.start.character 29 | let end = a:location.range.end.character 30 | endif 31 | call nvim_buf_add_highlight(s:bufnr, s:ns_id, 'Search', lnum, start, end) 32 | endfunction 33 | 34 | function! skylight#buffer#jump(location) abort 35 | let jmpcmd = '' 36 | if has_key(a:location, 'lnum') && a:location.lnum > -1 37 | let jmpcmd = a:location.lnum 38 | elseif has_key(a:location, 'cmd') && !empty(a:location.cmd) 39 | let jmpcmd = a:location.cmd 40 | endif 41 | if !empty(jmpcmd) 42 | noautocmd execute 'silent keepjumps ' . jmpcmd 43 | return v:true 44 | endif 45 | return v:false 46 | endfunction 47 | 48 | function! skylight#buffer#clear_highlight() abort 49 | if s:ns_id == -1 | return | endif 50 | if !bufexists(s:bufnr) | return | endif 51 | call nvim_buf_clear_namespace(s:bufnr, s:ns_id, 0, -1) 52 | let s:ns_id = -1 53 | endfunction 54 | 55 | function! skylight#buffer#create_border(configs) abort 56 | let repeat_width = a:configs.width - 2 57 | let title_width = strdisplaywidth(a:configs.title) 58 | let [c_top, c_right, c_bottom, c_left, c_topleft, c_topright, c_botright, c_botleft] = a:configs.borderchars 59 | let content = [c_topleft . a:configs.title . repeat(c_top, repeat_width - title_width) . c_topright] 60 | let content += repeat([c_left . repeat(' ', repeat_width) . c_right], a:configs.height-2) 61 | let content += [c_botleft . repeat(c_bottom, repeat_width) . c_botright] 62 | return skylight#buffer#create_scratch_buf(content) 63 | endfunction 64 | 65 | function! skylight#buffer#create_scratch_buf(...) abort 66 | let bufnr = nvim_create_buf(v:false, v:true) 67 | call nvim_buf_set_option(bufnr, 'buftype', 'nofile') 68 | call nvim_buf_set_option(bufnr, 'bufhidden', 'wipe') 69 | call nvim_buf_set_option(bufnr, 'swapfile', v:false) 70 | call nvim_buf_set_option(bufnr, 'undolevels', -1) 71 | let lines = get(a:, 1, v:null) 72 | if type(lines) != 7 73 | call nvim_buf_set_option(bufnr, 'modifiable', v:true) 74 | call nvim_buf_set_lines(bufnr, 0, -1, v:false, lines) 75 | call nvim_buf_set_option(bufnr, 'modifiable', v:false) 76 | endif 77 | return bufnr 78 | endfunction 79 | -------------------------------------------------------------------------------- /autoload/skylight/path.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: path.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " Description: This is borrowed from part of skywind3000/asyncrun 6 | " ============================================================================ 7 | 8 | if has('win32') || has('win64') 9 | let s:is_windows = 1 10 | else 11 | let s:is_windows = 0 12 | endif 13 | 14 | " find project root 15 | function! s:find_root(path, markers, strict) abort 16 | function! s:guess_root(filename, markers) abort 17 | let fullname = s:fullname(a:filename) 18 | if fullname =~ '^fugitive:/' 19 | if exists('b:git_dir') 20 | return fnamemodify(b:git_dir, ':h') 21 | endif 22 | return '' " skip any fugitive buffers early 23 | endif 24 | let pivot = fullname 25 | if !isdirectory(pivot) 26 | let pivot = fnamemodify(pivot, ':h') 27 | endif 28 | while 1 29 | let prev = pivot 30 | for marker in a:markers 31 | let newname = s:path_join(pivot, marker) 32 | if newname =~ '[\*\?\[\]]' 33 | if glob(newname) != '' 34 | return pivot 35 | endif 36 | elseif filereadable(newname) 37 | return pivot 38 | elseif isdirectory(newname) 39 | return pivot 40 | endif 41 | endfor 42 | let pivot = fnamemodify(pivot, ':h') 43 | if pivot == prev 44 | break 45 | endif 46 | endwhile 47 | return '' 48 | endfunction 49 | let root = s:guess_root(a:path, a:markers) 50 | if root != '' 51 | return s:fullname(root) 52 | elseif a:strict != 0 53 | return '' 54 | endif 55 | " Not found: return parent directory of current file / file itself. 56 | let fullname = s:fullname(a:path) 57 | if isdirectory(fullname) 58 | return fullname 59 | endif 60 | return s:fullname(fnamemodify(fullname, ':h')) 61 | endfunction 62 | 63 | " Replace string 64 | function! s:StringReplace(text, old, new) abort 65 | let l:data = split(a:text, a:old, 1) 66 | return join(l:data, a:new) 67 | endfunction 68 | 69 | function! s:fullname(f) abort 70 | let f = a:f 71 | if f =~ "'." 72 | try 73 | redir => m 74 | silent exe ':marks' f[1] 75 | redir END 76 | let f = split(split(m, '\n')[-1])[-1] 77 | let f = filereadable(f)? f : '' 78 | catch 79 | let f = '%' 80 | endtry 81 | endif 82 | let f = (f != '%')? f : expand('%') 83 | let f = fnamemodify(f, ':p') 84 | if s:is_windows 85 | let f = substitute(f, "\\", '/', 'g') 86 | endif 87 | if len(f) > 1 88 | let size = len(f) 89 | if f[size - 1] == '/' 90 | let f = strpart(f, 0, size - 1) 91 | endif 92 | endif 93 | return f 94 | endfunction 95 | 96 | function! s:path_join(home, name) abort 97 | let l:size = strlen(a:home) 98 | if l:size == 0 | return a:name | endif 99 | let l:last = strpart(a:home, l:size - 1, 1) 100 | if has("win32") || has("win64") || has("win16") || has('win95') 101 | let l:first = strpart(a:name, 0, 1) 102 | if l:first == "/" || l:first == "\\" 103 | let head = strpart(a:home, 1, 2) 104 | if index([":\\", ":/"], head) >= 0 105 | return strpart(a:home, 0, 2) . a:name 106 | endif 107 | return a:name 108 | elseif index([":\\", ":/"], strpart(a:name, 1, 2)) >= 0 109 | return a:name 110 | endif 111 | if l:last == "/" || l:last == "\\" 112 | return a:home . a:name 113 | else 114 | return a:home . '/' . a:name 115 | endif 116 | else 117 | if strpart(a:name, 0, 1) == '/' 118 | return a:name 119 | endif 120 | if l:last == "/" 121 | return a:home . a:name 122 | else 123 | return a:home . '/' . a:name 124 | endif 125 | endif 126 | endfunction 127 | 128 | function! skylight#path#get_root() abort 129 | let markers = ['.git', '.root'] 130 | let strict = 0 131 | let l:hr = s:find_root(getcwd(), markers, strict) 132 | if s:is_windows 133 | let l:hr = s:StringReplace(l:hr, '/', "\\") 134 | endif 135 | return l:hr 136 | endfunction 137 | 138 | function! skylight#path#chdir(path) abort 139 | if has('nvim') 140 | let cmd = haslocaldir()? 'lcd' : (haslocaldir(-1, 0)? 'tcd' : 'cd') 141 | else 142 | let cmd = haslocaldir()? ((haslocaldir() == 1)? 'lcd' : 'tcd') : 'cd' 143 | endif 144 | silent execute cmd . ' '. fnameescape(a:path) 145 | endfunction 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-skylight 2 | 3 | Search asynchronously and preview file/symbol/word under cursor in the floating window. 4 | 5 | ![](https://user-images.githubusercontent.com/20282795/107115215-fc7ee800-68a5-11eb-88cf-b66bb2c64257.png) 6 | 7 | - [Installation](#installation) 8 | - [Commands](#commands) 9 | - [Options](#options) 10 | - [Functions](#functions) 11 | - [Keymaps](#keymaps) 12 | - [Highlights](#highlights) 13 | - [Why](#why) 14 | - [More demos](#demos) 15 | 16 | ## Rationale 17 | 18 | File searching is initially inspired by vim's `gf`. It fundamentally works by 19 | invoking the build-in function `findfile()` to perform upward (up to the root 20 | directory) and downward searching but asynchronously. It will never block your 21 | actions. 22 | 23 | Symbol searching basically invokes `taglist()` function asynchronously to 24 | search for the pattern from pre-generated tag files. In addition, the plugin 25 | can also use LSP for searching symbols (both definition and references). 26 | Therefore it would be better to have an LSP client (only support [coc.nvim][1] 27 | by now) installed. 28 | 29 | Word searching will search for the `` in the current buffer. 30 | 31 | ## Installation 32 | 33 | ```vim 34 | Plug 'voldikss/vim-skylight' 35 | ``` 36 | 37 | Only works in NVIM >= 0.4.3 38 | 39 | ## Usage 40 | 41 | #### `:Skylight[!] [...]` search and open a drop-down menu to display the results 42 | 43 | - If `!` is given, perform live previewing for multiple entries 44 | - If use with an optional argument: 45 | - `:Skylight file` regard the text under cursor as a filepath and search 46 | - `:Skylight symbol` regard the text under cursor as a symbol and search 47 | - `:Skylight word`search for the `` in the current buffer 48 | - If without arguments (i.e., `:Skylight`), it firstly suppose the text is a 49 | filename and search. If failing to search then treat the text as a symbol 50 | and search again 51 | 52 | - In the drop-down menu, you can use: 53 | - `j` or `k` to move (and perform live previewing if the menu is opened by `: Skylight!`) 54 | - `` for previewing or jumping to 55 | - `` or `q` for closing 56 | - number keys(`1`, `2`, ...) to quickly jump to the corresponding entry 57 | - `p` to jump to the skylight window and jump back 58 | 59 | This command can also be used with a range, e.g., visual select and `'<,'>:Skylight! file`. 60 | 61 | ## Options 62 | 63 | - **`g:skylight_width`** 64 | 65 | Type `Number` (number of columns) or `Float` (between 0 and 1). If `Float`, 66 | the width is relative to `&columns`. 67 | 68 | Default: `0.5` 69 | 70 | - **`g:skylight_height`** 71 | 72 | Type `Number` (number of lines) or `Float` (between 0 and 1). If `Float`, the 73 | height is relative to `&lines`. 74 | 75 | Default: `0.5` 76 | 77 | - **`g:skylight_position`** 78 | 79 | Type `String`. The position of the floating window. 80 | 81 | Available: `'top'`, `'right'`, `'bottom'`, `'left'`, `'center'`, `'topleft'`, 82 | `'topright'`, `'bottomleft'`, `'bottomright'`, `'auto'(at the cursor place) `. 83 | 84 | Default: `'topright'`(recommended) 85 | 86 | - **`g:skylight_borderchars`** 87 | 88 | Type `List` of `String`. Characters for the border. 89 | 90 | Default: `['─', '│', '─', '│', '╭', '╮', '╯', '╰']` 91 | 92 | - **`g:skylight_opener`** 93 | 94 | Type `String`. Command used for jumping to 95 | 96 | Available: `'edit'`, `'split'`, `'vsplit'`, `'tabe'`, `'drop'`. 97 | 98 | Default: `'edit'` 99 | 100 | ## Keymaps 101 | 102 | ```vim 103 | " Configuration example 104 | nnoremap gp :Skylight! 105 | vnoremap gp :Skylight! 106 | ``` 107 | 108 | In the skylight-menu window, use `` to scroll forward and `` to 109 | scroll backward. 110 | 111 | ## Highlights 112 | 113 | `SkylightBorder`, which is linked to `Normal` by default, can be used to 114 | specify the border style. 115 | 116 | ```vim 117 | " Configuration example 118 | hi SkylightBorder guibg=orange guifg=cyan 119 | ``` 120 | 121 | ## Why 122 | 123 | For a long time I was hoping to preview file under cursor in a disposable 124 | floating window without actually opening it. Then I dug into the web and found 125 | some awesome projects, which, however, only support previewing files that are 126 | listed only in the quickfix window. What I want is to preview the file that is 127 | given by either relative or absolute path and occurs anywhere in vim, even 128 | those in the builtin terminal window (when I am using gdb's `bt` command). 129 | 130 | The codes were initially buildup in my personal dotfiles. After the whole 131 | feature was almost implemented, I decided to detach them from the dotfiles and 132 | reorganize them into a plugin in case of someone who has the same requirement 133 | needs it. 134 | 135 | ## Known issues 136 | 137 | Sometimes can not find the files in the hidden folders when performing 138 | downward searching. That is because vim's file searching doesn't include 139 | hidden directories, I am considering using another suitable file-finder cli 140 | tool for the plugin but this will not come out in the short term. 141 | 142 | Note that the plugin is developed with pure vimscript. Therefore, until the 143 | whole file content has been loaded into memory could it be previewed in the 144 | skylight window, so it's not recommended to perform `:Skylight[!]` on large 145 | size files as it will cost more time for nvim to open that file. 146 | 147 | ## Demos 148 | 149 | - with live preview 150 | ![](https://user-images.githubusercontent.com/20282795/107115209-f2f58000-68a5-11eb-9880-a260058e54cb.gif) 151 | 152 | - without live preview 153 | ![](https://user-images.githubusercontent.com/20282795/107115213-f6890700-68a5-11eb-91d8-7beecee2262c.gif) 154 | 155 | - quickfix 156 | ![](https://user-images.githubusercontent.com/20282795/103435745-4a248700-4c4e-11eb-943f-4aa78fb801f9.gif) 157 | 158 | - terminal 159 | ![](https://user-images.githubusercontent.com/20282795/103435599-d7b2a780-4c4b-11eb-94c6-a05398145c2f.gif) 160 | 161 | [1]: (https://github.com/neoclide/coc.nvim) 162 | -------------------------------------------------------------------------------- /autoload/skylight/cocf.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: cocf.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " Description: *F*unctions copied from coc.nvim *f*loat.vim 6 | " ============================================================================ 7 | 8 | " max firstline of lines, height > 0, width > 0 9 | function! s:max_firstline(lines, height, width) abort 10 | let max = len(a:lines) 11 | let remain = a:height 12 | for line in reverse(copy(a:lines)) 13 | let w = max([1, strdisplaywidth(line)]) 14 | let dh = float2nr(ceil(str2float(string(w))/a:width)) 15 | if remain - dh < 0 16 | break 17 | endif 18 | let remain = remain - dh 19 | let max = max - 1 20 | endfor 21 | return min([len(a:lines), max + 1]) 22 | endfunction 23 | 24 | " Get best lnum by topline 25 | function! s:get_cursorline(topline, lines, scrolloff, width, height) abort 26 | let lastline = len(a:lines) 27 | if a:topline == lastline 28 | return lastline 29 | endif 30 | let bottomline = a:topline 31 | let used = 0 32 | for lnum in range(a:topline, lastline) 33 | let w = max([1, strdisplaywidth(a:lines[lnum - 1])]) 34 | let dh = float2nr(ceil(str2float(string(w))/a:width)) 35 | let g:l = a:lines 36 | if used + dh >= a:height || lnum == lastline 37 | let bottomline = lnum 38 | break 39 | endif 40 | let used += dh 41 | endfor 42 | let cursorline = a:topline + a:scrolloff 43 | let g:b = bottomline 44 | let g:h = a:height 45 | if cursorline + a:scrolloff > bottomline 46 | " unable to satisfy scrolloff 47 | let cursorline = (a:topline + bottomline)/2 48 | endif 49 | return cursorline 50 | endfunction 51 | 52 | " Get firstline for full scroll 53 | function! s:get_topline(topline, lines, forward, height, width) abort 54 | let used = 0 55 | let lnums = a:forward ? range(a:topline, len(a:lines)) : reverse(range(1, a:topline)) 56 | let topline = a:forward ? len(a:lines) : 1 57 | for lnum in lnums 58 | let w = max([1, strdisplaywidth(a:lines[lnum - 1])]) 59 | let dh = float2nr(ceil(str2float(string(w))/a:width)) 60 | if used + dh >= a:height 61 | let topline = lnum 62 | break 63 | endif 64 | let used += dh 65 | endfor 66 | if topline == a:topline 67 | if a:forward 68 | let topline = min([len(a:lines), topline + 1]) 69 | else 70 | let topline = max([1, topline - 1]) 71 | endif 72 | endif 73 | return topline 74 | endfunction 75 | 76 | " topline content_height content_width 77 | function! s:get_options(winid) abort 78 | if has('nvim') 79 | let width = nvim_win_get_width(a:winid) 80 | if getwinvar(a:winid, '&foldcolumn', 0) 81 | let width = width - 1 82 | endif 83 | let info = getwininfo(a:winid)[0] 84 | return { 85 | \ 'topline': info['topline'], 86 | \ 'height': nvim_win_get_height(a:winid), 87 | \ 'width': width 88 | \ } 89 | else 90 | let pos = popup_getpos(a:winid) 91 | return { 92 | \ 'topline': pos['firstline'], 93 | \ 'width': pos['core_width'], 94 | \ 'height': pos['core_height'] 95 | \ } 96 | endif 97 | endfunction 98 | 99 | function! s:win_execute(winid, command) abort 100 | let curr = nvim_get_current_win() 101 | noa keepalt call win_gotoid(a:winid) 102 | exec a:command 103 | noa keepalt call win_gotoid(curr) 104 | endfunction 105 | 106 | function! s:win_setview(winid, topline, lnum) abort 107 | let cmd = 'call winrestview({"lnum":'.a:lnum.',"topline":'.a:topline.'})' 108 | call s:win_execute(a:winid, cmd) 109 | call timer_start(10, { -> skylight#cocf#refresh_scroll_bar(a:winid) }) 110 | endfunction 111 | 112 | function! skylight#cocf#scroll_win(winid, forward, amount) abort 113 | let opts = s:get_options(a:winid) 114 | let lines = getbufline(winbufnr(a:winid), 1, '$') 115 | let maxfirst = s:max_firstline(lines, opts['height'], opts['width']) 116 | let topline = opts['topline'] 117 | let height = opts['height'] 118 | let width = opts['width'] 119 | let scrolloff = getwinvar(a:winid, '&scrolloff', 0) 120 | if a:forward && topline >= maxfirst 121 | return 122 | endif 123 | if !a:forward && topline == 1 124 | return 125 | endif 126 | if a:amount == 0 127 | let topline = s:get_topline(opts['topline'], lines, a:forward, height, width) 128 | else 129 | let topline = topline + (a:forward ? a:amount : - a:amount) 130 | endif 131 | let topline = a:forward ? min([maxfirst, topline]) : max([1, topline]) 132 | let lnum = s:get_cursorline(topline, lines, scrolloff, width, height) 133 | call s:win_setview(a:winid, topline, lnum) 134 | let top = s:get_options(a:winid)['topline'] 135 | " not changed 136 | if top == opts['topline'] 137 | if a:forward 138 | call s:win_setview(a:winid, topline + 1, lnum + 1) 139 | else 140 | call s:win_setview(a:winid, topline - 1, lnum - 1) 141 | endif 142 | endif 143 | endfunction 144 | 145 | function! skylight#cocf#refresh_scroll_bar(winid) abort 146 | let bufnr = nvim_win_get_buf(a:winid) 147 | let height = nvim_win_get_height(a:winid) 148 | let linecount = nvim_buf_line_count(bufnr) 149 | 150 | let wininfo = getwininfo(a:winid)[0] 151 | let thumb_start = 0 152 | let thumb_length = max([1, float2nr(floor(height * (height + 0.0)/linecount))]) 153 | if wininfo['topline'] != 1 154 | let topline = wininfo['topline'] 155 | let botline = wininfo['botline'] 156 | if botline >= linecount 157 | let thumb_start = height - thumb_length 158 | else 159 | let thumb_start = max([1, float2nr(round((height - thumb_length + 0.0)*(topline - 1.0)/(linecount - height)))]) 160 | endif 161 | endif 162 | 163 | let sb_winid = nvim_win_get_var(a:winid, 'scroll_winid') 164 | if !nvim_win_is_valid(sb_winid) | return | endif 165 | let sb_bufnr = nvim_win_get_buf(sb_winid) 166 | call nvim_buf_clear_namespace(sb_bufnr, -1, 0, -1) 167 | for idx in range(0, height - 1) 168 | if idx >= thumb_start && idx < thumb_start + thumb_length 169 | call nvim_buf_add_highlight(sb_bufnr, -1, 'PmenuThumb', idx, 0, 1) 170 | else 171 | call nvim_buf_add_highlight(sb_bufnr, -1, 'PmenuSbar', idx, 0, 1) 172 | endif 173 | endfor 174 | endfunction 175 | -------------------------------------------------------------------------------- /ftplugin/skylightmenu.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: skylightmenu.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | let s:menu_details = nvim_buf_get_var(bufnr(), 'menu_details') 8 | let s:menu_live_preview = nvim_buf_get_var(bufnr(), 'menu_live_preview') 9 | let s:menu_saved_winid = nvim_buf_get_var(bufnr(), 'menu_saved_winid') 10 | 11 | let s:block_restore_cursor = v:false 12 | 13 | augroup skylight_menu 14 | autocmd! 15 | autocmd CursorMoved 16 | \ if s:menu_live_preview || len(s:menu_details) == 1 | 17 | \ call s:preview() | 18 | \ endif 19 | autocmd BufWipeout call s:restore_cursor() 20 | augroup END 21 | 22 | mapclear 23 | nnoremap (close) :call close() 24 | nnoremap (down) :call wrap_move('down') 25 | nnoremap (up) :call wrap_move('up') 26 | if s:menu_live_preview 27 | nnoremap (accept) :call jumpto() 28 | function! s:preview() abort 29 | call skylight#preview(s:get_selected_info()) 30 | endfunction 31 | function! s:close() abort 32 | call skylight#float#close() 33 | call s:close_menu() 34 | endfunction 35 | function! s:jumpto() abort 36 | let s:block_restore_cursor = v:true 37 | let location = s:get_selected_info() 38 | call s:close() 39 | call skylight#jumpto(location) 40 | endfunction 41 | else 42 | nnoremap (accept) :call preview() 43 | function! s:preview() abort 44 | let location = s:get_selected_info() 45 | call s:close_menu() 46 | call skylight#preview(location) 47 | endfunction 48 | function! s:close() abort 49 | call s:close_menu() 50 | endfunction 51 | endif 52 | 53 | function! s:close_menu() abort 54 | if &filetype == 'skylightmenu' 55 | call nvim_win_close(0, v:true) 56 | endif 57 | endfunction 58 | 59 | function! s:get_selected_info() abort 60 | let locations = s:menu_details 61 | let index = getpos('.')[1] - 1 62 | return locations[index] 63 | endfunction 64 | 65 | function! s:wrap_move(direction) abort 66 | let currlnum = line('.') 67 | let lastlnum = line('$') 68 | if currlnum == lastlnum && a:direction == 'down' 69 | normal! 1gg 70 | elseif currlnum == 1 && a:direction == 'up' 71 | normal! G 72 | else 73 | if a:direction == 'down' 74 | normal! j 75 | else 76 | normal! k 77 | endif 78 | endif 79 | endfunction 80 | 81 | function! s:restore_cursor() abort 82 | if s:block_restore_cursor 83 | return 84 | endif 85 | function! s:restore() abort 86 | " noautocmd: prevent from closing skylight preview window 87 | " which is opened by executing `:Skylight` and type Enter 88 | noa call nvim_set_current_win(s:menu_saved_winid) 89 | endfunction 90 | if nvim_get_current_win() != s:menu_saved_winid 91 | call timer_start(10, { -> s:restore() }) 92 | endif 93 | endfunction 94 | 95 | 96 | " modified from coc/prompt.vim 97 | "============================================================================= 98 | function! s:prompt_getc() abort 99 | let c = getchar() 100 | return type(c) == type(0) ? nr2char(c) : c 101 | endfunction 102 | 103 | function! s:prompt_getchar() abort 104 | let input = s:prompt_getc() 105 | if 1 != &iminsert 106 | return input 107 | endif 108 | "a language keymap is activated, so input must be resolved to the ch values. 109 | let partial_keymap = mapcheck(input, "l") 110 | while partial_keymap !=# "" 111 | let full_keymap = maparg(input, "l") 112 | if full_keymap ==# "" && len(input) >= 3 "HACK: assume there are no keymaps longer than 3. 113 | return input 114 | elseif full_keymap ==# partial_keymap 115 | return full_keymap 116 | endif 117 | let c = s:prompt_getc() 118 | if c ==# "\" || c ==# "\" 119 | "if the short sequence has a valid mapping, return that. 120 | if !empty(full_keymap) 121 | return full_keymap 122 | endif 123 | return input 124 | endif 125 | let input .= c 126 | let partial_keymap = mapcheck(input, "l") 127 | endwhile 128 | return input 129 | endfunction 130 | 131 | function! s:start_prompt() 132 | try 133 | while 1 134 | let ch = s:prompt_getchar() 135 | if ch ==# "\" || ch ==# "\" || ch ==# "\" 136 | continue 137 | else 138 | if ch == "\" || ch == "q" 139 | execute "normal \(close)" 140 | stopinsert 141 | return 142 | elseif ch == "\" || ch == "l" || ch == "h" 143 | execute "normal \(accept)" 144 | return 145 | elseif ch == "j" || ch == "\" 146 | execute "normal \(down)" 147 | doautocmd CursorMoved 148 | redraw 149 | elseif ch == "k" || ch == "\" 150 | execute "normal \(up)" 151 | doautocmd CursorMoved 152 | redraw 153 | elseif index(range(1, 10), str2nr(ch)) > -1 154 | execute ch 155 | if !s:menu_live_preview 156 | execute "normal \(accept)" 157 | return 158 | else 159 | doautocmd CursorMoved 160 | redraw 161 | endif 162 | elseif ch == "\" || ch == "\" 163 | autocmd CursorMoved ++once redraw | call s:start_prompt() 164 | if ch == "\" 165 | call skylight#float#scroll(1, 3) 166 | elseif ch == "\" 167 | call skylight#float#scroll(0, 3) 168 | endif 169 | return 170 | elseif ch == "\" 171 | autocmd CursorMoved ++once redraw | call s:start_prompt() 172 | let suffix = s:prompt_getc() 173 | if suffix == "p" 174 | wincmd p 175 | return 176 | endif 177 | endif 178 | endif 179 | endwhile 180 | catch /^Vim:Interrupt$/ 181 | return 182 | endtry 183 | endfunction 184 | 185 | nmap (accept) 186 | nmap (close) 187 | nmap (up) 188 | nmap (down) 189 | nmap q (close) 190 | nmap k (up) 191 | nmap j (down) 192 | 193 | call timer_start(150, {->s:start_prompt()}) 194 | -------------------------------------------------------------------------------- /autoload/skylight/float.vim: -------------------------------------------------------------------------------- 1 | " ============================================================================ 2 | " FileName: float.vim 3 | " Author: voldikss 4 | " GitHub: https://github.com/voldikss 5 | " ============================================================================ 6 | 7 | function! s:calculate_float_pos(width, height, pos) abort 8 | if a:pos == 'topright' 9 | let row = 1 10 | let col = &columns 11 | let anchor = 'NE' 12 | elseif a:pos == 'topleft' 13 | let row = 1 14 | let col = 0 15 | let anchor = 'NW' 16 | elseif a:pos == 'bottomright' 17 | let row = &lines - &cmdheight - 1 18 | let col = &columns 19 | let anchor = 'SE' 20 | elseif a:pos == 'bottomleft' 21 | let row = &lines - &cmdheight - 1 22 | let col = 0 23 | let anchor = 'SW' 24 | elseif a:pos == 'top' 25 | let row = 1 26 | let col = (&columns - a:width)/2 27 | let anchor = 'NW' 28 | elseif a:pos == 'right' 29 | let row = (&lines - a:height)/2 30 | let col = &columns 31 | let anchor = 'NE' 32 | elseif a:pos == 'bottom' 33 | let row = &lines - &cmdheight - 1 34 | let col = (&columns - a:width)/2 35 | let anchor = 'SW' 36 | elseif a:pos == 'left' 37 | let row = (&lines - a:height)/2 38 | let col = 0 39 | let anchor = 'NW' 40 | elseif a:pos == 'center' 41 | let row = (&lines - a:height)/2 42 | let col = (&columns - a:width)/2 43 | let anchor = 'NW' 44 | if row < 0 45 | let row = 0 46 | endif 47 | if col < 0 48 | let col = 0 49 | endif 50 | else " at the cursor place 51 | let winpos = win_screenpos(0) 52 | let row = winpos[0] - 1 + winline() 53 | let col = winpos[1] - 1 + wincol() 54 | if row + a:height <= &lines - &cmdheight - 1 55 | let vert = 'N' 56 | else 57 | let vert = 'S' 58 | let row -= 1 59 | endif 60 | if col + a:width <= &columns 61 | let hor = 'W' 62 | else 63 | let hor = 'E' 64 | endif 65 | let anchor = vert . hor 66 | endif 67 | if !has('nvim') 68 | let anchor = substitute(anchor, '\CN', 'top', '') 69 | let anchor = substitute(anchor, '\CS', 'bot', '') 70 | let anchor = substitute(anchor, '\CW', 'left', '') 71 | let anchor = substitute(anchor, '\CE', 'right', '') 72 | endif 73 | return [row, col, anchor] 74 | endfunction 75 | 76 | function! s:win_exists(winid) abort 77 | return !empty(getwininfo(a:winid)) 78 | endfunction 79 | 80 | function! s:register_autocmd() abort 81 | augroup close_skylight_float 82 | autocmd! 83 | autocmd CursorMoved * call timer_start(100, { -> skylight#float#close() }) 84 | augroup END 85 | endfunction 86 | 87 | function! skylight#float#close() abort 88 | if nvim_get_current_win() == s:winid | return | endif 89 | call skylight#buffer#clear_highlight() 90 | if s:win_exists(s:winid) 91 | call nvim_win_close(s:winid, v:true) 92 | let s:winid = -1 93 | endif 94 | if s:win_exists(s:bd_winid) 95 | call nvim_win_close(s:bd_winid, v:true) 96 | let s:bd_winid = -1 97 | endif 98 | if s:win_exists(s:sb_winid) 99 | call nvim_win_close(s:sb_winid, v:true) 100 | let s:sb_winid = -1 101 | endif 102 | if exists('#close_skylight_float') 103 | autocmd! close_skylight_float 104 | endif 105 | if exists('#refresh_scroll_bar') 106 | autocmd! refresh_scroll_bar 107 | endif 108 | endfunction 109 | 110 | function! skylight#float#locate(winid, location) abort 111 | noautocmd call nvim_set_current_win(a:winid) 112 | execute 'doautocmd filetypedetect BufNewFile' 113 | if skylight#buffer#jump(a:location) 114 | call skylight#buffer#add_highlight(a:location) 115 | endif 116 | augroup refresh_scroll_bar 117 | autocmd! 118 | execute printf( 119 | \ 'autocmd CursorMoved call skylight#cocf#refresh_scroll_bar(%s)', 120 | \ nvim_win_get_buf(a:winid), 121 | \ a:winid 122 | \ ) 123 | augroup END 124 | noautocmd wincmd p 125 | endfunction 126 | 127 | function! skylight#float#open(bufnr, configs) abort 128 | let [ 129 | \ a:configs.row, 130 | \ a:configs.col, 131 | \ a:configs.anchor 132 | \ ] = s:calculate_float_pos( 133 | \ a:configs.width, 134 | \ a:configs.height, 135 | \ a:configs.position 136 | \ ) 137 | let winid = s:nvim_create_skylight_win(a:bufnr, a:configs) 138 | call s:nvim_create_scroll_win(winid, a:configs) 139 | call s:nvim_create_border_win(winid, a:configs) 140 | call timer_start(100, { -> s:register_autocmd() }) 141 | return winid 142 | endfunction 143 | 144 | let s:winid = -1 145 | function! s:nvim_create_skylight_win(bufnr, configs) abort 146 | let options = { 147 | \ 'relative': 'editor', 148 | \ 'anchor': a:configs.anchor, 149 | \ 'row': a:configs.row + (a:configs.anchor[0] == 'N' ? 1 : -1), 150 | \ 'col': a:configs.col + (a:configs.anchor[1] == 'W' ? 1 : -2), 151 | \ 'width': a:configs.width - 3, 152 | \ 'height': a:configs.height - 2, 153 | \ 'style':'minimal', 154 | \ } 155 | let winid = nvim_open_win(a:bufnr, v:false, options) 156 | call nvim_win_set_option(winid, 'number', v:true) 157 | call nvim_win_set_option(winid, 'signcolumn', 'no') 158 | let s:winid = winid 159 | return winid 160 | endfunction 161 | 162 | let s:bd_winid = -1 163 | function! s:nvim_create_border_win(winid, configs) abort 164 | let bd_options = { 165 | \ 'relative': 'editor', 166 | \ 'anchor': a:configs.anchor, 167 | \ 'row': a:configs.row, 168 | \ 'col': a:configs.col, 169 | \ 'width': a:configs.width, 170 | \ 'height': a:configs.height, 171 | \ 'focusable': v:false, 172 | \ 'style':'minimal', 173 | \ } 174 | let bd_bufnr = skylight#buffer#create_border(a:configs) 175 | let bd_winid = nvim_open_win(bd_bufnr, v:false, bd_options) 176 | call nvim_win_set_option(bd_winid, 'winhl', 'Normal:SkylightBorder') 177 | call nvim_win_set_var(a:winid, 'border_winid', bd_winid) 178 | let s:bd_winid = bd_winid 179 | return bd_winid 180 | endfunction 181 | 182 | let s:sb_winid = -1 183 | function! s:nvim_create_scroll_win(winid, configs) abort 184 | let options = { 185 | \ 'relative': 'editor', 186 | \ 'anchor': a:configs.anchor, 187 | \ 'row': a:configs.row + (a:configs.anchor[0] == 'N' ? 1 : -1), 188 | \ 'col': a:configs.col + (a:configs.anchor[1] == 'W' ? (a:configs.width-2) : -1), 189 | \ 'width': 1, 190 | \ 'height': a:configs.height - 2, 191 | \ 'style': 'minimal', 192 | \ } 193 | let sb_bufnr = skylight#buffer#create_scratch_buf(repeat([' '], a:configs.height - 2)) 194 | let sb_winid = nvim_open_win(sb_bufnr, v:false, options) 195 | call nvim_win_set_var(a:winid, 'scroll_winid', sb_winid) 196 | call skylight#cocf#refresh_scroll_bar(a:winid) 197 | let s:sb_winid = sb_winid 198 | return sb_winid 199 | endfunction 200 | 201 | function! skylight#float#has_scroll() abort 202 | return s:win_exists(s:winid) && s:win_exists(s:sb_winid) 203 | endfunction 204 | 205 | function! skylight#float#scroll(forward, ...) abort 206 | let amount = get(a:, 1, 0) 207 | if !s:win_exists(s:winid) 208 | call skylight#util#show_msg('No skylight windows', 'error') 209 | else 210 | call skylight#cocf#scroll_win(s:winid, a:forward, amount) 211 | endif 212 | return mode() =~ '^i' || mode() ==# 'v' ? "" : "\" 213 | endfunction 214 | 215 | function! skylight#float#create_menu(lines, live_preview, details) abort 216 | call map(a:lines, { k,v -> printf('%s. %s', k+1, v) }) 217 | let bufnr = skylight#buffer#create_scratch_buf(a:lines) 218 | call nvim_buf_set_var(bufnr, 'menu_live_preview', a:live_preview) 219 | call nvim_buf_set_var(bufnr, 'menu_details', a:details) 220 | call nvim_buf_set_var(bufnr, 'menu_saved_winid', nvim_get_current_win()) 221 | call nvim_buf_set_option(bufnr, 'filetype', 'skylightmenu') 222 | call nvim_buf_set_option(bufnr, 'buftype', 'nofile') 223 | 224 | let options = { 225 | \ 'width': max(map(copy(a:lines), { _,v -> len(v) })) + 1, 226 | \ 'height': len(a:lines), 227 | \ 'relative': 'editor', 228 | \ 'style': 'minimal', 229 | \} 230 | let [ 231 | \ options.row, 232 | \ options.col, 233 | \ options.anchor 234 | \ ] = s:calculate_float_pos( 235 | \ options.width, 236 | \ options.height, 237 | \ 'auto' 238 | \ ) 239 | let winid = nvim_open_win(bufnr, v:true, options) 240 | call nvim_win_set_option(winid, 'foldcolumn', '1') 241 | call nvim_win_set_option(winid, 'cursorline', v:true) 242 | call nvim_win_set_option(winid, 'signcolumn', 'no') 243 | call nvim_win_set_option(winid, 'winhl', 'FoldColumn:PmenuSel,Normal:Pmenu,CursorLine:PmenuSel') 244 | call nvim_win_set_cursor(winid, [1, 0]) 245 | endfunction 246 | -------------------------------------------------------------------------------- /doc/skylight.txt: -------------------------------------------------------------------------------- 1 | *skylight.txt* vim-skylight 2 | 3 | =============================================================================== 4 | Contents ~ 5 | 6 | 1. Introduction |skylight-introduction| 7 | 2. Rationale |skylight-rationale| 8 | 3. Installation |skylight-installation| 9 | 4. Commands |skylight-commands| 10 | 1. :Skylight |skylight| 11 | 5. Options |skylight-options| 12 | 1. g:skylight_width |g:skylight_width| 13 | 2. g:skylight_height |g:skylight_height| 14 | 3. g:skylight_position |g:skylight_position| 15 | 4. g:skylight_borderchars |g:skylight_borderchars| 16 | 5. g:skylight_opener |g:skylight_opener| 17 | 6. Keymaps |skylight-keymaps| 18 | 7. Highlights |skylight-highlights| 19 | 8. Why |skylight-why| 20 | 9. Known issues |skylight-known-issues| 21 | 10. Screenshots |skylight-screenshots| 22 | 11. References |skylight-references| 23 | 24 | =============================================================================== 25 | *skylight-introduction* 26 | Introduction ~ 27 | 28 | Search asynchronously and preview file/symbol under cursor in the floating window. 29 | 30 | =============================================================================== 31 | *skylight-rationale* 32 | Rationale ~ 33 | 34 | File searching is initially inspired by vim's |gf|. It fundamentally works by 35 | invoking the build-in function |findfile()| to perform upward (up to the root 36 | directory) and downward searching but asynchronously. So it will never block 37 | your actions. 38 | 39 | Symbol searching basically invokes |taglist()| function asynchronously to 40 | search for the pattern from pre-generated tag files. In addition, the plugin 41 | can also use LSP for searching symbols (both definition and references). 42 | Therefore it would be better to have an LSP client (only support [coc.nvim][1] 43 | by now) installed. 44 | 45 | Word searching will search for the || in the current buffer. 46 | 47 | =============================================================================== 48 | *skylight-installation* 49 | Installation ~ 50 | > 51 | Plug 'voldikss/vim-skylight' 52 | < 53 | Only works in NVIM >= 0.4.3 54 | 55 | =============================================================================== 56 | *skylight-commands* 57 | Commands ~ 58 | 59 | ------------------------------------------------------------------------------- 60 | *Skylight* 61 | 62 | Skylight~ 63 | 64 | `:Skylight[!] [...]` search and open a drop-down menu to display the results 65 | 66 | - If `!` is given, perform live previewing for multiple entries 67 | - If use with an optional argument: 68 | - `:Skylight file` regard the text under cursor as a filepath and search 69 | - `:Skylight symbol` regard the text under cursor as a symbol and search 70 | - `:Skylight word` search for the `` in the current buffer 71 | - If without arguments (i.e., `:Skylight`), it firstly suppose the text is a 72 | filename and search. If failing to search then treat the text as a symbol 73 | and search again 74 | 75 | - In the drop-down menu, you can use: 76 | - `j` or `k` to move (and perform live previewing if the menu is opened by `: Skylight!`) 77 | - `` for previewing or jumping to 78 | - `` or `q` for closing 79 | - number keys(`1`, `2`, ...) to quickly jump to the corresponding entry 80 | - `p` to jump to the skylight window and jump back 81 | 82 | This command can also be used with a range, e.g., visual select and `'<,'>:Skylight! file`. 83 | 84 | =============================================================================== 85 | *skylight-options* 86 | Options ~ 87 | 88 | ------------------------------------------------------------------------------- 89 | *g:skylight_width* 90 | g:skylight_width ~ 91 | 92 | Type |Number| (number of columns) or |Float| (between 0 and 1). If |Float|, the 93 | width is relative to '&columns'. 94 | 95 | Default: '0.5' 96 | 97 | ------------------------------------------------------------------------------- 98 | *g:skylight_height* 99 | g:skylight_height ~ 100 | 101 | Type |Number| (number of lines) or |Float| (between 0 and 1). If |Float|, the 102 | height is relative to '&lines'. 103 | 104 | Default: '0.5' 105 | 106 | ------------------------------------------------------------------------------- 107 | *g:skylight_position* 108 | g:skylight_position ~ 109 | 110 | Type |String|. The position of the floating window. 111 | 112 | Available: "'top'", "'right'", "'bottom'", "'left'", "'center'", "'topleft'", 113 | "'topright'", "'bottomleft'", "'bottomright'", "'auto'(at the cursor place)". 114 | 115 | Default: "'topright'"(recommended) 116 | 117 | ------------------------------------------------------------------------------- 118 | *g:skylight_borderchars* 119 | g:skylight_borderchars ~ 120 | 121 | Type |List| of |String|. Characters for the border. 122 | 123 | Default: "['─', '│', '─', '│', '╭', '╮', '╯', '╰']" 124 | 125 | ------------------------------------------------------------------------------- 126 | *g:skylight_opener* 127 | g:skylight_opener ~ 128 | 129 | Type |String|. Command used for jumping to 130 | 131 | Available: "'edit'", "'split'", "'vsplit'", "'tabe'", "'drop'". 132 | 133 | Default: "'edit'" 134 | 135 | =============================================================================== 136 | *skylight-keymaps* 137 | Keymaps ~ 138 | > 139 | " Configuration example 140 | nnoremap gp :Skylight! 141 | vnoremap gp :Skylight! 142 | < 143 | In the skylight-menu window, use `` to scroll forward and `` to 144 | scroll backward. 145 | 146 | =============================================================================== 147 | *skylight-highlights* 148 | Highlights ~ 149 | 150 | 'SkylightBorder', which is linked to 'Normal' by default, can be used to 151 | specify the border style. 152 | > 153 | " Configuration example 154 | hi SkylightBorder guibg=orange guifg=cyan 155 | < 156 | =============================================================================== 157 | *skylight-why* 158 | Why ~ 159 | 160 | For a long time I was hoping to preview file under cursor in a disposable 161 | floating window without actually opening it. Then I dug into the web and found 162 | some awesome projects, which, however, only support previewing files that are 163 | listed in the quickfix window. What I want is to preview the file that occurs 164 | anywhere in vim, even those in the builtin terminal window (when I am using 165 | gdb's 'bt' command). 166 | 167 | The codes were initially buildup in my personal dotfiles. After the whole 168 | feature was almost implemented, I decided to detach them from the dotfiles and 169 | reorganize them into a plugin in case of someone who has the same requirement 170 | needs it. 171 | 172 | =============================================================================== 173 | *skylight-known-issues* 174 | Known issues ~ 175 | 176 | Sometimes can not find the files in the hidden folders when performing downward 177 | searching. That is because vim's file searching doesn't include hidden 178 | directories, I am considering using another suitable file-finder cli tool for 179 | the plugin but this will not come out in the short term. 180 | 181 | =============================================================================== 182 | *skylight-screenshots* 183 | Screenshots ~ 184 | 185 | - Preview files from quickfix window 186 | 187 | Image: (see reference [2]) 188 | 189 | - Preview symbol 190 | 191 | Image: (see reference [3]) 192 | 193 | - Preview files from terminal window 194 | 195 | Image: (see reference [4]) 196 | 197 | =============================================================================== 198 | *skylight-references* 199 | References ~ 200 | 201 | [1] https://github.com/neoclide/coc.nvim 202 | [2] https://user-images.githubusercontent.com/20282795/100506133-f4207780-31a7-11eb-9c69-30e8e254a2bb.gif 203 | [3] https://user-images.githubusercontent.com/20282795/100506082-ef5bc380-31a7-11eb-9618-fd37ad03f7cb.gif 204 | [4] https://user-images.githubusercontent.com/20282795/100506148-f5ea3b00-31a7-11eb-820e-b2f6dcc3840e.gif 205 | 206 | vim: ft=help 207 | --------------------------------------------------------------------------------