From c6944f8677797b10dbcdb26be131d6e52cded4f4 Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Sun, 15 Sep 2024 06:23:36 -0700 Subject: [PATCH 1/3] Add `by_bufroot` cache I'm building a [`nix fmt`](https://nix.dev/manual/nix/latest/command-ref/new-cli/nix3-fmt) formatter. The tricky thing about `nix fmt` is that for nontrivial projects, the entrypoint (`nix fmt`) can be slow enough to be a bad experience to run on every save, and can even trigger [neovim's lsp timeout](https://github.com/nvimtools/none-ls.nvim?tab=readme-ov-file#i-am-seeing-a-formatting-timeout-error-message). It's not the that underlying formatter it invokes are slow, it's that it can take 1-2 seconds for nix to evaluate the `flake.nix` and determine what command `nix fmt` should run under the hood. Since the underlying command shouldn't change very often for a given project, I feel like it would be reasonable to cache this result per project, hence this new `by_bufroot` callback. --- doc/HELPERS.md | 17 +++++++++++++++++ lua/null-ls/helpers/cache.lua | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/doc/HELPERS.md b/doc/HELPERS.md index 8ccfbe83..2aa72c99 100644 --- a/doc/HELPERS.md +++ b/doc/HELPERS.md @@ -376,3 +376,20 @@ be too performance-intensive to include out-of-the-box. Note that if `callback` returns `nil`, the helper will override the return value and instead cache `false` (so that it can determine that it already ran `callback` once and should not run it again). + +### by_bufroot(callback) + +Creates a function that caches the result of `callback`, indexed by `root`. On +the first run of the created function, null-ls will call `callback` with a +`params` table. On the next run, it will directly return the cached value +without calling `callback` again. + +This is useful when the return value of `callback` is not expected to change +over the lifetime of the buffer, which works well for `cwd` and +`runtime_condition` callbacks. Users can use it as a simple shortcut to improve +performance, and built-in authors can use it to add logic that would otherwise +be too performance-intensive to include out-of-the-box. + +Note that if `callback` returns `nil`, the helper will override the return value +and instead cache `false` (so that it can determine that it already ran +`callback` once and should not run it again). diff --git a/lua/null-ls/helpers/cache.lua b/lua/null-ls/helpers/cache.lua index 89a48a82..9795e440 100644 --- a/lua/null-ls/helpers/cache.lua +++ b/lua/null-ls/helpers/cache.lua @@ -25,6 +25,27 @@ M.by_bufnr = function(cb) end end +--- creates a function that caches the output of a callback, indexed by project root +---@param cb function +---@return fun(params: NullLsParams): any +M.by_bufroot = function(cb) + -- assign next available key, since we just want to avoid collisions + local key = next_key + M.cache[key] = {} + next_key = next_key + 1 + + return function(params) + local root = params.root + -- if we haven't cached a value yet, get it from cb + if M.cache[key][root] == nil then + -- make sure we always store a value so we know we've already called cb + M.cache[key][root] = cb(params) or false + end + + return M.cache[key][root] + end +end + M._reset = function() M.cache = {} end From ad2289070d8ff8ef9acbac351e56e72366de3401 Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Sun, 15 Sep 2024 08:18:40 -0700 Subject: [PATCH 2/3] Add "nix flake fmt" formatter This uses `nix fmt` under the hood. I introduced some caching make it performant, as `nix fmt` can take a little while to evaluate all your nix code to figure out the formatter entrypoint. --- doc/HELPERS.md | 2 +- .../builtins/formatting/nix_flake_fmt.lua | 147 ++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 lua/null-ls/builtins/formatting/nix_flake_fmt.lua diff --git a/doc/HELPERS.md b/doc/HELPERS.md index 2aa72c99..4e218305 100644 --- a/doc/HELPERS.md +++ b/doc/HELPERS.md @@ -169,7 +169,7 @@ Not compatible with `ignore_stderr`. Reads the contents of the temp file created by `to_temp_file` after running `command` and assigns it to `params.output`. Useful for formatters that don't -output to `stdin` (see `formatter_factory`). +output to `stdout` (see `formatter_factory`). This option depends on `to_temp_file`. diff --git a/lua/null-ls/builtins/formatting/nix_flake_fmt.lua b/lua/null-ls/builtins/formatting/nix_flake_fmt.lua new file mode 100644 index 00000000..4718f082 --- /dev/null +++ b/lua/null-ls/builtins/formatting/nix_flake_fmt.lua @@ -0,0 +1,147 @@ +local h = require("null-ls.helpers") +local methods = require("null-ls.methods") +local log = require("null-ls.logger") + +local FORMATTING = methods.internal.FORMATTING + +--- Return the command that `nix fmt` would run, or nil if we're not in a +--- flake. +--- +--- The formatter must follow treefmt's [formatter +--- spec](https://github.com/numtide/treefmt/blob/main/docs/formatter-spec.md). +--- +--- This basically re-implements the "entrypoint discovery" that `nix fmt` does. +--- So why are we doing this ourselves rather than just invoking `nix fmt`? +--- Unfortunately, it can take a few moments to evaluate all your nix code to +--- figure out the formatter entrypoint. It can even be slow enough to exceed +--- Neovim's default LSP timeout. +--- By doing this ourselves, we can cache the result. +--- +---@return string|nil +local find_nix_fmt = function(params) + -- Discovering currentSystem here lets us keep the *next* eval pure. + -- We want to keep that part pure as a performance improvement: an impure + -- eval that references the flake would copy *all* files (including + -- gitignored files!), which can be quite expensive if you've got many GiB + -- of artifacts in the directory. This optimization can probably go away + -- once the [Lazy trees PR] lands. + -- + -- [Lazy trees PR]: https://github.com/NixOS/nix/pull/6530 + local cp = vim.system({"nix", "eval", "--impure", "--expr", "builtins.currentSystem"}):wait() + if cp.code ~= 0 then + log:warn(string.format("unable to discover builtins.currentSystem from nix. stderr: %s", cp.stderr)) + return nil + end + local nix_current_system = cp.stdout + + local eval_nix_formatter = [[ + let + currentSystem = ]] .. nix_current_system .. [[; + # Various functions vendored from nixpkgs lib (to avoid adding a + # dependency on nixpkgs). + lib = rec { + getOutput = output: pkg: + if ! pkg ? outputSpecified || ! pkg.outputSpecified + then pkg.${output} or pkg.out or pkg + else pkg; + getBin = getOutput "bin"; + # Simplified by removing various type assertions. + getExe' = x: y: "${getBin x}/bin/${y}"; + # getExe is simplified to assume meta.mainProgram is specified. + getExe = x: getExe' x x.meta.mainProgram; + }; + in + formatterBySystem: + if formatterBySystem ? ${currentSystem} then + let + formatter = formatterBySystem.${currentSystem}; + drv = formatter.drvPath; + bin = lib.getExe formatter; + in + drv + "\n" + bin + "\n" + else + "" + ]] + + cp = vim.system( + { 'nix', 'eval', '.#formatter', '--raw', '--apply', eval_nix_formatter}, + { cwd = params.root } + ):wait() + if cp.code ~= 0 then + -- Dirty hack to check if the flake actually defines a formatter. + -- + -- I cannot for the *life* of me figure out a less hacky way of + -- checking if a flake defines a formatter. Things I've tried: + -- + -- - `nix eval . --apply '...'`: This doesn't not give me the flake + -- itself, it gives me the default package. + -- - `builtins.getFlake`: Every incantation I've tried requires + -- `--impure`, which has the performance downside described above. + -- - `nix flake show --json .`: This works, but it can be quite slow: + -- we end up evaluating all outputs, which can take a while for + -- `nixosConfigurations`. + if cp.stderr:find("error: flake .+ does not provide attribute .+ or 'formatter'") then + log:warn("this flake does not define a `nix fmt` entrypoint") + else + log:error(string.format("unable discover 'nix fmt' command. stderr: %s", cp.stderr)) + end + + return nil + end + + if cp.stdout == "" then + log:warn("this flake does not define a formatter for your system: %s", nix_current_system) + return nil + end + + -- stdout has 2 lines of output: + -- 1. drv path + -- 2. exe path + local drv_path, nix_fmt_path = cp.stdout:match("([^\n]+)\n([^\n]+)\n") + + -- Build the derivation. This ensures that `nix_fmt_path` exists. + cp = vim.system({ 'nix', 'build', '--no-link', drv_path .. '^out' }):wait() + if cp.code ~= 0 then + log:warn(string.format("unable to build 'nix fmt' entrypoint. stderr: %s", cp.stderr)) + return nil + end + + return nix_fmt_path +end + +return h.make_builtin({ + name = "nix flake fmt", + meta = { + url = "https://nix.dev/manual/nix/latest/command-ref/new-cli/nix3-fmt", + description = "`nix fmt` - reformat your code in the standard style (this is a generic formatter, not to be confused with nixfmt, a formatter for .nix files)", + }, + method = FORMATTING, + filetypes = {}, + generator_opts = { + -- It can take a few moments to find the `nix fmt` entrypoint. The + -- underlying command shouldn't change very often for a given + -- project, so cache it for the project root. + dynamic_command = h.cache.by_bufroot(find_nix_fmt), + args = { + -- `--walk` is specific to treefmt, and this formatter is supposed + -- to be generic. + -- Note: this could get converted to the new `TREEFMT_WALK` + -- environment variable once + -- + -- is fixed, which would at least play nicely with other types of + -- `nix fmt` entrypoints. + -- + -- However, IMO, the real fix is to change treefmt itself to be + -- willing to format files passed explicitly, even if they're + -- gitignored: + -- https://github.com/numtide/treefmt/issues/435 + '--walk=filesystem', + '$FILENAME', + }, + to_temp_file = true, + }, + condition = function(utils) + return utils.root_has_file("flake.nix") + end, + factory = h.formatter_factory, +}) From 53289032e09c9cd4b969e0ed23f0d39371ddda57 Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Fri, 11 Oct 2024 15:37:36 -0500 Subject: [PATCH 3/3] WIP/RFC: `dynamic_command` is now async, and runs immediately when opening buffers This implements . This is incomplete: I've redefined `dynamic_command` to be an async function, but I haven't actually updated all the builtins accordingly. I have updated `nix_flake_fmt` to demonstrate the idea. If folks like the direction I'm going with this, I'll polish up the PR so all `dynamic_command`s are async, and I'll get the tests passing. --- .../builtins/formatting/nix_flake_fmt.lua | 218 +++++++++++------- lua/null-ls/client.lua | 9 + lua/null-ls/helpers/cache.lua | 32 ++- lua/null-ls/helpers/generator_factory.lua | 119 +++++----- 4 files changed, 233 insertions(+), 145 deletions(-) diff --git a/lua/null-ls/builtins/formatting/nix_flake_fmt.lua b/lua/null-ls/builtins/formatting/nix_flake_fmt.lua index 4718f082..b53924c3 100644 --- a/lua/null-ls/builtins/formatting/nix_flake_fmt.lua +++ b/lua/null-ls/builtins/formatting/nix_flake_fmt.lua @@ -1,9 +1,20 @@ local h = require("null-ls.helpers") local methods = require("null-ls.methods") local log = require("null-ls.logger") +local client = require("null-ls.client") +local async = require("plenary.async") +local Job = require("plenary.job") local FORMATTING = methods.internal.FORMATTING +local run_job = async.wrap(function(opts, done) + opts.on_exit = function(j, status) + done(status, j:result(), j:stderr_result()) + end + + Job:new(opts):start() +end, 2) + --- Return the command that `nix fmt` would run, or nil if we're not in a --- flake. --- @@ -18,95 +29,136 @@ local FORMATTING = methods.internal.FORMATTING --- By doing this ourselves, we can cache the result. --- ---@return string|nil -local find_nix_fmt = function(params) - -- Discovering currentSystem here lets us keep the *next* eval pure. - -- We want to keep that part pure as a performance improvement: an impure - -- eval that references the flake would copy *all* files (including - -- gitignored files!), which can be quite expensive if you've got many GiB - -- of artifacts in the directory. This optimization can probably go away - -- once the [Lazy trees PR] lands. - -- - -- [Lazy trees PR]: https://github.com/NixOS/nix/pull/6530 - local cp = vim.system({"nix", "eval", "--impure", "--expr", "builtins.currentSystem"}):wait() - if cp.code ~= 0 then - log:warn(string.format("unable to discover builtins.currentSystem from nix. stderr: %s", cp.stderr)) - return nil - end - local nix_current_system = cp.stdout - - local eval_nix_formatter = [[ - let - currentSystem = ]] .. nix_current_system .. [[; - # Various functions vendored from nixpkgs lib (to avoid adding a - # dependency on nixpkgs). - lib = rec { - getOutput = output: pkg: - if ! pkg ? outputSpecified || ! pkg.outputSpecified - then pkg.${output} or pkg.out or pkg - else pkg; - getBin = getOutput "bin"; - # Simplified by removing various type assertions. - getExe' = x: y: "${getBin x}/bin/${y}"; - # getExe is simplified to assume meta.mainProgram is specified. - getExe = x: getExe' x x.meta.mainProgram; - }; - in - formatterBySystem: - if formatterBySystem ? ${currentSystem} then +local find_nix_fmt = function(opts, done) + async.run(function() + local title = "discovering `nix fmt` entrypoint" + local progress_token = "nix-flake-fmt-discovery" + + client.send_progress_notification(progress_token, { + kind = "begin", + title = title, + }) + + local root = opts.root + + -- Discovering currentSystem here lets us keep the *next* eval pure. + -- We want to keep that part pure as a performance improvement: an impure + -- eval that references the flake would copy *all* files (including + -- gitignored files!), which can be quite expensive if you've got many GiB + -- of artifacts in the directory. This optimization can probably go away + -- once the [Lazy trees PR] lands. + -- + -- [Lazy trees PR]: https://github.com/NixOS/nix/pull/6530 + local status, stdout_lines, stderr_lines = run_job({ + command = "nix", + args = { "eval", "--impure", "--expr", "builtins.currentSystem" }, + }) + + if status ~= 0 then + local stderr = table.concat(stderr_lines, "\n") + log:warn(string.format("unable to discover builtins.currentSystem from nix. stderr: %s", stderr)) + return nil + end + + local nix_current_system_expr = stdout_lines[1] + + local eval_nix_formatter = [[ let - formatter = formatterBySystem.${currentSystem}; - drv = formatter.drvPath; - bin = lib.getExe formatter; + currentSystem = ]] .. nix_current_system_expr .. [[; + # Various functions vendored from nixpkgs lib (to avoid adding a + # dependency on nixpkgs). + lib = rec { + getOutput = output: pkg: + if ! pkg ? outputSpecified || ! pkg.outputSpecified + then pkg.${output} or pkg.out or pkg + else pkg; + getBin = getOutput "bin"; + # Simplified by removing various type assertions. + getExe' = x: y: "${getBin x}/bin/${y}"; + # getExe is simplified to assume meta.mainProgram is specified. + getExe = x: getExe' x x.meta.mainProgram; + }; in - drv + "\n" + bin + "\n" - else - "" - ]] - - cp = vim.system( - { 'nix', 'eval', '.#formatter', '--raw', '--apply', eval_nix_formatter}, - { cwd = params.root } - ):wait() - if cp.code ~= 0 then - -- Dirty hack to check if the flake actually defines a formatter. - -- - -- I cannot for the *life* of me figure out a less hacky way of - -- checking if a flake defines a formatter. Things I've tried: - -- - -- - `nix eval . --apply '...'`: This doesn't not give me the flake - -- itself, it gives me the default package. - -- - `builtins.getFlake`: Every incantation I've tried requires - -- `--impure`, which has the performance downside described above. - -- - `nix flake show --json .`: This works, but it can be quite slow: - -- we end up evaluating all outputs, which can take a while for - -- `nixosConfigurations`. - if cp.stderr:find("error: flake .+ does not provide attribute .+ or 'formatter'") then - log:warn("this flake does not define a `nix fmt` entrypoint") - else - log:error(string.format("unable discover 'nix fmt' command. stderr: %s", cp.stderr)) + formatterBySystem: + if formatterBySystem ? ${currentSystem} then + let + formatter = formatterBySystem.${currentSystem}; + drv = formatter.drvPath; + bin = lib.getExe formatter; + in + drv + "\n" + bin + "\n" + else + "" + ]] + + client.send_progress_notification(progress_token, { + kind = "report", + title = title, + message = "evaluating", + }) + status, stdout_lines, stderr_lines = run_job({ + command = "nix", + args = { "eval", ".#formatter", "--raw", "--apply", eval_nix_formatter }, + cwd = root, + }) + + if status ~= 0 then + local stderr = table.concat(stderr_lines, "\n") + -- Dirty hack to check if the flake actually defines a formatter. + -- + -- I cannot for the *life* of me figure out a less hacky way of + -- checking if a flake defines a formatter. Things I've tried: + -- + -- - `nix eval . --apply '...'`: This doesn't not give me the flake + -- itself, it gives me the default package. + -- - `builtins.getFlake`: Every incantation I've tried requires + -- `--impure`, which has the performance downside described above. + -- - `nix flake show --json .`: This works, but it can be quite slow: + -- we end up evaluating all outputs, which can take a while for + -- `nixosConfigurations`. + if stderr:find("error: flake .+ does not provide attribute .+ or 'formatter'") then + log:warn("this flake does not define a `nix fmt` entrypoint") + else + log:error(string.format("unable discover 'nix fmt' command. stderr: %s", stderr)) + end + + return nil end - return nil - end + if #stdout_lines == 0 then + log:warn("this flake does not define a formatter for your system: %s", nix_current_system_expr) + return nil + end - if cp.stdout == "" then - log:warn("this flake does not define a formatter for your system: %s", nix_current_system) - return nil - end + -- stdout has 2 lines of output: + -- 1. drv path + -- 2. exe path + local drv_path, nix_fmt_path = unpack(stdout_lines) - -- stdout has 2 lines of output: - -- 1. drv path - -- 2. exe path - local drv_path, nix_fmt_path = cp.stdout:match("([^\n]+)\n([^\n]+)\n") + -- Build the derivation. This ensures that `nix_fmt_path` exists. + client.send_progress_notification(progress_token, { + kind = "report", + title = title, + message = "building", + }) + status, stdout_lines, stderr_lines = run_job({ + command = "nix", + args = { "build", "--no-link", drv_path .. "^out" }, + }) - -- Build the derivation. This ensures that `nix_fmt_path` exists. - cp = vim.system({ 'nix', 'build', '--no-link', drv_path .. '^out' }):wait() - if cp.code ~= 0 then - log:warn(string.format("unable to build 'nix fmt' entrypoint. stderr: %s", cp.stderr)) - return nil - end + if status ~= 0 then + log:warn(string.format("unable to build 'nix fmt' entrypoint. stderr: %s", job:stderr_results())) + return nil + end + + client.send_progress_notification(progress_token, { + kind = "end", + title = title, + message = "done", + }) - return nix_fmt_path + done(nix_fmt_path) + end) end return h.make_builtin({ @@ -135,8 +187,8 @@ return h.make_builtin({ -- willing to format files passed explicitly, even if they're -- gitignored: -- https://github.com/numtide/treefmt/issues/435 - '--walk=filesystem', - '$FILENAME', + "--walk=filesystem", + "$FILENAME", }, to_temp_file = true, }, diff --git a/lua/null-ls/client.lua b/lua/null-ls/client.lua index ad3aaf8a..29faf15a 100644 --- a/lua/null-ls/client.lua +++ b/lua/null-ls/client.lua @@ -162,6 +162,15 @@ M.setup_buffer = function(bufnr) return end + -- Notify each generator for this filetype. This gives them a chance to + -- precompute information. + local filetype = api.nvim_get_option_value("filetype", { buf = bufnr }) + for k, source in ipairs(require("null-ls.sources").get({ filetype = filetype })) do + source.generator.setup_buffer(bufnr, function() + -- Nothing to do here. + end) + end + local on_attach = c.get().on_attach if on_attach then on_attach(client, bufnr) diff --git a/lua/null-ls/helpers/cache.lua b/lua/null-ls/helpers/cache.lua index 9795e440..b75f3609 100644 --- a/lua/null-ls/helpers/cache.lua +++ b/lua/null-ls/helpers/cache.lua @@ -4,45 +4,57 @@ local M = {} M.cache = {} +---@class NullLsCacheParams +---@field bufnr number +---@field root string? + +--- >>> TODO: update all users to be async <<< --- creates a function that caches the output of a callback, indexed by bufnr ---@param cb function ----@return fun(params: NullLsParams): any +---@return fun(params: NullLsCacheParams): any M.by_bufnr = function(cb) -- assign next available key, since we just want to avoid collisions local key = next_key M.cache[key] = {} next_key = next_key + 1 - return function(params) + return function(params, done) local bufnr = params.bufnr -- if we haven't cached a value yet, get it from cb if M.cache[key][bufnr] == nil then -- make sure we always store a value so we know we've already called cb - M.cache[key][bufnr] = cb(params) or false + cb(params, function(result) + M.cache[key][bufnr] = result or false + done(M.cache[key][bufnr]) + end) + else + done(M.cache[key][bufnr]) end - - return M.cache[key][bufnr] end end --- creates a function that caches the output of a callback, indexed by project root ---@param cb function ----@return fun(params: NullLsParams): any +---@param done function +---@return fun(params: NullLsCacheParams): any M.by_bufroot = function(cb) -- assign next available key, since we just want to avoid collisions local key = next_key M.cache[key] = {} next_key = next_key + 1 - return function(params) + return function(params, done) local root = params.root -- if we haven't cached a value yet, get it from cb if M.cache[key][root] == nil then -- make sure we always store a value so we know we've already called cb - M.cache[key][root] = cb(params) or false + cb(params, function(result) + M.cache[key][root] = result or false + done(M.cache[key][root]) + end) + else + done(M.cache[key][root]) end - - return M.cache[key][root] end end diff --git a/lua/null-ls/helpers/generator_factory.lua b/lua/null-ls/helpers/generator_factory.lua index c7fea7b8..6caa33ed 100644 --- a/lua/null-ls/helpers/generator_factory.lua +++ b/lua/null-ls/helpers/generator_factory.lua @@ -2,6 +2,7 @@ local c = require("null-ls.config") local log = require("null-ls.logger") local s = require("null-ls.state") local u = require("null-ls.utils") +local client = require("null-ls.client") local output_formats = { raw = "raw", -- receive error_output and output directly @@ -178,7 +179,26 @@ return function(opts) return true end + local function get_command(bufnr, done) + if dynamic_command then + local root = u.get_root() + dynamic_command({ + bufnr = bufnr, + root = root, + }, done) + else + done(command) + end + end + return { + setup_buffer = function(bufnr, done) + -- Here we invoke `get_command` and throw away the result. Why? If + -- the underlying `dynamic_command` is expensive to compute, it's + -- nice to immediately start computing it, rather than waiting for + -- someone to need it. + get_command(bufnr, done) + end, fn = function(params, done) local loop = require("null-ls.loop") @@ -279,65 +299,60 @@ return function(opts) params.command = command - local resolved_command - if dynamic_command then - resolved_command = dynamic_command(params) - else - resolved_command = command - end - - -- if dynamic_command returns nil, don't fall back to command - if not resolved_command then - log:debug(string.format("unable to resolve command %s; aborting", command)) - return done() - end + get_command(bufnr, function(resolved_command) + -- if dynamic_command returns nil, don't fall back to command + if not resolved_command then + log:debug(string.format("unable to resolve command %s; aborting", command)) + return done() + end - local resolved_cwd = cwd and cwd(params) or root - params.cwd = resolved_cwd + local resolved_cwd = cwd and cwd(params) or root + params.cwd = resolved_cwd - if type(env) == "function" then - env = env(params) - end + if type(env) == "function" then + env = env(params) + end - local spawn_opts = { - cwd = resolved_cwd, - input = to_stdin and get_content(params) or nil, - handler = wrapper, - check_exit_code = check_exit_code, - timeout = timeout or c.get().default_timeout, - env = env, - } - - if to_temp_file then - local content = get_content(params) - local temp_path, cleanup = loop.temp_file(content, params.bufname, temp_dir or c.get().temp_dir) - - spawn_opts.on_stdout_end = function() - if from_temp_file then - params.output = loop.read_file(temp_path) + local spawn_opts = { + cwd = resolved_cwd, + input = to_stdin and get_content(params) or nil, + handler = wrapper, + check_exit_code = check_exit_code, + timeout = timeout or c.get().default_timeout, + env = env, + } + + if to_temp_file then + local content = get_content(params) + local temp_path, cleanup = loop.temp_file(content, params.bufname, temp_dir or c.get().temp_dir) + + spawn_opts.on_stdout_end = function() + if from_temp_file then + params.output = loop.read_file(temp_path) + end + cleanup() end - cleanup() + params.temp_path = temp_path end - params.temp_path = temp_path - end - - local resolved_args = args or {} - resolved_args = type(resolved_args) == "function" and resolved_args(params) or resolved_args - resolved_args = parse_args(resolved_args, params) - - opts._last_command = resolved_command - opts._last_args = resolved_args - opts._last_cwd = resolved_cwd - log:debug( - string.format( - "spawning command %s at %s with args %s", - vim.inspect(resolved_command), - resolved_cwd, - vim.inspect(resolved_args) + local resolved_args = args or {} + resolved_args = type(resolved_args) == "function" and resolved_args(params) or resolved_args + resolved_args = parse_args(resolved_args, params) + + opts._last_command = resolved_command + opts._last_args = resolved_args + opts._last_cwd = resolved_cwd + + log:debug( + string.format( + "spawning command %s at %s with args %s", + vim.inspect(resolved_command), + resolved_cwd, + vim.inspect(resolved_args) + ) ) - ) - loop.spawn(resolved_command, resolved_args, spawn_opts) + loop.spawn(resolved_command, resolved_args, spawn_opts) + end) end, filetypes = opts.filetypes, opts = opts,