command.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # encoding=utf-8
  2. import vim
  3. import re
  4. import sys
  5. import os.path
  6. import argparse
  7. import shlex
  8. from subprocess import Popen, PIPE
  9. from itertools import chain
  10. from vim_pandoc.utils import plugin_enabled_modules, ensure_string
  11. from vim_pandoc.bib.vim_completer import find_bibfiles
  12. from vim_pandoc.helpparser import PandocInfo
  13. class PandocCommand(object):
  14. def __init__(self):
  15. self.pandoc_info = PandocInfo(vim.vars["pandoc#command#path"])
  16. self.formats_table = {}
  17. self.build_formats_table()
  18. self._output_file_path = None
  19. self._run_command = None
  20. self._out = None
  21. self._python_executable = None
  22. def build_formats_table(self):
  23. for i in self.pandoc_info.output_formats:
  24. if i in ("asciidoc", "plain"):
  25. self.formats_table[i] = "txt"
  26. elif i in ("beamer", "pdf"):
  27. self.formats_table[i] = "pdf"
  28. elif i in ("dzslides", "html", "html5", "mediawiki", "revealjs", "s5", "slideous", "slidy"):
  29. self.formats_table[i] = "html"
  30. elif i in ("markdown", "gfm", "markdown_github", "markdown_mmd", "markdown_phpextra", "markdown_strict"):
  31. self.formats_table[i] = "md"
  32. elif i in ("odt", "opendocument"):
  33. self.formats_table[i] = "odt"
  34. elif i == "native":
  35. self.formats_table[i] = "hs"
  36. elif i == "texinfo":
  37. self.formats_table[i] = "info"
  38. elif i == "latex":
  39. self.formats_table[i] = "tex"
  40. else:
  41. self.formats_table[i] = i
  42. if "latex" in self.formats_table or "beamer" in self.formats_table and "pdf" not in self.formats_table:
  43. self.formats_table["pdf"] = "pdf"
  44. def __call__(self, args, should_open):
  45. largs = shlex.split(args)
  46. if largs == [] or largs[0].startswith('-'):
  47. largs = ['html'] + largs # make sure we pass an output format
  48. p = self.pandoc_info.build_argument_parser()
  49. c_vars = vars(p.parse_args(largs))
  50. # Infer arguments from vim environment
  51. # a) bibliographies
  52. if 'bibliographies' in plugin_enabled_modules():
  53. local_bibs = vim.eval('b:pandoc_biblio_bibs')
  54. found_bibs = find_bibfiles()
  55. if local_bibs or found_bibs and not c_vars['bibliography']:
  56. c_vars['bibliography'] = []
  57. if local_bibs:
  58. c_vars['bibliography'].extend(local_bibs)
  59. if found_bibs:
  60. c_vars['bibliography'].extend(found_bibs)
  61. # Now, we must determine what are our input and output files
  62. # a) First, let's see what is the desired output format...
  63. output_format = c_vars['output_format'] \
  64. if self.pandoc_info.is_valid_output_format(c_vars['output_format']) \
  65. or c_vars['output_format'] == 'pdf' \
  66. else "html"
  67. # overwrite --to with this value
  68. # 'pdf' is not a valid output format, we pass it to -o instead)
  69. if output_format != 'pdf':
  70. c_vars['to'] = output_format
  71. else:
  72. c_vars['to'] = 'latex'
  73. if output_format == 'pdf':
  74. # pdf engine
  75. if self.pandoc_info.version >= '2':
  76. engine_option = 'pdf_engine'
  77. else:
  78. engine_option = 'latex_engine'
  79. if not c_vars[engine_option]:
  80. try: # try a buffer local engine
  81. engine_var = ensure_string(vim.current.buffer.vars['pandoc_command_latex_engine'])
  82. except: # use te global value
  83. engine_var = ensure_string(vim.vars['pandoc#command#latex_engine'])
  84. c_vars[engine_option] = str(engine_var)
  85. if not c_vars['output']:
  86. self._output_file_path = vim.eval('expand("%:r")') + '.' \
  87. + self.formats_table[re.split("[-+]", output_format)[0]]
  88. c_vars['output'] = self._output_file_path
  89. else:
  90. self._output_file_path = os.path.expanduser(c_vars['output'][0])
  91. input_arg = '"' + vim.eval('expand("%")') + '"'
  92. # Now, we reconstruct the pandoc call
  93. arglist = []
  94. arglist.append(ensure_string(vim.vars['pandoc#compiler#command']))
  95. arglist.append(ensure_string(vim.vars['pandoc#compiler#arguments']))
  96. # Only consider enabled flags and arguments with values
  97. extra_arg_vars_keys = [k for k in c_vars.keys() if c_vars[k] and k != 'output_format']
  98. for var in extra_arg_vars_keys:
  99. real_var = var.replace("_", "-")
  100. val = c_vars[var]
  101. if type(val) == list and len(val) > 1: # multiple values, repeat keys
  102. for vv in val:
  103. if type(vv) == list and type(vv[0]) == list:
  104. vv = vv[0][0]
  105. elif type(vv) == list:
  106. vv = vv[0]
  107. elif type(val) == bool:
  108. vv = None
  109. if vv:
  110. vv = os.path.expanduser(vv)
  111. arglist.append("--" + real_var + '="' + str(vv) + '"')
  112. else:
  113. arglist.append("--" + real_var)
  114. else:
  115. if type(val) == list and type(val[0]) == list:
  116. val = val[0][0]
  117. elif type(val) == list:
  118. val = val[0]
  119. elif type(val) == bool:
  120. val = None
  121. if val:
  122. val = os.path.expanduser(val)
  123. arglist.append('--' + real_var + '="' + str(val) + '"')
  124. else:
  125. arglist.append('--' + real_var)
  126. arglist.append(input_arg)
  127. self._run_command = " ".join(arglist)
  128. # execute
  129. self.execute(should_open)
  130. def python_executable(self):
  131. if self._python_executable is None:
  132. if vim.eval("executable('python')") == '1':
  133. self._python_executable = 'python'
  134. elif vim.eval("executable('python3')") == '1':
  135. self._python_executable = 'python3'
  136. return self._python_executable
  137. def execute(self, should_open):
  138. with open("pandoc.out", 'w') as tmp:
  139. # for nvim
  140. if vim.eval("has('nvim')") == '1':
  141. try:
  142. should_open_s = str(int(should_open))
  143. except:
  144. should_open_s = '0'
  145. if int(vim.eval("bufloaded('pandoc-execute')")):
  146. wnr = vim.eval("bufwinnr('pandoc-execute')")
  147. vim.command(wnr + "wincmd c")
  148. vim.command(wnr + "put='Running pandoc...\n'")
  149. vim.command("botright 7new pandoc-execute")
  150. vim.command("setlocal buftype=nofile")
  151. vim.command("setlocal bufhidden=wipe")
  152. vim.command("setlocal nobuflisted")
  153. vim.command("map <buffer> q <Esc>:close<Enter>")
  154. vim.command("call termopen(" + \
  155. "['"+ "','".join(shlex.split(self._run_command)) + "'], " + \
  156. " extend({'should_open': '" + should_open_s + "'}," +\
  157. " {'on_exit': 'pandoc#command#JobHandler'," + \
  158. "'on_stdout': 'pandoc#command#JobHandler'," + \
  159. "'on_stderr': 'pandoc#command#JobHandler'}))")
  160. vim.command("file pandoc-execute")
  161. vim.command("normal G")
  162. vim.command("wincmd p")
  163. # for vim versions with clientserver support
  164. elif vim.eval("has('clientserver')") == '1' and \
  165. vim.eval("v:servername") != "" and \
  166. self.python_executable() is not None:
  167. async_runner = '"' + os.path.join(os.path.dirname(__file__), "async.py") + '"'
  168. servername_arg = "--servername=" + vim.eval("v:servername")
  169. open_arg = "--open" if should_open else "--noopen"
  170. async_command = " ".join([self.python_executable(), async_runner, servername_arg, open_arg, self._run_command])
  171. try:
  172. Popen(shlex.split(async_command), stdout=tmp, stderr=tmp)
  173. except:
  174. vim.command('echoe "vim-pandoc: could not execute pandoc asynchronously"')
  175. else:
  176. try: # fallback to synchronous execution
  177. com = Popen(shlex.split(self._run_command), stdout=tmp, stderr=tmp)
  178. com.wait()
  179. except:
  180. vim.command('echoe "vim-pandoc: could not execute pandoc"')
  181. return
  182. self.on_done(should_open, com.returncode)
  183. def on_done(self, should_open, returncode):
  184. if self._run_command and self._output_file_path:
  185. vim.command("echohl Statement")
  186. vim.command("echom strftime('%Y%m%d %T') . ' vim-pandoc:ran " + self._run_command + "'")
  187. vim.command("echohl None")
  188. if vim.eval("g:pandoc#command#use_message_buffers") == '1' \
  189. and returncode not in ('0', 0):
  190. vim.command("let split = &splitbelow")
  191. vim.command("set splitbelow")
  192. vim.command("5new pandoc\ output")
  193. vim.command("let &splitbelow = split")
  194. vim.command("setlocal wrap")
  195. vim.command("setlocal linebreak")
  196. vim.current.buffer[0] = "# Press q to close this"
  197. vim.current.buffer.append("> " + self._run_command)
  198. vim.command("normal! G")
  199. if vim.eval('filereadable("pandoc.out")') == '1':
  200. vim.command("silent r pandoc.out")
  201. vim.command("setlocal buftype=nofile")
  202. vim.command("setlocal nobuflisted")
  203. # pressing q on the buffer will delete it
  204. vim.command("map <buffer> q :bwipeout<cr>")
  205. # we will highlight some elements in the buffer
  206. vim.command("syn match PandocOutputMarks /^>>/")
  207. vim.command("syn match PandocCommand /^>.*$/hs=s+1")
  208. vim.command("syn match PandocInstructions /^#.*$/")
  209. vim.command("hi! link PandocOutputMarks Operator")
  210. vim.command("hi! link PandocCommand Debug")
  211. vim.command("hi! link PandocInstructions Comment")
  212. # under windows, pandoc.out is not closed by async.py in time sometimes,
  213. # so we wait a bit
  214. if sys.platform.startswith("win"):
  215. from time import sleep
  216. sleep(1)
  217. if os.path.exists("pandoc.out"):
  218. os.remove("pandoc.out")
  219. # open file if needed
  220. # nvim's python host doesn't change the directory the same way vim does
  221. try:
  222. if vim.eval('has("nvim")') == '1':
  223. os.chdir(vim.eval('getcwd()'))
  224. except:
  225. pass
  226. if vim.eval("g:pandoc#command#prefer_pdf") == "1":
  227. maybe_pdf = os.path.splitext(self._output_file_path)[0] + ".pdf"
  228. if os.path.splitext(self._output_file_path)[1] in [".tex", "*.latex"] \
  229. and os.path.exists(maybe_pdf):
  230. self._output_file_path = maybe_pdf
  231. if os.path.exists(os.path.abspath(self._output_file_path)) and should_open:
  232. # if g:pandoc#command#custom_open is defined and is a valid funcref
  233. if vim.eval("g:pandoc#command#custom_open") != "" \
  234. and vim.eval("exists('*"+vim.eval("g:pandoc#command#custom_open")+"')") == '1':
  235. custom_command = vim.eval(vim.eval("g:pandoc#command#custom_open") \
  236. + "('"+self._output_file_path+"')")
  237. Popen(shlex.split(custom_command))
  238. # otherwise use platform defaults:
  239. else:
  240. if sys.platform == "darwin" or sys.platform.startswith("linux"):
  241. if sys.platform == "darwin":
  242. open_command = "open" #OSX
  243. elif sys.platform.startswith("linux"):
  244. open_command = "xdg-open" # freedesktop/linux
  245. with open(os.devnull, 'wb') as fnull:
  246. Popen([open_command, self._output_file_path], stderr=fnull)
  247. elif sys.platform.startswith("win"):
  248. Popen('cmd /c "start ' + self._output_file_path + '"')
  249. # we reset this
  250. self._output_file_path = None
  251. self._run_command = None
  252. vim.command("redraw")
  253. if returncode in ("0", 0):
  254. vim.command("echohl Statement")
  255. vim.command("echom 'vim-pandoc:ran successfully.'")
  256. vim.command("echohl None")
  257. pandoc = PandocCommand()