Last active
May 30, 2018 11:17
-
-
Save adscriven/1fde7e3aa362b8f9700677352e97f56e to your computer and use it in GitHub Desktop.
Edit files under the cursor in :terminal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
" termedit.vim -- Edit a file under the cursor in a terminal window. | |
" https://gist.github.com/adscriven/1fde7e3aa362b8f9700677352e97f56e | |
" Public domain. | |
" [ ] add mappings for opening a terminal window and doing 'ls' | |
" REQUIREMENTS | |
" Vim 8.0.0693 or later with the +terminal feature. | |
" Supports bash with both vi and emacs keybindings, and cmd.exe. | |
" INSTALLATION | |
" Chuck this file in ~/.vim/plugin. | |
" USAGE | |
" In your vimrc, | |
" nmap {mykey} <Plug>[termedit] | |
" nmap {myotherkey} <Plug>[termsplit] | |
" DESCRIPTION | |
" * After e.g. ls or grep in a terminal window, place the cursor on | |
" a filename and press {mykey} to edit the file. | |
" * If the filename is immediately followed by a colon then | |
" a number, it will be treated as a line number to jump to. | |
" * Alternatively, type a file name at the prompt, switch to | |
" Terminal-Normal mode, then press {mykey} to edit it. | |
" * The mappings will do nothing if the cursor is not in a terminal | |
" buffer. | |
" * Note that :e! is used to edit the file for the <Plug>[termedit] | |
" mapping. The terminal window will be replaced with the window of | |
" the edited file, and the terminal job will be unceremoniously | |
" terminated. | |
" TIPS | |
" * To conditionally map e.g. <CR> only for a terminal buffer: | |
" augroup vimrc_terminal | |
" autocmd! | |
" autocmd TerminalOpen * | |
" \ nmap <buffer> <CR> <Plug>[termedit]| | |
" \ nmap <buffer> \<CR> <Plug>[termsplit] | |
" augroup END | |
" For Vim 8.0 use "autocmd BufWinEnter * if &buftype == 'terminal' | ..." | |
" instead. | |
" | |
" * Make it easy to open a terminal window with a directory listing | |
" already there. | |
" | |
" nno <silent> \1 :<c-u>exe 'term ++curwin' \| call feedkeys("ls\<lt>cr>")<cr> | |
" nno <silent> \2 :<c-u>exe 'term' \| call feedkeys("ls\<lt>cr>")<cr> | |
" | |
" * Make it easy to switch to Terminal-Normal mode: | |
" tnoremap <Esc> <C-W>N | |
" or whatever key combination you normally use to exit to Normal | |
" mode. | |
" | |
" Combining the above three tips gives a capable directory browser. | |
" KNOWN ISSUES | |
" 1. `:e!`, or `:hide e`? Should the terminal job be automatically | |
" killed or not? | |
" 2. MSYS Vim is particularly slow. term_wait() may be needed to | |
" prevent erratic results. | |
" 3. s:send() needs work to make sure it operates correctly with | |
" various shells and OSs. | |
" 4. Doesn't handles [nnn]c:\foo\bar.txt style line numbers for | |
" cmd.exe. ':' is considered to be a valid file-name character | |
" on Windows. | |
if !has('terminal') | |
echohl errormsg | |
echomsg '(termedit.vim) Vim 8.0.0693 or later with +terminal ' . | |
\ 'is required. Exiting.' | |
echohl None | |
finish | |
endif | |
let s:cpo = &cpo | |
set cpo&vim | |
let s:shell = matchstr(&shell, '[^/\\]\+$') | |
let s:clearline = { | |
\ 'bash': "\<esc>S\<c-k>\<c-u>", | |
\ 'bash.exe': "\<esc>S\<c-k>\<c-u>", | |
\ 'cmd.exe': "\<esc>\<esc>" | |
\ }[s:shell] | |
let s:cwdcmd = { | |
\ 'bash': 'pwd', | |
\ 'bash.exe': 'pwd', | |
\ 'cmd.exe': 'cd' | |
\ }[s:shell] | |
let s:outputlinedelim = { | |
\ 'bash': "\r\n", | |
\ 'bash.exe': "\r\n", | |
\ 'cmd.exe': "\r\n" | |
\ }[s:shell] | |
" Send a string, s, to the job for the terminal of the current buffer. | |
" Split the response on \r\n, and remove the first item of the resulting | |
" list (s echoed), and the last item (terminal prompt). Return the | |
" trimmed list. | |
" First sends a key sequence to clear the terminal input line. | |
" XXX: Maybe term_gettitle() could be used to get the cwd. Possibly | |
" not reliable, plus this won't work for cmd.exe. | |
let s:send = {s -> | |
\ split(ch_evalraw(term_getjob(bufnr('%')), s:clearline . s . "\<cr>"), | |
\ s:outputlinedelim)[1:-2]} | |
" Return the cwd of the terminal in the current buffer. | |
" Trims text from Esc onwards, to deal with cmd.exe output. | |
let s:pwd = {-> substitute(get(s:send(s:cwdcmd), 0, ''), '\e.*', '', '')} | |
" Return the filename under the cursor. Prepend with the result of | |
" s:pwd() for relative paths. For paths starting with '/' (Unix) | |
" or letter-colon (Windows) leave the filename untouched. | |
let s:cursorfile = {-> {s -> s == '' | |
\ ? '' | |
\ : s[0] == '/' || s[0:1] =~ '^\a:' ? s : s:pwd() . '/' . s | |
\ }(substitute(expand('<cfile>'), ':\d\+', '', ''))} | |
" Return a line number that may be following the filename, e.g. grep | |
" output such as | |
" foo.c:123:/* Behold awesome code! */ | |
" If no line number is found, return an empty string. | |
let s:cursorfilelnum = {-> | |
\ matchstr(expand('<cWORD>'), ':\zs\d\+')} | |
" Return an ex command to edit a file, maybe jumping to a line number. | |
let s:editcmd = {prefix -> {lnum -> {file -> file == '' | |
\ ? '' | |
\ : prefix . (lnum ? '+' . lnum : '') . ' ' . file | |
\ }(s:cursorfile())}(s:cursorfilelnum())} | |
fun! s:mapping(prefix) | |
if &buftype != 'terminal' | |
return '' | |
endif | |
return ":\<c-u>" . s:editcmd(a:prefix) . "\<cr>" | |
endfun | |
nnoremap <expr> <Plug>[termedit] <SID>mapping('e!') | |
nnoremap <expr> <Plug>[termsplit] <SID>mapping('sp') | |
let &cpo = s:cpo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment