391 lines
11 KiB
VimL
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
|