1165 lines
34 KiB
VimL
Executable file
1165 lines
34 KiB
VimL
Executable file
"=============================================================================
|
|
" File: gist.vim
|
|
" Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
|
|
" Last Change: 10-Oct-2016.
|
|
" Version: 7.3
|
|
" WebPage: http://github.com/mattn/vim-gist
|
|
" License: BSD
|
|
|
|
let s:save_cpo = &cpoptions
|
|
set cpoptions&vim
|
|
|
|
if exists('g:gist_disabled') && g:gist_disabled == 1
|
|
function! gist#Gist(...) abort
|
|
endfunction
|
|
finish
|
|
endif
|
|
|
|
if !exists('g:github_user') && !executable('git')
|
|
echohl ErrorMsg | echomsg 'Gist: require ''git'' command' | echohl None
|
|
finish
|
|
endif
|
|
|
|
if !executable('curl')
|
|
echohl ErrorMsg | echomsg 'Gist: require ''curl'' command' | echohl None
|
|
finish
|
|
endif
|
|
|
|
if globpath(&rtp, 'autoload/webapi/http.vim') ==# ''
|
|
echohl ErrorMsg | echomsg 'Gist: require ''webapi'', install https://github.com/mattn/webapi-vim' | echohl None
|
|
finish
|
|
else
|
|
call webapi#json#true()
|
|
endif
|
|
|
|
let s:gist_token_file = expand(get(g:, 'gist_token_file', '~/.gist-vim'))
|
|
let s:system = function(get(g:, 'webapi#system_function', 'system'))
|
|
|
|
if !exists('g:github_user')
|
|
let g:github_user = substitute(s:system('git config --get github.user'), "\n", '', '')
|
|
if strlen(g:github_user) == 0
|
|
let g:github_user = $GITHUB_USER
|
|
end
|
|
endif
|
|
|
|
if !exists('g:gist_api_url')
|
|
let g:gist_api_url = substitute(s:system('git config --get github.apiurl'), "\n", '', '')
|
|
if strlen(g:gist_api_url) == 0
|
|
let g:gist_api_url = 'https://api.github.com/'
|
|
end
|
|
if exists('g:github_api_url') && !exists('g:gist_shutup_issue154')
|
|
if matchstr(g:gist_api_url, 'https\?://\zs[^/]\+\ze') != matchstr(g:github_api_url, 'https\?://\zs[^/]\+\ze')
|
|
echohl WarningMsg
|
|
echo '--- Warning ---'
|
|
echo 'It seems that you set different URIs for github_api_url/gist_api_url.'
|
|
echo 'If you want to remove this message: let g:gist_shutup_issue154 = 1'
|
|
echohl None
|
|
if confirm('Continue?', '&Yes\n&No') != 1
|
|
let g:gist_disabled = 1
|
|
finish
|
|
endif
|
|
redraw!
|
|
endif
|
|
endif
|
|
endif
|
|
if g:gist_api_url !~# '/$'
|
|
let g:gist_api_url .= '/'
|
|
endif
|
|
|
|
if !exists('g:gist_update_on_write')
|
|
let g:gist_update_on_write = 1
|
|
endif
|
|
|
|
function! s:get_browser_command() abort
|
|
let gist_browser_command = get(g:, 'gist_browser_command', '')
|
|
if gist_browser_command ==# ''
|
|
if has('win32') || has('win64')
|
|
let gist_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%'
|
|
elseif has('mac') || has('macunix') || has('gui_macvim') || system('uname') =~? '^darwin'
|
|
let gist_browser_command = 'open %URL%'
|
|
elseif executable('xdg-open')
|
|
let gist_browser_command = 'xdg-open %URL%'
|
|
elseif executable('firefox')
|
|
let gist_browser_command = 'firefox %URL% &'
|
|
else
|
|
let gist_browser_command = ''
|
|
endif
|
|
endif
|
|
return gist_browser_command
|
|
endfunction
|
|
|
|
function! s:open_browser(url) abort
|
|
let cmd = s:get_browser_command()
|
|
if len(cmd) == 0
|
|
redraw
|
|
echohl WarningMsg
|
|
echo 'It seems that you don''t have general web browser. Open URL below.'
|
|
echohl None
|
|
echo a:url
|
|
return
|
|
endif
|
|
let quote = &shellxquote == '"' ? "'" : '"'
|
|
if cmd =~# '^!'
|
|
let cmd = substitute(cmd, '%URL%', '\=quote.a:url.quote', 'g')
|
|
silent! exec cmd
|
|
elseif cmd =~# '^:[A-Z]'
|
|
let cmd = substitute(cmd, '%URL%', '\=a:url', 'g')
|
|
exec cmd
|
|
else
|
|
let cmd = substitute(cmd, '%URL%', '\=quote.a:url.quote', 'g')
|
|
call system(cmd)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:shellwords(str) abort
|
|
let words = split(a:str, '\%(\([^ \t\''"]\+\)\|''\([^\'']*\)''\|"\(\%([^\"\\]\|\\.\)*\)"\)\zs\s*\ze')
|
|
let words = map(words, 'substitute(v:val, ''\\\([\\ ]\)'', ''\1'', "g")')
|
|
let words = map(words, 'matchstr(v:val, ''^\%\("\zs\(.*\)\ze"\|''''\zs\(.*\)\ze''''\|.*\)$'')')
|
|
return words
|
|
endfunction
|
|
|
|
function! s:truncate(str, num)
|
|
let mx_first = '^\(.\)\(.*\)$'
|
|
let str = a:str
|
|
let ret = ''
|
|
let width = 0
|
|
while 1
|
|
let char = substitute(str, mx_first, '\1', '')
|
|
let cells = strdisplaywidth(char)
|
|
if cells == 0 || width + cells > a:num
|
|
break
|
|
endif
|
|
let width = width + cells
|
|
let ret .= char
|
|
let str = substitute(str, mx_first, '\2', '')
|
|
endwhile
|
|
while width + 1 <= a:num
|
|
let ret .= ' '
|
|
let width = width + 1
|
|
endwhile
|
|
return ret
|
|
endfunction
|
|
|
|
function! s:format_gist(gist) abort
|
|
let files = sort(keys(a:gist.files))
|
|
if empty(files)
|
|
return ''
|
|
endif
|
|
let file = a:gist.files[files[0]]
|
|
let name = file.filename
|
|
if has_key(file, 'content')
|
|
let code = file.content
|
|
let code = "\n".join(map(split(code, "\n"), '" ".v:val'), "\n")
|
|
else
|
|
let code = ''
|
|
endif
|
|
let desc = type(a:gist.description)==0 || a:gist.description ==# '' ? '' : a:gist.description
|
|
let name = substitute(name, '[\r\n\t]', ' ', 'g')
|
|
let name = substitute(name, ' ', ' ', 'g')
|
|
let desc = substitute(desc, '[\r\n\t]', ' ', 'g')
|
|
let desc = substitute(desc, ' ', ' ', 'g')
|
|
" Display a nice formatted (and truncated if needed) table of gists on screen
|
|
" Calculate field lengths for gist-listing formatting on screen
|
|
redir =>a |exe 'sil sign place buffer='.bufnr('')|redir end
|
|
let signlist = split(a, '\n')
|
|
let width = winwidth(0) - ((&number||&relativenumber) ? &numberwidth : 0) - &foldcolumn - (len(signlist) > 2 ? 2 : 0)
|
|
let idlen = 33
|
|
let namelen = get(g:, 'gist_namelength', 30)
|
|
let desclen = width - (idlen + namelen + 10)
|
|
return printf('gist: %s %s %s', s:truncate(a:gist.id, idlen), s:truncate(name, namelen), s:truncate(desc, desclen))
|
|
endfunction
|
|
|
|
" Note: A colon in the file name has side effects on Windows due to NTFS Alternate Data Streams; avoid it.
|
|
let s:bufprefix = 'gist' . (has('unix') ? ':' : '_')
|
|
function! s:GistList(gistls, page) abort
|
|
if a:gistls ==# '-all'
|
|
let url = g:gist_api_url.'gists/public'
|
|
elseif get(g:, 'gist_show_privates', 0) && a:gistls ==# 'starred'
|
|
let url = g:gist_api_url.'gists/starred'
|
|
elseif get(g:, 'gist_show_privates') && a:gistls ==# 'mine'
|
|
let url = g:gist_api_url.'gists'
|
|
else
|
|
let url = g:gist_api_url.'users/'.a:gistls.'/gists'
|
|
endif
|
|
let winnum = bufwinnr(bufnr(s:bufprefix.a:gistls))
|
|
if winnum != -1
|
|
if winnum != bufwinnr('%')
|
|
exe winnum 'wincmd w'
|
|
endif
|
|
setlocal modifiable
|
|
else
|
|
if get(g:, 'gist_list_vsplit', 0)
|
|
exec 'silent noautocmd vsplit +set\ winfixwidth ' s:bufprefix.a:gistls
|
|
elseif get(g:, 'gist_list_rightbelow', 0)
|
|
exec 'silent noautocmd rightbelow 5 split +set\ winfixheight ' s:bufprefix.a:gistls
|
|
else
|
|
exec 'silent noautocmd split' s:bufprefix.a:gistls
|
|
endif
|
|
endif
|
|
if a:page > 1
|
|
let oldlines = getline(0, line('$'))
|
|
let url = url . '?page=' . a:page
|
|
endif
|
|
|
|
setlocal modifiable
|
|
let old_undolevels = &undolevels
|
|
let oldlines = []
|
|
silent %d _
|
|
|
|
redraw | echon 'Listing gists... '
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
bw!
|
|
redraw
|
|
echohl ErrorMsg | echomsg v:errmsg | echohl None
|
|
return
|
|
endif
|
|
let res = webapi#http#get(url, '', { 'Authorization': auth })
|
|
if v:shell_error != 0
|
|
bw!
|
|
redraw
|
|
echohl ErrorMsg | echomsg 'Gists not found' | echohl None
|
|
return
|
|
endif
|
|
let content = webapi#json#decode(res.content)
|
|
if type(content) == 4 && has_key(content, 'message') && len(content.message)
|
|
bw!
|
|
redraw
|
|
echohl ErrorMsg | echomsg content.message | echohl None
|
|
if content.message ==# 'Bad credentials'
|
|
call delete(s:gist_token_file)
|
|
endif
|
|
return
|
|
endif
|
|
|
|
let lines = map(filter(content, '!empty(v:val.files)'), 's:format_gist(v:val)')
|
|
call setline(1, split(join(lines, "\n"), "\n"))
|
|
|
|
$put='more...'
|
|
|
|
let b:gistls = a:gistls
|
|
let b:page = a:page
|
|
setlocal buftype=nofile bufhidden=hide noswapfile
|
|
setlocal cursorline
|
|
setlocal nomodified
|
|
setlocal nomodifiable
|
|
syntax match SpecialKey /^gist:/he=e-1
|
|
syntax match Title /^gist: \S\+/hs=s+5 contains=ALL
|
|
nnoremap <silent> <buffer> <cr> :call <SID>GistListAction(0)<cr>
|
|
nnoremap <silent> <buffer> o :call <SID>GistListAction(0)<cr>
|
|
nnoremap <silent> <buffer> b :call <SID>GistListAction(1)<cr>
|
|
nnoremap <silent> <buffer> y :call <SID>GistListAction(2)<cr>
|
|
nnoremap <silent> <buffer> p :call <SID>GistListAction(3)<cr>
|
|
nnoremap <silent> <buffer> <esc> :bw<cr>
|
|
nnoremap <silent> <buffer> <s-cr> :call <SID>GistListAction(1)<cr>
|
|
|
|
cal cursor(1+len(oldlines),1)
|
|
nohlsearch
|
|
redraw | echo ''
|
|
endfunction
|
|
|
|
function! gist#list_recursively(user, ...) abort
|
|
let use_cache = get(a:000, 0, 1)
|
|
let limit = get(a:000, 1, -1)
|
|
let verbose = get(a:000, 2, 1)
|
|
if a:user ==# 'mine'
|
|
let url = g:gist_api_url . 'gists'
|
|
elseif a:user ==# 'starred'
|
|
let url = g:gist_api_url . 'gists/starred'
|
|
else
|
|
let url = g:gist_api_url.'users/'.a:user.'/gists'
|
|
endif
|
|
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
" anonymous user cannot get gists to prevent infinite recursive loading
|
|
return []
|
|
endif
|
|
|
|
if use_cache && exists('g:gist_list_recursively_cache')
|
|
if has_key(g:gist_list_recursively_cache, a:user)
|
|
return webapi#json#decode(g:gist_list_recursively_cache[a:user])
|
|
endif
|
|
endif
|
|
|
|
let page = 1
|
|
let gists = []
|
|
let lastpage = -1
|
|
|
|
function! s:get_lastpage(res) abort
|
|
let links = split(a:res.header[match(a:res.header, 'Link')], ',')
|
|
let link = links[match(links, 'rel=[''"]last[''"]')]
|
|
let page = str2nr(matchlist(link, '\%(page=\)\(\d\+\)')[1])
|
|
return page
|
|
endfunction
|
|
|
|
if verbose > 0
|
|
redraw | echon 'Loading gists...'
|
|
endif
|
|
|
|
while limit == -1 || page <= limit
|
|
let res = webapi#http#get(url.'?page='.page, '', {'Authorization': auth})
|
|
if limit == -1
|
|
" update limit to the last page
|
|
let limit = s:get_lastpage(res)
|
|
endif
|
|
if verbose > 0
|
|
redraw | echon 'Loading gists... ' . page . '/' . limit . ' pages has loaded.'
|
|
endif
|
|
let gists = gists + webapi#json#decode(res.content)
|
|
let page = page + 1
|
|
endwhile
|
|
let g:gist_list_recursively_cache = get(g:, 'gist_list_recursively_cache', {})
|
|
let g:gist_list_recursively_cache[a:user] = webapi#json#encode(gists)
|
|
return gists
|
|
endfunction
|
|
|
|
function! gist#list(user, ...) abort
|
|
let page = get(a:000, 0, 0)
|
|
if a:user ==# '-all'
|
|
let url = g:gist_api_url.'gists/public'
|
|
elseif get(g:, 'gist_show_privates', 0) && a:user ==# 'starred'
|
|
let url = g:gist_api_url.'gists/starred'
|
|
elseif get(g:, 'gist_show_privates') && a:user ==# 'mine'
|
|
let url = g:gist_api_url.'gists'
|
|
else
|
|
let url = g:gist_api_url.'users/'.a:user.'/gists'
|
|
endif
|
|
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
return []
|
|
endif
|
|
let res = webapi#http#get(url, '', { 'Authorization': auth })
|
|
return webapi#json#decode(res.content)
|
|
endfunction
|
|
|
|
function! s:GistGetFileName(gistid) abort
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
return ''
|
|
endif
|
|
let res = webapi#http#get(g:gist_api_url.'gists/'.a:gistid, '', { 'Authorization': auth })
|
|
let gist = webapi#json#decode(res.content)
|
|
if has_key(gist, 'files')
|
|
return sort(keys(gist.files))[0]
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:GistDetectFiletype(gistid) abort
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
return ''
|
|
endif
|
|
let res = webapi#http#get(g:gist_api_url.'gists/'.a:gistid, '', { 'Authorization': auth })
|
|
let gist = webapi#json#decode(res.content)
|
|
let filename = sort(keys(gist.files))[0]
|
|
let ext = fnamemodify(filename, ':e')
|
|
if has_key(s:extmap, ext)
|
|
let type = s:extmap[ext]
|
|
else
|
|
let type = get(gist.files[filename], 'type', 'text')
|
|
endif
|
|
silent! exec 'setlocal ft='.tolower(type)
|
|
endfunction
|
|
|
|
function! s:GistWrite(fname) abort
|
|
if substitute(a:fname, '\\', '/', 'g') == expand("%:p:gs@\\@/@")
|
|
if g:gist_update_on_write != 2 || v:cmdbang
|
|
Gist -e
|
|
else
|
|
echohl ErrorMsg | echomsg 'Please type ":w!" to update a gist.' | echohl None
|
|
endif
|
|
else
|
|
exe 'w'.(v:cmdbang ? '!' : '') fnameescape(v:cmdarg) fnameescape(a:fname)
|
|
silent! exe 'file' fnameescape(a:fname)
|
|
silent! au! BufWriteCmd <buffer>
|
|
endif
|
|
endfunction
|
|
|
|
function! s:GistGet(gistid, clipboard) abort
|
|
redraw | echon 'Getting gist... '
|
|
let res = webapi#http#get(g:gist_api_url.'gists/'.a:gistid, '', { 'Authorization': s:GistGetAuthHeader() })
|
|
if res.status =~# '^2'
|
|
try
|
|
let gist = webapi#json#decode(res.content)
|
|
catch
|
|
redraw
|
|
echohl ErrorMsg | echomsg 'Gist seems to be broken' | echohl None
|
|
return
|
|
endtry
|
|
if get(g:, 'gist_get_multiplefile', 0) != 0
|
|
let num_file = len(keys(gist.files))
|
|
else
|
|
let num_file = 1
|
|
endif
|
|
redraw
|
|
if num_file > len(keys(gist.files))
|
|
echohl ErrorMsg | echomsg 'Gist not found' | echohl None
|
|
return
|
|
endif
|
|
augroup GistWrite
|
|
au!
|
|
augroup END
|
|
for n in range(num_file)
|
|
try
|
|
let old_undolevels = &undolevels
|
|
let filename = sort(keys(gist.files))[n]
|
|
|
|
let winnum = bufwinnr(bufnr(s:bufprefix.a:gistid.'/'.filename))
|
|
if winnum != -1
|
|
if winnum != bufwinnr('%')
|
|
exe winnum 'wincmd w'
|
|
endif
|
|
setlocal modifiable
|
|
else
|
|
if num_file == 1
|
|
if get(g:, 'gist_edit_with_buffers', 0)
|
|
let found = -1
|
|
for wnr in range(1, winnr('$'))
|
|
let bnr = winbufnr(wnr)
|
|
if bnr != -1 && !empty(getbufvar(bnr, 'gist'))
|
|
let found = wnr
|
|
break
|
|
endif
|
|
endfor
|
|
if found != -1
|
|
exe found 'wincmd w'
|
|
setlocal modifiable
|
|
else
|
|
if get(g:, 'gist_list_vsplit', 0)
|
|
exec 'silent noautocmd rightbelow vnew'
|
|
else
|
|
exec 'silent noautocmd rightbelow new'
|
|
endif
|
|
endif
|
|
else
|
|
silent only!
|
|
if get(g:, 'gist_list_vsplit', 0)
|
|
exec 'silent noautocmd rightbelow vnew'
|
|
else
|
|
exec 'silent noautocmd rightbelow new'
|
|
endif
|
|
endif
|
|
else
|
|
if get(g:, 'gist_list_vsplit', 0)
|
|
exec 'silent noautocmd rightbelow vnew'
|
|
else
|
|
exec 'silent noautocmd rightbelow new'
|
|
endif
|
|
endif
|
|
setlocal noswapfile
|
|
silent exec 'noautocmd file' s:bufprefix.a:gistid.'/'.fnameescape(filename)
|
|
endif
|
|
set undolevels=-1
|
|
filetype detect
|
|
silent %d _
|
|
|
|
let content = gist.files[filename].content
|
|
call setline(1, split(content, "\n"))
|
|
let b:gist = {
|
|
\ 'filename': filename,
|
|
\ 'id': gist.id,
|
|
\ 'description': gist.description,
|
|
\ 'private': gist.public =~# 'true',
|
|
\}
|
|
catch
|
|
let &undolevels = old_undolevels
|
|
bw!
|
|
redraw
|
|
echohl ErrorMsg | echomsg 'Gist contains binary' | echohl None
|
|
return
|
|
endtry
|
|
let &undolevels = old_undolevels
|
|
setlocal buftype=acwrite bufhidden=hide noswapfile
|
|
setlocal nomodified
|
|
doau StdinReadPost,BufRead,BufReadPost
|
|
let gist_detect_filetype = get(g:, 'gist_detect_filetype', 0)
|
|
if (&ft ==# '' && gist_detect_filetype == 1) || gist_detect_filetype == 2
|
|
call s:GistDetectFiletype(a:gistid)
|
|
endif
|
|
if a:clipboard
|
|
if exists('g:gist_clip_command')
|
|
exec 'silent w !'.g:gist_clip_command
|
|
elseif has('clipboard')
|
|
silent! %yank +
|
|
else
|
|
%yank
|
|
endif
|
|
endif
|
|
1
|
|
augroup GistWrite
|
|
au! BufWriteCmd <buffer> call s:GistWrite(expand("<amatch>"))
|
|
augroup END
|
|
endfor
|
|
else
|
|
bw!
|
|
redraw
|
|
echohl ErrorMsg | echomsg 'Gist not found' | echohl None
|
|
return
|
|
endif
|
|
endfunction
|
|
|
|
function! s:GistListAction(mode) abort
|
|
let line = getline('.')
|
|
let mx = '^gist:\s*\zs\(\w\+\)\ze.*'
|
|
if line =~# mx
|
|
let gistid = matchstr(line, mx)
|
|
if a:mode == 1
|
|
call s:open_browser('https://gist.github.com/' . gistid)
|
|
elseif a:mode == 0
|
|
call s:GistGet(gistid, 0)
|
|
wincmd w
|
|
bw
|
|
elseif a:mode == 2
|
|
call s:GistGet(gistid, 1)
|
|
" TODO close with buffe rname
|
|
bdelete
|
|
bdelete
|
|
elseif a:mode == 3
|
|
call s:GistGet(gistid, 1)
|
|
" TODO close with buffe rname
|
|
bdelete
|
|
bdelete
|
|
normal! "+p
|
|
endif
|
|
return
|
|
endif
|
|
if line =~# '^more\.\.\.$'
|
|
call s:GistList(b:gistls, b:page+1)
|
|
return
|
|
endif
|
|
endfunction
|
|
|
|
function! s:GistUpdate(content, gistid, gistnm, desc) abort
|
|
let gist = { 'id': a:gistid, 'files' : {}, 'description': '','public': function('webapi#json#true') }
|
|
if exists('b:gist')
|
|
if has_key(b:gist, 'filename') && len(a:gistnm) > 0
|
|
let gist.files[b:gist.filename] = { 'content': '', 'filename': b:gist.filename }
|
|
let b:gist.filename = a:gistnm
|
|
endif
|
|
if has_key(b:gist, 'private') && b:gist.private | let gist['public'] = function('webapi#json#false') | endif
|
|
if has_key(b:gist, 'description') | let gist['description'] = b:gist.description | endif
|
|
if has_key(b:gist, 'filename') | let filename = b:gist.filename | endif
|
|
else
|
|
let filename = a:gistnm
|
|
if len(filename) == 0 | let filename = s:GistGetFileName(a:gistid) | endif
|
|
if len(filename) == 0 | let filename = s:get_current_filename(1) | endif
|
|
endif
|
|
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
redraw
|
|
echohl ErrorMsg | echomsg v:errmsg | echohl None
|
|
return
|
|
endif
|
|
|
|
" Update description
|
|
" If no new description specified, keep the old description
|
|
if a:desc !=# ' '
|
|
let gist['description'] = a:desc
|
|
else
|
|
let res = webapi#http#get(g:gist_api_url.'gists/'.a:gistid, '', { 'Authorization': auth })
|
|
if res.status =~# '^2'
|
|
let old_gist = webapi#json#decode(res.content)
|
|
let gist['description'] = old_gist.description
|
|
endif
|
|
endif
|
|
|
|
let gist.files[filename] = { 'content': a:content, 'filename': filename }
|
|
|
|
redraw | echon 'Updating gist... '
|
|
let res = webapi#http#post(g:gist_api_url.'gists/' . a:gistid,
|
|
\ webapi#json#encode(gist), {
|
|
\ 'Authorization': auth,
|
|
\ 'Content-Type': 'application/json',
|
|
\})
|
|
if res.status =~# '^2'
|
|
let obj = webapi#json#decode(res.content)
|
|
let loc = obj['html_url']
|
|
let b:gist = {'id': a:gistid, 'filename': filename}
|
|
setlocal nomodified
|
|
redraw | echomsg 'Done: '.loc
|
|
else
|
|
let loc = ''
|
|
echohl ErrorMsg | echomsg 'Post failed: ' . res.message | echohl None
|
|
endif
|
|
return loc
|
|
endfunction
|
|
|
|
function! s:GistDelete(gistid) abort
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
redraw
|
|
echohl ErrorMsg | echomsg v:errmsg | echohl None
|
|
return
|
|
endif
|
|
|
|
redraw | echon 'Deleting gist... '
|
|
let res = webapi#http#post(g:gist_api_url.'gists/'.a:gistid, '', {
|
|
\ 'Authorization': auth,
|
|
\ 'Content-Type': 'application/json',
|
|
\}, 'DELETE')
|
|
if res.status =~# '^2'
|
|
if exists('b:gist')
|
|
unlet b:gist
|
|
endif
|
|
redraw | echomsg 'Done: '
|
|
else
|
|
echohl ErrorMsg | echomsg 'Delete failed: ' . res.message | echohl None
|
|
endif
|
|
endfunction
|
|
|
|
function! s:get_current_filename(no) abort
|
|
let filename = expand('%:t')
|
|
if len(filename) == 0 && &ft !=# ''
|
|
let pair = filter(items(s:extmap), 'v:val[1] == &ft')
|
|
if len(pair) > 0
|
|
let filename = printf('gistfile%d%s', a:no, pair[0][0])
|
|
endif
|
|
endif
|
|
if filename ==# ''
|
|
let filename = printf('gistfile%d.txt', a:no)
|
|
endif
|
|
return filename
|
|
endfunction
|
|
|
|
function! s:update_GistID(id) abort
|
|
let view = winsaveview()
|
|
normal! gg
|
|
let ret = 0
|
|
if search('\<GistID\>:\s*$')
|
|
let line = getline('.')
|
|
let line = substitute(line, '\s\+$', '', 'g')
|
|
call setline('.', line . ' ' . a:id)
|
|
let ret = 1
|
|
endif
|
|
call winrestview(view)
|
|
return ret
|
|
endfunction
|
|
|
|
" GistPost function:
|
|
" Post new gist to github
|
|
"
|
|
" if there is an embedded gist url or gist id in your file,
|
|
" it will just update it.
|
|
" -- by c9s
|
|
"
|
|
" embedded gist id format:
|
|
"
|
|
" GistID: 123123
|
|
"
|
|
function! s:GistPost(content, private, desc, anonymous) abort
|
|
let gist = { 'files' : {}, 'description': '','public': function('webapi#json#true') }
|
|
if a:desc !=# ' ' | let gist['description'] = a:desc | endif
|
|
if a:private | let gist['public'] = function('webapi#json#false') | endif
|
|
let filename = s:get_current_filename(1)
|
|
let gist.files[filename] = { 'content': a:content, 'filename': filename }
|
|
|
|
let header = {'Content-Type': 'application/json'}
|
|
if !a:anonymous
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
redraw
|
|
echohl ErrorMsg | echomsg v:errmsg | echohl None
|
|
return
|
|
endif
|
|
let header['Authorization'] = auth
|
|
endif
|
|
|
|
redraw | echon 'Posting it to gist... '
|
|
let res = webapi#http#post(g:gist_api_url.'gists', webapi#json#encode(gist), header)
|
|
if res.status =~# '^2'
|
|
let obj = webapi#json#decode(res.content)
|
|
let loc = obj['html_url']
|
|
let b:gist = {
|
|
\ 'filename': filename,
|
|
\ 'id': matchstr(loc, '[^/]\+$'),
|
|
\ 'description': gist['description'],
|
|
\ 'private': a:private,
|
|
\}
|
|
if s:update_GistID(b:gist['id'])
|
|
Gist -e
|
|
endif
|
|
redraw | echomsg 'Done: '.loc
|
|
else
|
|
let loc = ''
|
|
echohl ErrorMsg | echomsg 'Post failed: '. res.message | echohl None
|
|
endif
|
|
return loc
|
|
endfunction
|
|
|
|
function! s:GistPostBuffers(private, desc, anonymous) abort
|
|
let bufnrs = range(1, bufnr('$'))
|
|
let bn = bufnr('%')
|
|
let query = []
|
|
|
|
let gist = { 'files' : {}, 'description': '','public': function('webapi#json#true') }
|
|
if a:desc !=# ' ' | let gist['description'] = a:desc | endif
|
|
if a:private | let gist['public'] = function('webapi#json#false') | endif
|
|
|
|
let index = 1
|
|
for bufnr in bufnrs
|
|
if !bufexists(bufnr) || buflisted(bufnr) == 0
|
|
continue
|
|
endif
|
|
echo 'Creating gist content'.index.'... '
|
|
silent! exec 'buffer!' bufnr
|
|
let content = join(getline(1, line('$')), "\n")
|
|
let filename = s:get_current_filename(index)
|
|
let gist.files[filename] = { 'content': content, 'filename': filename }
|
|
let index = index + 1
|
|
endfor
|
|
silent! exec 'buffer!' bn
|
|
|
|
let header = {'Content-Type': 'application/json'}
|
|
if !a:anonymous
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
redraw
|
|
echohl ErrorMsg | echomsg v:errmsg | echohl None
|
|
return
|
|
endif
|
|
let header['Authorization'] = auth
|
|
endif
|
|
|
|
redraw | echon 'Posting it to gist... '
|
|
let res = webapi#http#post(g:gist_api_url.'gists', webapi#json#encode(gist), header)
|
|
if res.status =~# '^2'
|
|
let obj = webapi#json#decode(res.content)
|
|
let loc = obj['html_url']
|
|
let b:gist = {
|
|
\ 'filename': filename,
|
|
\ 'id': matchstr(loc, '[^/]\+$'),
|
|
\ 'description': gist['description'],
|
|
\ 'private': a:private,
|
|
\}
|
|
if s:update_GistID(b:gist['id'])
|
|
Gist -e
|
|
endif
|
|
redraw | echomsg 'Done: '.loc
|
|
else
|
|
let loc = ''
|
|
echohl ErrorMsg | echomsg 'Post failed: ' . res.message | echohl None
|
|
endif
|
|
return loc
|
|
endfunction
|
|
|
|
function! gist#Gist(count, bang, line1, line2, ...) abort
|
|
redraw
|
|
let bufname = bufname('%')
|
|
" find GistID: in content , then we should just update
|
|
let gistid = ''
|
|
let gistls = ''
|
|
let gistnm = ''
|
|
let gistdesc = ' '
|
|
let private = get(g:, 'gist_post_private', 0)
|
|
let multibuffer = 0
|
|
let clipboard = 0
|
|
let deletepost = 0
|
|
let editpost = 0
|
|
let anonymous = get(g:, 'gist_post_anonymous', 0)
|
|
let openbrowser = 0
|
|
let listmx = '^\%(-l\|--list\)\s*\([^\s]\+\)\?$'
|
|
let bufnamemx = '^' . s:bufprefix .'\(\zs[0-9a-f]\+\ze\|\zs[0-9a-f]\+\ze[/\\].*\)$'
|
|
if strlen(g:github_user) == 0 && anonymous == 0
|
|
echohl ErrorMsg | echomsg 'You have not configured a Github account. Read '':help gist-setup''.' | echohl None
|
|
return
|
|
endif
|
|
if a:bang == '!'
|
|
let gistidbuf = ''
|
|
elseif bufname =~# bufnamemx
|
|
let gistidbuf = matchstr(bufname, bufnamemx)
|
|
elseif exists('b:gist') && has_key(b:gist, 'id')
|
|
let gistidbuf = b:gist['id']
|
|
else
|
|
let gistidbuf = matchstr(join(getline(a:line1, a:line2), "\n"), 'GistID:\s*\zs\w\+')
|
|
endif
|
|
|
|
let args = (a:0 > 0) ? s:shellwords(a:1) : []
|
|
for arg in args
|
|
if arg =~# '^\(-h\|--help\)$\C'
|
|
help :Gist
|
|
return
|
|
elseif arg =~# '^\(-g\|--git\)$\C' && gistidbuf !=# '' && g:gist_api_url ==# 'https://api.github.com/' && has_key(b:, 'gist') && has_key(b:gist, 'id')
|
|
echo printf('git clone git@github.com:%s', b:gist['id'])
|
|
return
|
|
elseif arg =~# '^\(-G\|--gitclone\)$\C' && gistidbuf !=# '' && g:gist_api_url ==# 'https://api.github.com/' && has_key(b:, 'gist') && has_key(b:gist, 'id')
|
|
exe '!' printf('git clone git@github.com:%s', b:gist['id'])
|
|
return
|
|
elseif arg =~# '^\(-la\|--listall\)$\C'
|
|
let gistls = '-all'
|
|
elseif arg =~# '^\(-ls\|--liststar\)$\C'
|
|
let gistls = 'starred'
|
|
elseif arg =~# '^\(-l\|--list\)$\C'
|
|
if get(g:, 'gist_show_privates')
|
|
let gistls = 'mine'
|
|
else
|
|
let gistls = g:github_user
|
|
endif
|
|
elseif arg =~# '^\(-m\|--multibuffer\)$\C'
|
|
let multibuffer = 1
|
|
elseif arg =~# '^\(-p\|--private\)$\C'
|
|
let private = 1
|
|
elseif arg =~# '^\(-P\|--public\)$\C'
|
|
let private = 0
|
|
elseif arg =~# '^\(-a\|--anonymous\)$\C'
|
|
let anonymous = 1
|
|
elseif arg =~# '^\(-s\|--description\)$\C'
|
|
let gistdesc = ''
|
|
elseif arg =~# '^\(-c\|--clipboard\)$\C'
|
|
let clipboard = 1
|
|
elseif arg =~# '^--rawurl$\C' && gistidbuf !=# '' && g:gist_api_url ==# 'https://api.github.com/'
|
|
let gistid = gistidbuf
|
|
echo 'https://gist.github.com/raw/'.gistid
|
|
return
|
|
elseif arg =~# '^\(-d\|--delete\)$\C' && gistidbuf !=# ''
|
|
let gistid = gistidbuf
|
|
let deletepost = 1
|
|
elseif arg =~# '^\(-e\|--edit\)$\C'
|
|
if gistidbuf !=# ''
|
|
let gistid = gistidbuf
|
|
endif
|
|
let editpost = 1
|
|
elseif arg =~# '^\(+1\|--star\)$\C' && gistidbuf !=# ''
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
echohl ErrorMsg | echomsg v:errmsg | echohl None
|
|
else
|
|
let gistid = gistidbuf
|
|
let res = webapi#http#post(g:gist_api_url.'gists/'.gistid.'/star', '', { 'Authorization': auth }, 'PUT')
|
|
if res.status =~# '^2'
|
|
echomsg 'Starred' gistid
|
|
else
|
|
echohl ErrorMsg | echomsg 'Star failed' | echohl None
|
|
endif
|
|
endif
|
|
return
|
|
elseif arg =~# '^\(-1\|--unstar\)$\C' && gistidbuf !=# ''
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
echohl ErrorMsg | echomsg v:errmsg | echohl None
|
|
else
|
|
let gistid = gistidbuf
|
|
let res = webapi#http#post(g:gist_api_url.'gists/'.gistid.'/star', '', { 'Authorization': auth }, 'DELETE')
|
|
if res.status =~# '^2'
|
|
echomsg 'Unstarred' gistid
|
|
else
|
|
echohl ErrorMsg | echomsg 'Unstar failed' | echohl None
|
|
endif
|
|
endif
|
|
return
|
|
elseif arg =~# '^\(-f\|--fork\)$\C' && gistidbuf !=# ''
|
|
let auth = s:GistGetAuthHeader()
|
|
if len(auth) == 0
|
|
echohl ErrorMsg | echomsg v:errmsg | echohl None
|
|
return
|
|
else
|
|
let gistid = gistidbuf
|
|
let res = webapi#http#post(g:gist_api_url.'gists/'.gistid.'/fork', '', { 'Authorization': auth })
|
|
if res.status =~# '^2'
|
|
let obj = webapi#json#decode(res.content)
|
|
let gistid = obj['id']
|
|
else
|
|
echohl ErrorMsg | echomsg 'Fork failed' | echohl None
|
|
return
|
|
endif
|
|
endif
|
|
elseif arg =~# '^\(-b\|--browser\)$\C'
|
|
let openbrowser = 1
|
|
elseif arg !~# '^-' && len(gistnm) == 0
|
|
if gistdesc !=# ' '
|
|
let gistdesc = matchstr(arg, '^\s*\zs.*\ze\s*$')
|
|
elseif editpost == 1 || deletepost == 1
|
|
let gistnm = arg
|
|
elseif len(gistls) > 0 && arg !=# '^\w\+$\C'
|
|
let gistls = arg
|
|
elseif arg =~# '^[0-9a-z]\+$\C'
|
|
let gistid = arg
|
|
else
|
|
echohl ErrorMsg | echomsg 'Invalid arguments: '.arg | echohl None
|
|
unlet args
|
|
return 0
|
|
endif
|
|
elseif len(arg) > 0
|
|
echohl ErrorMsg | echomsg 'Invalid arguments: '.arg | echohl None
|
|
unlet args
|
|
return 0
|
|
endif
|
|
endfor
|
|
unlet args
|
|
"echom "gistid=".gistid
|
|
"echom "gistls=".gistls
|
|
"echom "gistnm=".gistnm
|
|
"echom "gistdesc=".gistdesc
|
|
"echom "private=".private
|
|
"echom "clipboard=".clipboard
|
|
"echom "editpost=".editpost
|
|
"echom "deletepost=".deletepost
|
|
|
|
if gistidbuf !=# '' && gistid ==# '' && editpost == 0 && deletepost == 0 && anonymous == 0
|
|
let editpost = 1
|
|
let gistid = gistidbuf
|
|
endif
|
|
|
|
if len(gistls) > 0
|
|
call s:GistList(gistls, 1)
|
|
elseif len(gistid) > 0 && editpost == 0 && deletepost == 0
|
|
call s:GistGet(gistid, clipboard)
|
|
else
|
|
let url = ''
|
|
if multibuffer == 1
|
|
let url = s:GistPostBuffers(private, gistdesc, anonymous)
|
|
else
|
|
if a:count < 1
|
|
let content = join(getline(a:line1, a:line2), "\n")
|
|
else
|
|
let save_regcont = @"
|
|
let save_regtype = getregtype('"')
|
|
silent! normal! gvy
|
|
let content = @"
|
|
call setreg('"', save_regcont, save_regtype)
|
|
endif
|
|
if editpost == 1
|
|
let url = s:GistUpdate(content, gistid, gistnm, gistdesc)
|
|
elseif deletepost == 1
|
|
call s:GistDelete(gistid)
|
|
else
|
|
let url = s:GistPost(content, private, gistdesc, anonymous)
|
|
endif
|
|
if a:count >= 1 && get(g:, 'gist_keep_selection', 0) == 1
|
|
silent! normal! gv
|
|
endif
|
|
endif
|
|
if type(url) == 1 && len(url) > 0
|
|
if get(g:, 'gist_open_browser_after_post', 0) == 1 || openbrowser
|
|
call s:open_browser(url)
|
|
endif
|
|
let gist_put_url_to_clipboard_after_post = get(g:, 'gist_put_url_to_clipboard_after_post', 1)
|
|
if gist_put_url_to_clipboard_after_post > 0 || clipboard
|
|
if gist_put_url_to_clipboard_after_post == 2
|
|
let url = url . "\n"
|
|
endif
|
|
if exists('g:gist_clip_command')
|
|
call system(g:gist_clip_command, url)
|
|
elseif has('clipboard')
|
|
let @+ = url
|
|
else
|
|
let @" = url
|
|
endif
|
|
endif
|
|
endif
|
|
endif
|
|
return 1
|
|
endfunction
|
|
|
|
function! s:GistGetAuthHeader() abort
|
|
if get(g:, 'gist_use_password_in_gitconfig', 0) != 0
|
|
let password = substitute(system('git config --get github.password'), "\n", '', '')
|
|
if password =~# '^!' | let password = system(password[1:]) | endif
|
|
return printf('basic %s', webapi#base64#b64encode(g:github_user.':'.password))
|
|
endif
|
|
let auth = ''
|
|
if !empty(get(g:, 'gist_token', $GITHUB_TOKEN))
|
|
let auth = 'token ' . get(g:, 'gist_token', $GITHUB_TOKEN)
|
|
elseif filereadable(s:gist_token_file)
|
|
let str = join(readfile(s:gist_token_file), '')
|
|
if type(str) == 1
|
|
let auth = str
|
|
endif
|
|
endif
|
|
if len(auth) > 0
|
|
return auth
|
|
endif
|
|
|
|
redraw
|
|
echohl WarningMsg
|
|
echo 'Gist.vim requires authorization to use the GitHub API. These settings are stored in "~/.gist-vim". If you want to revoke, do "rm ~/.gist-vim".'
|
|
echohl None
|
|
let password = inputsecret('GitHub Password for '.g:github_user.':')
|
|
if len(password) == 0
|
|
let v:errmsg = 'Canceled'
|
|
return ''
|
|
endif
|
|
let note = 'Gist.vim on '.hostname().' '.strftime('%Y/%m/%d-%H:%M:%S')
|
|
let note_url = 'http://www.vim.org/scripts/script.php?script_id=2423'
|
|
let insecureSecret = printf('basic %s', webapi#base64#b64encode(g:github_user.':'.password))
|
|
let res = webapi#http#post(g:gist_api_url.'authorizations', webapi#json#encode({
|
|
\ 'scopes' : ['gist'],
|
|
\ 'note' : note,
|
|
\ 'note_url' : note_url,
|
|
\}), {
|
|
\ 'Content-Type' : 'application/json',
|
|
\ 'Authorization' : insecureSecret,
|
|
\})
|
|
let h = filter(res.header, 'stridx(v:val, "X-GitHub-OTP:") == 0')
|
|
if len(h)
|
|
let otp = inputsecret('OTP:')
|
|
if len(otp) == 0
|
|
let v:errmsg = 'Canceled'
|
|
return ''
|
|
endif
|
|
let res = webapi#http#post(g:gist_api_url.'authorizations', webapi#json#encode({
|
|
\ 'scopes' : ['gist'],
|
|
\ 'note' : note,
|
|
\ 'note_url' : note_url,
|
|
\}), {
|
|
\ 'Content-Type' : 'application/json',
|
|
\ 'Authorization' : insecureSecret,
|
|
\ 'X-GitHub-OTP' : otp,
|
|
\})
|
|
endif
|
|
let authorization = webapi#json#decode(res.content)
|
|
if has_key(authorization, 'token')
|
|
let secret = printf('token %s', authorization.token)
|
|
call writefile([secret], s:gist_token_file)
|
|
if !(has('win32') || has('win64'))
|
|
call system('chmod go= '.s:gist_token_file)
|
|
endif
|
|
elseif has_key(authorization, 'message')
|
|
let secret = ''
|
|
let v:errmsg = authorization.message
|
|
endif
|
|
return secret
|
|
endfunction
|
|
|
|
let s:extmap = extend({
|
|
\'.adb': 'ada',
|
|
\'.ahk': 'ahk',
|
|
\'.arc': 'arc',
|
|
\'.as': 'actionscript',
|
|
\'.asm': 'asm',
|
|
\'.asp': 'asp',
|
|
\'.aw': 'php',
|
|
\'.b': 'b',
|
|
\'.bat': 'bat',
|
|
\'.befunge': 'befunge',
|
|
\'.bmx': 'bmx',
|
|
\'.boo': 'boo',
|
|
\'.c-objdump': 'c-objdump',
|
|
\'.c': 'c',
|
|
\'.cfg': 'cfg',
|
|
\'.cfm': 'cfm',
|
|
\'.ck': 'ck',
|
|
\'.cl': 'cl',
|
|
\'.clj': 'clj',
|
|
\'.cmake': 'cmake',
|
|
\'.coffee': 'coffee',
|
|
\'.cpp': 'cpp',
|
|
\'.cppobjdump': 'cppobjdump',
|
|
\'.cs': 'csharp',
|
|
\'.css': 'css',
|
|
\'.cw': 'cw',
|
|
\'.d-objdump': 'd-objdump',
|
|
\'.d': 'd',
|
|
\'.darcspatch': 'darcspatch',
|
|
\'.diff': 'diff',
|
|
\'.duby': 'duby',
|
|
\'.dylan': 'dylan',
|
|
\'.e': 'e',
|
|
\'.ebuild': 'ebuild',
|
|
\'.eclass': 'eclass',
|
|
\'.el': 'lisp',
|
|
\'.erb': 'erb',
|
|
\'.erl': 'erlang',
|
|
\'.f90': 'f90',
|
|
\'.factor': 'factor',
|
|
\'.feature': 'feature',
|
|
\'.fs': 'fs',
|
|
\'.fy': 'fy',
|
|
\'.go': 'go',
|
|
\'.groovy': 'groovy',
|
|
\'.gs': 'gs',
|
|
\'.gsp': 'gsp',
|
|
\'.haml': 'haml',
|
|
\'.hs': 'haskell',
|
|
\'.html': 'html',
|
|
\'.hx': 'hx',
|
|
\'.ik': 'ik',
|
|
\'.ino': 'ino',
|
|
\'.io': 'io',
|
|
\'.j': 'j',
|
|
\'.java': 'java',
|
|
\'.js': 'javascript',
|
|
\'.json': 'json',
|
|
\'.jsp': 'jsp',
|
|
\'.kid': 'kid',
|
|
\'.lhs': 'lhs',
|
|
\'.lisp': 'lisp',
|
|
\'.ll': 'll',
|
|
\'.lua': 'lua',
|
|
\'.ly': 'ly',
|
|
\'.m': 'objc',
|
|
\'.mak': 'mak',
|
|
\'.man': 'man',
|
|
\'.mao': 'mao',
|
|
\'.matlab': 'matlab',
|
|
\'.md': 'markdown',
|
|
\'.minid': 'minid',
|
|
\'.ml': 'ml',
|
|
\'.moo': 'moo',
|
|
\'.mu': 'mu',
|
|
\'.mustache': 'mustache',
|
|
\'.mxt': 'mxt',
|
|
\'.myt': 'myt',
|
|
\'.n': 'n',
|
|
\'.nim': 'nim',
|
|
\'.nu': 'nu',
|
|
\'.numpy': 'numpy',
|
|
\'.objdump': 'objdump',
|
|
\'.ooc': 'ooc',
|
|
\'.parrot': 'parrot',
|
|
\'.pas': 'pas',
|
|
\'.pasm': 'pasm',
|
|
\'.pd': 'pd',
|
|
\'.phtml': 'phtml',
|
|
\'.pir': 'pir',
|
|
\'.pl': 'perl',
|
|
\'.po': 'po',
|
|
\'.py': 'python',
|
|
\'.pytb': 'pytb',
|
|
\'.pyx': 'pyx',
|
|
\'.r': 'r',
|
|
\'.raw': 'raw',
|
|
\'.rb': 'ruby',
|
|
\'.rhtml': 'rhtml',
|
|
\'.rkt': 'rkt',
|
|
\'.rs': 'rs',
|
|
\'.rst': 'rst',
|
|
\'.s': 's',
|
|
\'.sass': 'sass',
|
|
\'.sc': 'sc',
|
|
\'.scala': 'scala',
|
|
\'.scm': 'scheme',
|
|
\'.scpt': 'scpt',
|
|
\'.scss': 'scss',
|
|
\'.self': 'self',
|
|
\'.sh': 'sh',
|
|
\'.sml': 'sml',
|
|
\'.sql': 'sql',
|
|
\'.st': 'smalltalk',
|
|
\'.swift': 'swift',
|
|
\'.tcl': 'tcl',
|
|
\'.tcsh': 'tcsh',
|
|
\'.tex': 'tex',
|
|
\'.textile': 'textile',
|
|
\'.tpl': 'smarty',
|
|
\'.twig': 'twig',
|
|
\'.txt' : 'text',
|
|
\'.v': 'verilog',
|
|
\'.vala': 'vala',
|
|
\'.vb': 'vbnet',
|
|
\'.vhd': 'vhdl',
|
|
\'.vim': 'vim',
|
|
\'.weechatlog': 'weechatlog',
|
|
\'.xml': 'xml',
|
|
\'.xq': 'xquery',
|
|
\'.xs': 'xs',
|
|
\'.yml': 'yaml',
|
|
\}, get(g:, 'gist_extmap', {}))
|
|
|
|
let &cpo = s:save_cpo
|
|
unlet s:save_cpo
|
|
|
|
" vim:set et:
|