diff options
Diffstat (limited to 'lua/nvim-lsp-installer/core')
42 files changed, 0 insertions, 4605 deletions
diff --git a/lua/nvim-lsp-installer/core/async/init.lua b/lua/nvim-lsp-installer/core/async/init.lua deleted file mode 100644 index 5c82e64a..00000000 --- a/lua/nvim-lsp-installer/core/async/init.lua +++ /dev/null @@ -1,224 +0,0 @@ -local functional = require "nvim-lsp-installer.core.functional" -local co = coroutine - -local exports = {} - -local Promise = {} -Promise.__index = Promise - -function Promise.new(resolver) - return setmetatable({ resolver = resolver, has_resolved = false }, Promise) -end - ----@param success boolean ----@param cb fun(success: boolean, value: table) -function Promise:_wrap_resolver_cb(success, cb) - return function(...) - if self.has_resolved then - return - end - self.has_resolved = true - cb(success, { ... }) - end -end - -function Promise:__call(callback) - self.resolver(self:_wrap_resolver_cb(true, callback), self:_wrap_resolver_cb(false, callback)) -end - -local function await(resolver) - local ok, value = co.yield(Promise.new(resolver)) - if not ok then - error(value[1], 2) - end - return unpack(value) -end - -local function table_pack(...) - return { n = select("#", ...), ... } -end - ----@param async_fn fun(...) ----@param should_reject_err boolean|nil @Whether the provided async_fn takes a callback with the signature `fun(err, result)` -local function promisify(async_fn, should_reject_err) - return function(...) - local args = table_pack(...) - return await(function(resolve, reject) - if should_reject_err then - args[args.n + 1] = function(err, result) - if err then - reject(err) - else - resolve(result) - end - end - else - args[args.n + 1] = resolve - end - local ok, err = pcall(async_fn, unpack(args, 1, args.n + 1)) - if not ok then - reject(err) - end - end) - end -end - -local function new_execution_context(suspend_fn, callback, ...) - local thread = co.create(suspend_fn) - local cancelled = false - local step - step = function(...) - if cancelled then - return - end - local ok, promise_or_result = co.resume(thread, ...) - if ok then - if co.status(thread) == "suspended" then - if getmetatable(promise_or_result) == Promise then - promise_or_result(step) - else - -- yield to parent coroutine - step(coroutine.yield(promise_or_result)) - end - else - callback(true, promise_or_result) - thread = nil - end - else - callback(false, promise_or_result) - thread = nil - end - end - - step(...) - return function() - cancelled = true - thread = nil - end -end - -exports.run = function(suspend_fn, callback, ...) - return new_execution_context(suspend_fn, callback, ...) -end - -exports.scope = function(suspend_fn) - return function(...) - return new_execution_context(suspend_fn, function(success, err) - if not success then - error(err, 0) - end - end, ...) - end -end - -exports.run_blocking = function(suspend_fn, ...) - local resolved, ok, result - local cancel_coroutine = new_execution_context(suspend_fn, function(a, b) - resolved = true - ok = a - result = b - end, ...) - - if vim.wait(60000, function() - return resolved == true - end, 50) then - if not ok then - error(result, 2) - end - return result - else - cancel_coroutine() - error("async function failed to resolve in time.", 2) - end -end - -exports.wait = await -exports.promisify = promisify - -exports.sleep = function(ms) - await(function(resolve) - vim.defer_fn(resolve, ms) - end) -end - -exports.scheduler = function() - await(vim.schedule) -end - ----Creates a oneshot channel that can only send once. -local function oneshot_channel() - local has_sent = false - local sent_value - local saved_callback - - return { - is_closed = function() - return has_sent - end, - send = function(...) - assert(not has_sent, "Oneshot channel can only send once.") - has_sent = true - sent_value = { ... } - if saved_callback then - saved_callback(unpack(sent_value)) - end - end, - receive = function() - return await(function(resolve) - if has_sent then - resolve(unpack(sent_value)) - else - saved_callback = resolve - end - end) - end, - } -end - ----@async ----@param suspend_fns async fun()[] -exports.wait_all = function(suspend_fns) - local channel = oneshot_channel() - - do - local results = {} - local thread_cancellations = {} - local count = #suspend_fns - local completed = 0 - - for i, suspend_fn in ipairs(suspend_fns) do - thread_cancellations[i] = exports.run(suspend_fn, function(success, result) - completed = completed + 1 - if not success then - if not channel.is_closed() then - for _, cancel_thread in ipairs(thread_cancellations) do - cancel_thread() - end - channel.send(false, result) - results = nil - thread_cancellations = {} - end - else - results[i] = result - if completed >= count then - channel.send(true, results) - results = nil - thread_cancellations = {} - end - end - end) - end - end - - local ok, results = channel.receive() - if not ok then - error(results, 2) - end - return unpack(results) -end - -function exports.blocking(suspend_fn) - return functional.partial(exports.run_blocking, suspend_fn) -end - -return exports diff --git a/lua/nvim-lsp-installer/core/async/uv.lua b/lua/nvim-lsp-installer/core/async/uv.lua deleted file mode 100644 index 69af4f26..00000000 --- a/lua/nvim-lsp-installer/core/async/uv.lua +++ /dev/null @@ -1,49 +0,0 @@ -local a = require "nvim-lsp-installer.core.async" - ----@type table<UvMethod, async fun(...)> -local M = setmetatable({}, { - __index = function(cache, method) - cache[method] = a.promisify(vim.loop[method], true) - return cache[method] - end, -}) - -return M - ----@alias UvMethod ----| '"fs_close"' ----| '"fs_open"' ----| '"fs_read"' ----| '"fs_unlink"' ----| '"fs_write"' ----| '"fs_mkdir"' ----| '"fs_mkdtemp"' ----| '"fs_mkstemp"' ----| '"fs_rmdir"' ----| '"fs_scandir"' ----| '"fs_stat"' ----| '"fs_fstat"' ----| '"fs_lstat"' ----| '"fs_rename"' ----| '"fs_fsync"' ----| '"fs_fdatasync"' ----| '"fs_ftruncate"' ----| '"fs_sendfile"' ----| '"fs_access"' ----| '"fs_chmod"' ----| '"fs_fchmod"' ----| '"fs_utime"' ----| '"fs_futime"' ----| '"fs_lutime"' ----| '"fs_link"' ----| '"fs_symlink"' ----| '"fs_readlink"' ----| '"fs_realpath"' ----| '"fs_chown"' ----| '"fs_fchown"' ----| '"fs_lchown"' ----| '"fs_copyfile"' ----| '"fs_opendir"' ----| '"fs_readdir"' ----| '"fs_closedir"' ----| '"fs_statfs"' diff --git a/lua/nvim-lsp-installer/core/clients/eclipse.lua b/lua/nvim-lsp-installer/core/clients/eclipse.lua deleted file mode 100644 index 0d169d9d..00000000 --- a/lua/nvim-lsp-installer/core/clients/eclipse.lua +++ /dev/null @@ -1,15 +0,0 @@ -local fetch = require "nvim-lsp-installer.core.fetch" -local M = {} - ----@param version string The version string as found in the latest.txt endpoint. ----@return string The parsed version number. -function M._parse_jdtls_version_string(version) - return vim.trim(version):gsub("^jdt%-language%-server%-", ""):gsub("%.tar%.gz$", "") -end - ----@async -function M.fetch_latest_jdtls_version() - return fetch("https://download.eclipse.org/jdtls/snapshots/latest.txt"):map(M._parse_jdtls_version_string) -end - -return M diff --git a/lua/nvim-lsp-installer/core/fetch.lua b/lua/nvim-lsp-installer/core/fetch.lua deleted file mode 100644 index 4a5ff4df..00000000 --- a/lua/nvim-lsp-installer/core/fetch.lua +++ /dev/null @@ -1,54 +0,0 @@ -local log = require "nvim-lsp-installer.log" -local platform = require "nvim-lsp-installer.core.platform" -local Result = require "nvim-lsp-installer.core.result" -local spawn = require "nvim-lsp-installer.core.spawn" -local powershell = require "nvim-lsp-installer.core.managers.powershell" - -local USER_AGENT = "nvim-lsp-installer (+https://github.com/williamboman/nvim-lsp-installer)" - -local HEADERS = { - wget = { "--header", ("User-Agent: %s"):format(USER_AGENT) }, - curl = { "-H", ("User-Agent: %s"):format(USER_AGENT) }, - iwr = ("-Headers @{'User-Agent' = '%s'}"):format(USER_AGENT), -} - ----@alias FetchOpts {out_file:string} - ----@async ----@param url string @The url to fetch. ----@param opts FetchOpts -local function fetch(url, opts) - opts = opts or {} - log.fmt_debug("Fetching URL %s", url) - - local platform_specific = Result.failure() - - if platform.is_win then - if opts.out_file then - platform_specific = powershell.command( - ([[iwr %s -UseBasicParsing -Uri %q -OutFile %q;]]):format(HEADERS.iwr, url, opts.out_file) - ) - else - platform_specific = powershell.command( - ([[Write-Output (iwr %s -UseBasicParsing -Uri %q).Content;]]):format(HEADERS.iwr, url) - ) - end - end - - return platform_specific - :recover_catching(function() - return spawn.wget({ HEADERS.wget, "-nv", "-O", opts.out_file or "-", url }):get_or_throw() - end) - :recover_catching(function() - return spawn.curl({ HEADERS.curl, "-fsSL", opts.out_file and { "-o", opts.out_file } or vim.NIL, url }):get_or_throw() - end) - :map(function(result) - if opts.out_file then - return result - else - return result.stdout - end - end) -end - -return fetch diff --git a/lua/nvim-lsp-installer/core/fs.lua b/lua/nvim-lsp-installer/core/fs.lua deleted file mode 100644 index 08e6f04f..00000000 --- a/lua/nvim-lsp-installer/core/fs.lua +++ /dev/null @@ -1,147 +0,0 @@ -local log = require "nvim-lsp-installer.log" -local a = require "nvim-lsp-installer.core.async" -local Path = require "nvim-lsp-installer.core.path" -local settings = require "nvim-lsp-installer.settings" - -local function make_module(uv) - local M = {} - - ---@param path string - function M.fstat(path) - log.trace("fs: fstat", path) - local fd = uv.fs_open(path, "r", 438) - local fstat = uv.fs_fstat(fd) - uv.fs_close(fd) - return fstat - end - - ---@param path string - function M.file_exists(path) - log.trace("fs: file_exists", path) - local ok, fstat = pcall(M.fstat, path) - if not ok then - return false - end - return fstat.type == "file" - end - - ---@param path string - function M.dir_exists(path) - log.trace("fs: dir_exists", path) - local ok, fstat = pcall(M.fstat, path) - if not ok then - return false - end - return fstat.type == "directory" - end - - ---@param path string - function M.rmrf(path) - assert( - Path.is_subdirectory(settings.current.install_root_dir, path), - ( - "Refusing to rmrf %q which is outside of the allowed boundary %q. Please report this error at https://github.com/williamboman/nvim-lsp-installer/issues/new" - ):format(path, settings.current.install_root_dir) - ) - log.debug("fs: rmrf", path) - if vim.in_fast_event() then - a.scheduler() - end - if vim.fn.delete(path, "rf") ~= 0 then - log.debug "fs: rmrf failed" - error(("rmrf: Could not remove directory %q."):format(path)) - end - end - - ---@param path string - function M.unlink(path) - log.debug("fs: unlink", path) - uv.fs_unlink(path) - end - - ---@param path string - function M.mkdir(path) - log.debug("fs: mkdir", path) - uv.fs_mkdir(path, 493) -- 493(10) == 755(8) - end - - ---@param path string - function M.mkdirp(path) - log.debug("fs: mkdirp", path) - if vim.in_fast_event() then - a.scheduler() - end - if vim.fn.mkdir(path, "p") ~= 1 then - log.debug "fs: mkdirp failed" - error(("mkdirp: Could not create directory %q."):format(path)) - end - end - - ---@param path string - ---@param new_path string - function M.rename(path, new_path) - log.debug("fs: rename", path, new_path) - uv.fs_rename(path, new_path) - end - - ---@param path string - ---@param contents string - ---@param flags string|nil @Defaults to "w". - function M.write_file(path, contents, flags) - log.debug("fs: write_file", path) - local fd = uv.fs_open(path, flags or "w", 438) - uv.fs_write(fd, contents, -1) - uv.fs_close(fd) - end - - ---@param path string - ---@param contents string - function M.append_file(path, contents) - M.write_file(path, contents, "a") - end - - ---@param path string - function M.read_file(path) - log.trace("fs: read_file", path) - local fd = uv.fs_open(path, "r", 438) - local fstat = uv.fs_fstat(fd) - local contents = uv.fs_read(fd, fstat.size, 0) - uv.fs_close(fd) - return contents - end - - ---@alias ReaddirEntry {name: string, type: string} - - ---@param path string @The full path to the directory to read. - ---@return ReaddirEntry[] - function M.readdir(path) - log.trace("fs: fs_opendir", path) - local dir = vim.loop.fs_opendir(path, nil, 25) - local all_entries = {} - local exhausted = false - - repeat - local entries = uv.fs_readdir(dir) - log.trace("fs: fs_readdir", path, entries) - if entries and #entries > 0 then - for i = 1, #entries do - all_entries[#all_entries + 1] = entries[i] - end - else - log.trace("fs: fs_readdir exhausted scan", path) - exhausted = true - end - until exhausted - - uv.fs_closedir(dir) - - return all_entries - end - - return M -end - -return { - async = make_module(require "nvim-lsp-installer.core.async.uv"), - sync = make_module(vim.loop), -} diff --git a/lua/nvim-lsp-installer/core/functional/data.lua b/lua/nvim-lsp-installer/core/functional/data.lua deleted file mode 100644 index da6f1efd..00000000 --- a/lua/nvim-lsp-installer/core/functional/data.lua +++ /dev/null @@ -1,30 +0,0 @@ -local _ = {} - -_.table_pack = function(...) - return { n = select("#", ...), ... } -end - ----@generic T : string ----@param values T[] ----@return table<T, T> -_.enum = function(values) - local result = {} - for i = 1, #values do - local v = values[i] - result[v] = v - end - return result -end - ----@generic T ----@param list T[] ----@return table<T, boolean> -_.set_of = function(list) - local set = {} - for i = 1, #list do - set[list[i]] = true - end - return set -end - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/function.lua b/lua/nvim-lsp-installer/core/functional/function.lua deleted file mode 100644 index 0d70aa92..00000000 --- a/lua/nvim-lsp-installer/core/functional/function.lua +++ /dev/null @@ -1,89 +0,0 @@ -local data = require "nvim-lsp-installer.core.functional.data" - -local _ = {} - ----@generic T : fun(...) ----@param fn T ----@param arity integer ----@return T -_.curryN = function(fn, arity) - return function(...) - local args = data.table_pack(...) - if args.n >= arity then - return fn(unpack(args, 1, arity)) - else - return _.curryN(_.partial(fn, unpack(args, 1, args.n)), arity - args.n) - end - end -end - -_.compose = function(...) - local functions = data.table_pack(...) - assert(functions.n > 0, "compose requires at least one function") - return function(...) - local result = data.table_pack(...) - for i = functions.n, 1, -1 do - result = data.table_pack(functions[i](unpack(result, 1, result.n))) - end - return unpack(result, 1, result.n) - end -end - ----@generic T ----@param fn fun(...): T ----@return fun(...): T -_.partial = function(fn, ...) - local bound_args = data.table_pack(...) - return function(...) - local args = data.table_pack(...) - local merged_args = {} - for i = 1, bound_args.n do - merged_args[i] = bound_args[i] - end - for i = 1, args.n do - merged_args[bound_args.n + i] = args[i] - end - return fn(unpack(merged_args, 1, bound_args.n + args.n)) - end -end - -_.identity = function(a) - return a -end - -_.always = function(a) - return function() - return a - end -end - -_.T = _.always(true) -_.F = _.always(false) - ----@generic T : fun(...) ----@param fn T ----@param cache_key_generator (fun(...): string | nil)|nil ----@return T -_.memoize = function(fn, cache_key_generator) - cache_key_generator = cache_key_generator or _.identity - local cache = {} - return function(...) - local key = cache_key_generator(...) - if not cache[key] then - cache[key] = data.table_pack(fn(...)) - end - return unpack(cache[key], 1, cache[key].n) - end -end - ----@generic T ----@param fn fun(): T ----@return fun(): T -_.lazy = function(fn) - local memoized = _.memoize(fn, _.always "lazyval") - return function() - return memoized() - end -end - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/init.lua b/lua/nvim-lsp-installer/core/functional/init.lua deleted file mode 100644 index 3e29037f..00000000 --- a/lua/nvim-lsp-installer/core/functional/init.lua +++ /dev/null @@ -1,97 +0,0 @@ -local _ = {} - --- data -local data = require "nvim-lsp-installer.core.functional.data" -_.table_pack = data.table_pack -_.enum = data.enum -_.set_of = data.set_of - --- function -local fun = require "nvim-lsp-installer.core.functional.function" -_.curryN = fun.curryN -_.compose = fun.compose -_.partial = fun.partial -_.identity = fun.identity -_.always = fun.always -_.T = fun.T -_.F = fun.F -_.memoize = fun.memoize -_.lazy = fun.lazy - --- list -local list = require "nvim-lsp-installer.core.functional.list" -_.reverse = list.reverse -_.list_not_nil = list.list_not_nil -_.list_copy = list.list_copy -_.find_first = list.find_first -_.any = list.any -_.filter = list.filter -_.map = list.map -_.each = list.each -_.concat = list.concat -_.zip_table = list.zip_table -_.nth = list.nth -_.head = list.head -_.length = list.length - --- relation -local relation = require "nvim-lsp-installer.core.functional.relation" -_.equals = relation.equals -_.prop_eq = relation.prop_eq -_.prop_satisfies = relation.prop_satisfies - --- logic -local logic = require "nvim-lsp-installer.core.functional.logic" -_.all_pass = logic.all_pass -_.if_else = logic.if_else -_.is_not = logic.is_not -_.complement = logic.complement -_.cond = logic.cond - --- number -local number = require "nvim-lsp-installer.core.functional.number" -_.negate = number.negate -_.gt = number.gt -_.gte = number.gte -_.lt = number.lt -_.lte = number.lte -_.inc = number.inc -_.dec = number.dec - --- string -local string = require "nvim-lsp-installer.core.functional.string" -_.matches = string.matches -_.format = string.format -_.split = string.split -_.gsub = string.gsub - --- table -local tbl = require "nvim-lsp-installer.core.functional.table" -_.prop = tbl.prop - --- type -local typ = require "nvim-lsp-installer.core.functional.type" -_.is_nil = typ.is_nil -_.is = typ.is - --- TODO do something else with these - -_.coalesce = function(...) - local args = _.table_pack(...) - for i = 1, args.n do - local variable = args[i] - if variable ~= nil then - return variable - end - end -end - -_.when = function(condition, value) - return condition and value or nil -end - -_.lazy_when = function(condition, value) - return condition and value() or nil -end - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/list.lua b/lua/nvim-lsp-installer/core/functional/list.lua deleted file mode 100644 index 89393cfb..00000000 --- a/lua/nvim-lsp-installer/core/functional/list.lua +++ /dev/null @@ -1,123 +0,0 @@ -local fun = require "nvim-lsp-installer.core.functional.function" -local data = require "nvim-lsp-installer.core.functional.data" - -local _ = {} - ----@generic T ----@param list T[] ----@return T[] -_.reverse = function(list) - local result = {} - for i = #list, 1, -1 do - result[#result + 1] = list[i] - end - return result -end - -_.list_not_nil = function(...) - local result = {} - local args = data.table_pack(...) - for i = 1, args.n do - if args[i] ~= nil then - result[#result + 1] = args[i] - end - end - return result -end - ----@generic T ----@param predicate fun(item: T): boolean ----@param list T[] ----@return T | nil -_.find_first = fun.curryN(function(predicate, list) - local result - for i = 1, #list do - local entry = list[i] - if predicate(entry) then - return entry - end - end - return result -end, 2) - ----@generic T ----@param predicate fun(item: T): boolean ----@param list T[] ----@return boolean -_.any = fun.curryN(function(predicate, list) - for i = 1, #list do - if predicate(list[i]) then - return true - end - end - return false -end, 2) - ----@generic T ----@param filter_fn fun(item: T): boolean ----@return fun(list: T[]): T[] -_.filter = fun.curryN(vim.tbl_filter, 2) - ----@generic T ----@param map_fn fun(item: T): boolean ----@return fun(list: T[]): T[] -_.map = fun.curryN(vim.tbl_map, 2) - ----@generic T ----@param fn fun(item: T, index: integer) ----@param list T[] -_.each = fun.curryN(function(fn, list) - for k, v in pairs(list) do - fn(v, k) - end -end, 2) - ----@generic T ----@param list T[] ----@return T[] @A shallow copy of the list. -_.list_copy = _.map(fun.identity) - -_.concat = fun.curryN(function(a, b) - if type(a) == "table" then - assert(type(b) == "table", "concat: expected table") - return vim.list_extend(_.list_copy(a), b) - elseif type(a) == "string" then - assert(type(b) == "string", "concat: expected string") - return a .. b - end -end, 2) - ----@generic T ----@generic U ----@param keys T[] ----@param values U[] ----@return table<T, U> -_.zip_table = fun.curryN(function(keys, values) - local res = {} - for i, key in ipairs(keys) do - res[key] = values[i] - end - return res -end, 2) - ----@generic T ----@param offset number ----@param value T[]|string ----@return T|string|nil -_.nth = fun.curryN(function(offset, value) - local index = offset < 0 and (#value + (offset + 1)) or offset - if type(value) == "string" then - return string.sub(value, index, index) - else - return value[index] - end -end, 2) - -_.head = _.nth(1) - ----@param value string|any[] -_.length = function(value) - return #value -end - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/logic.lua b/lua/nvim-lsp-installer/core/functional/logic.lua deleted file mode 100644 index 70b349dd..00000000 --- a/lua/nvim-lsp-installer/core/functional/logic.lua +++ /dev/null @@ -1,51 +0,0 @@ -local fun = require "nvim-lsp-installer.core.functional.function" - -local _ = {} - ----@generic T ----@param predicates (fun(item: T): boolean)[] ----@return fun(item: T): boolean -_.all_pass = fun.curryN(function(predicates, item) - for i = 1, #predicates do - if not predicates[i](item) then - return false - end - end - return true -end, 2) - ----@generic T ----@param predicate fun(item: T): boolean ----@param a fun(item: T): any ----@param b fun(item: T): any ----@param value T -_.if_else = fun.curryN(function(predicate, a, b, value) - if predicate(value) then - return a(value) - else - return b(value) - end -end, 4) - ----@param value boolean -_.is_not = function(value) - return not value -end - ----@generic T ----@param predicate fun(value: T): boolean ----@param value T -_.complement = fun.curryN(function(predicate, value) - return not predicate(value) -end, 2) - -_.cond = fun.curryN(function(predicate_transformer_pairs, value) - for _, pair in ipairs(predicate_transformer_pairs) do - local predicate, transformer = pair[1], pair[2] - if predicate(value) then - return transformer(value) - end - end -end, 2) - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/number.lua b/lua/nvim-lsp-installer/core/functional/number.lua deleted file mode 100644 index b123eb20..00000000 --- a/lua/nvim-lsp-installer/core/functional/number.lua +++ /dev/null @@ -1,34 +0,0 @@ -local fun = require "nvim-lsp-installer.core.functional.function" - -local _ = {} - ----@param number number -_.negate = function(number) - return -number -end - -_.gt = fun.curryN(function(number, value) - return value > number -end, 2) - -_.gte = fun.curryN(function(number, value) - return value >= number -end, 2) - -_.lt = fun.curryN(function(number, value) - return value < number -end, 2) - -_.lte = fun.curryN(function(number, value) - return value <= number -end, 2) - -_.inc = fun.curryN(function(increment, value) - return value + increment -end, 2) - -_.dec = fun.curryN(function(decrement, value) - return value - decrement -end, 2) - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/relation.lua b/lua/nvim-lsp-installer/core/functional/relation.lua deleted file mode 100644 index d9786a6a..00000000 --- a/lua/nvim-lsp-installer/core/functional/relation.lua +++ /dev/null @@ -1,17 +0,0 @@ -local fun = require "nvim-lsp-installer.core.functional.function" - -local _ = {} - -_.equals = fun.curryN(function(expected, value) - return value == expected -end, 2) - -_.prop_eq = fun.curryN(function(property, value, tbl) - return tbl[property] == value -end, 3) - -_.prop_satisfies = fun.curryN(function(predicate, property, tbl) - return predicate(tbl[property]) -end, 3) - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/string.lua b/lua/nvim-lsp-installer/core/functional/string.lua deleted file mode 100644 index 212fc0d9..00000000 --- a/lua/nvim-lsp-installer/core/functional/string.lua +++ /dev/null @@ -1,30 +0,0 @@ -local fun = require "nvim-lsp-installer.core.functional.function" - -local _ = {} - ----@param pattern string ----@param str string -_.matches = fun.curryN(function(pattern, str) - return str:match(pattern) ~= nil -end, 2) - ----@param template string ----@param str string -_.format = fun.curryN(function(template, str) - return template:format(str) -end, 2) - ----@param sep string ----@param str string -_.split = fun.curryN(function(sep, str) - return vim.split(str, sep) -end, 2) - ----@param pattern string ----@param repl string|function|table ----@param str string -_.gsub = fun.curryN(function(pattern, repl, str) - return string.gsub(str, pattern, repl) -end, 3) - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/table.lua b/lua/nvim-lsp-installer/core/functional/table.lua deleted file mode 100644 index 37aee19e..00000000 --- a/lua/nvim-lsp-installer/core/functional/table.lua +++ /dev/null @@ -1,9 +0,0 @@ -local fun = require "nvim-lsp-installer.core.functional.function" - -local _ = {} - -_.prop = fun.curryN(function(index, tbl) - return tbl[index] -end, 2) - -return _ diff --git a/lua/nvim-lsp-installer/core/functional/type.lua b/lua/nvim-lsp-installer/core/functional/type.lua deleted file mode 100644 index 23d961ba..00000000 --- a/lua/nvim-lsp-installer/core/functional/type.lua +++ /dev/null @@ -1,14 +0,0 @@ -local fun = require "nvim-lsp-installer.core.functional.function" -local rel = require "nvim-lsp-installer.core.functional.relation" - -local _ = {} - -_.is_nil = rel.equals(nil) - ----@param typ type ----@param value any -_.is = fun.curryN(function(typ, value) - return type(value) == typ -end, 2) - -return _ diff --git a/lua/nvim-lsp-installer/core/installer/context.lua b/lua/nvim-lsp-installer/core/installer/context.lua deleted file mode 100644 index aaec4ff5..00000000 --- a/lua/nvim-lsp-installer/core/installer/context.lua +++ /dev/null @@ -1,199 +0,0 @@ -local spawn = require "nvim-lsp-installer.core.spawn" -local log = require "nvim-lsp-installer.log" -local fs = require "nvim-lsp-installer.core.fs" -local path = require "nvim-lsp-installer.core.path" -local platform = require "nvim-lsp-installer.core.platform" -local receipt = require "nvim-lsp-installer.core.receipt" -local installer = require "nvim-lsp-installer.core.installer" -local a = require "nvim-lsp-installer.core.async" - ----@class ContextualSpawn ----@field cwd CwdManager ----@field stdio_sink StdioSink -local ContextualSpawn = {} - ----@param cwd CwdManager ----@param stdio_sink StdioSink -function ContextualSpawn.new(cwd, stdio_sink) - return setmetatable({ cwd = cwd, stdio_sink = stdio_sink }, ContextualSpawn) -end -function ContextualSpawn.__index(self, cmd) - return function(args) - args.cwd = args.cwd or self.cwd:get() - args.stdio_sink = args.stdio_sink or self.stdio_sink - -- We get_or_throw() here for convenience reasons. - -- Almost every time spawn is called via context we want the command to succeed. - return spawn[cmd](args):get_or_throw() - end -end - ----@class ContextualFs ----@field private cwd CwdManager -local ContextualFs = {} -ContextualFs.__index = ContextualFs - ----@param cwd CwdManager -function ContextualFs.new(cwd) - return setmetatable({ cwd = cwd }, ContextualFs) -end - ----@async ----@param rel_path string @The relative path from the current working directory to the file to append. ----@param contents string -function ContextualFs:append_file(rel_path, contents) - return fs.async.append_file(path.concat { self.cwd:get(), rel_path }, contents) -end - ----@async ----@param rel_path string @The relative path from the current working directory to the file to write. ----@param contents string -function ContextualFs:write_file(rel_path, contents) - return fs.async.write_file(path.concat { self.cwd:get(), rel_path }, contents) -end - ----@async ----@param rel_path string @The relative path from the current working directory. -function ContextualFs:file_exists(rel_path) - return fs.async.file_exists(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param rel_path string @The relative path from the current working directory. -function ContextualFs:dir_exists(rel_path) - return fs.async.dir_exists(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param rel_path string @The relative path from the current working directory. -function ContextualFs:rmrf(rel_path) - return fs.async.rmrf(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param rel_path string @The relative path from the current working directory. -function ContextualFs:unlink(rel_path) - return fs.async.unlink(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param old_path string ----@param new_path string -function ContextualFs:rename(old_path, new_path) - return fs.async.rename(path.concat { self.cwd:get(), old_path }, path.concat { self.cwd:get(), new_path }) -end - ----@async ----@param dirpath string -function ContextualFs:mkdir(dirpath) - return fs.async.mkdir(path.concat { self.cwd:get(), dirpath }) -end - ----@class CwdManager ----@field private boundary_path string @Defines the upper boundary for which paths are allowed as cwd. ----@field private cwd string -local CwdManager = {} -CwdManager.__index = CwdManager - -function CwdManager.new(boundary_path, cwd) - assert(type(boundary_path) == "string") - return setmetatable({ - boundary_path = boundary_path, - cwd = cwd, - }, CwdManager) -end - -function CwdManager:get() - assert(self.cwd ~= nil, "Tried to access cwd before it was set.") - return self.cwd -end - ----@param new_cwd string -function CwdManager:set(new_cwd) - assert(type(new_cwd) == "string") - assert( - path.is_subdirectory(self.boundary_path, new_cwd), - ("%q is not a subdirectory of %q"):format(new_cwd, self.boundary_path) - ) - self.cwd = new_cwd -end - ----@class InstallContext ----@field public name string ----@field public receipt InstallReceiptBuilder ----@field public requested_version Optional ----@field public fs ContextualFs ----@field public spawn JobSpawn ----@field public cwd CwdManager ----@field public destination_dir string ----@field public stdio_sink StdioSink ----@field public boundary_path string -local InstallContext = {} -InstallContext.__index = InstallContext - -function InstallContext.new(opts) - local cwd_manager = CwdManager.new(opts.boundary_path) - return setmetatable({ - name = opts.name, - cwd = cwd_manager, - spawn = ContextualSpawn.new(cwd_manager, opts.stdio_sink), - fs = ContextualFs.new(cwd_manager), - receipt = receipt.InstallReceiptBuilder.new(), - boundary_path = opts.boundary_path, - destination_dir = opts.destination_dir, - requested_version = opts.requested_version, - stdio_sink = opts.stdio_sink, - }, InstallContext) -end - ----@async -function InstallContext:promote_cwd() - local cwd = self.cwd:get() - if self.destination_dir == cwd then - log.fmt_debug("cwd %s is already promoted (at %s)", cwd, self.destination_dir) - return - end - log.fmt_debug("Promoting cwd %s to %s", cwd, self.destination_dir) - -- 1. Remove destination dir, if it exists - if fs.async.dir_exists(self.destination_dir) then - fs.async.rmrf(self.destination_dir) - end - -- 2. Prepare for renaming cwd to destination - if platform.is_unix then - -- Some Unix systems will raise an error when renaming a directory to a destination that does not already exist. - fs.async.mkdir(self.destination_dir) - end - -- 3. Move the cwd to the final installation directory - fs.async.rename(cwd, self.destination_dir) - -- 4. Update cwd - self.cwd:set(self.destination_dir) -end - ----Runs the provided async functions concurrently and returns their result, once all are resolved. ----This is really just a wrapper around a.wait_all() that makes sure to patch the coroutine context before creating the ----new async execution contexts. ----@async ----@param suspend_fns async fun(ctx: InstallContext)[] -function InstallContext:run_concurrently(suspend_fns) - return a.wait_all(vim.tbl_map(function(suspend_fn) - return function() - return installer.run_installer(self, suspend_fn) - end - end, suspend_fns)) -end - ----@param rel_path string @The relative path from the current working directory to change cwd to. Will only restore to the initial cwd after execution of fn (if provided). ----@param fn async fun() @(optional) The function to run in the context of the given path. -function InstallContext:chdir(rel_path, fn) - local old_cwd = self.cwd:get() - self.cwd:set(path.concat { old_cwd, rel_path }) - if fn then - local ok, result = pcall(fn) - self.cwd:set(old_cwd) - if not ok then - error(result, 0) - end - return result - end -end - -return InstallContext diff --git a/lua/nvim-lsp-installer/core/installer/init.lua b/lua/nvim-lsp-installer/core/installer/init.lua deleted file mode 100644 index 3bb9590d..00000000 --- a/lua/nvim-lsp-installer/core/installer/init.lua +++ /dev/null @@ -1,87 +0,0 @@ -local log = require "nvim-lsp-installer.log" -local path = require "nvim-lsp-installer.core.path" -local fs = require "nvim-lsp-installer.core.fs" -local Result = require "nvim-lsp-installer.core.result" - -local M = {} - ----@async ----@param context InstallContext -local function write_receipt(context) - if context.receipt.is_marked_invalid then - return log.fmt_debug("Skipping writing receipt for %s because it is marked as invalid.", context.name) - end - context.receipt:with_name(context.name):with_schema_version("1.0a"):with_completion_time(vim.loop.gettimeofday()) - local receipt_success, install_receipt = pcall(context.receipt.build, context.receipt) - if receipt_success then - local receipt_path = path.concat { context.cwd:get(), "nvim-lsp-installer-receipt.json" } - pcall(fs.async.write_file, receipt_path, vim.json.encode(install_receipt)) - else - log.fmt_error("Failed to build receipt for installation=%s, error=%s", context.name, install_receipt) - end -end - -local CONTEXT_REQUEST = {} - ----@return InstallContext -function M.context() - return coroutine.yield(CONTEXT_REQUEST) -end - ----@async ----@param context InstallContext ----@param installer async fun(context: InstallContext) -function M.run_installer(context, installer) - local thread = coroutine.create(installer) - local step - local ret_val - step = function(...) - local ok, result = coroutine.resume(thread, ...) - if not ok then - error(result, 0) - elseif result == CONTEXT_REQUEST then - step(context) - elseif coroutine.status(thread) == "suspended" then - -- yield to parent coroutine - step(coroutine.yield(result)) - else - ret_val = result - end - end - step(context) - return ret_val -end - ----@async ----@param context InstallContext ----@param installer async fun(ctx: InstallContext) -function M.execute(context, installer) - log.fmt_debug("Executing installer for name=%s", context.name) - local tmp_installation_dir = ("%s.tmp"):format(context.destination_dir) - return Result.run_catching(function() - -- 1. prepare installation dir - context.receipt:with_start_time(vim.loop.gettimeofday()) - if fs.async.dir_exists(tmp_installation_dir) then - fs.async.rmrf(tmp_installation_dir) - end - fs.async.mkdirp(tmp_installation_dir) - context.cwd:set(tmp_installation_dir) - - -- 2. run installer - M.run_installer(context, installer) - - -- 3. finalize - log.fmt_debug("Finalizing installer for name=%s", context.name) - write_receipt(context) - context:promote_cwd() - pcall(fs.async.rmrf, tmp_installation_dir) - end):on_failure(function(failure) - log.fmt_error("Installation failed, name=%s, error=%s", context.name, tostring(failure)) - context.stdio_sink.stderr(tostring(failure)) - context.stdio_sink.stderr "\n" - pcall(fs.async.rmrf, tmp_installation_dir) - pcall(fs.async.rmrf, context.cwd:get()) - end) -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/cargo/client.lua b/lua/nvim-lsp-installer/core/managers/cargo/client.lua deleted file mode 100644 index d4f0e2a7..00000000 --- a/lua/nvim-lsp-installer/core/managers/cargo/client.lua +++ /dev/null @@ -1,14 +0,0 @@ -local fetch = require "nvim-lsp-installer.core.fetch" - -local M = {} - ----@alias CrateResponse {crate: {id: string, max_stable_version: string, max_version: string, newest_version: string}} - ----@async ----@param crate string ----@return Result @of Crate -function M.fetch_crate(crate) - return fetch(("https://crates.io/api/v1/crates/%s"):format(crate)):map_catching(vim.json.decode) -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/cargo/init.lua b/lua/nvim-lsp-installer/core/managers/cargo/init.lua deleted file mode 100644 index 77ec5ae8..00000000 --- a/lua/nvim-lsp-installer/core/managers/cargo/init.lua +++ /dev/null @@ -1,126 +0,0 @@ -local process = require "nvim-lsp-installer.core.process" -local path = require "nvim-lsp-installer.core.path" -local spawn = require "nvim-lsp-installer.core.spawn" -local a = require "nvim-lsp-installer.core.async" -local Optional = require "nvim-lsp-installer.core.optional" -local installer = require "nvim-lsp-installer.core.installer" -local client = require "nvim-lsp-installer.core.managers.cargo.client" - ----@param crate string -local function with_receipt(crate) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.cargo(crate)) - end -end - -local M = {} - ----@async ----@param crate string The crate to install. ----@param opts {git:boolean, features:string|nil} -function M.crate(crate, opts) - return function() - return M.install(crate, opts).with_receipt() - end -end - ----@async ----@param crate string The crate to install. ----@param opts {git:boolean, features:string|nil} -function M.install(crate, opts) - local ctx = installer.context() - opts = opts or {} - ctx.requested_version:if_present(function() - assert(not opts.git, "Providing a version when installing a git crate is not allowed.") - end) - - local final_crate = crate - - if opts.git then - final_crate = { "--git" } - if type(opts.git) == "string" then - table.insert(final_crate, opts.git) - end - table.insert(final_crate, crate) - end - - ctx.spawn.cargo { - "install", - "--root", - ".", - "--locked", - ctx.requested_version - :map(function(version) - return { "--version", version } - end) - :or_else(vim.NIL), - opts.features and { "--features", opts.features } or vim.NIL, - final_crate, - } - - return { - with_receipt = with_receipt(crate), - } -end - ----@param output string @The `cargo install --list` output. ----@return table<string, string> @Key is the crate name, value is its version. -function M.parse_installed_crates(output) - local installed_crates = {} - for _, line in ipairs(vim.split(output, "\n")) do - local name, version = line:match "^(.+)%s+v([.%S]+)[%s:]" - if name and version then - installed_crates[name] = version - end - end - return installed_crates -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - return M.get_installed_primary_package_version(receipt, install_dir):map_catching(function(installed_version) - ---@type CrateResponse - local crate_response = client.fetch_crate(receipt.primary_source.package):get_or_throw() - if installed_version ~= crate_response.crate.max_stable_version then - return { - name = receipt.primary_source.package, - current_version = installed_version, - latest_version = crate_response.crate.max_stable_version, - } - else - error "Primary package is not outdated." - end - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - return spawn.cargo({ - "install", - "--list", - "--root", - ".", - cwd = install_dir, - }):map_catching(function(result) - local installed_crates = M.parse_installed_crates(result.stdout) - if vim.in_fast_event() then - a.scheduler() -- needed because vim.fn.* call - end - local package = vim.fn.fnamemodify(receipt.primary_source.package, ":t") - return Optional.of_nilable(installed_crates[package]):or_else_throw "Failed to find cargo package version." - end) -end - ----@param install_dir string -function M.env(install_dir) - return { - PATH = process.extend_path { path.concat { install_dir, "bin" } }, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/composer/init.lua b/lua/nvim-lsp-installer/core/managers/composer/init.lua deleted file mode 100644 index e2330c40..00000000 --- a/lua/nvim-lsp-installer/core/managers/composer/init.lua +++ /dev/null @@ -1,120 +0,0 @@ -local _ = require "nvim-lsp-installer.core.functional" -local process = require "nvim-lsp-installer.core.process" -local path = require "nvim-lsp-installer.core.path" -local Result = require "nvim-lsp-installer.core.result" -local spawn = require "nvim-lsp-installer.core.spawn" -local Optional = require "nvim-lsp-installer.core.optional" -local installer = require "nvim-lsp-installer.core.installer" - -local M = {} - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - - ctx.receipt:with_primary_source(ctx.receipt.composer(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.composer(packages[i])) - end - end -end - ----@async ----@param packages string[] The composer packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one. -function M.packages(packages) - return function() - return M.require(packages).with_receipt() - end -end - ----@async ----@param packages string[] The composer packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one. -function M.require(packages) - local ctx = installer.context() - local pkgs = _.list_copy(packages) - - if not ctx.fs:file_exists "composer.json" then - ctx.spawn.composer { "init", "--no-interaction", "--stability=stable" } - end - - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s:%s"):format(pkgs[1], version) - end) - - ctx.spawn.composer { "require", pkgs } - - return { - with_receipt = with_receipt(packages), - } -end - ----@async -function M.install() - local ctx = installer.context() - ctx.spawn.composer { - "install", - "--no-interaction", - "--no-dev", - "--optimize-autoloader", - "--classmap-authoritative", - } -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "composer" then - return Result.failure "Receipt does not have a primary source of type composer" - end - return spawn.composer({ - "outdated", - "--no-interaction", - "--format=json", - cwd = install_dir, - }):map_catching(function(result) - local outdated_packages = vim.json.decode(result.stdout) - local outdated_package = _.find_first(function(package) - return package.name == receipt.primary_source.package - end, outdated_packages.installed) - return Optional.of_nilable(outdated_package) - :map(function(package) - if package.version ~= package.latest then - return { - name = package.name, - current_version = package.version, - latest_version = package.latest, - } - end - end) - :or_else_throw "Primary package is not outdated." - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if receipt.primary_source.type ~= "composer" then - return Result.failure "Receipt does not have a primary source of type composer" - end - return spawn.composer({ - "info", - "--format=json", - receipt.primary_source.package, - cwd = install_dir, - }):map_catching(function(result) - local info = vim.json.decode(result.stdout) - return info.versions[1] - end) -end - ----@param install_dir string -function M.env(install_dir) - return { - PATH = process.extend_path { path.concat { install_dir, "vendor", "bin" } }, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/dotnet/init.lua b/lua/nvim-lsp-installer/core/managers/dotnet/init.lua deleted file mode 100644 index 3c16a4ad..00000000 --- a/lua/nvim-lsp-installer/core/managers/dotnet/init.lua +++ /dev/null @@ -1,50 +0,0 @@ -local process = require "nvim-lsp-installer.core.process" -local installer = require "nvim-lsp-installer.core.installer" - -local M = {} - ----@param package string -local function with_receipt(package) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.dotnet(package)) - end -end - ----@async ----@param package string -function M.package(package) - return function() - return M.install(package).with_receipt() - end -end - ----@async ----@param package string -function M.install(package) - local ctx = installer.context() - ctx.spawn.dotnet { - "tool", - "update", - "--tool-path", - ".", - ctx.requested_version - :map(function(version) - return { "--version", version } - end) - :or_else(vim.NIL), - package, - } - - return { - with_receipt = with_receipt(package), - } -end - -function M.env(root_dir) - return { - PATH = process.extend_path { root_dir }, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/gem/init.lua b/lua/nvim-lsp-installer/core/managers/gem/init.lua deleted file mode 100644 index e70c89c7..00000000 --- a/lua/nvim-lsp-installer/core/managers/gem/init.lua +++ /dev/null @@ -1,145 +0,0 @@ -local _ = require "nvim-lsp-installer.core.functional" -local process = require "nvim-lsp-installer.core.process" -local path = require "nvim-lsp-installer.core.path" -local Result = require "nvim-lsp-installer.core.result" -local spawn = require "nvim-lsp-installer.core.spawn" -local Optional = require "nvim-lsp-installer.core.optional" -local installer = require "nvim-lsp-installer.core.installer" - -local M = {} - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.gem(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.gem(packages[i])) - end - end -end - ----@async ----@param packages string[] @The Gem packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one. -function M.packages(packages) - return function() - return M.install(packages).with_receipt() - end -end - ----@async ----@param packages string[] @The Gem packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one. -function M.install(packages) - local ctx = installer.context() - local pkgs = _.list_copy(packages or {}) - - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s:%s"):format(pkgs[1], version) - end) - - ctx.spawn.gem { - "install", - "--no-user-install", - "--install-dir=.", - "--bindir=bin", - "--no-document", - pkgs, - } - - return { - with_receipt = with_receipt(packages), - } -end - ----@alias GemOutdatedPackage {name:string, current_version: string, latest_version: string} - ----Parses a string input like "package (0.1.0 < 0.2.0)" into its components ----@param outdated_gem string ----@return GemOutdatedPackage -function M.parse_outdated_gem(outdated_gem) - local package_name, version_expression = outdated_gem:match "^(.+) %((.+)%)" - if not package_name or not version_expression then - -- unparseable - return nil - end - local current_version, latest_version = unpack(vim.split(version_expression, "<")) - - ---@type GemOutdatedPackage - local outdated_package = { - name = vim.trim(package_name), - current_version = vim.trim(current_version), - latest_version = vim.trim(latest_version), - } - return outdated_package -end - ----Parses the stdout of the `gem list` command into a table<package_name, version> ----@param output string -function M.parse_gem_list_output(output) - ---@type table<string, string> - local gem_versions = {} - for _, line in ipairs(vim.split(output, "\n")) do - local gem_package, version = line:match "^(%S+) %((%S+)%)$" - if gem_package and version then - gem_versions[gem_package] = version - end - end - return gem_versions -end - -local function not_empty(s) - return s ~= nil and s ~= "" -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "gem" then - return Result.failure "Receipt does not have a primary source of type gem" - end - return spawn.gem({ "outdated", cwd = install_dir, env = M.env(install_dir) }):map_catching(function(result) - ---@type string[] - local lines = vim.split(result.stdout, "\n") - local outdated_gems = vim.tbl_map(M.parse_outdated_gem, vim.tbl_filter(not_empty, lines)) - - local outdated_gem = _.find_first(function(gem) - return gem.name == receipt.primary_source.package and gem.current_version ~= gem.latest_version - end, outdated_gems) - - return Optional.of_nilable(outdated_gem) - :map(function(gem) - return { - name = receipt.primary_source.package, - current_version = assert(gem.current_version), - latest_version = assert(gem.latest_version), - } - end) - :or_else_throw "Primary package is not outdated." - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - return spawn.gem({ - "list", - cwd = install_dir, - env = M.env(install_dir), - }):map_catching(function(result) - local gems = M.parse_gem_list_output(result.stdout) - return Optional.of_nilable(gems[receipt.primary_source.package]):or_else_throw "Failed to find gem package version." - end) -end - ----@param install_dir string -function M.env(install_dir) - return { - GEM_HOME = install_dir, - GEM_PATH = install_dir, - PATH = process.extend_path { path.concat { install_dir, "bin" } }, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/git/init.lua b/lua/nvim-lsp-installer/core/managers/git/init.lua deleted file mode 100644 index 559703c9..00000000 --- a/lua/nvim-lsp-installer/core/managers/git/init.lua +++ /dev/null @@ -1,73 +0,0 @@ -local spawn = require "nvim-lsp-installer.core.spawn" -local Result = require "nvim-lsp-installer.core.result" -local installer = require "nvim-lsp-installer.core.installer" -local _ = require "nvim-lsp-installer.core.functional" - -local M = {} - ----@param repo string -local function with_receipt(repo) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.git_remote(repo)) - end -end - ----@async ----@param opts {[1]: string, recursive: boolean, version: Optional|nil} @The first item in the table is the repository to clone. -function M.clone(opts) - local ctx = installer.context() - local repo = assert(opts[1], "No git URL provided.") - ctx.spawn.git { - "clone", - "--depth", - "1", - opts.recursive and "--recursive" or vim.NIL, - repo, - ".", - } - _.coalesce(opts.version, ctx.requested_version):if_present(function(version) - ctx.spawn.git { "fetch", "--depth", "1", "origin", version } - ctx.spawn.git { "checkout", "FETCH_HEAD" } - end) - - return { - with_receipt = with_receipt(repo), - } -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_git_clone(receipt, install_dir) - if receipt.primary_source.type ~= "git" then - return Result.failure "Receipt does not have a primary source of type git" - end - return spawn.git({ "fetch", "origin", "HEAD", cwd = install_dir }):map_catching(function() - local result = spawn.git({ "rev-parse", "FETCH_HEAD", "HEAD", cwd = install_dir }):get_or_throw() - local remote_head, local_head = unpack(vim.split(result.stdout, "\n")) - if remote_head == local_head then - error("Git clone is up to date.", 2) - end - return { - name = receipt.primary_source.remote, - current_version = assert(local_head), - latest_version = assert(remote_head), - } - end) -end - ----@async ----@param install_dir string -function M.get_installed_revision(install_dir) - return spawn.git({ - "rev-parse", - "--short", - "HEAD", - cwd = install_dir, - }):map_catching(function(result) - return assert(vim.trim(result.stdout)) - end) -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/github/client.lua b/lua/nvim-lsp-installer/core/managers/github/client.lua deleted file mode 100644 index 530fee06..00000000 --- a/lua/nvim-lsp-installer/core/managers/github/client.lua +++ /dev/null @@ -1,120 +0,0 @@ -local _ = require "nvim-lsp-installer.core.functional" -local log = require "nvim-lsp-installer.log" -local fetch = require "nvim-lsp-installer.core.fetch" -local spawn = require "nvim-lsp-installer.core.spawn" - -local M = {} - ----@alias GitHubReleaseAsset {url: string, id: integer, name: string, browser_download_url: string, created_at: string, updated_at: string, size: integer, download_count: integer} ----@alias GitHubRelease {tag_name: string, prerelease: boolean, draft: boolean, assets:GitHubReleaseAsset[]} ----@alias GitHubTag {name: string} - ----@param path string ----@return Result @JSON decoded response. -local function api_call(path) - return spawn.gh({ "api", path }) - :map(function(result) - return result.stdout - end) - :recover_catching(function() - return fetch(("https://api.github.com/%s"):format(path)):get_or_throw() - end) - :map_catching(vim.json.decode) -end - ----@async ----@param repo string @The GitHub repo ("username/repo"). ----@return Result @of GitHubRelease[] -function M.fetch_releases(repo) - log.fmt_trace("Fetching GitHub releases for repo=%s", repo) - local path = ("repos/%s/releases"):format(repo) - return api_call(path):map_err(function() - return ("Failed to fetch releases for GitHub repository %s."):format(repo) - end) -end - ----@async ----@param repo string @The GitHub repo ("username/repo"). ----@param tag_name string @The tag_name of the release to fetch. -function M.fetch_release(repo, tag_name) - log.fmt_trace("Fetching GitHub release for repo=%s, tag_name=%s", repo, tag_name) - local path = ("repos/%s/releases/tags/%s"):format(repo, tag_name) - return api_call(path):map_err(function() - return ("Failed to fetch release %q for GitHub repository %s."):format(tag_name, repo) - end) -end - ----@param opts {include_prerelease: boolean, tag_name_pattern: string} -function M.release_predicate(opts) - local is_not_draft = _.prop_eq("draft", false) - local is_not_prerelease = _.prop_eq("prerelease", false) - local tag_name_matches = _.prop_satisfies(_.matches(opts.tag_name_pattern), "tag_name") - - return _.all_pass { - _.if_else(_.always(opts.include_prerelease), _.T, is_not_prerelease), - _.if_else(_.always(opts.tag_name_pattern), tag_name_matches, _.T), - is_not_draft, - } -end - ----@alias FetchLatestGithubReleaseOpts {tag_name_pattern:string|nil, include_prerelease: boolean} - ----@async ----@param repo string @The GitHub repo ("username/repo"). ----@param opts FetchLatestGithubReleaseOpts|nil ----@return Result @of GitHubRelease -function M.fetch_latest_release(repo, opts) - opts = opts or { - tag_name_pattern = nil, - include_prerelease = false, - } - return M.fetch_releases(repo):map_catching( - ---@param releases GitHubRelease[] - function(releases) - local is_stable_release = M.release_predicate(opts) - ---@type GitHubRelease|nil - local latest_release = _.find_first(is_stable_release, releases) - - if not latest_release then - log.fmt_info("Failed to find latest release. repo=%s, opts=%s", repo, opts) - error "Failed to find latest release." - end - - log.fmt_debug("Resolved latest version repo=%s, tag_name=%s", repo, latest_release.tag_name) - return latest_release - end - ) -end - ----@async ----@param repo string @The GitHub repo ("username/repo"). ----@return Result @of GitHubTag[] -function M.fetch_tags(repo) - local path = ("repos/%s/tags"):format(repo) - return api_call(path):map_err(function() - return ("Failed to fetch tags for GitHub repository %s."):format(repo) - end) -end - ----@async ----@param repo string @The GitHub repo ("username/repo"). ----@return Result @of GitHubTag -function M.fetch_latest_tag(repo) - return M.fetch_tags(repo):map_catching(function(tags) - if vim.tbl_count(tags) == 0 then - error "No tags found." - end - return tags[1] - end) -end - ----@alias GitHubRateLimit {limit: integer, remaining: integer, reset: integer, used: integer} ----@alias GitHubRateLimitResponse {resources: { core: GitHubRateLimit }} - ----@async ---@return Result @of GitHubRateLimitResponse -function M.fetch_rate_limit() - return api_call "rate_limit" -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/github/init.lua b/lua/nvim-lsp-installer/core/managers/github/init.lua deleted file mode 100644 index f74fde76..00000000 --- a/lua/nvim-lsp-installer/core/managers/github/init.lua +++ /dev/null @@ -1,175 +0,0 @@ -local installer = require "nvim-lsp-installer.core.installer" -local std = require "nvim-lsp-installer.core.managers.std" -local client = require "nvim-lsp-installer.core.managers.github.client" -local platform = require "nvim-lsp-installer.core.platform" -local Result = require "nvim-lsp-installer.core.result" -local _ = require "nvim-lsp-installer.core.functional" -local settings = require "nvim-lsp-installer.settings" - -local M = {} - ----@param repo string ----@param asset_file string ----@param release string -local function with_release_file_receipt(repo, asset_file, release) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source { - type = "github_release_file", - repo = repo, - file = asset_file, - release = release, - } - end -end - ----@param repo string ----@param tag string -local function with_tag_receipt(repo, tag) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source { - type = "github_tag", - repo = repo, - tag = tag, - } - end -end - ----@async ----@param opts {repo: string, version: Optional|nil, asset_file: string|fun(release: string):string} -function M.release_file(opts) - local ctx = installer.context() - local release = _.coalesce(opts.version, ctx.requested_version):or_else_get(function() - return client.fetch_latest_release(opts.repo) - :map(_.prop "tag_name") - :get_or_throw "Failed to fetch latest release from GitHub API. Refer to :h nvim-lsp-installer-errors-github-api for more information." - end) - ---@type string - local asset_file - if type(opts.asset_file) == "function" then - asset_file = opts.asset_file(release) - else - asset_file = opts.asset_file - end - if not asset_file then - error( - ( - "Could not find which release file to download.\nMost likely the current operating system, architecture (%s), or libc (%s) is not supported." - ):format(platform.arch, platform.get_libc()), - 0 - ) - end - local download_url = settings.current.github.download_url_template:format(opts.repo, release, asset_file) - return { - release = release, - download_url = download_url, - asset_file = asset_file, - with_receipt = with_release_file_receipt(opts.repo, download_url, release), - } -end - ----@async ----@param opts {repo: string, version: Optional|nil} -function M.tag(opts) - local ctx = installer.context() - local tag = _.coalesce(opts.version, ctx.requested_version):or_else_get(function() - return client.fetch_latest_tag(opts.repo) - :map(_.prop "name") - :get_or_throw "Failed to fetch latest tag from GitHub API." - end) - - return { - tag = tag, - with_receipt = with_tag_receipt(opts.repo, tag), - } -end - ----@param filename string ----@param processor async fun() -local function release_file_processor(filename, processor) - ---@async - ---@param opts {repo: string, asset_file: string|fun(release: string):string} - return function(opts) - local release_file_source = M.release_file(opts) - std.download_file(release_file_source.download_url, filename) - processor(release_file_source) - return release_file_source - end -end - -M.unzip_release_file = release_file_processor("archive.zip", function() - std.unzip("archive.zip", ".") -end) - -M.untarxz_release_file = release_file_processor("archive.tar.xz", function() - std.untarxz "archive.tar.xz" -end) - -M.untargz_release_file = release_file_processor("archive.tar.gz", function() - std.untar "archive.tar.gz" -end) - ----@async ----@param opts {repo: string, out_file:string, asset_file: string|fun(release: string):string} -function M.download_release_file(opts) - local release_file_source = M.release_file(opts) - std.download_file(release_file_source.download_url, assert(opts.out_file, "out_file is required")) - return release_file_source -end - ----@async ----@param opts {repo: string, out_file:string, asset_file: string|fun(release: string):string} -function M.gunzip_release_file(opts) - local release_file_source = M.release_file(opts) - local gzipped_file = ("%s.gz"):format(assert(opts.out_file, "out_file must be specified")) - std.download_file(release_file_source.download_url, gzipped_file) - std.gunzip(gzipped_file) - return release_file_source -end - ----@async ----@param receipt InstallReceipt -function M.check_outdated_primary_package_release(receipt) - local source = receipt.primary_source - if source.type ~= "github_release" and source.type ~= "github_release_file" then - return Result.failure "Receipt does not have a primary source of type (github_release|github_release_file)." - end - return client.fetch_latest_release(source.repo, { tag_name_pattern = source.tag_name_pattern }):map_catching( - ---@param latest_release GitHubRelease - function(latest_release) - if source.release ~= latest_release.tag_name then - return { - name = source.repo, - current_version = source.release, - latest_version = latest_release.tag_name, - } - end - error "Primary package is not outdated." - end - ) -end - ----@async ----@param receipt InstallReceipt -function M.check_outdated_primary_package_tag(receipt) - local source = receipt.primary_source - if source.type ~= "github_tag" then - return Result.failure "Receipt does not have a primary source of type github_tag." - end - return client.fetch_latest_tag(source.repo):map_catching( - ---@param latest_tag GitHubTag - function(latest_tag) - if source.tag ~= latest_tag.name then - return { - name = source.repo, - current_version = source.tag, - latest_version = latest_tag.name, - } - end - error "Primary package is not outdated." - end - ) -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/go/init.lua b/lua/nvim-lsp-installer/core/managers/go/init.lua deleted file mode 100644 index 067cff58..00000000 --- a/lua/nvim-lsp-installer/core/managers/go/init.lua +++ /dev/null @@ -1,130 +0,0 @@ -local installer = require "nvim-lsp-installer.core.installer" -local process = require "nvim-lsp-installer.core.process" -local platform = require "nvim-lsp-installer.core.platform" -local spawn = require "nvim-lsp-installer.core.spawn" -local a = require "nvim-lsp-installer.core.async" -local Optional = require "nvim-lsp-installer.core.optional" - -local M = {} - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.go(packages[1])) - -- Install secondary packages - for i = 2, #packages do - local package = packages[i] - ctx.receipt:with_secondary_source(ctx.receipt.go(package)) - end - end -end - ----@async ----@param packages string[] The Go packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one. -function M.packages(packages) - return function() - M.install(packages).with_receipt() - end -end - ----@async ----@param packages string[] The Go packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one. -function M.install(packages) - local ctx = installer.context() - local env = { - GOBIN = ctx.cwd:get(), - } - -- Install the head package - do - local head_package = packages[1] - local version = ctx.requested_version:or_else "latest" - ctx.spawn.go { - "install", - "-v", - ("%s@%s"):format(head_package, version), - env = env, - } - end - - -- Install secondary packages - for i = 2, #packages do - ctx.spawn.go { "install", "-v", ("%s@latest"):format(packages[i]), env = env } - end - - return { - with_receipt = with_receipt(packages), - } -end - ----@param output string @The output from `go version -m` command. -function M.parse_mod_version_output(output) - ---@type {path: string[], mod: string[], dep: string[], build: string[]} - local result = {} - local lines = vim.split(output, "\n") - for _, line in ipairs { unpack(lines, 2) } do - local type, id, value = unpack(vim.split(line, "%s+", { trimempty = true })) - if type and id then - result[type] = result[type] or {} - result[type][id] = value or "" - end - end - return result -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if vim.in_fast_event() then - a.scheduler() - end - -- trims e.g. golang.org/x/tools/gopls to gopls - local executable = vim.fn.fnamemodify(receipt.primary_source.package, ":t") - return spawn.go({ - "version", - "-m", - platform.is_win and ("%s.exe"):format(executable) or executable, - cwd = install_dir, - }):map_catching(function(result) - local parsed_output = M.parse_mod_version_output(result.stdout) - return Optional.of_nilable(parsed_output.mod[receipt.primary_source.package]):or_else_throw "Failed to parse mod version" - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - return spawn.go({ - "list", - "-json", - "-m", - ("%s@latest"):format(receipt.primary_source.package), - cwd = install_dir, - }):map_catching(function(result) - ---@type {Path: string, Version: string} - local output = vim.json.decode(result.stdout) - return Optional.of_nilable(output.Version) - :map(function(latest_version) - local installed_version = M.get_installed_primary_package_version(receipt, install_dir):get_or_throw() - if installed_version ~= latest_version then - return { - name = receipt.primary_source.package, - current_version = assert(installed_version), - latest_version = assert(latest_version), - } - end - end) - :or_else_throw "Primary package is not outdated." - end) -end - ----@param install_dir string -function M.env(install_dir) - return { - PATH = process.extend_path { install_dir }, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/luarocks/init.lua b/lua/nvim-lsp-installer/core/managers/luarocks/init.lua deleted file mode 100644 index e25c92a2..00000000 --- a/lua/nvim-lsp-installer/core/managers/luarocks/init.lua +++ /dev/null @@ -1,130 +0,0 @@ -local installer = require "nvim-lsp-installer.core.installer" -local _ = require "nvim-lsp-installer.core.functional" -local process = require "nvim-lsp-installer.core.process" -local path = require "nvim-lsp-installer.core.path" -local Result = require "nvim-lsp-installer.core.result" -local spawn = require "nvim-lsp-installer.core.spawn" -local Optional = require "nvim-lsp-installer.core.optional" - -local M = {} - ----@param package string -local function with_receipt(package) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.luarocks(package)) - end -end - ----@param package string @The luarock package to install. ----@param opts {dev: boolean}|nil -function M.package(package, opts) - return function() - return M.install(package, opts).with_receipt() - end -end - ----@async ----@param package string @The luarock package to install. ----@param opts {dev: boolean}|nil -function M.install(package, opts) - opts = opts or {} - local ctx = installer.context() - ctx:promote_cwd() - ctx.spawn.luarocks { - "install", - "--tree", - ctx.cwd:get(), - opts.dev and "--dev" or vim.NIL, - package, - ctx.requested_version:or_else(vim.NIL), - } - return { - with_receipt = with_receipt(package), - } -end - ----@alias InstalledLuarock {package: string, version: string, arch: string, nrepo: string, namespace: string} - ----@type fun(output: string): InstalledLuarock[] -M.parse_installed_rocks = _.compose( - _.map(_.compose( - -- https://github.com/luarocks/luarocks/blob/fbd3566a312e647cde57b5d774533731e1aa844d/src/luarocks/search.lua#L317 - _.zip_table { "package", "version", "arch", "nrepo", "namespace" }, - _.split "\t" - )), - _.split "\n" -) - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if receipt.primary_source.type ~= "luarocks" then - return Result.failure "Receipt does not have a primary source of type luarocks" - end - local primary_package = receipt.primary_source.package - return spawn.luarocks({ - "list", - "--tree", - install_dir, - "--porcelain", - }):map_catching(function(result) - local luarocks = M.parse_installed_rocks(result.stdout) - return Optional.of_nilable(_.find_first(_.prop_eq("package", primary_package), luarocks)) - :map(_.prop "version") - :or_else_throw() - end) -end - ----@alias OutdatedLuarock {name: string, installed: string, available: string, repo: string} - ----@type fun(output: string): OutdatedLuarock[] -M.parse_outdated_rocks = _.compose( - _.map(_.compose( - -- https://github.com/luarocks/luarocks/blob/fbd3566a312e647cde57b5d774533731e1aa844d/src/luarocks/cmd/list.lua#L59 - _.zip_table { "name", "installed", "available", "repo" }, - _.split "\t" - )), - _.split "\n" -) - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "luarocks" then - return Result.failure "Receipt does not have a primary source of type luarocks" - end - local primary_package = receipt.primary_source.package - return spawn.luarocks({ - "list", - "--outdated", - "--tree", - install_dir, - "--porcelain", - }):map_catching(function(result) - local outdated_rocks = M.parse_outdated_rocks(result.stdout) - return Optional.of_nilable(_.find_first(_.prop_eq("name", primary_package), outdated_rocks)) - :map( - ---@param outdated_rock OutdatedLuarock - function(outdated_rock) - return { - name = outdated_rock.name, - current_version = assert(outdated_rock.installed), - latest_version = assert(outdated_rock.available), - } - end - ) - :or_else_throw() - end) -end - ----@param install_dir string -function M.env(install_dir) - return { - PATH = process.extend_path { path.concat { install_dir, "bin" } }, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/npm/init.lua b/lua/nvim-lsp-installer/core/managers/npm/init.lua deleted file mode 100644 index ba3d670e..00000000 --- a/lua/nvim-lsp-installer/core/managers/npm/init.lua +++ /dev/null @@ -1,132 +0,0 @@ -local functional = require "nvim-lsp-installer.core.functional" -local spawn = require "nvim-lsp-installer.core.spawn" -local Optional = require "nvim-lsp-installer.core.optional" -local installer = require "nvim-lsp-installer.core.installer" -local Result = require "nvim-lsp-installer.core.result" -local process = require "nvim-lsp-installer.core.process" -local path = require "nvim-lsp-installer.core.path" - -local list_copy = functional.list_copy - -local M = {} - ----@async ----@param ctx InstallContext -local function ensure_npm_root(ctx) - if not (ctx.fs:dir_exists "node_modules" or ctx.fs:file_exists "package.json") then - -- Create a package.json to set a boundary for where npm installs packages. - ctx.spawn.npm { "init", "--yes", "--scope=lsp-installer" } - end -end - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.npm(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.npm(packages[i])) - end - end -end - ----@async ----@param packages string[] @The npm packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - return M.install(packages).with_receipt() - end -end - ----@async ----@param packages string[] @The npm packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.install(packages) - local ctx = installer.context() - local pkgs = list_copy(packages) - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s@%s"):format(pkgs[1], version) - end) - - -- Use global-style. The reasons for this are: - -- a) To avoid polluting the executables (aka bin-links) that npm creates. - -- b) The installation is, after all, more similar to a "global" installation. We don't really gain - -- any of the benefits of not using global style (e.g., deduping the dependency tree). - -- - -- We write to .npmrc manually instead of going through npm because managing a local .npmrc file - -- is a bit unreliable across npm versions (especially <7), so we take extra measures to avoid - -- inadvertently polluting global npm config. - ctx.fs:append_file(".npmrc", "global-style=true") - - ensure_npm_root(ctx) - ctx.spawn.npm { "install", pkgs } - - return { - with_receipt = with_receipt(packages), - } -end - ----@async ----@param exec_args string[] @The arguments to pass to npm exec. -function M.exec(exec_args) - local ctx = installer.context() - ctx.spawn.npm { "exec", "--yes", "--", exec_args } -end - ----@async ----@param script string @The npm script to run. -function M.run(script) - local ctx = installer.context() - ctx.spawn.npm { "run", script } -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if receipt.primary_source.type ~= "npm" then - return Result.failure "Receipt does not have a primary source of type npm" - end - return spawn.npm({ "ls", "--json", cwd = install_dir }):map_catching(function(result) - local npm_packages = vim.json.decode(result.stdout) - return npm_packages.dependencies[receipt.primary_source.package].version - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "npm" then - return Result.failure "Receipt does not have a primary source of type npm" - end - local primary_package = receipt.primary_source.package - local npm_outdated = spawn.npm { "outdated", "--json", primary_package, cwd = install_dir } - if npm_outdated:is_success() then - return Result.failure "Primary package is not outdated." - end - return npm_outdated:recover_catching(function(result) - assert(result.exit_code == 1, "Expected npm outdated to return exit code 1.") - local data = vim.json.decode(result.stdout) - - return Optional.of_nilable(data[primary_package]) - :map(function(outdated_package) - if outdated_package.current ~= outdated_package.latest then - return { - name = primary_package, - current_version = assert(outdated_package.current), - latest_version = assert(outdated_package.latest), - } - end - end) - :or_else_throw() - end) -end - ----@param install_dir string -function M.env(install_dir) - return { - PATH = process.extend_path { path.concat { install_dir, "node_modules", ".bin" } }, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/opam/init.lua b/lua/nvim-lsp-installer/core/managers/opam/init.lua deleted file mode 100644 index a4e1163e..00000000 --- a/lua/nvim-lsp-installer/core/managers/opam/init.lua +++ /dev/null @@ -1,58 +0,0 @@ -local functional = require "nvim-lsp-installer.core.functional" -local path = require "nvim-lsp-installer.core.path" -local process = require "nvim-lsp-installer.core.process" -local installer = require "nvim-lsp-installer.core.installer" - -local M = {} - -local list_copy = functional.list_copy - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.opam(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.opam(packages[i])) - end - end -end - ----@async ----@param packages string[] @The opam packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - return M.install(packages).with_receipt() - end -end - ----@async ----@param packages string[] @The opam packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.install(packages) - local ctx = installer.context() - local pkgs = list_copy(packages) - - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s.%s"):format(pkgs[1], version) - end) - - ctx.spawn.opam { - "install", - "--destdir=.", - "--yes", - "--verbose", - pkgs, - } - - return { - with_receipt = with_receipt(packages), - } -end - -function M.env(root_dir) - return { - PATH = process.extend_path { path.concat { root_dir, "bin" } }, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/pip3/init.lua b/lua/nvim-lsp-installer/core/managers/pip3/init.lua deleted file mode 100644 index 3b617745..00000000 --- a/lua/nvim-lsp-installer/core/managers/pip3/init.lua +++ /dev/null @@ -1,160 +0,0 @@ -local _ = require "nvim-lsp-installer.core.functional" -local settings = require "nvim-lsp-installer.settings" -local process = require "nvim-lsp-installer.core.process" -local path = require "nvim-lsp-installer.core.path" -local platform = require "nvim-lsp-installer.core.platform" -local Optional = require "nvim-lsp-installer.core.optional" -local installer = require "nvim-lsp-installer.core.installer" -local Result = require "nvim-lsp-installer.core.result" -local spawn = require "nvim-lsp-installer.core.spawn" - -local VENV_DIR = "venv" - -local M = {} - ----@param packages string[] -local function with_receipt(packages) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.pip3(packages[1])) - for i = 2, #packages do - ctx.receipt:with_secondary_source(ctx.receipt.pip3(packages[i])) - end - end -end - ----@async ----@param packages string[] @The pip packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.packages(packages) - return function() - return M.install(packages).with_receipt() - end -end - ----@async ----@param packages string[] @The pip packages to install. The first item in this list will be the recipient of the requested version, if set. -function M.install(packages) - local ctx = installer.context() - local pkgs = _.list_copy(packages) - - ctx.requested_version:if_present(function(version) - pkgs[1] = ("%s==%s"):format(pkgs[1], version) - end) - - local executables = platform.is_win and _.list_not_nil(vim.g.python3_host_prog, "python", "python3") - or _.list_not_nil(vim.g.python3_host_prog, "python3", "python") - - -- pip3 will hardcode the full path to venv executables, so we need to promote cwd to make sure pip uses the final destination path. - ctx:promote_cwd() - - -- Find first executable that manages to create venv - local executable = _.find_first(function(executable) - return pcall(ctx.spawn[executable], { "-m", "venv", VENV_DIR }) - end, executables) - - Optional.of_nilable(executable) - :if_present(function() - ctx.spawn.python { - "-m", - "pip", - "install", - "-U", - settings.current.pip.install_args, - pkgs, - with_paths = { M.venv_path(ctx.cwd:get()) }, - } - end) - :or_else_throw "Unable to create python3 venv environment." - - return { - with_receipt = with_receipt(packages), - } -end - ----@param package string ----@return string -function M.normalize_package(package) - -- https://stackoverflow.com/a/60307740 - local s = package:gsub("%[.*%]", "") - return s -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - if receipt.primary_source.type ~= "pip3" then - return Result.failure "Receipt does not have a primary source of type pip3" - end - local normalized_package = M.normalize_package(receipt.primary_source.package) - return spawn.python({ - "-m", - "pip", - "list", - "--outdated", - "--format=json", - cwd = install_dir, - with_paths = { M.venv_path(install_dir) }, - }):map_catching(function(result) - ---@alias PipOutdatedPackage {name: string, version: string, latest_version: string} - ---@type PipOutdatedPackage[] - local packages = vim.json.decode(result.stdout) - - local outdated_primary_package = _.find_first(function(outdated_package) - return outdated_package.name == normalized_package - and outdated_package.version ~= outdated_package.latest_version - end, packages) - - return Optional.of_nilable(outdated_primary_package) - :map(function(package) - return { - name = normalized_package, - current_version = assert(package.version), - latest_version = assert(package.latest_version), - } - end) - :or_else_throw "Primary package is not outdated." - end) -end - ----@async ----@param receipt InstallReceipt ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) - if receipt.primary_source.type ~= "pip3" then - return Result.failure "Receipt does not have a primary source of type pip3" - end - return spawn.python({ - "-m", - "pip", - "list", - "--format=json", - cwd = install_dir, - with_paths = { M.venv_path(install_dir) }, - }):map_catching(function(result) - local pip_packages = vim.json.decode(result.stdout) - local normalized_pip_package = M.normalize_package(receipt.primary_source.package) - local pip_package = _.find_first(function(package) - return package.name == normalized_pip_package - end, pip_packages) - return Optional.of_nilable(pip_package) - :map(function(package) - return package.version - end) - :or_else_throw "Unable to find pip package." - end) -end - ----@param install_dir string -function M.env(install_dir) - return { - PATH = process.extend_path { M.venv_path(install_dir) }, - } -end - ----@param install_dir string -function M.venv_path(install_dir) - return path.concat { install_dir, VENV_DIR, platform.is_win and "Scripts" or "bin" } -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/powershell/init.lua b/lua/nvim-lsp-installer/core/managers/powershell/init.lua deleted file mode 100644 index 8e94820a..00000000 --- a/lua/nvim-lsp-installer/core/managers/powershell/init.lua +++ /dev/null @@ -1,46 +0,0 @@ -local spawn = require "nvim-lsp-installer.core.spawn" -local process = require "nvim-lsp-installer.core.process" - -local M = {} - -local PWSHOPT = { - progress_preference = [[ $ProgressPreference = 'SilentlyContinue'; ]], -- https://stackoverflow.com/a/63301751 - security_protocol = [[ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ]], -} - ----@param script string ----@param opts JobSpawnOpts ----@param custom_spawn JobSpawn -function M.script(script, opts, custom_spawn) - opts = opts or {} - ---@type JobSpawn - local spawner = custom_spawn or spawn - return spawner.powershell(vim.tbl_extend("keep", { - "-NoProfile", - on_spawn = function(_, stdio) - local stdin = stdio[1] - stdin:write(PWSHOPT.progress_preference) - stdin:write(PWSHOPT.security_protocol) - stdin:write(script) - stdin:close() - end, - env_raw = process.graft_env(opts.env or {}, { "PSMODULEPATH" }), - }, opts)) -end - ----@param command string ----@param opts JobSpawnOpts ----@param custom_spawn JobSpawn -function M.command(command, opts, custom_spawn) - opts = opts or {} - ---@type JobSpawn - local spawner = custom_spawn or spawn - return spawner.powershell(vim.tbl_extend("keep", { - "-NoProfile", - "-Command", - PWSHOPT.progress_preference .. PWSHOPT.security_protocol .. command, - env_raw = process.graft_env(opts.env or {}, { "PSMODULEPATH" }), - }, opts)) -end - -return M diff --git a/lua/nvim-lsp-installer/core/managers/std/init.lua b/lua/nvim-lsp-installer/core/managers/std/init.lua deleted file mode 100644 index e46a1045..00000000 --- a/lua/nvim-lsp-installer/core/managers/std/init.lua +++ /dev/null @@ -1,187 +0,0 @@ -local a = require "nvim-lsp-installer.core.async" -local installer = require "nvim-lsp-installer.core.installer" -local fetch = require "nvim-lsp-installer.core.fetch" -local platform = require "nvim-lsp-installer.core.platform" -local powershell = require "nvim-lsp-installer.core.managers.powershell" -local path = require "nvim-lsp-installer.core.path" -local Result = require "nvim-lsp-installer.core.result" - -local M = {} - -local function with_system_executable_receipt(executable) - return function() - local ctx = installer.context() - ctx.receipt:with_primary_source(ctx.receipt.system(executable)) - end -end - ----@async ----@param executable string ----@param opts {help_url:string|nil} -function M.system_executable(executable, opts) - return function() - M.ensure_executable(executable, opts).with_receipt() - end -end - ----@async ----@param executable string ----@param opts {help_url:string|nil} -function M.ensure_executable(executable, opts) - local ctx = installer.context() - opts = opts or {} - if vim.in_fast_event() then - a.scheduler() - end - if vim.fn.executable(executable) ~= 1 then - ctx.stdio_sink.stderr(("%s was not found in path.\n"):format(executable)) - if opts.help_url then - ctx.stdio_sink.stderr(("See %s for installation instructions.\n"):format(opts.help_url)) - end - error("Installation failed: system executable was not found.", 0) - end - - return { - with_receipt = with_system_executable_receipt(executable), - } -end - ----@async ----@param url string ----@param out_file string -function M.download_file(url, out_file) - local ctx = installer.context() - ctx.stdio_sink.stdout(("Downloading file %q...\n"):format(url)) - fetch(url, { - out_file = path.concat { ctx.cwd:get(), out_file }, - }) - :map_err(function(err) - return ("Failed to download file %q.\n%s"):format(url, err) - end) - :get_or_throw() -end - ----@async ----@param file string ----@param dest string -function M.unzip(file, dest) - local ctx = installer.context() - platform.when { - unix = function() - ctx.spawn.unzip { "-d", dest, file } - end, - win = function() - powershell.command( - ("Microsoft.PowerShell.Archive\\Expand-Archive -Path %q -DestinationPath %q"):format(file, dest), - {}, - ctx.spawn - ) - end, - } - pcall(function() - -- make sure the .zip archive doesn't linger - ctx.fs:unlink(file) - end) -end - ----@param file string -local function win_extract(file) - local ctx = installer.context() - Result.run_catching(function() - ctx.spawn["7z"] { "x", "-y", "-r", file } - end) - :recover_catching(function() - ctx.spawn.peazip { "-ext2here", path.concat { ctx.cwd:get(), file } } -- peazip requires absolute paths - end) - :recover_catching(function() - ctx.spawn.wzunzip { file } - end) - :recover_catching(function() - ctx.spawn.winrar { "e", file } - end) - :get_or_throw(("Unable to unpack %s."):format(file)) -end - ----@async ----@param file string ----@param opts {strip_components:integer} -function M.untar(file, opts) - opts = opts or {} - local ctx = installer.context() - ctx.spawn.tar { - opts.strip_components and { "--strip-components", opts.strip_components } or vim.NIL, - "-xvf", - file, - } - pcall(function() - ctx.fs:unlink(file) - end) -end - ----@async ----@param file string ----@param opts {strip_components:integer} -function M.untarxz(file, opts) - opts = opts or {} - local ctx = installer.context() - platform.when { - unix = function() - M.untar(file, opts) - end, - win = function() - Result.run_catching(function() - win_extract(file) -- unpack .tar.xz to .tar - local uncompressed_tar = file:gsub(".xz$", "") - M.untar(uncompressed_tar, opts) - end):recover(function() - ctx.spawn.arc { - "unarchive", - opts.strip_components and { "--strip-components", opts.strip_components } or vim.NIL, - file, - } - pcall(function() - ctx.fs:unlink(file) - end) - end) - end, - } -end - ----@async ----@param file string -function M.gunzip(file) - platform.when { - unix = function() - local ctx = installer.context() - ctx.spawn.gzip { "-d", file } - end, - win = function() - win_extract(file) - end, - } -end - ----@async ----@param flags string @The chmod flag to apply. ----@param files string[] @A list of relative paths to apply the chmod on. -function M.chmod(flags, files) - if platform.is_unix then - local ctx = installer.context() - ctx.spawn.chmod { flags, files } - end -end - ----@async ----Wrapper around vim.ui.select. ----@param items table ----@params opts -function M.select(items, opts) - assert(not platform.is_headless, "Tried to prompt for user input while in headless mode.") - if vim.in_fast_event() then - a.scheduler() - end - local async_select = a.promisify(vim.ui.select) - return async_select(items, opts) -end - -return M diff --git a/lua/nvim-lsp-installer/core/optional.lua b/lua/nvim-lsp-installer/core/optional.lua deleted file mode 100644 index 10af8ccb..00000000 --- a/lua/nvim-lsp-installer/core/optional.lua +++ /dev/null @@ -1,100 +0,0 @@ ----@class Optional<T> ----@field private _value unknown -local Optional = {} -Optional.__index = Optional - ----@param value any -function Optional.new(value) - return setmetatable({ _value = value }, Optional) -end - -local EMPTY = Optional.new(nil) - ----@param value any -function Optional.of_nilable(value) - if value == nil then - return EMPTY - else - return Optional.new(value) - end -end - -function Optional.empty() - return EMPTY -end - ----@param value any -function Optional.of(value) - return Optional.new(value) -end - ----@param mapper_fn fun(value: any): any -function Optional:map(mapper_fn) - if self:is_present() then - return Optional.of_nilable(mapper_fn(self._value)) - else - return EMPTY - end -end - -function Optional:get() - if not self:is_present() then - error("No value present.", 2) - end - return self._value -end - ----@param value any -function Optional:or_else(value) - if self:is_present() then - return self._value - else - return value - end -end - ----@param supplier fun(): any -function Optional:or_else_get(supplier) - if self:is_present() then - return self._value - else - return supplier() - end -end - ----@param supplier fun(): Optional ----@return Optional -function Optional:or_(supplier) - if self:is_present() then - return self - else - return supplier() - end -end - ----@param exception any @(optional) The exception to throw if the result is a failure. -function Optional:or_else_throw(exception) - if self:is_present() then - return self._value - else - if exception then - error(exception, 2) - else - error("No value present.", 2) - end - end -end - ----@param fn fun(value: any) -function Optional:if_present(fn) - if self:is_present() then - fn(self._value) - end - return self -end - -function Optional:is_present() - return self._value ~= nil -end - -return Optional diff --git a/lua/nvim-lsp-installer/core/path.lua b/lua/nvim-lsp-installer/core/path.lua deleted file mode 100644 index 2f0fd84b..00000000 --- a/lua/nvim-lsp-installer/core/path.lua +++ /dev/null @@ -1,36 +0,0 @@ -local uv = vim.loop - -local sep = (function() - ---@diagnostic disable-next-line: undefined-global - if jit then - ---@diagnostic disable-next-line: undefined-global - local os = string.lower(jit.os) - if os == "linux" or os == "osx" or os == "bsd" then - return "/" - else - return "\\" - end - else - return package.config:sub(1, 1) - end -end)() - -local M = {} - -function M.cwd() - return uv.fs_realpath "." -end - ----@param path_components string[] ----@return string -function M.concat(path_components) - return table.concat(path_components, sep) -end - ----@path root_path string ----@path path string -function M.is_subdirectory(root_path, path) - return root_path == path or path:sub(1, #root_path + 1) == root_path .. sep -end - -return M diff --git a/lua/nvim-lsp-installer/core/platform.lua b/lua/nvim-lsp-installer/core/platform.lua deleted file mode 100644 index 10427a63..00000000 --- a/lua/nvim-lsp-installer/core/platform.lua +++ /dev/null @@ -1,154 +0,0 @@ -local _ = require "nvim-lsp-installer.core.functional" -local Result = require "nvim-lsp-installer.core.result" -local M = {} - -local uname = vim.loop.os_uname() - ----@alias Platform ----| '"win"' ----| '"unix"' ----| '"linux"' ----| '"mac"' - -local arch_aliases = { - ["x86_64"] = "x64", - ["i386"] = "x86", - ["i686"] = "x86", -- x86 compat - ["aarch64"] = "arm64", - ["aarch64_be"] = "arm64", - ["armv8b"] = "arm64", -- arm64 compat - ["armv8l"] = "arm64", -- arm64 compat -} - -M.arch = arch_aliases[uname.machine] or uname.machine - -M.is_win = vim.fn.has "win32" == 1 -M.is_unix = vim.fn.has "unix" == 1 -M.is_mac = vim.fn.has "mac" == 1 -M.is_linux = not M.is_mac and M.is_unix - --- PATH separator -M.path_sep = M.is_win and ";" or ":" - -M.is_headless = #vim.api.nvim_list_uis() == 0 - ----@generic T ----@param platform_table table<Platform, T> ----@return T -local function get_by_platform(platform_table) - if M.is_mac then - return platform_table.mac or platform_table.unix - elseif M.is_linux then - return platform_table.linux or platform_table.unix - elseif M.is_unix then - return platform_table.unix - elseif M.is_win then - return platform_table.win - else - return nil - end -end - -function M.when(cases) - local case = get_by_platform(cases) - if case then - return case() - else - error "Current platform is not supported." - end -end - ----@type async fun(): table -M.os_distribution = _.lazy(function() - ---Parses the provided contents of an /etc/\*-release file and identifies the Linux distribution. - ---@param contents string @The contents of a /etc/\*-release file. - ---@return table<string, any> - local function parse_linux_dist(contents) - local lines = vim.split(contents, "\n") - - local entries = {} - - for i = 1, #lines do - local line = lines[i] - local index = line:find "=" - if index then - local key = line:sub(1, index - 1) - local value = line:sub(index + 1) - entries[key] = value - end - end - - if entries.ID == "ubuntu" then - -- Parses the Ubuntu OS VERSION_ID into their version components, e.g. "18.04" -> {major=18, minor=04} - local version_id = entries.VERSION_ID:gsub([["]], "") - local version_parts = vim.split(version_id, "%.") - local major = tonumber(version_parts[1]) - local minor = tonumber(version_parts[2]) - - return { - id = "ubuntu", - version_id = version_id, - version = { major = major, minor = minor }, - } - else - return { - id = "linux-generic", - } - end - end - - return M.when { - linux = function() - local spawn = require "nvim-lsp-installer.core.spawn" - return spawn.bash({ "-c", "cat /etc/*-release" }) - :map_catching(function(result) - return parse_linux_dist(result.stdout) - end) - :recover(function() - return { id = "linux-generic" } - end) - :get_or_throw() - end, - mac = function() - return Result.success { id = "macOS" } - end, - win = function() - return Result.success { id = "windows" } - end, - } -end) - ----@type async fun() Result @of String -M.get_homebrew_prefix = _.lazy(function() - assert(M.is_mac, "Can only locate Homebrew installation on Mac systems.") - local spawn = require "nvim-lsp-installer.core.spawn" - return spawn.brew({ "--prefix" }) - :map_catching(function(result) - return vim.trim(result.stdout) - end) - :map_err(function() - return "Failed to locate Homebrew installation." - end) -end) - --- @return string @The libc found on the system, musl or glibc (glibc if ldd is not found) -M.get_libc = _.lazy(function() - local _, _, libc_exit_code = os.execute "ldd --version 2>&1 | grep -q musl" - if libc_exit_code == 0 then - return "musl" - else - return "glibc" - end -end) - -M.is = setmetatable({}, { - __index = function(_, key) - local platform, arch = unpack(vim.split(key, "_", { plain = true })) - if arch and M.arch ~= arch then - return false - end - return M["is_" .. platform] == true - end, -}) - -return M diff --git a/lua/nvim-lsp-installer/core/process.lua b/lua/nvim-lsp-installer/core/process.lua deleted file mode 100644 index 9841c11c..00000000 --- a/lua/nvim-lsp-installer/core/process.lua +++ /dev/null @@ -1,314 +0,0 @@ -local log = require "nvim-lsp-installer.log" -local _ = require "nvim-lsp-installer.core.functional" -local platform = require "nvim-lsp-installer.core.platform" -local uv = vim.loop - ----@alias luv_pipe any ----@alias luv_handle any - ----@class StdioSink ----@field stdout fun(chunk: string) ----@field stderr fun(chunk: string) - -local M = {} - ----@param pipe luv_pipe ----@param sink fun(chunk: string) -local function connect_sink(pipe, sink) - ---@param err string | nil - ---@param data string | nil - return function(err, data) - if err then - log.error("Unexpected error when reading pipe.", err) - end - if data ~= nil then - sink(data) - else - pipe:read_stop() - pipe:close() - end - end -end - --- We gather the root env immediately, primarily because of E5560. --- Also, there's no particular reason we need to refresh the environment (yet). -local initial_environ = vim.fn.environ() - ----@param new_paths string[] @A list of paths to prepend the existing PATH with. -function M.extend_path(new_paths) - local new_path_str = table.concat(new_paths, platform.path_sep) - return ("%s%s%s"):format(new_path_str, platform.path_sep, initial_environ.PATH or "") -end - ----Merges the provided env param with the user's full environent. Provided env has precedence. ----@param env table<string, string> ----@param excluded_var_names string[]|nil -function M.graft_env(env, excluded_var_names) - local excluded_var_names_set = excluded_var_names and _.set_of(excluded_var_names) or {} - local merged_env = {} - for key, val in pairs(initial_environ) do - if not excluded_var_names_set[key] and env[key] == nil then - merged_env[#merged_env + 1] = key .. "=" .. val - end - end - for key, val in pairs(env) do - if not excluded_var_names_set[key] then - merged_env[#merged_env + 1] = key .. "=" .. val - end - end - return merged_env -end - ----@param env_list string[] -local function sanitize_env_list(env_list) - local sanitized_list = {} - for __, env in ipairs(env_list) do - local safe_envs = { - "GO111MODULE", - "GOBIN", - "GOPATH", - "PATH", - "GEM_HOME", - "GEM_PATH", - } - local is_safe_env = _.any(function(safe_env) - return env:find(safe_env .. "=") == 1 - end, safe_envs) - if is_safe_env then - sanitized_list[#sanitized_list + 1] = env - else - local idx = env:find "=" - sanitized_list[#sanitized_list + 1] = env:sub(1, idx) .. "<redacted>" - end - end - return sanitized_list -end - ----@alias JobSpawnCallback fun(success: boolean, exit_code: integer) - ----@class JobSpawnOpts ----@field env string[] @List of "key=value" string. ----@field args string[] ----@field cwd string ----@field stdio_sink StdioSink - ----@param cmd string @The command/executable. ----@param opts JobSpawnOpts ----@param callback JobSpawnCallback ----@return luv_handle,luv_pipe[]|nil @Returns the job handle and the stdio array on success, otherwise returns nil. -function M.spawn(cmd, opts, callback) - local stdin = uv.new_pipe(false) - local stdout = uv.new_pipe(false) - local stderr = uv.new_pipe(false) - - local stdio = { stdin, stdout, stderr } - - local spawn_opts = { - env = opts.env, - stdio = stdio, - args = opts.args, - cwd = opts.cwd, - detached = false, - hide = true, - } - - log.lazy_debug(function() - local sanitized_env = opts.env and sanitize_env_list(opts.env) or nil - return "Spawning cmd=%s, spawn_opts=%s", - cmd, - { - args = opts.args, - cwd = opts.cwd, - env = sanitized_env, - } - end) - - local handle, pid_or_err - handle, pid_or_err = uv.spawn(cmd, spawn_opts, function(exit_code, signal) - local successful = exit_code == 0 and signal == 0 - handle:close() - if not stdin:is_closing() then - stdin:close() - end - - -- ensure all pipes are closed, for I am a qualified plumber - local check = uv.new_check() - check:start(function() - for i = 1, #stdio do - local pipe = stdio[i] - if not pipe:is_closing() then - return - end - end - check:stop() - callback(successful, exit_code) - end) - - log.fmt_debug("Job pid=%s exited with exit_code=%s, signal=%s", pid_or_err, exit_code, signal) - end) - - if handle == nil then - log.fmt_error("Failed to spawn process. cmd=%s, err=%s", cmd, pid_or_err) - if type(pid_or_err) == "string" and pid_or_err:find "ENOENT" == 1 then - opts.stdio_sink.stderr(("Could not find executable %q in path.\n"):format(cmd)) - else - opts.stdio_sink.stderr(("Failed to spawn process cmd=%s err=%s\n"):format(cmd, pid_or_err)) - end - callback(false) - return nil, nil - end - - log.debug("Spawned with pid", pid_or_err) - - stdout:read_start(connect_sink(stdout, opts.stdio_sink.stdout)) - stderr:read_start(connect_sink(stderr, opts.stdio_sink.stderr)) - - return handle, stdio -end - ----@param opts JobSpawnOpts @The job spawn opts to apply in every job in this "chain". -function M.chain(opts) - local jobs = {} - return { - ---@param cmd string - ---@param args string[] - run = function(cmd, args) - jobs[#jobs + 1] = M.lazy_spawn( - cmd, - vim.tbl_deep_extend("force", opts, { - args = args, - }) - ) - end, - ---@param callback JobSpawnCallback - spawn = function(callback) - local function execute(idx) - local ok, err = pcall(jobs[idx], function(successful) - if successful and jobs[idx + 1] then - -- iterate - execute(idx + 1) - else - -- we done - callback(successful) - end - end) - if not ok then - log.fmt_error("Chained job failed to execute. Error=%s", tostring(err)) - callback(false) - end - end - - execute(1) - end, - } -end - -function M.empty_sink() - local function noop() end - return { - stdout = noop, - stderr = noop, - } -end - -function M.simple_sink() - return { - stdout = vim.schedule_wrap(vim.api.nvim_out_write), - stderr = vim.schedule_wrap(vim.api.nvim_err_write), - } -end - -function M.in_memory_sink() - local buffers = { stdout = {}, stderr = {} } - return { - buffers = buffers, - sink = { - stdout = function(chunk) - buffers.stdout[#buffers.stdout + 1] = chunk - end, - stderr = function(chunk) - buffers.stderr[#buffers.stderr + 1] = chunk - end, - }, - } -end - ---- This probably belongs elsewhere ¯\_(ツ)_/¯ ----@generic T ----@param debounced_fn fun(arg1: T) ----@return fun(arg1: T) -function M.debounced(debounced_fn) - local queued = false - local last_arg = nil - return function(a) - last_arg = a - if queued then - return - end - queued = true - vim.schedule(function() - debounced_fn(last_arg) - queued = false - last_arg = nil - end) - end -end - ----@alias LazyJob fun(callback: JobSpawnCallback) - ----@param cmd string ----@param opts JobSpawnOpts -function M.lazy_spawn(cmd, opts) - ---@param callback JobSpawnCallback - return function(callback) - return M.spawn(cmd, opts, callback) - end -end - ----@class JobAttemptOpts ----@field jobs LazyJob[] ----@field on_finish JobSpawnCallback ----@field on_iterate fun() - ----@param opts JobAttemptOpts -function M.attempt(opts) - local jobs, on_finish, on_iterate = opts.jobs, opts.on_finish, opts.on_iterate - if #jobs == 0 then - error "process.attempt(...) needs at least one job." - end - - local spawn, on_job_exit - - on_job_exit = function(cur_idx, success) - if success then - -- this job succeeded. exit early - on_finish(true) - elseif jobs[cur_idx + 1] then - -- iterate - if on_iterate then - on_iterate() - end - log.debug "Previous job failed, attempting next." - spawn(cur_idx + 1) - else - -- we exhausted all jobs without success - log.debug "All jobs failed." - on_finish(false) - end - end - - spawn = function(idx) - local ok, err = pcall(jobs[idx], function(success) - on_job_exit(idx, success) - end) - if not ok then - log.fmt_error("Job failed to execute. Error=%s", tostring(err)) - on_job_exit(idx, false) - on_finish(false) - end - end - - spawn(1) -end - -return M diff --git a/lua/nvim-lsp-installer/core/receipt.lua b/lua/nvim-lsp-installer/core/receipt.lua deleted file mode 100644 index 608673a6..00000000 --- a/lua/nvim-lsp-installer/core/receipt.lua +++ /dev/null @@ -1,170 +0,0 @@ -local M = {} - ----@alias InstallReceiptSchemaVersion ----| '"1.0"' ----| '"1.0a"' - ----@alias InstallReceiptSourceType ----| '"npm"' ----| '"pip3"' ----| '"gem"' ----| '"go"' ----| '"cargo"' ----| '"opam"' ----| '"dotnet"' ----| '"r_package"' ----| '"unmanaged"' ----| '"system"' ----| '"jdtls"' ----| '"git"' ----| '"github_tag"' ----| '"github_release"' ----| '"github_release_file"' - ----@alias InstallReceiptSource {type: InstallReceiptSourceType} - ----@class InstallReceiptBuilder ----@field public is_marked_invalid boolean Whether this instance of the builder has been marked as invalid. This is an exception that only apply to a few select servers whose installation is not yet compatible with the receipt schema due to having a too complicated installation structure. ----@field private secondary_sources InstallReceiptSource[] ----@field private epoch_time number -local InstallReceiptBuilder = {} -InstallReceiptBuilder.__index = InstallReceiptBuilder - -function InstallReceiptBuilder.new() - return setmetatable({ - is_marked_invalid = false, - secondary_sources = {}, - }, InstallReceiptBuilder) -end - -function InstallReceiptBuilder:mark_invalid() - self.is_marked_invalid = true - return self -end - ----@param name string -function InstallReceiptBuilder:with_name(name) - self.name = name - return self -end - ----@param version InstallReceiptSchemaVersion -function InstallReceiptBuilder:with_schema_version(version) - self.schema_version = version - return self -end - ----@param source InstallReceiptSource -function InstallReceiptBuilder:with_primary_source(source) - self.primary_source = source - return self -end - ----@param source InstallReceiptSource -function InstallReceiptBuilder:with_secondary_source(source) - table.insert(self.secondary_sources, source) - return self -end - ----@param seconds integer ----@param microseconds integer -local function to_ms(seconds, microseconds) - return (seconds * 1000) + math.floor(microseconds / 1000) -end - ----vim.loop.gettimeofday() ----@param seconds integer ----@param microseconds integer -function InstallReceiptBuilder:with_completion_time(seconds, microseconds) - self.completion_time = to_ms(seconds, microseconds) - return self -end - ----vim.loop.gettimeofday() ----@param seconds integer ----@param microseconds integer -function InstallReceiptBuilder:with_start_time(seconds, microseconds) - self.start_time = to_ms(seconds, microseconds) - return self -end - -function InstallReceiptBuilder:build() - assert(self.name, "name is required") - assert(self.schema_version, "schema_version is required") - assert(self.start_time, "start_time is required") - assert(self.completion_time, "completion_time is required") - assert(self.primary_source, "primary_source is required") - return { - name = self.name, - schema_version = self.schema_version, - metrics = { - start_time = self.start_time, - completion_time = self.completion_time, - }, - primary_source = self.primary_source, - secondary_sources = self.secondary_sources, - } -end - ----@param type InstallReceiptSourceType -local function package_source(type) - ---@param package string - return function(package) - return { type = type, package = package } - end -end - -InstallReceiptBuilder.npm = package_source "npm" -InstallReceiptBuilder.pip3 = package_source "pip3" -InstallReceiptBuilder.gem = package_source "gem" -InstallReceiptBuilder.go = package_source "go" -InstallReceiptBuilder.dotnet = package_source "dotnet" -InstallReceiptBuilder.cargo = package_source "cargo" -InstallReceiptBuilder.composer = package_source "composer" -InstallReceiptBuilder.r_package = package_source "r_package" -InstallReceiptBuilder.opam = package_source "opam" -InstallReceiptBuilder.luarocks = package_source "luarocks" - -InstallReceiptBuilder.unmanaged = { type = "unmanaged" } - ----@param repo string ----@param release string -function InstallReceiptBuilder.github_release(repo, release) - return { - type = "github_release", - repo = repo, - release = release, - } -end - ----@param dependency string -function InstallReceiptBuilder.system(dependency) - return { type = "system", dependency = dependency } -end - ----@param remote_url string -function InstallReceiptBuilder.git_remote(remote_url) - return { type = "git", remote = remote_url } -end - ----@class InstallReceipt ----@field public name string ----@field public schema_version InstallReceiptSchemaVersion ----@field public metrics {start_time:integer, completion_time:integer} ----@field public primary_source InstallReceiptSource ----@field public secondary_sources InstallReceiptSource[] -local InstallReceipt = {} -InstallReceipt.__index = InstallReceipt - -function InstallReceipt.new(props) - return setmetatable(props, InstallReceipt) -end - -function InstallReceipt.from_json(json) - return InstallReceipt.new(json) -end - -M.InstallReceiptBuilder = InstallReceiptBuilder -M.InstallReceipt = InstallReceipt - -return M diff --git a/lua/nvim-lsp-installer/core/result.lua b/lua/nvim-lsp-installer/core/result.lua deleted file mode 100644 index 132e2758..00000000 --- a/lua/nvim-lsp-installer/core/result.lua +++ /dev/null @@ -1,152 +0,0 @@ ----@class Failure ----@field error any -local Failure = {} -Failure.__index = Failure - -function Failure.new(error) - return setmetatable({ error = error }, Failure) -end - ----@class Result ----@field value any -local Result = {} -Result.__index = Result - -function Result.new(value) - return setmetatable({ - value = value, - }, Result) -end - -function Result.success(value) - return Result.new(value) -end - -function Result.failure(error) - return Result.new(Failure.new(error)) -end - -function Result:get_or_nil() - if self:is_success() then - return self.value - end -end - -function Result:get_or_else(value) - if self:is_success() then - return self.value - else - return value - end -end - ----@param exception any @(optional) The exception to throw if the result is a failure. -function Result:get_or_throw(exception) - if self:is_success() then - return self.value - else - if exception ~= nil then - error(exception, 2) - else - error(self.value.error, 2) - end - end -end - -function Result:err_or_nil() - if self:is_failure() then - return self.value.error - end -end - -function Result:is_failure() - return getmetatable(self.value) == Failure -end - -function Result:is_success() - return getmetatable(self.value) ~= Failure -end - ----@param mapper_fn fun(value: any): any -function Result:map(mapper_fn) - if self:is_success() then - return Result.success(mapper_fn(self.value)) - else - return self - end -end - ----@param mapper_fn fun(value: any): any -function Result:map_err(mapper_fn) - if self:is_failure() then - return Result.failure(mapper_fn(self.value.error)) - else - return self - end -end - ----@param mapper_fn fun(value: any): any -function Result:map_catching(mapper_fn) - if self:is_success() then - local ok, result = pcall(mapper_fn, self.value) - if ok then - return Result.success(result) - else - return Result.failure(result) - end - else - return self - end -end - ----@param recover_fn fun(value: any): any -function Result:recover(recover_fn) - if self:is_failure() then - return Result.success(recover_fn(self:err_or_nil())) - else - return self - end -end - ----@param recover_fn fun(value: any): any -function Result:recover_catching(recover_fn) - if self:is_failure() then - local ok, value = pcall(recover_fn, self:err_or_nil()) - if ok then - return Result.success(value) - else - return Result.failure(value) - end - else - return self - end -end - ----@param fn fun(value: any): any -function Result:on_failure(fn) - if self:is_failure() then - fn(self.value.error) - end - return self -end - ----@param fn fun(value: any): any -function Result:on_success(fn) - if self:is_success() then - fn(self.value) - end - return self -end - ----@param fn fun(): any ----@return Result -function Result.run_catching(fn) - local ok, result = pcall(fn) - if ok then - return Result.success(result) - else - return Result.failure(result) - end -end - -return Result diff --git a/lua/nvim-lsp-installer/core/spawn.lua b/lua/nvim-lsp-installer/core/spawn.lua deleted file mode 100644 index 13e05a5b..00000000 --- a/lua/nvim-lsp-installer/core/spawn.lua +++ /dev/null @@ -1,121 +0,0 @@ -local a = require "nvim-lsp-installer.core.async" -local Result = require "nvim-lsp-installer.core.result" -local process = require "nvim-lsp-installer.core.process" -local platform = require "nvim-lsp-installer.core.platform" -local functional = require "nvim-lsp-installer.core.functional" -local log = require "nvim-lsp-installer.log" - ----@alias JobSpawn table<string, async fun(opts: JobSpawnOpts): Result> ----@type JobSpawn -local spawn = { - _aliases = { - npm = platform.is_win and "npm.cmd" or "npm", - gem = platform.is_win and "gem.cmd" or "gem", - composer = platform.is_win and "composer.bat" or "composer", - gradlew = platform.is_win and "gradlew.bat" or "gradlew", - -- for hererocks installations - luarocks = (platform.is_win and vim.fn.executable "luarocks.bat" == 1) and "luarocks.bat" or "luarocks", - }, -} - -local function Failure(err, cmd) - return Result.failure(setmetatable(err, { - __tostring = function() - if err.exit_code ~= nil then - return ("spawn: %s failed with exit code %d. %s"):format(cmd, err.exit_code, err.stderr or "") - else - return ("spawn: %s failed with no exit code. %s"):format(cmd, err.stderr or "") - end - end, - })) -end - -local function parse_args(args, dest) - for _, arg in ipairs(args) do - if type(arg) == "table" then - parse_args(arg, dest) - elseif arg ~= vim.NIL then - dest[#dest + 1] = arg - end - end - return dest -end - -local is_executable = functional.memoize(function(cmd) - if vim.in_fast_event() then - a.scheduler() - end - return vim.fn.executable(cmd) == 1 -end, functional.identity) - ----@class SpawnArgs ----@field with_paths string[] @Optional. Paths to add to the PATH environment variable. ----@field env table<string, string> @Optional. Example { SOME_ENV = "value", SOME_OTHER_ENV = "some_value" } ----@field env_raw string[] @Optional. Example: { "SOME_ENV=value", "SOME_OTHER_ENV=some_value" } ----@field stdio_sink StdioSink @Optional. If provided, will be used to write to stdout and stderr. ----@field cwd string @Optional ----@field on_spawn fun(handle: luv_handle, stdio: luv_pipe[]) @Optional. Will be called when the process successfully spawns. ----@field check_executable boolean @Optional. Whether to check if the provided command is executable (defaults to true). - -setmetatable(spawn, { - ---@param normalized_cmd string - __index = function(self, normalized_cmd) - ---@param args SpawnArgs - return function(args) - local cmd_args = {} - parse_args(args, cmd_args) - - local env = args.env - - if args.with_paths then - env = env or {} - env.PATH = process.extend_path(args.with_paths) - end - - ---@type JobSpawnOpts - local spawn_args = { - stdio_sink = args.stdio_sink, - cwd = args.cwd, - env = env and process.graft_env(env) or args.env_raw, - args = cmd_args, - } - - local stdio - if not spawn_args.stdio_sink then - stdio = process.in_memory_sink() - spawn_args.stdio_sink = stdio.sink - end - - local cmd = self._aliases[normalized_cmd] or normalized_cmd - - if (env and env.PATH) == nil and args.check_executable ~= false and not is_executable(cmd) then - log.fmt_debug("%s is not executable", cmd) - return Failure({ - stderr = ("%s is not executable"):format(cmd), - }, cmd) - end - - local _, exit_code = a.wait(function(resolve) - local handle, stdio = process.spawn(cmd, spawn_args, resolve) - if args.on_spawn and handle and stdio then - args.on_spawn(handle, stdio) - end - end) - - if exit_code == 0 then - return Result.success { - stdout = stdio and table.concat(stdio.buffers.stdout, "") or nil, - stderr = stdio and table.concat(stdio.buffers.stderr, "") or nil, - } - else - return Failure({ - exit_code = exit_code, - stdout = stdio and table.concat(stdio.buffers.stdout, "") or nil, - stderr = stdio and table.concat(stdio.buffers.stderr, "") or nil, - }, cmd) - end - end - end, -}) - -return spawn diff --git a/lua/nvim-lsp-installer/core/ui/display.lua b/lua/nvim-lsp-installer/core/ui/display.lua deleted file mode 100644 index 5e4618f5..00000000 --- a/lua/nvim-lsp-installer/core/ui/display.lua +++ /dev/null @@ -1,458 +0,0 @@ -local log = require "nvim-lsp-installer.log" -local process = require "nvim-lsp-installer.core.process" -local state = require "nvim-lsp-installer.core.ui.state" - -local M = {} - ----@param line string ----@param render_context RenderContext -local function get_styles(line, render_context) - local indentation = 0 - - for i = 1, #render_context.applied_block_styles do - local styles = render_context.applied_block_styles[i] - for j = 1, #styles do - local style = styles[j] - if style == "INDENT" then - indentation = indentation + 2 - elseif style == "CENTERED" then - local padding = math.floor((render_context.viewport_context.win_width - #line) / 2) - indentation = math.max(0, padding) -- CENTERED overrides any already applied indentation - end - end - end - - return { - indentation = indentation, - } -end - ----@param viewport_context ViewportContext ----@param node INode ----@param _render_context RenderContext|nil ----@param _output RenderOutput|nil -local function render_node(viewport_context, node, _render_context, _output) - ---@class RenderContext - ---@field viewport_context ViewportContext - ---@field applied_block_styles CascadingStyle[] - local render_context = _render_context - or { - viewport_context = viewport_context, - applied_block_styles = {}, - } - ---@class RenderHighlight - ---@field hl_group string - ---@field line number - ---@field col_start number - ---@field col_end number - - ---@class RenderKeybind - ---@field line number - ---@field key string - ---@field effect string - ---@field payload any - - ---@class RenderDiagnostic - ---@field line number - ---@field diagnostic {message: string, severity: integer, source: string|nil} - - ---@class RenderOutput - ---@field lines string[] @The buffer lines. - ---@field virt_texts string[][] @List of (text, highlight) tuples. - ---@field highlights RenderHighlight[] - ---@field keybinds RenderKeybind[] - ---@field diagnostics RenderDiagnostic[] - local output = _output - or { - lines = {}, - virt_texts = {}, - highlights = {}, - keybinds = {}, - diagnostics = {}, - } - - if node.type == "VIRTUAL_TEXT" then - output.virt_texts[#output.virt_texts + 1] = { - line = #output.lines - 1, - content = node.virt_text, - } - elseif node.type == "HL_TEXT" then - for i = 1, #node.lines do - local line = node.lines[i] - local line_highlights = {} - local full_line = "" - for j = 1, #line do - local span = line[j] - local content, hl_group = span[1], span[2] - local col_start = #full_line - full_line = full_line .. content - if hl_group ~= "" then - line_highlights[#line_highlights + 1] = { - hl_group = hl_group, - line = #output.lines, - col_start = col_start, - col_end = col_start + #content, - } - end - end - - local active_styles = get_styles(full_line, render_context) - - -- apply indentation - full_line = (" "):rep(active_styles.indentation) .. full_line - for j = 1, #line_highlights do - local highlight = line_highlights[j] - highlight.col_start = highlight.col_start + active_styles.indentation - highlight.col_end = highlight.col_end + active_styles.indentation - output.highlights[#output.highlights + 1] = highlight - end - - output.lines[#output.lines + 1] = full_line - end - elseif node.type == "NODE" or node.type == "CASCADING_STYLE" then - if node.type == "CASCADING_STYLE" then - render_context.applied_block_styles[#render_context.applied_block_styles + 1] = node.styles - end - for i = 1, #node.children do - render_node(viewport_context, node.children[i], render_context, output) - end - if node.type == "CASCADING_STYLE" then - render_context.applied_block_styles[#render_context.applied_block_styles] = nil - end - elseif node.type == "KEYBIND_HANDLER" then - output.keybinds[#output.keybinds + 1] = { - line = node.is_global and -1 or #output.lines, - key = node.key, - effect = node.effect, - payload = node.payload, - } - elseif node.type == "DIAGNOSTICS" then - output.diagnostics[#output.diagnostics + 1] = { - line = #output.lines, - message = node.diagnostic.message, - severity = node.diagnostic.severity, - source = node.diagnostic.source, - } - end - - return output -end - --- exported for tests -M._render_node = render_node - ----@param opts DisplayOpenOpts ----@param sizes_only boolean @Whether to only return properties that control the window size. -local function create_popup_window_opts(opts, sizes_only) - local win_height = vim.o.lines - vim.o.cmdheight - 2 -- Add margin for status and buffer line - local win_width = vim.o.columns - local height = math.floor(win_height * 0.9) - local width = math.floor(win_width * 0.8) - local popup_layout = { - height = height, - width = width, - row = math.floor((win_height - height) / 2), - col = math.floor((win_width - width) / 2), - relative = "editor", - style = "minimal", - zindex = 1, - } - - if not sizes_only then - popup_layout.border = opts.border - end - - return popup_layout -end - -function M.new_view_only_win(name) - local namespace = vim.api.nvim_create_namespace(("lsp_installer_%s"):format(name)) - local bufnr, renderer, mutate_state, get_state, unsubscribe, win_id, window_mgmt_augroup, autoclose_augroup, registered_keymaps, registered_keybinds, registered_effect_handlers - local has_initiated = false - - local function delete_win_buf() - -- We queue the win_buf to be deleted in a schedule call, otherwise when used with folke/which-key (and - -- set timeoutlen=0) we run into a weird segfault. - -- It should probably be unnecessary once https://github.com/neovim/neovim/issues/15548 is resolved - vim.schedule(function() - if win_id and vim.api.nvim_win_is_valid(win_id) then - log.trace "Deleting window" - vim.api.nvim_win_close(win_id, true) - end - if bufnr and vim.api.nvim_buf_is_valid(bufnr) then - log.trace "Deleting buffer" - vim.api.nvim_buf_delete(bufnr, { force = true }) - end - end) - end - - ---@param line number - ---@param key string - local function call_effect_handler(line, key) - local line_keybinds = registered_keybinds[line] - if line_keybinds then - local keybind = line_keybinds[key] - if keybind then - local effect_handler = registered_effect_handlers[keybind.effect] - if effect_handler then - log.fmt_trace("Calling handler for effect %s on line %d for key %s", keybind.effect, line, key) - effect_handler { payload = keybind.payload } - return true - end - end - end - return false - end - - local function dispatch_effect(key) - local line = vim.api.nvim_win_get_cursor(0)[1] - log.fmt_trace("Dispatching effect on line %d, key %s, bufnr %s", line, key, bufnr) - call_effect_handler(line, key) -- line keybinds - call_effect_handler(-1, key) -- global keybinds - end - - local draw = function(view) - local win_valid = win_id ~= nil and vim.api.nvim_win_is_valid(win_id) - local buf_valid = bufnr ~= nil and vim.api.nvim_buf_is_valid(bufnr) - log.fmt_trace("got bufnr=%s", bufnr) - log.fmt_trace("got win_id=%s", win_id) - - if not win_valid or not buf_valid then - -- the window has been closed or the buffer is somehow no longer valid - unsubscribe(true) - log.trace("Buffer or window is no longer valid", win_id, bufnr) - return - end - - local win_width = vim.api.nvim_win_get_width(win_id) - ---@class ViewportContext - local viewport_context = { - win_width = win_width, - } - local output = render_node(viewport_context, view) - local lines, virt_texts, highlights, keybinds, diagnostics = - output.lines, output.virt_texts, output.highlights, output.keybinds, output.diagnostics - - -- set line contents - vim.api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) - vim.api.nvim_buf_set_option(bufnr, "modifiable", true) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - vim.api.nvim_buf_set_option(bufnr, "modifiable", false) - - -- set virtual texts - for i = 1, #virt_texts do - local virt_text = virt_texts[i] - vim.api.nvim_buf_set_extmark(bufnr, namespace, virt_text.line, 0, { - virt_text = virt_text.content, - }) - end - - -- set diagnostics - vim.diagnostic.set( - namespace, - bufnr, - vim.tbl_map(function(diagnostic) - return { - lnum = diagnostic.line - 1, - col = 0, - message = diagnostic.message, - severity = diagnostic.severity, - source = diagnostic.source, - } - end, diagnostics), - { - signs = false, - } - ) - - -- set highlights - for i = 1, #highlights do - local highlight = highlights[i] - vim.api.nvim_buf_add_highlight( - bufnr, - namespace, - highlight.hl_group, - highlight.line, - highlight.col_start, - highlight.col_end - ) - end - - -- set keybinds - registered_keybinds = {} - for i = 1, #keybinds do - local keybind = keybinds[i] - if not registered_keybinds[keybind.line] then - registered_keybinds[keybind.line] = {} - end - registered_keybinds[keybind.line][keybind.key] = keybind - if not registered_keymaps[keybind.key] then - registered_keymaps[keybind.key] = true - vim.keymap.set("n", keybind.key, function() - dispatch_effect(keybind.key) - end, { - buffer = bufnr, - nowait = true, - silent = true, - }) - end - end - end - - ---@param opts DisplayOpenOpts - local function open(opts) - opts = opts or {} - local highlight_groups = opts.highlight_groups - bufnr = vim.api.nvim_create_buf(false, true) - win_id = vim.api.nvim_open_win(bufnr, true, create_popup_window_opts(opts, false)) - - registered_effect_handlers = opts.effects - registered_keybinds = {} - registered_keymaps = {} - - local buf_opts = { - modifiable = false, - swapfile = false, - textwidth = 0, - buftype = "nofile", - bufhidden = "wipe", - buflisted = false, - filetype = "lsp-installer", - undolevels = -1, - } - - local win_opts = { - number = false, - relativenumber = false, - wrap = false, - spell = false, - foldenable = false, - signcolumn = "no", - colorcolumn = "", - cursorline = true, - } - - -- window options - for key, value in pairs(win_opts) do - vim.api.nvim_win_set_option(win_id, key, value) - end - - -- buffer options - for key, value in pairs(buf_opts) do - vim.api.nvim_buf_set_option(bufnr, key, value) - end - - vim.cmd [[ syntax clear ]] - - window_mgmt_augroup = vim.api.nvim_create_augroup("LspInstallerWindowMgmt", {}) - autoclose_augroup = vim.api.nvim_create_augroup("LspInstallerWindow", {}) - - vim.api.nvim_create_autocmd({ "VimResized" }, { - group = window_mgmt_augroup, - buffer = bufnr, - callback = function() - if vim.api.nvim_win_is_valid(win_id) then - draw(renderer(get_state())) - vim.api.nvim_win_set_config(win_id, create_popup_window_opts(opts, true)) - end - end, - }) - - vim.api.nvim_create_autocmd({ "BufHidden", "BufUnload" }, { - group = autoclose_augroup, - buffer = bufnr, - callback = function() - -- Schedule is done because otherwise the window wont actually close in some cases (for example if - -- you're loading another buffer into it) - vim.schedule(function() - if vim.api.nvim_win_is_valid(win_id) then - vim.api.nvim_win_close(win_id, true) - end - end) - end, - }) - - local win_enter_aucmd - win_enter_aucmd = vim.api.nvim_create_autocmd({ "WinEnter" }, { - group = autoclose_augroup, - pattern = "*", - callback = function() - local buftype = vim.api.nvim_buf_get_option(0, "buftype") - -- This allows us to keep the floating window open for things like diagnostic popups, UI inputs á la dressing.nvim, etc. - if buftype ~= "prompt" and buftype ~= "nofile" then - delete_win_buf() - vim.api.nvim_del_autocmd(win_enter_aucmd) - end - end, - }) - - if highlight_groups then - for i = 1, #highlight_groups do - -- TODO use vim.api.nvim_set_hl() - vim.cmd(highlight_groups[i]) - end - end - - return win_id - end - - return { - ---@param _renderer fun(state: table): table - view = function(_renderer) - renderer = _renderer - end, - ---@generic T : table - ---@param initial_state T - ---@return fun(mutate_fn: fun(current_state: T)), fun(): T - init = function(initial_state) - assert(renderer ~= nil, "No view function has been registered. Call .view() before .init().") - has_initiated = true - - mutate_state, get_state, unsubscribe = state.create_state_container( - initial_state, - process.debounced(function(new_state) - draw(renderer(new_state)) - end) - ) - - -- we don't need to subscribe to state changes until the window is actually opened - unsubscribe(true) - - return mutate_state, get_state - end, - ---@alias DisplayOpenOpts {effects: table<string, fun()>, highlight_groups: string[]|nil, border: string|table} - ---@type fun(opts: DisplayOpenOpts) - open = vim.schedule_wrap(function(opts) - log.trace "Opening window" - assert(has_initiated, "Display has not been initiated, cannot open.") - if win_id and vim.api.nvim_win_is_valid(win_id) then - -- window is already open - return - end - unsubscribe(false) - open(opts) - draw(renderer(get_state())) - end), - ---@type fun() - close = vim.schedule_wrap(function() - assert(has_initiated, "Display has not been initiated, cannot close.") - unsubscribe(true) - log.fmt_trace("Closing window win_id=%s, bufnr=%s", win_id, bufnr) - delete_win_buf() - vim.api.nvim_del_augroup_by_id(window_mgmt_augroup) - vim.api.nvim_del_augroup_by_id(autoclose_augroup) - end), - ---@param pos number[] @(row, col) tuple - set_cursor = function(pos) - assert(win_id ~= nil, "Window has not been opened, cannot set cursor.") - return vim.api.nvim_win_set_cursor(win_id, pos) - end, - ---@return number[] @(row, col) tuple - get_cursor = function() - assert(win_id ~= nil, "Window has not been opened, cannot get cursor.") - return vim.api.nvim_win_get_cursor(win_id) - end, - } -end - -return M diff --git a/lua/nvim-lsp-installer/core/ui/init.lua b/lua/nvim-lsp-installer/core/ui/init.lua deleted file mode 100644 index 5b855949..00000000 --- a/lua/nvim-lsp-installer/core/ui/init.lua +++ /dev/null @@ -1,141 +0,0 @@ -local _ = require "nvim-lsp-installer.core.functional" -local M = {} - ----@alias NodeType ----| '"NODE"' ----| '"CASCADING_STYLE"' ----| '"VIRTUAL_TEXT"' ----| '"DIAGNOSTICS"' ----| '"HL_TEXT"' ----| '"KEYBIND_HANDLER"' - ----@alias INode Node | HlTextNode | CascadingStyleNode | VirtualTextNode | KeybindHandlerNode | DiagnosticsNode - ----@param children INode[] -function M.Node(children) - ---@class Node - local node = { - type = "NODE", - children = children, - } - return node -end - ----@param lines_with_span_tuples string[][]|string[] -function M.HlTextNode(lines_with_span_tuples) - if type(lines_with_span_tuples[1]) == "string" then - -- this enables a convenience API for just rendering a single line (with just a single span) - lines_with_span_tuples = { { lines_with_span_tuples } } - end - ---@class HlTextNode - local node = { - type = "HL_TEXT", - lines = lines_with_span_tuples, - } - return node -end - -local create_unhighlighted_lines = _.map(function(line) - return { { line, "" } } -end) - ----@param lines string[] -function M.Text(lines) - return M.HlTextNode(create_unhighlighted_lines(lines)) -end - ----@alias CascadingStyle ----| '"INDENT"' ----| '"CENTERED"' - ----@param styles CascadingStyle[] ----@param children INode[] -function M.CascadingStyleNode(styles, children) - ---@class CascadingStyleNode - local node = { - type = "CASCADING_STYLE", - styles = styles, - children = children, - } - return node -end - ----@param virt_text string[][] @List of (text, highlight) tuples. -function M.VirtualTextNode(virt_text) - ---@class VirtualTextNode - local node = { - type = "VIRTUAL_TEXT", - virt_text = virt_text, - } - return node -end - ----@param diagnostic {message: string, severity: integer, source: string|nil} -function M.DiagnosticsNode(diagnostic) - ---@class DiagnosticsNode - local node = { - type = "DIAGNOSTICS", - diagnostic = diagnostic, - } - return node -end - ----@param condition boolean ----@param node INode | fun(): INode ----@param default_val any -function M.When(condition, node, default_val) - if condition then - if type(node) == "function" then - return node() - else - return node - end - end - return default_val or M.Node {} -end - ----@param key string @The keymap to register to. Example: "<CR>". ----@param effect string @The effect to call when keymap is triggered by the user. ----@param payload any @The payload to pass to the effect handler when triggered. ----@param is_global boolean @Whether to register the keybind to apply on all lines in the buffer. -function M.Keybind(key, effect, payload, is_global) - ---@class KeybindHandlerNode - local node = { - type = "KEYBIND_HANDLER", - key = key, - effect = effect, - payload = payload, - is_global = is_global or false, - } - return node -end - -function M.EmptyLine() - return M.Text { "" } -end - ----@param rows string[][][] @A list of rows to include in the table. Each row consists of an array of (text, highlight) tuples (aka spans). -function M.Table(rows) - local col_maxwidth = {} - for i = 1, #rows do - local row = rows[i] - for j = 1, #row do - local col = row[j] - local content = col[1] - col_maxwidth[j] = math.max(vim.api.nvim_strwidth(content), col_maxwidth[j] or 0) - end - end - - for i = 1, #rows do - local row = rows[i] - for j = 1, #row do - local col = row[j] - local content = col[1] - col[1] = content .. string.rep(" ", col_maxwidth[j] - vim.api.nvim_strwidth(content) + 1) -- +1 for default minimum padding - end - end - - return M.HlTextNode(rows) -end - -return M diff --git a/lua/nvim-lsp-installer/core/ui/state.lua b/lua/nvim-lsp-installer/core/ui/state.lua deleted file mode 100644 index 9d7bcdda..00000000 --- a/lua/nvim-lsp-installer/core/ui/state.lua +++ /dev/null @@ -1,24 +0,0 @@ -local M = {} - ----@generic T : table ----@param initial_state T ----@param subscriber fun(state: T) -function M.create_state_container(initial_state, subscriber) - -- we do deepcopy to make sure instances of state containers doesn't mutate the initial state - local state = vim.deepcopy(initial_state) - local has_unsubscribed = false - - ---@param mutate_fn fun(current_state: table) - return function(mutate_fn) - mutate_fn(state) - if not has_unsubscribed then - subscriber(state) - end - end, function() - return state - end, function(val) - has_unsubscribed = val - end -end - -return M |
