nvim/pack/acp/start/vim-asciidoctor/autoload/asciidoctor.vim

391 lines
11 KiB
VimL

" Maintainer: Maxim Kim (habamax@gmail.com)
" vim: et sw=4
if exists("g:loaded_asciidoctor_autoload")
finish
endif
let g:loaded_asciidoctor_autoload = 1
"" Trim string
" Unfortunately built-in trim is not widely available yet
" return trimmed string
func! s:trim(str) abort
return substitute(a:str, '^\s*\|\s*$', '', 'g')
endfunc
"" Return name of an image directory.
"
" It is either
" * '' (empty)
" * or value of :imagesdir: (stated at the top of the buffer, first 50 lines)
func! s:asciidoctorImagesDir()
let imagesdirs = filter(getline(1, 50), {k,v -> v =~ '^:imagesdir:.*'})
if len(imagesdirs)>0
return matchstr(imagesdirs[0], ':imagesdir:\s*\zs\f\+\ze$').'/'
else
return ''
endif
endfunc
"" Return full path of an image
"
" It is 'current buffer path'/:imagesdir:
func! s:asciidoctorImagesPath()
return expand('%:p:h').'/'.s:asciidoctorImagesDir()
endfunc
"" Return list of generated images for the current buffer.
"
" If buffer name is `document.adoc`, search in a given path for the file
" pattern `g:asciidoctor_img_paste_pattern`.
"
" Example:
" `img_document_1.png`
" `img_document_2.png`
func! s:asciidoctorListImages(path)
let rxpattern = '\V\[\\/]'.printf(g:asciidoctor_img_paste_pattern, expand('%:t:r'), '\d\+').'\$'
let images = globpath(a:path, '*.png', 1, 1)
return filter(images, {k,v -> v =~ rxpattern})
endfunc
"" Return index of the image file name
"
" `img_document_23.png` --> 23
" `img_document.png` --> 0
" `any other` --> 0
func! s:asciidoctorExtractIndex(filename)
let rxpattern = '\V\[\\/]'.printf(g:asciidoctor_img_paste_pattern, expand('%:t:r'), '\zs\d\+\ze').'\$'
let index = matchstr(a:filename, rxpattern)
if index == ''
let index = '0'
endif
return str2nr(index)
endfunc
"" Return new image name
"
" Having the list of images in a give path:
" `img_document_1.png`
" `img_document_2.png`
" ...
" Generate a new image name:
" `img_document_3.png
func! s:asciidoctorGenerateImageName(path)
let index = max(map(s:asciidoctorListImages(a:path),
\{k,v -> s:asciidoctorExtractIndex(v)})) + 1
return printf(g:asciidoctor_img_paste_pattern, expand('%:t:r'), index)
endfunc
"" Paste image from the clipboard.
"
" * Save image as png file to the :imagesdir:
" * Insert `image::link.png[]` at cursor position
func! asciidoctor#pasteImage() abort
let path = s:asciidoctorImagesPath()
if !isdirectory(path)
echoerr 'Image directory '.path.' doesn''t exist!'
return
endif
let fname = s:asciidoctorGenerateImageName(path)
let cmd = printf(g:asciidoctor_img_paste_command, path, fname)
let res = system(cmd)
if v:shell_error
echohl Error | echomsg s:trim(res) | echohl None
return
endif
let sav_reg_x = @x
let @x = printf('image::%s[]', fname)
put x
let @x = sav_reg_x
endfunc
"" Check header (20 lines) of the file for default source language
func! asciidoctor#detect_source_language()
for line in getline(1, 20)
let m = matchlist(line, '^:source-language: \(.*\)$')
if !empty(m)
let src_lang = s:trim(m[1])
if src_lang != ''
let b:asciidoctor_source_language = s:trim(m[1])
break
endif
endif
endfor
endfunc
"" Refresh highlighting for default source language.
"
" Should be called on buffer write.
func! asciidoctor#refresh_source_language_hl()
let cur_b_source_language = get(b:, "asciidoctor_source_language", "NONE")
call asciidoctor#detect_source_language()
if cur_b_source_language != get(b:, "asciidoctor_source_language", "NONE")
syn enable
endif
endfunc
"" Test bibliography completefunc
func! asciidoctor#complete_bibliography(findstart, base)
if a:findstart
let prefix = strpart(getline('.'), 0, col('.')-1)
let m = match(prefix, 'cite\%(np\)\?:\[\zs[[:alnum:]]*$')
if m != -1
return m
else
return -3
endif
else
" return filtered list of
" [{"word": "citation1", "menu": "article"}, {"word": "citation2", "menu": "book"}, ...]
" if "word" matches with a:base
return filter(
\ map(s:read_all_bibtex(), {_, val -> {'word': matchstr(val, '{\zs.\{-}\ze,'), 'menu': matchstr(val, '@\zs.\{-}\ze{')}}),
\ {_, val -> val['word'] =~ '^'.a:base.'.*'})
endif
endfunc
"" Read bibtex file
"
" Return list of citations
func! s:read_bibtex(file)
let citation_types = '@book\|@article\|@booklet\|@conference\|@inbook'
\.'\|@incollection\|@inproceedings\|@manual\|@mastersthesis'
\.'\|@misc\|@phdthesis\|@proceedings\|@techreport\|@unpublished'
let citations = filter(readfile(a:file), {_, val -> val =~ citation_types})
return citations
endfunc
"" Read all bibtex files from a current file's path
"
" Return list of citations
func! s:read_all_bibtex()
let citations = []
for bibfile in globpath(expand('%:p:h'), '*.bib', 0, 1)
call extend(citations, s:read_bibtex(bibfile))
endfor
return citations
endfunc
"" Check header (30 lines) of the file for theme name
" return theme name
func! asciidoctor#detect_pdf_theme()
let result = ''
for line in getline(1, 30)
let m = matchlist(line, '^:pdf-theme: \(.*\)$')
if !empty(m)
let result = s:trim(m[1])
if result != ''
return result
endif
endif
endfor
endfunc
"" asciidoctor header text object
" * inner object is the text between prev section header(excluded) and the next
" section of the same level(excluded) or end of file.
" Except for `= Title`: text between title(excluded) and first `== Section`(excluded) or end of file.
" * an object is the text between prev section header(included) and the next section of the same
" level(excluded) or end of file.
" Except for `= Title`: text between title(included) and first `== Section`(excluded) or end of file.
func! asciidoctor#header_textobj(inner) abort
let lnum_start = search('^=\+\s\+[^[:space:]=]', "ncbW")
if lnum_start
let lvlheader = matchstr(getline(lnum_start), '^=\+')
let lnum_end = search('^=\{2,'..len(lvlheader)..'}\s', "nW")
if !lnum_end
let lnum_end = search('\%$', 'cnW')
else
let lnum_end -= 1
endif
if a:inner && getline(lnum_start + 1) !~ '^=\+\s\+[^[:space:]=]'
let lnum_start += 1
endif
exe lnum_end
normal! V
exe lnum_start
endif
endfunc
"" asciidoctor delimited block text object
" * inner object is the text between delimiters
" * an object is the text between between delimiters plus delimiters included.
func! asciidoctor#delimited_block_textobj(inner) abort
let lnum_start = search('^\(\(-\{2,}\)\|\(=\{4,}\)\|\(_\{4,}\)\|\(\*\{4,}\)\|\(\.\{4,}\)\)\s*$', "ncbW")
if lnum_start
let delim = getline(lnum_start)
let lnum_end = search('^'..delim[0]..'\{'..len(delim)..'}\s*$', "nW")
if lnum_end
if a:inner
let lnum_start += 1
let lnum_end -= 1
endif
exe lnum_end
normal! V
exe lnum_start
endif
endif
endfunc
"" Return Windows path from WSL
func! s:wsl_to_windows_path(path) abort
if !exists("$WSLENV")
return a:path
endif
if !executable('wslpath')
return a:path
endif
let res = systemlist('wslpath -w ' . a:path)
if !empty(res)
return res[0]
else
return a:path
endif
endfunc
func! asciidoctor#open_file(filename)
if filereadable(a:filename)
if exists("$WSLENV")
exe g:asciidoctor_opener . ' '
\ . shellescape(s:wsl_to_windows_path(a:filename))
else
exe g:asciidoctor_opener . ' ' . shellescape(a:filename)
endif
else
echom a:filename . " doesn't exist!"
endif
endfunc
"" to open URLs/files with gx/gf mappings
func! asciidoctor#open_url(...) abort
let cmd = get(a:, 1, g:asciidoctor_opener)
" by default check WORD under cursor
let word = expand("<cWORD>")
" But if cursor is surrounded by [ ], like for http://ya.ru[yandex search]
" take a cWORD from first char before [
let save_cursor = getcurpos()
let line = getline('.')
if searchpair('\[', '', '\]', 'b', '', line('.'))
let word = expand("<cWORD>")
endif
call setpos('.', save_cursor)
" Check asciidoc URL http://bla-bla.com[desc
let aURL = matchstr(word, '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\+\ze\[')
if aURL != ""
exe cmd . ' "' . escape(aURL, '#%!') . '"'
return
endif
" Check asciidoc link link:file.txt[desc
let aLNK = matchstr(word, 'link:/*\zs\S\+\ze\[')
if aLNK != ""
execute "lcd ". expand("%:p:h")
exe cmd . ' ' . fnameescape(fnamemodify(aLNK, ":p"))
lcd -
return
endif
" Check asciidoc URL http://bla-bla.com
let URL = matchstr(word, '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\+')
if URL != ""
exe cmd . ' "' . escape(URL, '#%!') . '"'
return
endif
" probably path?
if word =~ '^[~.$].*'
exe cmd . ' ' . expand(word)
return
endif
try
exe "normal! gf"
catch /E447/
echohl Error
echomsg matchstr(v:exception, 'Vim(normal):\zs.*$')
echohl None
endtry
endfunc
"" Promote sections including subsections
"" * Doesn't check for the real syntax sections (might fail with "pseudo" sections
"" embedded into source blocks)
"" * Doesn't work for underlined sections.
func! asciidoctor#promote_section() abort
let save = winsaveview()
try
if search('^=\+\s\+\S', 'cbW')
let lvl = len(matchstr(getline('.'), '^=\+'))
if lvl > 5
return
endif
let next_lvl = lvl + 1
while lvl < next_lvl
call setline('.', '='..getline('.'))
if search('^=\{'..(lvl + 1)..',}\s\+\S', 'W')
let next_lvl = len(matchstr(getline('.'), '^=\+'))
else
break
endif
endwhile
endif
finally
call winrestview(save)
endtry
endfunc
"" Demote sections including subsections
"" * Doesn't check for the real syntax sections (might fail with "pseudo" sections
"" embedded into source blocks)
"" * Doesn't work for underlined sections.
func! asciidoctor#demote_section() abort
let save = winsaveview()
try
if search('^=\+\s\+\S', 'cbW')
let lvl = len(matchstr(getline('.'), '^=\+'))
let parent_section = search('^=\{1,'..max([(lvl - 1), 1])..'}\s\+\S', 'nbW')
let parent_lvl = len(matchstr(getline(parent_section), '^=\+'))
let next_lvl = lvl + 1
while lvl < next_lvl && (lvl > parent_lvl+1)
if lvl == 1
break
else
call setline('.', getline('.')[1:])
endif
if search('^=\{'..(lvl + 1)..',}\s\+\S', 'W')
let next_lvl = len(matchstr(getline('.'), '^=\+'))
else
break
endif
endwhile
endif
finally
call winrestview(save)
endtry
endfunc