aboutsummaryrefslogtreecommitdiff
path: root/lua/tasks/module/cargo.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/tasks/module/cargo.lua')
-rw-r--r--lua/tasks/module/cargo.lua171
1 files changed, 171 insertions, 0 deletions
diff --git a/lua/tasks/module/cargo.lua b/lua/tasks/module/cargo.lua
new file mode 100644
index 0000000..3e21371
--- /dev/null
+++ b/lua/tasks/module/cargo.lua
@@ -0,0 +1,171 @@
1local utils = require('tasks.utils')
2local Job = require('plenary.job')
3local Path = require('plenary.path')
4local cargo = {}
5
6-- Modified version of `errorformat` from the official Rust plugin for Vim:
7-- https://github.com/rust-lang/rust.vim/blob/4aa69b84c8a58fcec6b6dad6fe244b916b1cf830/compiler/rustc.vim#L32
8-- https://github.com/rust-lang/rust.vim/blob/4aa69b84c8a58fcec6b6dad6fe244b916b1cf830/compiler/cargo.vim#L35
9-- We display all lines (not only error messages) since we show output in quickfix.
10-- Zero-width look-ahead regex is used to avoid marking general messages as errors: %\%%(ignored text%\)%\@!.
11local errorformat = [[%Eerror: %\%%(aborting %\|could not compile%\)%\@!%m,]]
12 .. [[%Eerror[E%n]: %m,]]
13 .. [[%Inote: %m,]]
14 .. [[%Wwarning: %\%%(%.%# warning%\)%\@!%m,]]
15 .. [[%C %#--> %f:%l:%c,]]
16 .. [[%E left:%m,%C right:%m %f:%l:%c,%Z,]]
17 .. [[%.%#panicked at \'%m\'\, %f:%l:%c]]
18
19--- Detects package name from command line arguments.
20---@param args table
21---@return string?
22local function detect_package_name(args)
23 for index, value in ipairs(args) do
24 if value == '-p' or value == '--package' or value == '--bin' then
25 return args[index + 1]
26 end
27 end
28 return nil
29end
30
31--- Returns only a packages that can be executed.
32---@param packages table: Packages to filter.
33---@return table
34local function find_executable_packages(packages)
35 local executables = {}
36 for _, line in pairs(packages) do
37 local package = vim.json.decode(line)
38 if package.executable and package.executable ~= vim.NIL then
39 table.insert(executables, package)
40 end
41 end
42 return executables
43end
44
45--- Finds executable package name from a list of packages.
46---@param packages table
47---@param args table?: Command line arguments that will be used to detect an executable if JSON message from cargo is missing this info.
48---@return table?
49local function get_executable_package(packages, args)
50 local executable_packages = find_executable_packages(packages)
51 if #executable_packages == 1 then
52 return executable_packages[1]
53 end
54
55 -- Try to detect package name from arguments
56 local package_name = detect_package_name(args or {})
57 if not package_name then
58 local available_names = {}
59 for _, package in ipairs(executable_packages) do
60 table.insert(available_names, package.target.name)
61 end
62 utils.notify(
63 'Could not determine which binary to run\nUse the "--bin" or "--package" option to specify a binary\nAvailable binaries: ' .. table.concat(available_names, ', '),
64 vim.log.levels.ERROR
65 )
66 return nil
67 end
68
69 for _, package in ipairs(executable_packages) do
70 if package.target.name == package_name then
71 return package
72 end
73 end
74
75 utils.notify(string.format('Unable to find package named "%s"', package_name), vim.log.levels.ERROR)
76 return nil
77end
78
79---@return table: List of functions for each cargo subcommand that return a task table.
80local function get_cargo_subcommands()
81 local cargo_subcommands = {}
82
83 local job = Job:new({
84 command = 'cargo',
85 args = { '--list' },
86 enabled_recording = true,
87 })
88 job:sync()
89
90 if job.code ~= 0 or job.signal ~= 0 then
91 utils.notify('Unable to get list of available cargo subcommands', vim.log.levels.ERROR)
92 return {}
93 end
94
95 local start_offset = 5
96 for index, line in ipairs(job:result()) do
97 if index ~= 1 and not line:find('alias:') then
98 local subcommand_end = line:find(' ', start_offset)
99 local subcommand = line:sub(start_offset, subcommand_end and subcommand_end - 1 or nil)
100 cargo_subcommands[subcommand] =
101 function(module_config, _) return { cmd = 'cargo', args = vim.list_extend({ subcommand }, utils.split_args(module_config.global_cargo_args)), errorformat = errorformat } end
102 end
103 end
104
105 return cargo_subcommands
106end
107
108--- Task
109---@return table?
110local function build_test(module_config, _)
111 return {
112 cmd = 'cargo',
113 args = vim.list_extend({ 'test', '--no-run', '--message-format=json' }, utils.split_args(module_config.global_cargo_args)),
114 errorformat = errorformat,
115 ignore_stdout = true,
116 }
117end
118
119--- Task
120---@param module_config table
121---@param previous_job table
122---@return table?
123local function debug_test(module_config, previous_job)
124 local package = get_executable_package(previous_job:result(), vim.tbl_get(module_config, 'args', 'debug_test'))
125 if not package then
126 return
127 end
128
129 return {
130 cmd = package.executable,
131 dap_name = module_config.dap_name,
132 errorformat = errorformat,
133 }
134end
135
136--- Task
137---@param module_config table
138---@return table?
139local function build(module_config, _)
140 return {
141 cmd = 'cargo',
142 args = vim.list_extend({ 'build', '--message-format=json' }, utils.split_args(module_config.global_cargo_args)),
143 ignore_stdout = true,
144 }
145end
146
147--- Task
148---@param module_config table
149---@param previous_job table
150---@return table?
151local function debug(module_config, previous_job)
152 local package = get_executable_package(previous_job:result(), vim.tbl_get(module_config, 'args', 'debug'))
153 if not package then
154 return
155 end
156
157 return {
158 cmd = package.executable,
159 dap_name = module_config.dap_name,
160 errorformat = errorformat,
161 }
162end
163
164cargo.params = {
165 'dap_name',
166 'global_cargo_args',
167}
168cargo.condition = function() return Path:new('Cargo.toml'):exists() end
169cargo.tasks = vim.tbl_extend('force', get_cargo_subcommands(), { debug_test = { build_test, debug_test }, debug = { build, debug } })
170
171return cargo