diff options
Diffstat (limited to 'lua/tasks/module/cargo.lua')
-rw-r--r-- | lua/tasks/module/cargo.lua | 171 |
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 @@ | |||
1 | local utils = require('tasks.utils') | ||
2 | local Job = require('plenary.job') | ||
3 | local Path = require('plenary.path') | ||
4 | local 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%\)%\@!. | ||
11 | local 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? | ||
22 | local 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 | ||
29 | end | ||
30 | |||
31 | --- Returns only a packages that can be executed. | ||
32 | ---@param packages table: Packages to filter. | ||
33 | ---@return table | ||
34 | local 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 | ||
43 | end | ||
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? | ||
49 | local 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 | ||
77 | end | ||
78 | |||
79 | ---@return table: List of functions for each cargo subcommand that return a task table. | ||
80 | local 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 | ||
106 | end | ||
107 | |||
108 | --- Task | ||
109 | ---@return table? | ||
110 | local 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 | } | ||
117 | end | ||
118 | |||
119 | --- Task | ||
120 | ---@param module_config table | ||
121 | ---@param previous_job table | ||
122 | ---@return table? | ||
123 | local 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 | } | ||
134 | end | ||
135 | |||
136 | --- Task | ||
137 | ---@param module_config table | ||
138 | ---@return table? | ||
139 | local 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 | } | ||
145 | end | ||
146 | |||
147 | --- Task | ||
148 | ---@param module_config table | ||
149 | ---@param previous_job table | ||
150 | ---@return table? | ||
151 | local 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 | } | ||
162 | end | ||
163 | |||
164 | cargo.params = { | ||
165 | 'dap_name', | ||
166 | 'global_cargo_args', | ||
167 | } | ||
168 | cargo.condition = function() return Path:new('Cargo.toml'):exists() end | ||
169 | cargo.tasks = vim.tbl_extend('force', get_cargo_subcommands(), { debug_test = { build_test, debug_test }, debug = { build, debug } }) | ||
170 | |||
171 | return cargo | ||