From eab48c09cdee548b0446f50e49979a0a82470b93 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 14 Sep 2021 18:08:45 +0200 Subject: Improve the texcompile plugin to work on windows Borrow code from the rxi/console implementation to reliably run commands on windows. Ideally should be based on the new reproc library but this has to be done. Problems of the current implementation: - the output of the commands is ignored including warning and error messages - the exit code of the command process is ignored As a modification from the previous version of the plugin we no longer use environment variables but we rely on "config" variables. --- plugins/texcompile.lua | 169 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 148 insertions(+), 21 deletions(-) diff --git a/plugins/texcompile.lua b/plugins/texcompile.lua index 2f53513..517aa9b 100644 --- a/plugins/texcompile.lua +++ b/plugins/texcompile.lua @@ -1,44 +1,171 @@ -- mod-version:2 -- lite-xl 2.0 local core = require "core" +local config = require "core.config" local command = require "core.command" +local common = require "core.common" local keymap = require "core.keymap" -command.add("core.docview", { - ["texcompile:tex-compile"] = function() - local av = core.active_view +-- This code use an adaptation of the rxi/console plugin code to +-- start commands. + +local pending_threads = {} +local thread_active = false --- User's home directory - local homedir = "" +local function push_output(str, opt) + -- By default we just ignore the output of a command. + -- print(">>OUTPUT:", str) +end + +local function read_file(filename, offset) + local fp = io.open(filename, "rb") + fp:seek("set", offset or 0) + local res = fp:read("*a") + fp:close() + return res +end + +local function write_file(filename, text) + local fp = io.open(filename, "w") + fp:write(text) + fp:close() +end + +local function init_opt(opt) + local res = { + command = "", + arguments = {}, + on_complete = function() end, + } + for k, v in pairs(res) do + res[k] = opt[k] or v + end + return res +end +local files = { + script = core.temp_filename(PLATFORM == "Windows" and ".bat"), + script2 = core.temp_filename(PLATFORM == "Windows" and ".bat"), + output = core.temp_filename(), + complete = core.temp_filename(), +} + +local function command_run(opt) + opt = init_opt(opt) + + local function thread() + -- init script file(s) + local args_quoted = {} + for i, arg in ipairs(opt.arguments) do args_quoted[i] = string.format("%q", arg) end + local args_concat = table.concat(args_quoted, " ") + local working_dir = opt.working_dir or "." if PLATFORM == "Windows" then - homedir = os.getenv("USERPROFILE") + write_file(files.script, string.format("%s %s\n", opt.command, args_concat)) + write_file(files.script2, string.format([[ + @echo off + cd %q + call %q >%q 2>&1 + echo "" >%q + exit + ]], working_dir, files.script, files.output, files.complete)) + system.exec(string.format("call %q", files.script2)) + else + write_file(files.script, string.format([[ + cd %q + %s %s + touch %q + ]], working_dir, opt.command, args_concat, files.complete)) + system.exec(string.format("bash %q >%q 2>&1", files.script, files.output)) + end + + -- checks output file for change and reads + local last_size = 0 + local function check_output_file() + if PLATFORM == "Windows" then + local fp = io.open(files.output) + if fp then fp:close() end + end + local info = system.get_file_info(files.output) + if info and info.size > last_size then + local text = read_file(files.output, last_size) + push_output(text, opt) + last_size = info.size + end + end + + -- read output file until we get a file indicating completion + while not system.get_file_info(files.complete) do + check_output_file() + coroutine.yield(0.1) + end + check_output_file() + + -- clean up and finish + for _, file in pairs(files) do + os.remove(file) + end + opt.on_complete() + + -- handle pending thread + local pending = table.remove(pending_threads, 1) + if pending then + core.add_thread(pending) else - homedir = os.getenv("HOME") + thread_active = false end + end + + -- push/init thread + if thread_active then + table.insert(pending_threads, thread) + else + core.add_thread(thread) + thread_active = true + end +end --- The current (La)TeX file and path +command.add("core.docview", { + ["texcompile:tex-compile"] = function() + local av = core.active_view + + -- The current (La)TeX file and path local texname = av:get_name() - local texpath = av:get_filename() - texpath = string.gsub(texpath, '~', homedir) - texpath = string.gsub(texpath, texname, '') + local texpath = common.dirname(av:get_filename()) + + -- Add in your user's config file something like: + -- + -- config.texcompile = { + -- latex_command = "pdflatex", + -- view_command = "evince", + -- } + -- + -- On windows you may use the full path for the command like: + -- + -- latex_command = [[c:\miktex\miktex\bin\x64\pdflatex.exe]], --- LaTeX compiler - is there any provided by the environment - local texcmd = os.getenv("LITE_LATEX_COMPILER") + -- LaTeX compiler - is there any provided by the environment + local texcmd = config.texcompile and config.texcompile.latex_command + local viewcmd = config.texcompile and config.texcompile.view_command - if texcmd == nil then + if not texcmd then core.log("No LaTeX compiler found") else core.log("LaTeX compiler is %s, compiling %s", texcmd, texname) - system.exec(string.format("cd %q && %q %q", texpath, texcmd, texname)) - end + command_run { + command = texcmd, + arguments = { texname }, + working_dir = texpath, + on_complete = function() core.log("Tex compiling command terminated.") end + } --- core.add_thread(function() --- coroutine.yield(5) --- os.remove(htmlfile) --- end) + local pdfname = texname:gsub("%.tex$", ".pdf") + command_run { + command = viewcmd, + arguments = { pdfname }, + working_dir = texpath + } + end end }) - keymap.add { ["ctrl+shift+t"] = "texcompile:tex-compile" } -- cgit v1.2.3