typst.lua 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. -- local function parse_stderr(data)
  2. -- local message = table.concat(data, "\n")
  3. -- local level = "info"
  4. --
  5. -- -- Check each line for error or warning
  6. -- for line in message:gmatch("[^\r\n]+") do
  7. -- local lower_line = line:lower()
  8. -- if lower_line:match("^%s*error:") then
  9. -- level = "error"
  10. -- break
  11. -- elseif lower_line:match("^%s*warning:") then
  12. -- level = "warn"
  13. -- -- Don't break here, in case an error appears later
  14. -- end
  15. -- end
  16. --
  17. -- return message, level
  18. -- end
  19. local function parse_stderr(data, filter_level)
  20. local messages = {}
  21. local current_message = {}
  22. local current_level = "info"
  23. -- Split data into blocks
  24. for _, line in ipairs(data) do
  25. if line:match("^%s*$") then
  26. -- Empty line, end of block
  27. if #current_message > 0 then
  28. table.insert(messages, { message = table.concat(current_message, "\n"), level = current_level })
  29. current_message = {}
  30. current_level = "info"
  31. end
  32. else
  33. -- Check for error/warning/info at the beginning of the block
  34. local lower_line = line:lower()
  35. if lower_line:match("^%s*error:") then
  36. current_level = "error"
  37. elseif lower_line:match("^%s*warning:") then
  38. current_level = "warn"
  39. elseif lower_line:match("^%s*info:") then
  40. current_level = "info"
  41. end
  42. table.insert(current_message, line)
  43. end
  44. end
  45. -- Add last block if exists
  46. if #current_message > 0 then
  47. table.insert(messages, { message = table.concat(current_message, "\n"), level = current_level })
  48. end
  49. -- Filter messages based on filter_level
  50. local filtered_messages = {}
  51. for _, msg in ipairs(messages) do
  52. if not filter_level or msg.level == filter_level then
  53. table.insert(filtered_messages, msg)
  54. end
  55. end
  56. return filtered_messages
  57. end
  58. local function typst_build_and_open_sioyek()
  59. local bufname = vim.fn.expand("%:p")
  60. local pdf_path = vim.fn.fnamemodify(bufname, ":r") .. ".pdf"
  61. local cmd = string.format('typst compile "%s"', bufname)
  62. vim.fn.jobstart(cmd, {
  63. on_exit = function(_, exit_code)
  64. if exit_code == 0 then
  65. vim.notify("Typst build completed successfully", vim.log.levels.INFO, {
  66. title = "Typst Build",
  67. timeout = 3000,
  68. })
  69. -- Now open or reload Sioyek
  70. local sioyek_running = vim.fn.system("pgrep sioyek"):match("%d+")
  71. if sioyek_running then
  72. vim.fn.system("sioyek --execute-command reload_no_flicker")
  73. vim.notify("Sioyek reloaded", vim.log.levels.INFO, {
  74. title = "Sioyek",
  75. timeout = 2000,
  76. })
  77. else
  78. vim.fn.jobstart('sioyek "' .. pdf_path .. '"', {
  79. on_exit = function(_, sioyek_exit_code)
  80. if sioyek_exit_code == 0 then
  81. vim.notify("Sioyek opened", vim.log.levels.INFO, {
  82. title = "Sioyek",
  83. timeout = 2000,
  84. })
  85. else
  86. vim.notify("Failed to open Sioyek", vim.log.levels.ERROR, {
  87. title = "Sioyek",
  88. timeout = 3000,
  89. })
  90. end
  91. end,
  92. })
  93. end
  94. else
  95. vim.notify("Typst build failed", vim.log.levels.ERROR, {
  96. title = "Typst Build",
  97. timeout = 5000,
  98. })
  99. end
  100. end,
  101. stderr_buffered = true,
  102. on_stderr = function(_, data)
  103. if data and #data > 0 then
  104. local messages = parse_stderr(data)
  105. local error_count = 0
  106. local warning_count = 0
  107. local info_count = 0
  108. for _, msg in ipairs(messages) do
  109. if msg.level == "error" then
  110. error_count = error_count + 1
  111. elseif msg.level == "warn" then
  112. warning_count = warning_count + 1
  113. elseif msg.level == "info" then
  114. info_count = info_count + 1
  115. end
  116. end
  117. local notify = require("notify")
  118. local original_opts = vim.deepcopy(notify.options)
  119. notify.setup({
  120. stages = "static",
  121. -- Add any other temporary options here
  122. })
  123. for _, msg in ipairs(messages) do
  124. if msg.level ~= "error" then
  125. -- Route warnings and info to notify without displaying
  126. vim.notify(msg.message, vim.log.levels[msg.level:upper()], {
  127. title = "Typst Build " .. msg.level,
  128. timeout = 0,
  129. silent = true,
  130. log = true,
  131. })
  132. end
  133. end
  134. notify.setup(original_opts)
  135. for _, msg in ipairs(messages) do
  136. if msg.level == "error" then
  137. vim.notify(msg.message, vim.log.levels.ERROR, {
  138. title = "Typst Build Error",
  139. timeout = 5000,
  140. })
  141. end
  142. end
  143. if warning_count > 0 then
  144. vim.notify(string.format("%d warning(s)", warning_count), vim.log.levels.WARN, {
  145. title = "Typst Build Summary",
  146. timeout = 3000,
  147. })
  148. end
  149. if info_count > 0 then
  150. vim.notify(string.format("%d info message(s)", info_count), vim.log.levels.INFO, {
  151. title = "Typst Build Summary",
  152. timeout = 2000,
  153. })
  154. end
  155. end
  156. end,
  157. })
  158. end
  159. vim.api.nvim_create_user_command("TypstBuild", typst_build_and_open_sioyek, {})
  160. -- Create an autocommand group
  161. local typst_group = vim.api.nvim_create_augroup("TypstBuildGroup", { clear = true })
  162. -- Create a command to toggle the autocommand
  163. local typst_auto_build_enabled = false
  164. -- Function to create the autocommand
  165. local function create_typst_autocmd()
  166. vim.api.nvim_create_autocmd("BufWritePost", {
  167. group = typst_group,
  168. pattern = "*.typ",
  169. callback = function()
  170. vim.cmd("TypstBuild")
  171. end,
  172. })
  173. end
  174. -- Create a command to toggle the autocommand
  175. vim.api.nvim_create_user_command("TypstAutoBuild", function()
  176. typst_auto_build_enabled = not typst_auto_build_enabled
  177. if typst_auto_build_enabled then
  178. create_typst_autocmd()
  179. vim.notify("Typst auto-build enabled", vim.log.levels.INFO)
  180. else
  181. vim.api.nvim_clear_autocmds({ group = typst_group })
  182. vim.notify("Typst auto-build disabled", vim.log.levels.INFO)
  183. end
  184. end, {})
  185. return {
  186. -- { "kaarmu/typst.vim", ft = "typst", lazy = false },
  187. -- {
  188. -- "chomosuke/typst-preview.nvim",
  189. -- lazy = false, -- or ft = 'typst'
  190. -- version = "0.3.*",
  191. -- config = function()
  192. -- require("typst-preview").setup({
  193. -- -- Setting this true will enable printing debug information with print()
  194. -- debug = false,
  195. --
  196. -- -- Custom format string to open the output link provided with %s
  197. -- -- Example: open_cmd = 'firefox %s -P typst-preview --class typst-preview'
  198. -- open_cmd = 'sioyek --execute-command reload_no_flicker %s',
  199. --
  200. -- -- Setting this to 'always' will invert black and white in the preview
  201. -- -- Setting this to 'auto' will invert depending if the browser has enable
  202. -- -- dark mode
  203. -- invert_colors = "never",
  204. --
  205. -- -- Whether the preview will follow the cursor in the source file
  206. -- follow_cursor = true,
  207. --
  208. -- -- Provide the path to binaries for dependencies.
  209. -- -- Setting this will skip the download of the binary by the plugin.
  210. -- -- Warning: Be aware that your version might be older than the one
  211. -- -- required.
  212. -- dependencies_bin = {
  213. -- -- if you are using tinymist, just set ['typst-preview'] = "tinymist".
  214. -- ["typst-preview"] = nil,
  215. -- ["websocat"] = nil,
  216. -- },
  217. --
  218. -- -- This function will be called to determine the root of the typst project
  219. -- get_root = function(path_of_main_file)
  220. -- return vim.fn.fnamemodify(path_of_main_file, ":p:h")
  221. -- end,
  222. --
  223. -- -- This function will be called to determine the main file of the typst
  224. -- -- project.
  225. -- get_main_file = function(path_of_buffer)
  226. -- return path_of_buffer
  227. -- end,
  228. -- })
  229. -- end,
  230. -- build = function()
  231. -- require("typst-preview").update()
  232. -- end,
  233. -- },
  234. }