1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
local is_win = vim.fn.has("win32") ~= 0
local M = {}
local windows_exe_ext = { "exe", "bat", "cmd", "ps1" }
--- Find real path (with ext) for an executable.
--- @param dir string
--- @param name string | string[]
--- @return string | nil
function M.find_exe_file(dir, name)
if type(name) == "string" then
name = { name }
end
for _, n in ipairs(name) do
if vim.uv.fs_stat(vim.fs.joinpath(dir, n)) ~= nil then
return n
end
if is_win then
for _, ext in ipairs(windows_exe_ext) do
if vim.uv.fs_stat(vim.fs.joinpath(dir, n .. "." .. ext)) ~= nil then
return n .. "." .. ext
end
end
end
end
return nil
end
--- Walk up until found an executable in node_modules.
--- @param path string
--- @param name string
--- @return string | nil exe_path Path to the executable.
function M.find_node_modules_exe(path, name)
local bin_dirs = vim.fs.find("node_modules/.bin", { path = path, upward = true, type = "directory" })
if #bin_dirs == 0 then return nil end
local exe = M.find_exe_file(bin_dirs[1], name)
return exe and vim.fs.joinpath(bin_dirs[1], exe)
end
--- Find executable in PATH.
--- @param name string
--- @return string | nil
function M.find_global_exe(name)
local exe = vim.fn.exepath(name)
if exe == "" then return nil end
return exe
end
--- @alias ExePlace "node_modules" | "global"
--- @param path string
--- @param name string
--- @param places ExePlace[]
--- @return string | nil, ExePlace?
function M.find_exe(path, name, places)
for _, place in ipairs(places) do
if place == "node_modules" then
local r = M.find_node_modules_exe(path, name)
if r then return r, "node_modules" end
end
if place == "global" then
local r = M.find_global_exe(name)
if r then return r, "global" end
end
end
return nil, nil
end
--- @alias FindExeForBufOpts { name: string, exe: string?, places: ExePlace[], config_files: string[]?, filetypes: string[]? }
--- @alias FindExeForBufResult { name: string, file: string, exe: string, exe_path: string, place: ExePlace, config_file: string?, filetype: string? }
--- @param buf number
--- @param opts FindExeForBufOpts
--- @return FindExeForBufResult | nil
function M.find_exe_for_buf(buf, opts)
local r = {} --- @type FindExeForBufResult
r.name = opts.name
r.file = vim.api.nvim_buf_get_name(buf)
r.exe = opts.exe or opts.name
if opts.filetypes then
r.filetype = vim.api.nvim_get_option_value("filetype", { scope = "buffer", buf = buf })
if not vim.tbl_contains(opts.filetypes, r.filetype) then return nil end
end
if opts.config_files then
local config_file_list = vim.fs.find(opts.config_files, { path = r.file, upward = true })
if #config_file_list == 0 then return nil end
r.config_file = config_file_list[1]
end
local exe_path, place = M.find_exe(r.file, r.exe, opts.places)
if exe_path == nil then return nil end
r.exe_path = exe_path
--- @cast place ExePlace
r.place = place
return r
end
return M
|