" 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("") " 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("") 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