init.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. -- Copyright 2007-2017 Mitchell mitchell.att.foicica.com. See LICENSE.
  2. local M = {}
  3. --[[ This comment is for LuaDoc.
  4. ---
  5. -- The reST module.
  6. -- It provides utilities for editing reST and Sphinx documents.
  7. --
  8. -- ## Key Bindings
  9. --
  10. -- + `Ctrl+Alt+J` (`^⌘J` | `M-S-J`)
  11. -- Jump to the selected section.
  12. -- + `Shift+Enter` (`⇧↩` | `S-Enter`)
  13. -- Open the image specified by the directive on the current line.
  14. --
  15. -- @field DOCUTILS_PATH (string)
  16. -- The absolute path to the directory that contains the Python [Docutils][]
  17. -- library if it is not in the environment's `PYTHONPATH`.
  18. -- The default value is `nil`, which indicates Docutils is installed.
  19. --
  20. -- [Docutils]: http://docutils.sourceforge.net/
  21. module('_M.rest')]]
  22. M.DOCUTILS_PATH = nil
  23. -- Autocompletion and documentation.
  24. local sep = string.char(buffer.auto_c_type_separator)
  25. local XPM = textadept.editing.XPM_IMAGES
  26. local dirs = {
  27. 'admonition', 'attention', 'caution', 'citations', 'class', 'code',
  28. 'compound', 'container', 'contents', 'csv-table', 'danger', 'date',
  29. 'default-role', 'epigraph', 'error', 'figure', 'footer', 'footnotes',
  30. 'header', 'highlights', 'hint', 'image', 'important', 'include', 'line-block',
  31. 'list-table', 'math', 'meta', 'note', 'parsed-literal', 'pull-quote', 'raw',
  32. 'replace', 'restructuredtext-test-directive', 'role', 'rubric',
  33. 'section-autonumbering', 'sectnum', 'sidebar', 'table', 'target-notes', 'tip',
  34. 'title', 'topic', 'unicode', 'warning',
  35. }
  36. for i = 1, #dirs do
  37. dirs[i] = ("%s::%s%d"):format(dirs[i], sep, XPM.METHOD)
  38. end
  39. local sphinx_dirs = {
  40. 'centered', 'code-block', 'deprecated', 'glossary', 'highlight', 'hlist',
  41. 'index', 'literalinclude', 'note', 'only', 'productionlist', 'rubric',
  42. 'sectionauthor', 'seealso', 'tabularcolumns', 'toctree', 'versionadded',
  43. 'versionchanged', 'warning',
  44. }
  45. for i = 1, #sphinx_dirs do
  46. dirs[#dirs + 1] = ("%s::%s%d"):format(sphinx_dirs[i], sep, XPM.SLOT)
  47. end
  48. local options = {
  49. image = {'alt', 'height', 'width', 'scale', 'align', 'target'},
  50. figure = {
  51. 'alt', 'height', 'width', 'scale', 'align', 'target', 'figwidth', 'figclass'
  52. },
  53. sidebar = {'subtitle'}, code = {'number-lines'},
  54. ['csv-table'] = {
  55. 'widths', 'header-rows', 'stub-columns', 'header', 'file', 'url',
  56. 'encoding', 'delim', 'quote', 'keepspace', 'escape'
  57. },
  58. ['list-table'] = {'widths', 'header-rows', 'stub-columns'},
  59. contents = {'depth', 'local', 'backlinks'},
  60. sectnum = {'depth', 'prefix', 'suffix', 'start'},
  61. ['section-autonumbering'] = {'depth', 'prefix', 'suffix', 'start'},
  62. unicode = {'ltrim', 'rtrim', 'trim'},
  63. include = {
  64. 'start-line', 'end-line', 'start-after', 'end-before', 'literal', 'code',
  65. 'number-lines', 'encoding', 'tab-width'
  66. },
  67. raw = {'file', 'url', 'encoding'}, role = {'class'},
  68. toctree = {
  69. 'maxdepth', 'numbered', 'titlesonly', 'glob', 'hidden', 'includehidden'
  70. },
  71. hlist = {'columns'}, glossary = {'sorted'}, highlight = {'linenothreshold'},
  72. ['code-block'] = {'linenos', 'emphasize-lines'},
  73. sourcecode = {'linenos', 'emphasize-lines'},
  74. ['literal-include'] = {
  75. 'tab-width', 'linenos', 'emphasize-lines', 'language', 'encoding',
  76. 'pyobject', 'lines', 'start-after', 'end-before', 'prepend', 'append'
  77. },
  78. }
  79. for _, v in pairs(options) do
  80. v[#v + 1], v[#v + 2] = 'name', 'class' -- global options
  81. for i = 1, #v do v[i] = ("%s:%s%d"):format(v[i], sep, XPM.VARIABLE) end
  82. end
  83. local roles = {
  84. 'emphasis', 'literal', 'code', 'math', 'pep-reference', 'PEP',
  85. 'rfc-reference', 'RFC', 'string', 'subscript', 'sub', 'superscript', 'sup',
  86. 'title-reference', 'title', 't', 'raw'
  87. }
  88. for i = 1, #roles do
  89. roles[i] = ("%s:%s%d"):format(roles[i], sep, XPM.METHOD)
  90. end
  91. local sphinx_roles = {
  92. 'ref', 'doc', 'download', 'envvar', 'token', 'keyword', 'option', 'term',
  93. 'abbr', 'command', 'dfn', 'file', 'guilabel', 'kbd', 'mailheader', 'makevar',
  94. 'manpage', 'menuselection', 'mimetype', 'newsgroup', 'program', 'regexp',
  95. 'samp', 'index', 'pep', 'rfc'
  96. }
  97. for i = 1, #sphinx_roles do
  98. roles[#roles + 1] = ("%s:%s%d"):format(sphinx_roles[i], sep, XPM.SLOT)
  99. end
  100. textadept.editing.autocompleters.rest = function()
  101. local list = {}
  102. -- Retrieve the symbol behind the caret.
  103. local line, pos = buffer:get_cur_line()
  104. local line_part = line:sub(1, pos)
  105. local part = line_part:match('[%w-]*$')
  106. local name = '^'..part
  107. -- Determine whether or not the symbol is a directive, parameter, or role, and
  108. -- autocomplete as appropriate.
  109. if line_part:find('^%s*%.%. [%w-]*$') then
  110. -- Autocomplete directive.
  111. for i = 1, #dirs do
  112. if dirs[i]:find(name) then list[#list + 1] = dirs[i] end
  113. end
  114. elseif line_part:find('^%s*:[%w-]*$') then
  115. -- Autocomplete parameter or role.
  116. for i = buffer:line_from_position(buffer.current_pos) - 1, 0, -1 do
  117. line = buffer:get_line(i)
  118. local dir_options = options[line:match('^%s*%.%. ([%w-]+)::[ \r\n]')]
  119. if dir_options then
  120. -- Autocomplete parameter.
  121. for i = 1, #dir_options do
  122. if dir_options[i]:find(name) then list[#list + 1] = dir_options[i] end
  123. end
  124. break
  125. end
  126. if not line:find('^%s*:') then
  127. -- Autocomplete role.
  128. for i = 1, #roles do
  129. if roles[i]:find(name) then list[#list + 1] = roles[i] end
  130. end
  131. break
  132. end
  133. end
  134. elseif line_part:find('%s+:[%w-]*$') then
  135. -- Autocomplete role.
  136. for i = 1, #roles do
  137. if roles[i]:find(name) then list[#list + 1] = roles[i] end
  138. end
  139. end
  140. return #part, list
  141. end
  142. textadept.editing.api_files.rest = {
  143. _HOME..'/modules/rest/api', _USERHOME..'/modules/rest/api'
  144. }
  145. -- Commands.
  146. -- Add '`' to autopaired and typeover characters.
  147. events.connect(events.LEXER_LOADED, function(lexer)
  148. local rest = lexer == 'rest'
  149. textadept.editing.auto_pairs[string.byte('`')] = rest and '`' or nil
  150. textadept.editing.typeover_chars[string.byte('`')] = rest and 1 or nil
  151. end)
  152. -- Enable folding by the Sphinx convention for detected Sphinx files:
  153. -- # > * > = > - > ^ > ".
  154. events.connect(events.LEXER_LOADED, function(lexer)
  155. if lexer == 'rest' and buffer:get_line(0):find('^%s*%.%. .-sphinx') then
  156. buffer.property['fold.by.sphinx.convention'] = '1'
  157. buffer:colourise(0, buffer.end_styled)
  158. end
  159. end)
  160. local cmd = 'python "'.._HOME..'/modules/rest/rst2pseudoxml.py" '..
  161. '--report=2 --halt=5 "%s"'
  162. -- Show syntax errors as annotations.
  163. events.connect(events.FILE_AFTER_SAVE, function()
  164. if buffer:get_lexer() ~= 'rest' then return end
  165. buffer:annotation_clear_all()
  166. buffer.annotation_visible = buffer.ANNOTATION_BOXED
  167. local jumped = false
  168. local filename = buffer.filename:iconv(_CHARSET, 'UTF-8')
  169. spawn(cmd:format(filename), M.DOCUTILS_PATH, nil, function(chunk)
  170. for line in chunk:gmatch('[^\r\n]+') do
  171. local line_num, msg = line:match('^[^:]+:(%d+):%s*(.+)$')
  172. if line_num and msg and
  173. (msg:find('WARNING') or msg:find('ERROR') or msg:find('SEVERE')) then
  174. if msg:find('Unknown interpreted text role') then
  175. -- Ignore role errors when it comes to Sphinx roles.
  176. -- TODO: determine if the document is Sphinx or not?
  177. for i = 1, #sphinx_roles do
  178. if msg:find('"'..sphinx_roles[i]..'"') then goto continue end
  179. end
  180. end
  181. buffer.annotation_text[line_num - 1] = msg
  182. buffer.annotation_style[line_num - 1] = 8 -- error style number
  183. if not jumped then
  184. buffer:goto_line(line_num - 1)
  185. jumped = true
  186. end
  187. ::continue::
  188. end
  189. end
  190. end)
  191. end)
  192. ---
  193. -- Prompts the user to select a section title to jump to.
  194. -- Requires the entire document to be styled.
  195. -- @name goto_section
  196. function M.goto_section()
  197. if buffer.end_styled < buffer.length - 1 then
  198. buffer:colourise(0, buffer.length - 1)
  199. end
  200. local items = {}
  201. for i = 0, buffer.line_count - 2 do
  202. if bit32.band(buffer.fold_level[i + 1], buffer.FOLDLEVELHEADERFLAG) > 0 then
  203. local name = buffer:get_line(i + 1):match('^.')..
  204. buffer:get_line(i):match('^[^\r\n]*')
  205. if name then items[#items + 1], items[#items + 2] = i + 1, name end
  206. end
  207. end
  208. local button, i = ui.dialogs.filteredlist{
  209. title = 'Goto Section', columns = {'Line', 'Name'}, items = items,
  210. search_column = 2, string_output = true
  211. }
  212. if button ~= _L['_OK'] then return end
  213. textadept.editing.goto_line(tonumber(i) - 1)
  214. end
  215. ---
  216. -- Opens the image specified in an "image" or "figure" directive on the current
  217. -- line.
  218. -- @name open_image
  219. function M.open_image()
  220. local line = buffer:get_cur_line()
  221. local file = line:match('^%s*%.%. image::%s+(%S+)') or
  222. line:match('^%s*%.%. figure::%s+(%S+)')
  223. if not file or not buffer.filename then return end
  224. local cmd = 'xdg-open "%s"'
  225. if WIN32 then
  226. cmd = 'start "" "%s"'
  227. elseif OSX then
  228. cmd = 'open "file://%s"'
  229. end
  230. spawn(cmd:format(buffer.filename:match('^.+[/\\]')..file))
  231. end
  232. ---
  233. -- Container for reST-specific key bindings.
  234. -- @class table
  235. -- @name _G.keys.rest
  236. keys.rest = {
  237. [not OSX and 'cag' or 'cmg'] = M.goto_section,
  238. ['s\n'] = M.open_image,
  239. }
  240. -- Snippets.
  241. if type(snippets) == 'table' then
  242. ---
  243. -- Container for reST-specific snippets.
  244. -- @class table
  245. -- @name _G.snippets.rest
  246. snippets.rest = {
  247. attention = [[
  248. .. attention::
  249. %0
  250. ]],
  251. danger = [[
  252. .. danger::
  253. %0
  254. ]],
  255. error = [[
  256. .. error::
  257. %0
  258. ]],
  259. hint = [[
  260. .. hint::
  261. %0
  262. ]],
  263. important = [[
  264. .. important::
  265. %0
  266. ]],
  267. note = [[
  268. .. note::
  269. %0
  270. ]],
  271. tip = [[
  272. .. tip::
  273. %0
  274. ]],
  275. warning = [[
  276. .. warning::
  277. %0
  278. ]],
  279. admonition = [[
  280. .. admonition:: %1(title)
  281. %0
  282. ]],
  283. image = [[
  284. .. image:: %1(picture.jpg)
  285. :height: %2(100px)
  286. :width: %3(200px)
  287. :scale: %4(50%%)
  288. :alt: %5(alternate text)
  289. :align: %6(right)
  290. ]],
  291. figure = [[
  292. .. figure:: %1(picture.jpg)
  293. :height: %2(100px)
  294. :width: %3(200px)
  295. :scale: %4(50%%)
  296. :alt: %5(alternate text)
  297. :align: %6(right)
  298. :figwidth: %7(figure width)
  299. :figclass: %8(figure class)
  300. %9(caption)
  301. %10(legend)
  302. ]],
  303. codeblock = [[
  304. .. code-block:: %1(language)
  305. %2(:linenos:
  306. )%0
  307. ]],
  308. footnote = [[[#f%1(number)]_
  309. .. rubric:: %2(Footnotes)
  310. .. [#f1] Text of the first footnote.
  311. .. [#f2] Text of the second footnote.
  312. ]],
  313. replace = [[
  314. .. |%1(name)| replace:: %2(replacement *text*)
  315. ]],
  316. repeatingimage = [[
  317. .. |%1(caution)| image:: %2(warning.png)
  318. :height: %3(100px)
  319. :width: %4(200px)
  320. :scale: %5(50%%)
  321. :alt: %6(warning)
  322. :align: %7(right)
  323. ]],
  324. comment = ".. %0",
  325. anchor = [[
  326. .. _`%1(anchor)`: ]],
  327. reference = [[:ref:`%1(anchor)`]],
  328. emphasis = [[*%1(text)*]],
  329. strong_emphasis = [[**%1(text)**]],
  330. code = [[``%1(text)``]],
  331. external_link_together = [[`%1(link text) <%2(http://example.com/)>`_]],
  332. external_link_separated = [[`%1(link text)`_
  333. .. _`%1`: %2(http://example.com/)]],
  334. }
  335. end
  336. return M