diff --git a/lua/crates/.luarc.json b/lua/crates/.luarc.json new file mode 100644 index 00000000..4149a4f2 --- /dev/null +++ b/lua/crates/.luarc.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime": { + "version": "LuaJIT" + }, + "workspace": { + "library": [ + "lua", + "$VIMRUNTIME", + "${3rd}/busted/library", + "${3rd}/luassert/library", + "${3rd}/luv/library" + ], + "checkThirdParty": false + }, + "diagnostics": { + "groupFileStatus": { + "strict": "Opened", + "strong": "Opened", + "ambiguity" : "Opened", + "duplicate" : "Opened", + "global" : "Opened", + "luadoc" : "Opened", + "redefined" : "Opened", + "type-check" : "Opened", + "unbalanced" : "Opened", + "unused" : "Opened" + }, + "groupSeverity": { + "strong": "Warning", + "strict": "Warning" + }, + "unusedLocalExclude": [ "_*" ] + } +} diff --git a/lua/crates/actions.lua b/lua/crates/actions.lua deleted file mode 100644 index bf1e1813..00000000 --- a/lua/crates/actions.lua +++ /dev/null @@ -1,237 +0,0 @@ -local M = {} - -local edit = require("crates.edit") -local util = require("crates.util") -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Diagnostic = types.Diagnostic -local Range = types.Range - -function M.upgrade_crate(alt) - local buf = util.current_buf() - local line = util.cursor_pos() - local crates = util.get_line_crates(buf, Range.pos(line)) - local info = util.get_buf_info(buf) - if next(crates) and info then - edit.upgrade_crates(buf, crates, info, alt) - end -end - -function M.upgrade_crates(alt) - local buf = util.current_buf() - local lines = Range.new( - vim.api.nvim_buf_get_mark(0, "<")[1] - 1, - vim.api.nvim_buf_get_mark(0, ">")[1]) - - local crates = util.get_line_crates(buf, lines) - local info = util.get_buf_info(buf) - if next(crates) and info then - edit.upgrade_crates(buf, crates, info, alt) - end -end - -function M.upgrade_all_crates(alt) - local buf = util.current_buf() - local cache = state.buf_cache[buf] - if cache.crates and cache.info then - edit.upgrade_crates(buf, cache.crates, cache.info, alt) - end -end - -function M.update_crate(alt) - local buf = util.current_buf() - local line = util.cursor_pos() - local crates = util.get_line_crates(buf, Range.pos(line)) - local info = util.get_buf_info(buf) - if next(crates) and info then - edit.update_crates(buf, crates, info, alt) - end -end - -function M.update_crates(alt) - local buf = util.current_buf() - local lines = Range.new( - vim.api.nvim_buf_get_mark(0, "<")[1] - 1, - vim.api.nvim_buf_get_mark(0, ">")[1]) - - local crates = util.get_line_crates(buf, lines) - local info = util.get_buf_info(buf) - if next(crates) and info then - edit.update_crates(buf, crates, info, alt) - end -end - -function M.update_all_crates(alt) - local buf = util.current_buf() - local cache = state.buf_cache[buf] - if cache.crates and cache.info then - edit.update_crates(buf, cache.crates, cache.info, alt) - end -end - -function M.expand_plain_crate_to_inline_table() - local buf = util.current_buf() - local line = util.cursor_pos() - local _, crate = next(util.get_line_crates(buf, Range.pos(line))) - if crate then - edit.expand_plain_crate_to_inline_table(buf, crate) - end -end - -function M.extract_crate_into_table() - local buf = util.current_buf() - local line = util.cursor_pos() - local _, crate = next(util.get_line_crates(buf, Range.pos(line))) - if crate then - edit.extract_crate_into_table(buf, crate) - end -end - -function M.open_homepage() - local buf = util.current_buf() - local line = util.cursor_pos() - local crates = util.get_line_crates(buf, Range.pos(line)) - local _, crate = next(crates) - if crate then - local crate_info = state.api_cache[crate:package()] - if crate_info and crate_info.homepage then - util.open_url(crate_info.homepage) - else - util.notify(vim.log.levels.INFO, "The crate '%s' has no homepage specified", crate:package()) - end - end -end - -function M.open_repository() - local buf = util.current_buf() - local line = util.cursor_pos() - local crates = util.get_line_crates(buf, Range.pos(line)) - local _, crate = next(crates) - if crate then - local crate_info = state.api_cache[crate:package()] - if crate_info and crate_info.repository then - util.open_url(crate_info.repository) - else - util.notify(vim.log.levels.INFO, "The crate '%s' has no repository specified", crate:package()) - end - end -end - -function M.open_documentation() - local buf = util.current_buf() - local line = util.cursor_pos() - local crates = util.get_line_crates(buf, Range.pos(line)) - local _, crate = next(crates) - if crate then - local crate_info = state.api_cache[crate:package()] - local url = crate_info and crate_info.documentation - url = url or util.docs_rs_url(crate:package()) - util.open_url(url) - end -end - -function M.open_crates_io() - local buf = util.current_buf() - local line = util.cursor_pos() - local crates = util.get_line_crates(buf, Range.pos(line)) - local _, crate = next(crates) - if crate then - util.open_url(util.crates_io_url(crate:package())) - end -end - -local function rename_crate_package_action(buf, crate, name) - return function() - edit.rename_crate_package(buf, crate, name) - end -end - -local function remove_diagnostic_range_action(buf, d) - return function() - vim.api.nvim_buf_set_text(buf, d.lnum, d.col, d.end_lnum, d.end_col, {}) - end -end - -local function remove_lines_action(buf, lines) - return function() - vim.api.nvim_buf_set_lines(buf, lines.s, lines.e, false, {}) - end -end - -local function remove_feature_action(buf, crate, feat) - return function() - edit.disable_feature(buf, crate, feat) - end -end - -function M.get_actions() - local actions = {} - - local buf = util.current_buf() - local line, col = util.cursor_pos() - local crates = util.get_line_crates(buf, Range.pos(line)) - local key, crate = next(crates) - if crate then - local info = util.get_crate_info(buf, key) - if info then - if info.vers_update then - actions["update_crate"] = M.update_crate - end - if info.vers_upgrade then - actions["upgrade_crate"] = M.upgrade_crate - end - end - - - if crate.syntax == "plain" then - actions["expand_crate_to_inline_table"] = M.expand_plain_crate_to_inline_table - end - if crate.syntax ~= "table" then - actions["extract_crate_into_table"] = M.extract_crate_into_table - end - end - - local diagnostics = util.get_buf_diagnostics(buf) or {} - for _, d in ipairs(diagnostics) do - if not d:contains(line, col) then - goto continue - end - - if d.kind == "section_dup" then - actions["remove_duplicate_section"] = remove_diagnostic_range_action(buf, d) - elseif d.kind == "section_dup_orig" then - actions["remove_original_section"] = remove_lines_action(buf, d.data["lines"]) - elseif d.kind == "section_invalid" then - actions["remove_invalid_dependency_section"] = remove_diagnostic_range_action(buf, d) - - elseif d.kind == "crate_dup" then - actions["remove_duplicate_crate"] = remove_diagnostic_range_action(buf, d) - elseif d.kind == "crate_dup_orig" then - actions["remove_original_crate"] = remove_diagnostic_range_action(buf, d) - elseif d.kind == "crate_name_case" then - actions["rename_crate"] = rename_crate_package_action(buf, d.data["crate"], d.data["crate_name"]) - - elseif crate and d.kind == "feat_dup" then - actions["remove_duplicate_feature"] = remove_feature_action(buf, crate, d.data["feat"]) - elseif crate and d.kind == "feat_dup_orig" then - actions["remove_original_feature"] = remove_feature_action(buf, crate, d.data["feat"]) - elseif crate and d.kind == "feat_invalid" then - actions["remove_invalid_feature"] = remove_feature_action(buf, crate, d.data["feat"]) - end - - ::continue:: - end - - if crate then - actions["open_documentation"] = M.open_documentation - actions["open_crates.io"] = M.open_crates_io - end - - actions["update_all_crates"] = M.update_all_crates - actions["upgrade_all_crates"] = M.upgrade_all_crates - - return actions -end - -return M diff --git a/teal/crates/actions.tl b/lua/crates/actions.tl similarity index 100% rename from teal/crates/actions.tl rename to lua/crates/actions.tl diff --git a/lua/crates/api.lua b/lua/crates/api.lua deleted file mode 100644 index 52f7e077..00000000 --- a/lua/crates/api.lua +++ /dev/null @@ -1,386 +0,0 @@ -local M = {CrateJob = {}, DepsJob = {}, QueuedJob = {}, } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -local semver = require("crates.semver") -local state = require("crates.state") -local time = require("crates.time") -local DateTime = time.DateTime -local types = require("crates.types") -local Dependency = types.Dependency -local Crate = types.Crate -local Features = types.Features -local Version = types.Version -local Job = require("plenary.job") - -local ENDPOINT = "https://crates.io/api/v1" -local USERAGENT = vim.fn.shellescape("crates.nvim (https://github.com/saecki/crates.nvim)") -local JSON_DECODE_OPTS = { luanil = { object = true, array = true } } - -M.crate_jobs = {} -M.deps_jobs = {} -M.queued_jobs = {} -M.num_requests = 0 - - -local function parse_json(json_str) - if not json_str then - return - end - - local success, json = pcall(vim.json.decode, json_str, JSON_DECODE_OPTS) - if not success then - return - end - - if json and type(json) == "table" then - return json - end -end - -local function request_job(url, on_exit) - return Job:new({ - command = "curl", - args = { unpack(state.cfg.curl_args), "-A", USERAGENT, url }, - on_exit = vim.schedule_wrap(on_exit), - }) -end - -local function enqueue_crate_job(name, callbacks) - for _, j in ipairs(M.queued_jobs) do - if j.kind == "crate" and j.name == name then - vim.list_extend(j.crate_callbacks, callbacks) - return - end - end - - table.insert(M.queued_jobs, { - kind = "crate", - name = name, - crate_callbacks = callbacks, - }) -end - -local function enqueue_deps_job(name, version, callbacks) - for _, j in ipairs(M.queued_jobs) do - if j.kind == "deps" and j.name == name and j.version == version then - vim.list_extend(j.deps_callbacks, callbacks) - return - end - end - - table.insert(M.queued_jobs, { - kind = "deps", - name = name, - version = version, - deps_callbacks = callbacks, - }) -end - - -function M.parse_crate(json_str) - local json = parse_json(json_str) - if not (json and json.crate) then - return - end - - local c = json.crate - local crate = { - name = c.id, - description = c.description, - created = DateTime.parse_rfc_3339(c.created_at), - updated = DateTime.parse_rfc_3339(c.updated_at), - downloads = c.downloads, - homepage = c.homepage, - documentation = c.documentation, - repository = c.repository, - categories = {}, - keywords = {}, - versions = {}, - } - - for _, ct_id in ipairs(c.categories) do - for _, ct in ipairs(json.categories) do - if ct.id == ct_id then - table.insert(crate.categories, ct.category) - end - end - end - - for _, kw_id in ipairs(c.keywords) do - for _, kw in ipairs(json.keywords) do - if kw.id == kw_id then - table.insert(crate.keywords, kw.keyword) - end - end - end - - for _, v in ipairs(json.versions) do - if v.num then - local version = { - num = v.num, - features = Features.new({}), - yanked = v.yanked, - parsed = semver.parse_version(v.num), - created = DateTime.parse_rfc_3339(v.created_at), - } - - for n, m in pairs(v.features) do - table.sort(m) - version.features:insert({ - name = n, - members = m, - }) - end - - - for _, f in ipairs(version.features.list) do - for _, m in ipairs(f.members) do - - if not string.find(m, "/") and not version.features:get_feat(m) then - version.features:insert({ - name = m, - members = {}, - }) - end - end - end - - - version.features:sort() - - - if not version.features.list[1] or not (version.features.list[1].name == "default") then - version.features:insert({ - name = "default", - members = {}, - }) - end - - table.insert(crate.versions, version) - end - end - - - return crate -end - -local function fetch_crate(name, callbacks) - local existing = M.crate_jobs[name] - if existing then - vim.list_extend(existing.callbacks, callbacks) - return - end - - if M.num_requests >= state.cfg.max_parallel_requests then - enqueue_crate_job(name, callbacks) - return - end - - local url = string.format("%s/crates/%s", ENDPOINT, name) - - local function on_exit(j, code, signal) - local cancelled = signal ~= 0 - - local json = nil - if code == 0 then - json = table.concat(j:result(), "\n") - end - - local crate = nil - if not cancelled then - crate = M.parse_crate(json) - end - for _, c in ipairs(callbacks) do - c(crate, cancelled) - end - - M.crate_jobs[name] = nil - M.num_requests = M.num_requests - 1 - - M.run_queued_jobs() - end - - local job = request_job(url, on_exit) - M.num_requests = M.num_requests + 1 - M.crate_jobs[name] = { - job = job, - callbacks = callbacks, - } - job:start() -end - -function M.fetch_crate(name) - return coroutine.yield(function(resolve) - fetch_crate(name, { resolve }) - end) -end - - -function M.parse_deps(json_str) - local json = parse_json(json_str) - if not (json and json.dependencies) then - return - end - - local dependencies = {} - for _, d in ipairs(json.dependencies) do - if d.crate_id then - local dependency = { - name = d.crate_id, - opt = d.optional or false, - kind = d.kind or "normal", - vers = { - text = d.req, - reqs = semver.parse_requirements(d.req), - }, - } - table.insert(dependencies, dependency) - end - end - - return dependencies -end - -local function fetch_deps(name, version, callbacks) - local jobname = name .. ":" .. version - local existing = M.deps_jobs[jobname] - if existing then - vim.list_extend(existing.callbacks, callbacks) - return - end - - if M.num_requests >= state.cfg.max_parallel_requests then - enqueue_deps_job(name, version, callbacks) - return - end - - local url = string.format("%s/crates/%s/%s/dependencies", ENDPOINT, name, version) - - local function on_exit(j, code, signal) - local cancelled = signal ~= 0 - - local json = nil - if code == 0 then - json = table.concat(j:result(), "\n") - end - - local deps = nil - if not cancelled then - deps = M.parse_deps(json) - end - for _, c in ipairs(callbacks) do - c(deps, cancelled) - end - - M.num_requests = M.num_requests - 1 - M.deps_jobs[jobname] = nil - - M.run_queued_jobs() - end - - local job = request_job(url, on_exit) - M.num_requests = M.num_requests + 1 - M.deps_jobs[jobname] = { - job = job, - callbacks = callbacks, - } - job:start() -end - -function M.fetch_deps(name, version) - return coroutine.yield(function(resolve) - fetch_deps(name, version, { resolve }) - end) -end - - -function M.is_fetching_crate(name) - return M.crate_jobs[name] ~= nil -end - -function M.is_fetching_deps(name, version) - return M.deps_jobs[name .. ":" .. version] ~= nil -end - -local function add_crate_callback(name, callback) - table.insert( - M.crate_jobs[name].callbacks, - callback) - -end - -function M.await_crate(name) - return coroutine.yield(function(resolve) - add_crate_callback(name, resolve) - end) -end - -local function add_deps_callback(name, version, callback) - table.insert( - M.deps_jobs[name .. ":" .. version].callbacks, - callback) - -end - -function M.await_deps(name, version) - return coroutine.yield(function(resolve) - add_deps_callback(name, version, resolve) - end) -end - -function M.run_queued_jobs() - if #M.queued_jobs == 0 then - return - end - - local job = table.remove(M.queued_jobs, 1) - if job.kind == "crate" then - fetch_crate(job.name, job.crate_callbacks) - elseif job.kind == "deps" then - fetch_deps(job.name, job.version, job.deps_callbacks) - end -end - -function M.cancel_jobs() - for _, r in pairs(M.crate_jobs) do - r.job:shutdown(1, 1) - end - for _, r in pairs(M.deps_jobs) do - r.job:shutdown(1, 1) - end - M.crate_jobs = {} - M.deps_jobs = {} -end - -return M diff --git a/teal/crates/api.tl b/lua/crates/api.tl similarity index 100% rename from teal/crates/api.tl rename to lua/crates/api.tl diff --git a/lua/crates/async.lua b/lua/crates/async.lua index 05966e88..d0a7f807 100644 --- a/lua/crates/async.lua +++ b/lua/crates/async.lua @@ -1,65 +1,82 @@ local M = {} +---@param f function +---@param ... any function M.launch(f, ...) - local t = coroutine.create(f) - local function exec(...) - local ok, data = coroutine.resume(t, ...) - if not ok then - error(debug.traceback(t, data)) - end - if coroutine.status(t) ~= "dead" then - data(exec) - end - end - exec(...) + local t = coroutine.create(f) + local function exec(...) + local ok, data = coroutine.resume(t, ...) + if not ok then + error(debug.traceback(t, data)) + end + if coroutine.status(t) ~= "dead" then + data(exec) + end + end + exec(...) end +---@param f function +---@return function function M.wrap(f) - return function(...) - M.launch(f, ...) - end + return function(...) + M.launch(f, ...) + end end +---@class vim.loop.Timer +---@field start fun(self, integer, integer, function) +---@field stop fun(self) +---@field close fun(self) +---Throttle a function using tail calling +---@param f function +---@param timeout integer +---@return function function M.throttle(f, timeout) - local last_call = 0; + local last_call = 0; - local timer = nil + ---@type vim.loop.Timer|nil + local timer = nil - return function(...) - - if timer then - timer:stop() - end - - local rem = timeout - (vim.loop.now() - last_call) - - if rem > 0 then - - if timer == nil then - timer = vim.loop.new_timer() - end - - local args = { ... } - timer:start(rem, 0, vim.schedule_wrap(function() + return function(...) + -- Make sure to stop any scheduled timers + if timer then timer:stop() - timer:close() - timer = nil - - - - - - + end + + ---@type integer + local rem = timeout - (vim.loop.now() - last_call) + -- Schedule a tail call + if rem > 0 then + -- Reuse timer + if timer == nil then + ---@type vim.loop.Timer + timer = vim.loop.new_timer() + end + + local args = { ... } + timer:start(rem, 0, vim.schedule_wrap(function() + timer:stop() + timer:close() + timer = nil + + -- Reset here to ensure timeout between the execution of the + -- tail call, and not the last call to throttle + + -- If it was reset in the throttle call, it could be a shorter + -- interval between calls to f + ---@type integer + last_call = vim.loop.now() + + f(unpack(args)) + end)) + else + ---@type integer last_call = vim.loop.now() - - f(unpack(args)) - end)) - else - last_call = vim.loop.now() - f(...) - end - end + f(...) + end + end end return M diff --git a/lua/crates/command.lua b/lua/crates/command.lua deleted file mode 100644 index a10c9768..00000000 --- a/lua/crates/command.lua +++ /dev/null @@ -1,77 +0,0 @@ -local M = {} - -local actions = require("crates.actions") -local core = require("crates.core") -local popup = require("crates.popup") - -local sub_commands = { - { "hide", core.hide }, - { "show", core.show }, - { "toggle", core.toggle }, - { "update", core.update }, - { "reload", core.reload }, - - { "upgrade_crate", actions.upgrade_crate }, - { "upgrade_crates", actions.upgrade_crates }, - { "upgrade_all_crates", actions.upgrade_all_crates }, - { "update_crate", actions.update_crate }, - { "update_crates", actions.update_crates }, - { "update_all_crates", actions.update_all_crates }, - - { "expand_plain_crate_to_inline_table", actions.expand_plain_crate_to_inline_table }, - { "extract_crate_into_table", actions.extract_crate_into_table }, - - { "open_homepage", actions.open_homepage }, - { "open_repository", actions.open_repository }, - { "open_documentation", actions.open_documentation }, - { "open_cratesio", actions.open_crates_io }, - - { "popup_available", popup.available }, - { "show_popup", popup.show }, - { "show_crate_popup", popup.show_crate }, - { "show_versions_popup", popup.show_versions }, - { "show_features_popup", popup.show_features }, - { "show_dependencies_popup", popup.show_dependencies }, - { "focus_popup", popup.focus }, - { "hide_popup", popup.hide }, -} - -local function complete(arglead, line) - local matches = {} - - local words = vim.split(line, "%s+") - if #words > 2 then - return matches - end - - for _, s in ipairs(sub_commands) do - if vim.startswith(s[1], arglead) then - table.insert(matches, s[1]) - end - end - return matches -end - -local function exec(cmd) - for _, s in ipairs(sub_commands) do - if s[1] == cmd.args then - local ret = s[2]() - if ret ~= nil then - print(vim.inspect(ret)) - end - return - end - end - - print(string.format("unknown sub command \"%s\"", cmd.args)) -end - -function M.register() - vim.api.nvim_create_user_command("Crates", exec, { - nargs = 1, - range = true, - complete = complete, - }) -end - -return M diff --git a/teal/crates/command.tl b/lua/crates/command.tl similarity index 100% rename from teal/crates/command.tl rename to lua/crates/command.tl diff --git a/lua/crates/config.lua b/lua/crates/config.lua index 1ca43f76..52e67c64 100644 --- a/lua/crates/config.lua +++ b/lua/crates/config.lua @@ -1,273 +1,288 @@ -local M = {Config = {TextConfig = {}, HighlightConfig = {}, DiagnosticConfig = {}, PopupConfig = {}, PopupTextConfig = {}, PopupHighlightConfig = {}, PopupKeyConfig = {}, SrcConfig = {}, SrcTextConfig = {}, CoqConfig = {}, CmpConfig = {}, CmpKindTextConfig = {}, CmpKindHighlightConfig = {}, NullLsConfig = {}, LspConfig = {}, }, SchemaElement = {Deprecated = {}, }, } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -local Config = M.Config -local SchemaElement = M.SchemaElement -local SchemaType = M.SchemaType +local M = {} + +---@class Config +---@field smart_insert boolean +---@field insert_closing_quote boolean +---@field autoload boolean +---@field autoupdate boolean +---@field autoupdate_throttle integer +---@field loading_indicator boolean +---@field date_format string +---@field thousands_separator string +---@field notification_title string +---@field curl_args string[] +---@field open_programs string[] +---@field max_parallel_requests integer +---@field expand_crate_moves_cursor boolean +---@field disable_invalid_feature_diagnostic boolean +---@field enable_update_available_warning boolean +---@field on_attach fun(bufnr: integer) +---@field text TextConfig +---@field highlight HighlightConfig +---@field diagnostic DiagnosticConfig +---@field popup PopupConfig +---@field src SrcConfig +---@field null_ls NullLsConfig +---@field lsp LspConfig + +---@class TextConfig +---@field loading string +---@field version string +---@field prerelease string +---@field yanked string +---@field nomatch string +---@field upgrade string +---@field error string + +---@class HighlightConfig +---@field loading string +---@field version string +---@field prerelease string +---@field yanked string +---@field nomatch string +---@field upgrade string +---@field error string + +---@class DiagnosticConfig +---@field section_invalid string +---@field workspace_section_not_default string +---@field workspace_section_has_target string +---@field section_dup string +---@field section_dup_orig string +---@field crate_dup string +---@field crate_dup_orig string +---@field crate_novers string +---@field crate_error_fetching string +---@field crate_name_case string +---@field vers_upgrade string +---@field vers_pre string +---@field vers_yanked string +---@field vers_nomatch string +---@field def_invalid string +---@field feat_dup string +---@field feat_dup_orig string +---@field feat_invalid string + +---@class PopupConfig +---@field autofocus boolean +---@field hide_on_select boolean +---@field copy_register string +---@field style string +---@field border string|string[] +---@field show_version_date boolean +---@field show_dependency_version boolean +---@field max_height integer +---@field min_width integer +---@field padding integer +---@field text PopupTextConfig +---@field highlight PopupHighlightConfig +---@field keys PopupKeyConfig + +---@class PopupTextConfig +---@field title string +---@field pill_left string +---@field pill_right string +-- crate +---@field description string +---@field created string +---@field created_label string +---@field updated string +---@field updated_label string +---@field downloads string +---@field downloads_label string +---@field homepage string +---@field homepage_label string +---@field repository string +---@field repository_label string +---@field documentation string +---@field documentation_label string +---@field crates_io string +---@field crates_io_label string +---@field categories_label string +---@field keywords_label string +-- version +---@field version string +---@field prerelease string +---@field yanked string +---@field version_date string +-- feature +---@field feature string +---@field enabled string +---@field transitive string +-- dependencies +---@field normal_dependencies_title string +---@field build_dependencies_title string +---@field dev_dependencies_title string +---@field dependency string +---@field optional string +---@field dependency_version string +---@field loading string + +---@class PopupHighlightConfig +---@field title string +---@field pill_text string +---@field pill_border string +-- crate +---@field created string +---@field created_label string +---@field updated string +---@field updated_label string +---@field description string +---@field downloads string +---@field downloads_label string +---@field homepage string +---@field homepage_label string +---@field repository string +---@field repository_label string +---@field documentation string +---@field documentation_label string +---@field crates_io string +---@field crates_io_label string +---@field categories_label string +---@field keywords_label string +-- version +---@field version string +---@field prerelease string +---@field yanked string +---@field version_date string +-- feature +---@field feature string +---@field enabled string +---@field transitive string +-- dependencies +---@field normal_dependencies_title string +---@field build_dependencies_title string +---@field dev_dependencies_title string +---@field dependency string +---@field optional string +---@field dependency_version string +---@field loading string + +---@class PopupKeyConfig +---@field hide string[] +---@field open_url string[] +---@field select string[] +---@field select_alt string[] +---@field toggle_feature string[] +---@field copy_value string[] +---@field goto_item string[] +---@field jump_forward string[] +---@field jump_back string[] + +---@class SrcConfig +---@field insert_closing_quote boolean +---@field text SrcTextConfig +---@field coq CoqConfig +---@field cmp CmpConfig + +---@class SrcTextConfig +---@field prerelease string +---@field yanked string + +---@class CoqConfig +---@field enabled boolean +---@field name string + +---@class CmpConfig +---@field enabled boolean +---@field use_custom_kind boolean +---@field kind_text CmpKindTextConfig +---@field kind_highlight CmpKindHighlightConfig + +---@class CmpKindTextConfig +---@field version string +---@field feature string + +---@class CmpKindHighlightConfig +---@field version string +---@field feature string + +---@class NullLsConfig +---@field enabled boolean +---@field name string + +---@class LspConfig +---@field enabled boolean +---@field name string +---@field on_attach fun(client: lsp.Client, bufnr: integer) +---@field actions boolean +---@field completion boolean + +---@enum SchemaType +local SchemaType = { + -- A record of grouped options + section = "section", + -- The rest are lua types checked at runtime + table = "table", + string = "string", + number = "number", + boolean = "boolean", + fun = "function", +} + +---@alias SchemaElement +---| SectionSchemaElement +---| HiddenSectionSchemaElement +---| FieldSchemaElement +---| HiddenFieldSchemaElement +---| DeprecatedSchemaElement + +---@class SectionSchemaElement +---@field name string +---@field type SchemaType|SchemaType[] +---@field description string +---@field fields table + +---@class HiddenSectionSchemaElement +---@field name string +---@field type SchemaType|SchemaType[] +---@field fields table +---@field hidden boolean + +---@class FieldSchemaElement +---@field name string +---@field type SchemaType|SchemaType[] +---@field default any +---@field default_text string|nil +---@field description string + +---@class HiddenFieldSchemaElement +---@field name string +---@field type SchemaType|SchemaType[] +---@field default any +---@field hidden boolean + +---@class DeprecatedSchemaElement +---@field name string +---@field type SchemaType|SchemaType[] +---@field deprecated Deprecated|nil + +---@class Deprecated +---@field new_field string[]|nil +---@field hard boolean|nil + +---@param schema table +---@param elem SchemaElement +local function entry(schema, elem) + table.insert(schema, elem) + schema[elem.name] = elem +end -local function entry(schema, name, elem) - elem.name = name - table.insert(schema, elem) - schema[name] = elem +---@param schema table +---@param elem SectionSchemaElement|HiddenSectionSchemaElement +---@return table +local function section_entry(schema, elem) + table.insert(schema, elem) + schema[elem.name] = elem + return elem.fields end M.schema = {} -entry(M.schema, "smart_insert", { - type = "boolean", - default = true, - description = [[ +entry(M.schema, { + name = "smart_insert", + type = "boolean", + default = true, + description = [[ Try to be smart about inserting versions, by respecting existing version requirements. Example: ~ @@ -282,52 +297,59 @@ entry(M.schema, "smart_insert", { `>0.8, <1.6` ]], }) -entry(M.schema, "insert_closing_quote", { - type = "boolean", - default = true, - description = [[ +entry(M.schema, { + name = "insert_closing_quote", + type = "boolean", + default = true, + description = [[ Insert a closing quote when updating or upgrading a version, if there is none. ]], }) -entry(M.schema, "autoload", { - type = "boolean", - default = true, - description = [[ +entry(M.schema, { + name = "autoload", + type = "boolean", + default = true, + description = [[ Automatically run update when opening a Cargo.toml. ]], }) -entry(M.schema, "autoupdate", { - type = "boolean", - default = true, - description = [[ +entry(M.schema, { + name = "autoupdate", + type = "boolean", + default = true, + description = [[ Automatically update when editing text. ]], }) -entry(M.schema, "autoupdate_throttle", { - type = "number", - default = 250, - description = [[ +entry(M.schema, { + name = "autoupdate_throttle", + type = "number", + default = 250, + description = [[ Rate limit the auto update in milliseconds ]], }) -entry(M.schema, "loading_indicator", { - type = "boolean", - default = true, - description = [[ +entry(M.schema, { + name = "loading_indicator", + type = "boolean", + default = true, + description = [[ Show a loading indicator while fetching crate versions. ]], }) -entry(M.schema, "date_format", { - type = "string", - default = "%Y-%m-%d", - description = [[ +entry(M.schema, { + name = "date_format", + type = "string", + default = "%Y-%m-%d", + description = [[ The date format passed to `os.date`. ]], }) -entry(M.schema, "thousands_separator", { - type = "string", - default = ".", - description = [[ +entry(M.schema, { + name = "thousands_separator", + type = "string", + default = ".", + description = [[ The separator used to separate thousands of a number: Example: ~ @@ -338,1047 +360,1194 @@ entry(M.schema, "thousands_separator", { `14,502,265` ]], }) -entry(M.schema, "notification_title", { - type = "string", - default = "crates.nvim", - description = [[ +entry(M.schema, { + name = "notification_title", + type = "string", + default = "crates.nvim", + description = [[ The title displayed in notifications. ]], }) -entry(M.schema, "curl_args", { - type = "table", - default = { "-sL", "--retry", "1" }, - description = [[ +entry(M.schema, { + name = "curl_args", + type = "table", + default = { "-sL", "--retry", "1" }, + description = [[ A list of arguments passed to curl when fetching metadata from crates.io. ]], }) -entry(M.schema, "max_parallel_requests", { - type = "number", - default = 80, - description = [[ +entry(M.schema, { + name = "max_parallel_requests", + type = "number", + default = 80, + description = [[ Maximum number of parallel requests. ]], }) -entry(M.schema, "expand_crate_moves_cursor", { - type = "boolean", - default = true, - description = [[ +entry(M.schema, { + name = "expand_crate_moves_cursor", + type = "boolean", + default = true, + description = [[ Whether to move the cursor on |crates.expand_plain_crate_to_inline_table()|. ]], }) -entry(M.schema, "open_programs", { - type = "table", - default = { "xdg-open", "open" }, - description = [[ +entry(M.schema, { + name = "open_programs", + type = "table", + default = { "xdg-open", "open" }, + description = [[ A list of programs that used to open urls. ]], }) - -entry(M.schema, "disable_invalid_feature_diagnostic", { - type = "boolean", - default = false, - description = [[ +-- TODO: Blocked on: https://github.com/rust-lang/crates.io/issues/1539 +entry(M.schema, { + name = "disable_invalid_feature_diagnostic", + type = "boolean", + default = false, + description = [[ This is a temporary solution for: https://github.com/Saecki/crates.nvim/issues/14 ]], }) -entry(M.schema, "enable_update_available_warning", { - type = "boolean", - default = true, - description = [[ +entry(M.schema, { + name = "enable_update_available_warning", + type = "boolean", + default = true, + description = [[ Enable warnings for outdated crates. ]], }) -entry(M.schema, "on_attach", { - type = "function", - default = function(_bufnr) end, - default_text = "function(bufnr) end", - description = [[ +entry(M.schema, { + name = "on_attach", + type = "function", + default = function(_) end, + default_text = "function(bufnr) end", + description = [[ Callback to run when a `Cargo.toml` file is opened. NOTE: Ignored if |crates-config-autoload| is disabled. ]], }) - -entry(M.schema, "avoid_prerelease", { - type = "boolean", - deprecated = { - hard = true, - }, +-- deprecated +entry(M.schema, { + name = "avoid_prerelease", + type = "boolean", + deprecated = { + hard = true, + }, }) -entry(M.schema, "text", { - type = "section", - description = [[ +entry(M.schema, { + name = "text", + type = "section", + description = [[ Strings used to format virtual text. ]], - fields = {}, + fields = {}, }) +---@type table local schema_text = M.schema.text.fields -entry(schema_text, "loading", { - type = "string", - default = "  Loading", - description = [[ +entry(schema_text, { + name = "loading", + type = "string", + default = "  Loading", + description = [[ Format string used while loading crate information. ]], }) -entry(schema_text, "version", { - type = "string", - default = "  %s", - description = [[ +entry(schema_text, { + name = "version", + type = "string", + default = "  %s", + description = [[ format string used for the latest compatible version ]], }) -entry(schema_text, "prerelease", { - type = "string", - default = "  %s", - description = [[ +entry(schema_text, { + name = "prerelease", + type = "string", + default = "  %s", + description = [[ Format string used for pre-release versions. ]], }) -entry(schema_text, "yanked", { - type = "string", - default = "  %s", - description = [[ +entry(schema_text, { + name = "yanked", + type = "string", + default = "  %s", + description = [[ Format string used for yanked versions. ]], }) -entry(schema_text, "nomatch", { - type = "string", - default = "  No match", - description = [[ +entry(schema_text, { + name = "nomatch", + type = "string", + default = "  No match", + description = [[ Format string used when there is no matching version. ]], }) -entry(schema_text, "upgrade", { - type = "string", - default = "  %s", - description = [[ +entry(schema_text, { + name = "upgrade", + type = "string", + default = "  %s", + description = [[ Format string used when there is an upgrade candidate. ]], }) -entry(schema_text, "error", { - type = "string", - default = "  Error fetching crate", - description = [[ +entry(schema_text, { + name = "error", + type = "string", + default = "  Error fetching crate", + description = [[ Format string used when there was an error loading crate information. ]], }) -entry(M.schema, "highlight", { - type = "section", - description = [[ +entry(M.schema, { + name = "highlight", + type = "section", + description = [[ Highlight groups used for virtual text. ]], - fields = {}, + fields = {}, }) +---@type table local schema_hi = M.schema.highlight.fields -entry(schema_hi, "loading", { - type = "string", - default = "CratesNvimLoading", - description = [[ +entry(schema_hi, { + name = "loading", + type = "string", + default = "CratesNvimLoading", + description = [[ Highlight group used while loading crate information. ]], }) -entry(schema_hi, "version", { - type = "string", - default = "CratesNvimVersion", - description = [[ +entry(schema_hi, { + name = "version", + type = "string", + default = "CratesNvimVersion", + description = [[ Highlight group used for the latest compatible version. ]], }) -entry(schema_hi, "prerelease", { - type = "string", - default = "CratesNvimPreRelease", - description = [[ +entry(schema_hi, { + name = "prerelease", + type = "string", + default = "CratesNvimPreRelease", + description = [[ Highlight group used for pre-release versions. ]], }) -entry(schema_hi, "yanked", { - type = "string", - default = "CratesNvimYanked", - description = [[ +entry(schema_hi, { + name = "yanked", + type = "string", + default = "CratesNvimYanked", + description = [[ Highlight group used for yanked versions. ]], }) -entry(schema_hi, "nomatch", { - type = "string", - default = "CratesNvimNoMatch", - description = [[ +entry(schema_hi, { + name = "nomatch", + type = "string", + default = "CratesNvimNoMatch", + description = [[ Highlight group used when there is no matching version. ]], }) -entry(schema_hi, "upgrade", { - type = "string", - default = "CratesNvimUpgrade", - description = [[ +entry(schema_hi, { + name = "upgrade", + type = "string", + default = "CratesNvimUpgrade", + description = [[ Highlight group used when there is an upgrade candidate. ]], }) -entry(schema_hi, "error", { - type = "string", - default = "CratesNvimError", - description = [[ +entry(schema_hi, { + name = "error", + type = "string", + default = "CratesNvimError", + description = [[ Highlight group used when there was an error loading crate information. ]], }) -entry(M.schema, "diagnostic", { - type = "section", - fields = {}, - hidden = true, -}) -local schema_diagnostic = M.schema.diagnostic.fields -entry(schema_diagnostic, "section_invalid", { - type = "string", - default = "Invalid dependency section", - hidden = true, -}) -entry(schema_diagnostic, "workspace_section_not_default", { - type = "string", - default = "Workspace dependency sections don't support other kinds of dependencies like build or dev", - hidden = true, -}) -entry(schema_diagnostic, "workspace_section_has_target", { - type = "string", - default = "Workspace dependency sections don't support target specifiers", - hidden = true, -}) -entry(schema_diagnostic, "section_dup", { - type = "string", - default = "Duplicate dependency section", - hidden = true, -}) -entry(schema_diagnostic, "section_dup_orig", { - type = "string", - default = "Original dependency section is defined here", - hidden = true, -}) -entry(schema_diagnostic, "crate_dup", { - type = "string", - default = "Duplicate crate entry", - hidden = true, -}) -entry(schema_diagnostic, "crate_dup_orig", { - type = "string", - default = "Original crate entry is defined here", - hidden = true, -}) -entry(schema_diagnostic, "crate_novers", { - type = "string", - default = "Missing version requirement", - hidden = true, -}) -entry(schema_diagnostic, "crate_error_fetching", { - type = "string", - default = "Error fetching crate", - hidden = true, -}) -entry(schema_diagnostic, "crate_name_case", { - type = "string", - default = "Incorrect crate name casing", - hidden = true, -}) -entry(schema_diagnostic, "vers_upgrade", { - type = "string", - default = "There is an upgrade available", - hidden = true, -}) -entry(schema_diagnostic, "vers_pre", { - type = "string", - default = "Requirement only matches a pre-release version\nIf you want to use the pre-release package, it needs to be specified explicitly", - hidden = true, -}) -entry(schema_diagnostic, "vers_yanked", { - type = "string", - default = "Requirement only matches a yanked version", - hidden = true, -}) -entry(schema_diagnostic, "vers_nomatch", { - type = "string", - default = "Requirement doesn't match a version", - hidden = true, -}) -entry(schema_diagnostic, "def_invalid", { - type = "string", - default = "Invalid boolean value", - hidden = true, -}) -entry(schema_diagnostic, "feat_dup", { - type = "string", - default = "Duplicate feature entry", - hidden = true, -}) -entry(schema_diagnostic, "feat_dup_orig", { - type = "string", - default = "Original feature entry is defined here", - hidden = true, -}) -entry(schema_diagnostic, "feat_invalid", { - type = "string", - default = "Invalid feature", - hidden = true, -}) - - -entry(M.schema, "popup", { - type = "section", - description = [[ - popup config - ]], - fields = {}, -}) -local schema_popup = M.schema.popup.fields -entry(schema_popup, "autofocus", { - type = "boolean", - default = false, - description = [[ +local schema_diagnostic = section_entry(M.schema, { + name = "diagnostic", + type = "section", + fields = {}, + hidden = true, +}) +entry(schema_diagnostic, { + name = "section_invalid", + type = "string", + default = "Invalid dependency section", + hidden = true, +}) +entry(schema_diagnostic, { + name = "workspace_section_not_default", + type = "string", + default = "Workspace dependency sections don't support other kinds of dependencies like build or dev", + hidden = true, +}) +entry(schema_diagnostic, { + name = "workspace_section_has_target", + type = "string", + default = "Workspace dependency sections don't support target specifiers", + hidden = true, +}) +entry(schema_diagnostic, { + name = "section_dup", + type = "string", + default = "Duplicate dependency section", + hidden = true, +}) +entry(schema_diagnostic, { + name = "section_dup_orig", + type = "string", + default = "Original dependency section is defined here", + hidden = true, +}) +entry(schema_diagnostic, { + name = "crate_dup", + type = "string", + default = "Duplicate crate entry", + hidden = true, +}) +entry(schema_diagnostic, { + name = "crate_dup_orig", + type = "string", + default = "Original crate entry is defined here", + hidden = true, +}) +entry(schema_diagnostic, { + name = "crate_novers", + type = "string", + default = "Missing version requirement", + hidden = true, +}) +entry(schema_diagnostic, { + name = "crate_error_fetching", + type = "string", + default = "Error fetching crate", + hidden = true, +}) +entry(schema_diagnostic, { + name = "crate_name_case", + type = "string", + default = "Incorrect crate name casing", + hidden = true, +}) +entry(schema_diagnostic, { + name = "vers_upgrade", + type = "string", + default = "There is an upgrade available", + hidden = true, +}) +entry(schema_diagnostic, { + name = "vers_pre", + type = "string", + default = "Requirement only matches a pre-release version\nIf you want to use the pre-release package, it needs to be specified explicitly", + hidden = true, +}) +entry(schema_diagnostic, { + name = "vers_yanked", + type = "string", + default = "Requirement only matches a yanked version", + hidden = true, +}) +entry(schema_diagnostic, { + name = "vers_nomatch", + type = "string", + default = "Requirement doesn't match a version", + hidden = true, +}) +entry(schema_diagnostic, { + name = "def_invalid", + type = "string", + default = "Invalid boolean value", + hidden = true, +}) +entry(schema_diagnostic, { + name = "feat_dup", + type = "string", + default = "Duplicate feature entry", + hidden = true, +}) +entry(schema_diagnostic, { + name = "feat_dup_orig", + type = "string", + default = "Original feature entry is defined here", + hidden = true, +}) +entry(schema_diagnostic, { + name = "feat_invalid", + type = "string", + default = "Invalid feature", + hidden = true, +}) + + +local schema_popup = section_entry(M.schema, { + name = "popup", + type = "section", + description = [[ + Popup configuration. + ]], + fields = {}, +}) +entry(schema_popup, { + name = "autofocus", + type = "boolean", + default = false, + description = [[ Focus the versions popup when opening it. ]], }) -entry(schema_popup, "hide_on_select", { - type = "boolean", - default = false, - description = [[ +entry(schema_popup, { + name = "hide_on_select", + type = "boolean", + default = false, + description = [[ Hides the popup after selecting a version. ]], }) -entry(schema_popup, "copy_register", { - type = "string", - default = '"', - description = [[ +entry(schema_popup, { + name = "copy_register", + type = "string", + default = '"', + description = [[ The register into which the version will be copied. ]], }) -entry(schema_popup, "style", { - type = "string", - default = "minimal", - description = [[ +entry(schema_popup, { + name = "style", + type = "string", + default = "minimal", + description = [[ Same as nvim_open_win config.style. ]], }) -entry(schema_popup, "border", { - type = { "string", "table" }, - default = "none", - description = [[ +entry(schema_popup, { + name = "border", + type = { "string", "table" }, + default = "none", + description = [[ Same as nvim_open_win config.border. ]], }) -entry(schema_popup, "show_version_date", { - type = "boolean", - default = false, - description = [[ +entry(schema_popup, { + name = "show_version_date", + type = "boolean", + default = false, + description = [[ Display when a version was released. ]], }) -entry(schema_popup, "show_dependency_version", { - type = "boolean", - default = true, - description = [[ +entry(schema_popup, { + name = "show_dependency_version", + type = "boolean", + default = true, + description = [[ Display when a version was released. ]], }) -entry(schema_popup, "max_height", { - type = "number", - default = 30, - description = [[ +entry(schema_popup, { + name = "max_height", + type = "number", + default = 30, + description = [[ The maximum height of the popup. ]], }) -entry(schema_popup, "min_width", { - type = "number", - default = 20, - description = [[ +entry(schema_popup, { + name = "min_width", + type = "number", + default = 20, + description = [[ The minimum width of the popup. ]], }) -entry(schema_popup, "padding", { - type = "number", - default = 1, - description = [[ +entry(schema_popup, { + name = "padding", + type = "number", + default = 1, + description = [[ The horizontal padding of the popup. ]], }) - -entry(schema_popup, "version_date", { - type = "boolean", - deprecated = { - new_field = { "popup", "show_version_date" }, - hard = true, - }, +-- deprecated +entry(schema_popup, { + name = "version_date", + type = "boolean", + deprecated = { + new_field = { "popup", "show_version_date" }, + hard = true, + } }) -entry(schema_popup, "text", { - type = "section", - description = [[ +entry(schema_popup, { + name = "text", + type = "section", + description = [[ Strings used to format the text inside the popup. ]], - fields = {}, + fields = {}, }) local schema_popup_text = schema_popup.text.fields -entry(schema_popup_text, "title", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "title", + type = "string", + default = " %s", + description = [[ Format string used for the popup title. ]], }) -entry(schema_popup_text, "pill_left", { - type = "string", - default = "", - description = [[ +entry(schema_popup_text, { + name = "pill_left", + type = "string", + default = "", + description = [[ Left border of a pill (keywords and categories). ]], }) -entry(schema_popup_text, "pill_right", { - type = "string", - default = "", - description = [[ +entry(schema_popup_text, { + name = "pill_right", + type = "string", + default = "", + description = [[ Right border of a pill (keywords and categories). ]], }) - -entry(schema_popup_text, "description", { - type = "string", - default = "%s", - description = [[ +-- crate +entry(schema_popup_text, { + name = "description", + type = "string", + default = "%s", + description = [[ Format string used for the description. ]], }) -entry(schema_popup_text, "created_label", { - type = "string", - default = " created ", - description = [[ +entry(schema_popup_text, { + name = "created_label", + type = "string", + default = " created ", + description = [[ Label string used for the creation date. ]], }) -entry(schema_popup_text, "created", { - type = "string", - default = "%s", - description = [[ +entry(schema_popup_text, { + name = "created", + type = "string", + default = "%s", + description = [[ Format string used for the creation date. ]], }) -entry(schema_popup_text, "updated_label", { - type = "string", - default = " updated ", - description = [[ +entry(schema_popup_text, { + name = "updated_label", + type = "string", + default = " updated ", + description = [[ Label string used for the updated date. ]], }) -entry(schema_popup_text, "updated", { - type = "string", - default = "%s", - description = [[ +entry(schema_popup_text, { + name = "updated", + type = "string", + default = "%s", + description = [[ Format string used for the updated date. ]], }) -entry(schema_popup_text, "downloads_label", { - type = "string", - default = " downloads ", - description = [[ +entry(schema_popup_text, { + name = "downloads_label", + type = "string", + default = " downloads ", + description = [[ Label string used for the download count. ]], }) -entry(schema_popup_text, "downloads", { - type = "string", - default = "%s", - description = [[ +entry(schema_popup_text, { + name = "downloads", + type = "string", + default = "%s", + description = [[ Format string used for the download count. ]], }) -entry(schema_popup_text, "homepage_label", { - type = "string", - default = " homepage ", - description = [[ +entry(schema_popup_text, { + name = "homepage_label", + type = "string", + default = " homepage ", + description = [[ Label string used for the homepage url. ]], }) -entry(schema_popup_text, "homepage", { - type = "string", - default = "%s", - description = [[ +entry(schema_popup_text, { + name = "homepage", + type = "string", + default = "%s", + description = [[ Format string used for the homepage url. ]], }) -entry(schema_popup_text, "repository_label", { - type = "string", - default = " repository ", - description = [[ +entry(schema_popup_text, { + name = "repository_label", + type = "string", + default = " repository ", + description = [[ Label string used for the repository url. ]], }) -entry(schema_popup_text, "repository", { - type = "string", - default = "%s", - description = [[ +entry(schema_popup_text, { + name = "repository", + type = "string", + default = "%s", + description = [[ Format string used for the repository url. ]], }) -entry(schema_popup_text, "documentation_label", { - type = "string", - default = " documentation ", - description = [[ +entry(schema_popup_text, { + name = "documentation_label", + type = "string", + default = " documentation ", + description = [[ Label string used for the documentation url. ]], }) -entry(schema_popup_text, "documentation", { - type = "string", - default = "%s", - description = [[ +entry(schema_popup_text, { + name = "documentation", + type = "string", + default = "%s", + description = [[ Format string used for the documentation url. ]], }) -entry(schema_popup_text, "crates_io_label", { - type = "string", - default = " crates.io ", - description = [[ +entry(schema_popup_text, { + name = "crates_io_label", + type = "string", + default = " crates.io ", + description = [[ Label string used for the crates.io url. ]], }) -entry(schema_popup_text, "crates_io", { - type = "string", - default = "%s", - description = [[ +entry(schema_popup_text, { + name = "crates_io", + type = "string", + default = "%s", + description = [[ Format string used for the crates.io url. ]], }) -entry(schema_popup_text, "categories_label", { - type = "string", - default = " categories ", - description = [[ +entry(schema_popup_text, { + name = "categories_label", + type = "string", + default = " categories ", + description = [[ Label string used for the categories label. ]], }) -entry(schema_popup_text, "keywords_label", { - type = "string", - default = " keywords ", - description = [[ +entry(schema_popup_text, { + name = "keywords_label", + type = "string", + default = " keywords ", + description = [[ Label string used for the keywords label. ]], }) - -entry(schema_popup_text, "version", { - type = "string", - default = " %s", - description = [[ +-- versions +entry(schema_popup_text, { + name = "version", + type = "string", + default = " %s", + description = [[ Format string used for release versions. ]], }) -entry(schema_popup_text, "prerelease", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "prerelease", + type = "string", + default = " %s", + description = [[ Format string used for prerelease versions. ]], }) -entry(schema_popup_text, "yanked", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "yanked", + type = "string", + default = " %s", + description = [[ Format string used for yanked versions. ]], }) -entry(schema_popup_text, "version_date", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "version_date", + type = "string", + default = " %s", + description = [[ Format string used for appending the version release date. ]], }) - -entry(schema_popup_text, "feature", { - type = "string", - default = " %s", - description = [[ +-- features +entry(schema_popup_text, { + name = "feature", + type = "string", + default = " %s", + description = [[ Format string used for disabled features. ]], }) -entry(schema_popup_text, "enabled", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "enabled", + type = "string", + default = " %s", + description = [[ Format string used for enabled features. ]], }) -entry(schema_popup_text, "transitive", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "transitive", + type = "string", + default = " %s", + description = [[ Format string used for transitively enabled features. ]], }) - -entry(schema_popup_text, "normal_dependencies_title", { - type = "string", - default = " Dependencies", - description = [[ +-- dependencies +entry(schema_popup_text, { + name = "normal_dependencies_title", + type = "string", + default = " Dependencies", + description = [[ Format string used for the title of the normal dependencies section. ]], }) -entry(schema_popup_text, "build_dependencies_title", { - type = "string", - default = " Build dependencies", - description = [[ +entry(schema_popup_text, { + name = "build_dependencies_title", + type = "string", + default = " Build dependencies", + description = [[ Format string used for the title of the build dependencies section. ]], }) -entry(schema_popup_text, "dev_dependencies_title", { - type = "string", - default = " Dev dependencies", - description = [[ +entry(schema_popup_text, { + name = "dev_dependencies_title", + type = "string", + default = " Dev dependencies", + description = [[ Format string used for the title of the dev dependencies section. ]], }) -entry(schema_popup_text, "dependency", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "dependency", + type = "string", + default = " %s", + description = [[ Format string used for dependencies and their version requirement. ]], }) -entry(schema_popup_text, "optional", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "optional", + type = "string", + default = " %s", + description = [[ Format string used for optional dependencies and their version requirement. ]], }) -entry(schema_popup_text, "dependency_version", { - type = "string", - default = " %s", - description = [[ +entry(schema_popup_text, { + name = "dependency_version", + type = "string", + default = " %s", + description = [[ Format string used for appending the dependency version. ]], }) -entry(schema_popup_text, "loading", { - type = "string", - default = "  ", - description = [[ +entry(schema_popup_text, { + name = "loading", + type = "string", + default = "  ", + description = [[ Format string used as a loading indicator when fetching dependencies. ]], }) - -entry(schema_popup_text, "date", { - type = "string", - deprecated = { - new_field = { "popup", "text", "version_date" }, - hard = true, - }, +-- deprecated +entry(schema_popup_text, { + name = "date", + type = "string", + deprecated = { + new_field = { "popup", "text", "version_date" }, + hard = true, + } }) -entry(schema_popup, "highlight", { - type = "section", - description = [[ +entry(schema_popup, { + name = "highlight", + type = "section", + description = [[ Highlight groups for popup elements. ]], - fields = {}, + fields = {}, }) local schema_popup_hi = schema_popup.highlight.fields -entry(schema_popup_hi, "title", { - type = "string", - default = "CratesNvimPopupTitle", - description = [[ +entry(schema_popup_hi, { + name = "title", + type = "string", + default = "CratesNvimPopupTitle", + description = [[ Highlight group used for the popup title. ]], }) -entry(schema_popup_hi, "pill_text", { - type = "string", - default = "CratesNvimPopupPillText", - description = [[ +entry(schema_popup_hi, { + name = "pill_text", + type = "string", + default = "CratesNvimPopupPillText", + description = [[ Highlight group used for a pill's text (keywords and categories). ]], }) -entry(schema_popup_hi, "pill_border", { - type = "string", - default = "CratesNvimPopupPillBorder", - description = [[ +entry(schema_popup_hi, { + name = "pill_border", + type = "string", + default = "CratesNvimPopupPillBorder", + description = [[ Highlight group used for a pill's border (keywords and categories). ]], }) - -entry(schema_popup_hi, "description", { - type = "string", - default = "CratesNvimPopupDescription", - description = [[ +-- crate +entry(schema_popup_hi, { + name = "description", + type = "string", + default = "CratesNvimPopupDescription", + description = [[ Highlight group used for the crate description. ]], }) -entry(schema_popup_hi, "created_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "created_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the creation date label. ]], }) -entry(schema_popup_hi, "created", { - type = "string", - default = "CratesNvimPopupValue", - description = [[ +entry(schema_popup_hi, { + name = "created", + type = "string", + default = "CratesNvimPopupValue", + description = [[ Highlight group used for the creation date. ]], }) -entry(schema_popup_hi, "updated_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "updated_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the updated date label. ]], }) -entry(schema_popup_hi, "updated", { - type = "string", - default = "CratesNvimPopupValue", - description = [[ +entry(schema_popup_hi, { + name = "updated", + type = "string", + default = "CratesNvimPopupValue", + description = [[ Highlight group used for the updated date. ]], }) -entry(schema_popup_hi, "downloads_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "downloads_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the download count label. ]], }) -entry(schema_popup_hi, "downloads", { - type = "string", - default = "CratesNvimPopupValue", - description = [[ +entry(schema_popup_hi, { + name = "downloads", + type = "string", + default = "CratesNvimPopupValue", + description = [[ Highlight group used for the download count. ]], }) -entry(schema_popup_hi, "homepage_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "homepage_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the homepage url label. ]], }) -entry(schema_popup_hi, "homepage", { - type = "string", - default = "CratesNvimPopupUrl", - description = [[ +entry(schema_popup_hi, { + name = "homepage", + type = "string", + default = "CratesNvimPopupUrl", + description = [[ Highlight group used for the homepage url. ]], }) -entry(schema_popup_hi, "repository_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "repository_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the repository url label. ]], }) -entry(schema_popup_hi, "repository", { - type = "string", - default = "CratesNvimPopupUrl", - description = [[ +entry(schema_popup_hi, { + name = "repository", + type = "string", + default = "CratesNvimPopupUrl", + description = [[ Highlight group used for the repository url. ]], }) -entry(schema_popup_hi, "documentation_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "documentation_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the documentation url label. ]], }) -entry(schema_popup_hi, "documentation", { - type = "string", - default = "CratesNvimPopupUrl", - description = [[ +entry(schema_popup_hi, { + name = "documentation", + type = "string", + default = "CratesNvimPopupUrl", + description = [[ Highlight group used for the documentation url. ]], }) -entry(schema_popup_hi, "crates_io_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "crates_io_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the crates.io url label. ]], }) -entry(schema_popup_hi, "crates_io", { - type = "string", - default = "CratesNvimPopupUrl", - description = [[ +entry(schema_popup_hi, { + name = "crates_io", + type = "string", + default = "CratesNvimPopupUrl", + description = [[ Highlight group used for the crates.io url. ]], }) -entry(schema_popup_hi, "categories_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "categories_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the categories label. ]], }) -entry(schema_popup_hi, "keywords_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ +entry(schema_popup_hi, { + name = "keywords_label", + type = "string", + default = "CratesNvimPopupLabel", + description = [[ Highlight group used for the keywords label. ]], }) - -entry(schema_popup_hi, "version", { - type = "string", - default = "CratesNvimPopupVersion", - description = [[ +-- versions +entry(schema_popup_hi, { + name = "version", + type = "string", + default = "CratesNvimPopupVersion", + description = [[ Highlight group used for versions inside the popup. ]], }) -entry(schema_popup_hi, "prerelease", { - type = "string", - default = "CratesNvimPopupPreRelease", - description = [[ +entry(schema_popup_hi, { + name = "prerelease", + type = "string", + default = "CratesNvimPopupPreRelease", + description = [[ Highlight group used for pre-release versions inside the popup. ]], }) -entry(schema_popup_hi, "yanked", { - type = "string", - default = "CratesNvimPopupYanked", - description = [[ +entry(schema_popup_hi, { + name = "yanked", + type = "string", + default = "CratesNvimPopupYanked", + description = [[ Highlight group used for yanked versions inside the popup. ]], }) -entry(schema_popup_hi, "version_date", { - type = "string", - default = "CratesNvimPopupVersionDate", - description = [[ +entry(schema_popup_hi, { + name = "version_date", + type = "string", + default = "CratesNvimPopupVersionDate", + description = [[ Highlight group used for the version date inside the popup. ]], }) - -entry(schema_popup_hi, "feature", { - type = "string", - default = "CratesNvimPopupFeature", - description = [[ +-- features +entry(schema_popup_hi, { + name = "feature", + type = "string", + default = "CratesNvimPopupFeature", + description = [[ Highlight group used for disabled features inside the popup. ]], }) -entry(schema_popup_hi, "enabled", { - type = "string", - default = "CratesNvimPopupEnabled", - description = [[ +entry(schema_popup_hi, { + name = "enabled", + type = "string", + default = "CratesNvimPopupEnabled", + description = [[ Highlight group used for enabled features inside the popup. ]], }) -entry(schema_popup_hi, "transitive", { - type = "string", - default = "CratesNvimPopupTransitive", - description = [[ +entry(schema_popup_hi, { + name = "transitive", + type = "string", + default = "CratesNvimPopupTransitive", + description = [[ Highlight group used for transitively enabled features inside the popup. ]], }) - -entry(schema_popup_hi, "normal_dependencies_title", { - type = "string", - default = "CratesNvimPopupNormalDependenciesTitle", - description = [[ +-- dependencies +entry(schema_popup_hi, { + name = "normal_dependencies_title", + type = "string", + default = "CratesNvimPopupNormalDependenciesTitle", + description = [[ Highlight group used for the title of the normal dependencies section. ]], }) -entry(schema_popup_hi, "build_dependencies_title", { - type = "string", - default = "CratesNvimPopupBuildDependenciesTitle", - description = [[ +entry(schema_popup_hi, { + name = "build_dependencies_title", + type = "string", + default = "CratesNvimPopupBuildDependenciesTitle", + description = [[ Highlight group used for the title of the build dependencies section. ]], }) -entry(schema_popup_hi, "dev_dependencies_title", { - type = "string", - default = "CratesNvimPopupDevDependenciesTitle", - description = [[ +entry(schema_popup_hi, { + name = "dev_dependencies_title", + type = "string", + default = "CratesNvimPopupDevDependenciesTitle", + description = [[ Highlight group used for the title of the dev dependencies section. ]], }) -entry(schema_popup_hi, "dependency", { - type = "string", - default = "CratesNvimPopupDependency", - description = [[ +entry(schema_popup_hi, { + name = "dependency", + type = "string", + default = "CratesNvimPopupDependency", + description = [[ Highlight group used for dependencies inside the popup. ]], }) -entry(schema_popup_hi, "optional", { - type = "string", - default = "CratesNvimPopupOptional", - description = [[ +entry(schema_popup_hi, { + name = "optional", + type = "string", + default = "CratesNvimPopupOptional", + description = [[ Highlight group used for optional dependencies inside the popup. ]], }) -entry(schema_popup_hi, "dependency_version", { - type = "string", - default = "CratesNvimPopupDependencyVersion", - description = [[ +entry(schema_popup_hi, { + name = "dependency_version", + type = "string", + default = "CratesNvimPopupDependencyVersion", + description = [[ Highlight group used for the dependency version inside the popup. ]], }) -entry(schema_popup_hi, "loading", { - type = "string", - default = "CratesNvimPopupLoading", - description = [[ +entry(schema_popup_hi, { + name = "loading", + type = "string", + default = "CratesNvimPopupLoading", + description = [[ Highlight group for the loading indicator inside the popup. ]], }) -entry(schema_popup, "keys", { - type = "section", - description = [[ +entry(schema_popup, { + name = "keys", + type = "section", + description = [[ Key mappings inside the popup. ]], - fields = {}, + fields = {}, }) local schema_popup_keys = schema_popup.keys.fields -entry(schema_popup_keys, "hide", { - type = "table", - default = { "q", "" }, - description = [[ +entry(schema_popup_keys, { + name = "hide", + type = "table", + default = { "q", "" }, + description = [[ Hides the popup. ]], }) - -entry(schema_popup_keys, "open_url", { - type = "table", - default = { "" }, - description = [[ +-- crate +entry(schema_popup_keys, { + name = "open_url", + type = "table", + default = { "" }, + description = [[ Key mappings to open the url on the current line. ]], }) - -entry(schema_popup_keys, "select", { - type = "table", - default = { "" }, - description = [[ +-- versions +entry(schema_popup_keys, { + name = "select", + type = "table", + default = { "" }, + description = [[ Key mappings to insert the version respecting the |crates-config-smart_insert| flag. ]], }) -entry(schema_popup_keys, "select_alt", { - type = "table", - default = { "s" }, - description = [[ +entry(schema_popup_keys, { + name = "select_alt", + type = "table", + default = { "s" }, + description = [[ Key mappings to insert the version using the opposite of |crates-config-smart_insert| flag. ]], }) - -entry(schema_popup_keys, "toggle_feature", { - type = "table", - default = { "" }, - description = [[ +-- features +entry(schema_popup_keys, { + name = "toggle_feature", + type = "table", + default = { "" }, + description = [[ Key mappings to enable or disable the feature on the current line inside the popup. ]], }) - -entry(schema_popup_keys, "copy_value", { - type = "table", - default = { "yy" }, - description = [[ +-- common +entry(schema_popup_keys, { + name = "copy_value", + type = "table", + default = { "yy" }, + description = [[ Key mappings to copy the value on the current line inside the popup. ]], }) -entry(schema_popup_keys, "goto_item", { - type = "table", - default = { "gd", "K", "" }, - description = [[ +entry(schema_popup_keys, { + name = "goto_item", + type = "table", + default = { "gd", "K", "" }, + description = [[ Key mappings to go to the item on the current line inside the popup. ]], }) -entry(schema_popup_keys, "jump_forward", { - type = "table", - default = { "" }, - description = [[ +entry(schema_popup_keys, { + name = "jump_forward", + type = "table", + default = { "" }, + description = [[ Key mappings to jump forward in the popup jump history. ]], }) -entry(schema_popup_keys, "jump_back", { - type = "table", - default = { "", "" }, - description = [[ +entry(schema_popup_keys, { + name = "jump_back", + type = "table", + default = { "", "" }, + description = [[ Key mappings to go back in the popup jump history. ]], }) - -entry(schema_popup_keys, "goto_feature", { - type = "table", - deprecated = { - new_field = { "popup", "keys", "goto_item" }, - hard = true, - }, -}) -entry(schema_popup_keys, "jump_forward_feature", { - type = "table", - deprecated = { - new_field = { "popup", "keys", "jump_forward" }, - hard = true, - }, -}) -entry(schema_popup_keys, "jump_back_feature", { - type = "table", - deprecated = { - new_field = { "popup", "keys", "jump_back" }, - hard = true, - }, -}) -entry(schema_popup_keys, "copy_version", { - type = "table", - deprecated = { - new_field = { "popup", "keys", "copy_value" }, - hard = true, - }, -}) - - -entry(M.schema, "src", { - type = "section", - description = [[ +-- deprecated +entry(schema_popup_keys, { + name = "goto_feature", + type = "table", + deprecated = { + new_field = { "popup", "keys", "goto_item" }, + hard = true, + } +}) +entry(schema_popup_keys, { + name = "jump_forward_feature", + type = "table", + deprecated = { + new_field = { "popup", "keys", "jump_forward" }, + hard = true, + } +}) +entry(schema_popup_keys, { + name = "jump_back_feature", + type = "table", + deprecated = { + new_field = { "popup", "keys", "jump_back" }, + hard = true, + } +}) +entry(schema_popup_keys, { + name = "copy_version", + type = "table", + deprecated = { + new_field = { "popup", "keys", "copy_value" }, + hard = true, + } +}) + + +local schema_src = section_entry(M.schema, { + name = "src", + type = "section", + description = [[ Configuration options for completion sources. ]], - fields = {}, + fields = {}, }) -local schema_src = M.schema.src.fields -entry(schema_src, "insert_closing_quote", { - type = "boolean", - default = true, - description = [[ +entry(schema_src, { + name = "insert_closing_quote", + type = "boolean", + default = true, + description = [[ Insert a closing quote on completion if there is none. ]], }) -entry(schema_src, "text", { - type = "section", - description = [[ +entry(schema_src, { + name = "text", + type = "section", + description = [[ Text shown in the completion source documentation preview. ]], - fields = {}, + fields = {}, }) local schema_src_text = schema_src.text.fields -entry(schema_src_text, "prerelease", { - type = "string", - default = "  pre-release ", - description = [[ +entry(schema_src_text, { + name = "prerelease", + type = "string", + default = "  pre-release ", + description = [[ Text shown in the completion source documentation preview for pre-release versions. ]], }) -entry(schema_src_text, "yanked", { - type = "string", - default = "  yanked ", - description = [[ +entry(schema_src_text, { + name = "yanked", + type = "string", + default = "  yanked ", + description = [[ Text shown in the completion source documentation preview for yanked versions. ]], }) -entry(schema_src, "cmp", { - type = "section", - description = [[ +entry(schema_src, { + name = "cmp", + type = "section", + description = [[ Configuration options for the |nvim-cmp| completion source. ]], - fields = {}, + fields = {}, }) local schema_src_cmp = schema_src.cmp.fields -entry(schema_src_cmp, "enabled", { - type = "boolean", - default = false, - description = [[ +entry(schema_src_cmp, { + name = "enabled", + type = "boolean", + default = false, + description = [[ Whether to load and register the |nvim-cmp| source. NOTE: Ignored if |crates-config-autoload| is disabled. @@ -1388,316 +1557,364 @@ entry(schema_src_cmp, "enabled", { < ]], }) -entry(schema_src_cmp, "use_custom_kind", { - type = "boolean", - default = true, - description = [[ +entry(schema_src_cmp, { + name = "use_custom_kind", + type = "boolean", + default = true, + description = [[ Use custom a custom kind to display inside the |nvim-cmp| completion menu. ]], }) -entry(schema_src_cmp, "kind_text", { - type = "section", - description = [[ +entry(schema_src_cmp, { + name = "kind_text", + type = "section", + description = [[ The kind text shown in the |nvim-cmp| completion menu. ]], - fields = {}, + fields = {}, }) local schema_src_cmp_kind_text = schema_src_cmp.kind_text.fields -entry(schema_src_cmp_kind_text, "version", { - type = "string", - default = "Version", - description = [[ +entry(schema_src_cmp_kind_text, { + name = "version", + type = "string", + default = "Version", + description = [[ The version kind text shown in the |nvim-cmp| completion menu. ]], }) -entry(schema_src_cmp_kind_text, "feature", { - type = "string", - default = "Feature", - description = [[ +entry(schema_src_cmp_kind_text, { + name = "feature", + type = "string", + default = "Feature", + description = [[ The feature kind text shown in the |nvim-cmp| completion menu. ]], }) -entry(schema_src_cmp, "kind_highlight", { - type = "section", - description = [[ +entry(schema_src_cmp, { + name = "kind_highlight", + type = "section", + description = [[ Highlight groups used for the kind text in the |nvim-cmp| completion menu. ]], - fields = {}, + fields = {}, }) local schema_src_cmp_kind_hi = schema_src_cmp.kind_highlight.fields -entry(schema_src_cmp_kind_hi, "version", { - type = "string", - default = "CmpItemKindVersion", - description = [[ +entry(schema_src_cmp_kind_hi, { + name = "version", + type = "string", + default = "CmpItemKindVersion", + description = [[ Highlight group used for the version kind text in the |nvim-cmp| completion menu. ]], }) -entry(schema_src_cmp_kind_hi, "feature", { - type = "string", - default = "CmpItemKindFeature", - description = [[ +entry(schema_src_cmp_kind_hi, { + name = "feature", + type = "string", + default = "CmpItemKindFeature", + description = [[ Highlight group used for the feature kind text in the |nvim-cmp| completion menu. ]], }) -entry(schema_src, "coq", { - type = "section", - description = [[ +entry(schema_src, { + name = "coq", + type = "section", + description = [[ Configuration options for the |coq_nvim| completion source. ]], - fields = {}, + fields = {}, }) local schema_src_coq = schema_src.coq.fields -entry(schema_src_coq, "enabled", { - type = "boolean", - default = false, - description = [[ +entry(schema_src_coq, { + name = "enabled", + type = "boolean", + default = false, + description = [[ Whether to load and register the |coq_nvim| source. ]], }) -entry(schema_src_coq, "name", { - type = "string", - default = "crates.nvim", - description = [[ +entry(schema_src_coq, { + name = "name", + type = "string", + default = "crates.nvim", + description = [[ The source name displayed by |coq_nvim|. ]], }) -entry(M.schema, "null_ls", { - type = "section", - description = [[ +local schema_null_ls = section_entry(M.schema, { + name = "null_ls", + type = "section", + description = [[ Configuration options for null-ls.nvim actions. ]], - fields = {}, + fields = {}, }) -local schema_null_ls = M.schema.null_ls.fields -entry(schema_null_ls, "enabled", { - type = "boolean", - default = false, - description = [[ +entry(schema_null_ls, { + name = "enabled", + type = "boolean", + default = false, + description = [[ Whether to register the |null-ls.nvim| source. ]], }) -entry(schema_null_ls, "name", { - type = "string", - default = "crates.nvim", - description = [[ +entry(schema_null_ls, { + name = "name", + type = "string", + default = "crates.nvim", + description = [[ The |null-ls.nvim| name. ]], }) -entry(M.schema, "lsp", { - type = "section", - description = [[ +local schema_lsp = section_entry(M.schema, { + name = "lsp", + type = "section", + description = [[ Configuration options for the in-process language server. ]], - fields = {}, + fields = {}, }) -local schema_lsp = M.schema.lsp.fields -entry(schema_lsp, "enabled", { - type = "boolean", - default = false, - description = [[ +entry(schema_lsp, { + name = "enabled", + type = "boolean", + default = false, + description = [[ Whether to enable the in-process language server. ]], }) -entry(schema_lsp, "name", { - type = "string", - default = "crates.nvim", - description = [[ +entry(schema_lsp, { + name = "name", + type = "string", + default = "crates.nvim", + description = [[ The lsp server name. ]], }) -entry(schema_lsp, "on_attach", { - type = "function", - default = function(_client, _bufnr) end, - default_text = "function(client, bufnr) end", - description = [[ +entry(schema_lsp, { + name = "on_attach", + type = "function", + default = function(_client, _bufnr) end, + default_text = "function(client, bufnr) end", + description = [[ Callback to run when the in-process language server attaches to a buffer. NOTE: Ignored if |crates-config-autoload| is disabled. ]], }) -entry(schema_lsp, "actions", { - type = "boolean", - default = false, - description = [[ +entry(schema_lsp, { + name = "actions", + type = "boolean", + default = false, + description = [[ Whether to enable the `codeActionProvider` capability. ]], }) -entry(schema_lsp, "completion", { - type = "boolean", - default = false, - description = [[ +entry(schema_lsp, { + name = "completion", + type = "boolean", + default = false, + description = [[ Whether to enable the `completionProvider` capability. ]], }) - +---@param s string +---@param ... any local function warn(s, ...) - vim.notify(s:format(...), vim.log.levels.WARN, { title = "crates.nvim" }) + vim.notify(s:format(...), vim.log.levels.WARN, { title = "crates.nvim" }) end +---@param path string[] +---@param component string +---@return string[] local function join_path(path, component) - local p = {} - for i, c in ipairs(path) do - p[i] = c - end - table.insert(p, component) - return p + ---@type string[] + local p = {} + for i,c in ipairs(path) do + p[i] = c + end + table.insert(p, component) + return p end +---@param t table +---@param path string[] +---@param value any local function table_set_path(t, path, value) - local current = t - for i, c in ipairs(path) do - if i == #path then - current[c] = value - elseif type(current[c]) == "table" then - current = current[c] - elseif current[c] == nil then - current[c] = {} - current = current[c] - else - break - end - end + ---@type table + local current = t + for i,c in ipairs(path) do + if i == #path then + current[c] = value + elseif type(current[c]) == "table" then + ---@type table + current = current[c] + elseif current[c] == nil then + current[c] = {} + current = current[c] + else + break -- don't overwrite existing value + end + end end +---comment +---@param path string[] +---@param schema table +---@param root_config table +---@param user_config table local function handle_deprecated(path, schema, root_config, user_config) - for k, v in pairs(user_config) do - local elem = schema[k] - - if elem then - local p = join_path(path, k) - local dep = elem.deprecated - - if dep then - if dep.new_field and not dep.hard then - table_set_path(root_config, dep.new_field, v) + for k,v in pairs(user_config) do + local elem = schema[k] + + if elem then + local p = join_path(path, k) + local dep = elem.deprecated + + if dep then + if dep.new_field and not dep.hard then + table_set_path(root_config, dep.new_field, v) + end + elseif elem.type == "section" and type(v) == "table" then + ---@cast elem SectionSchemaElement|HiddenSectionSchemaElement + ---@cast v table + handle_deprecated(p, elem.fields, root_config, v) end - elseif elem.type == "section" and type(v) == "table" then - handle_deprecated(p, elem.fields, root_config, v) - end - end - end + end + end end +---@param path string[] +---@param schema table +---@param user_config table local function validate_schema(path, schema, user_config) - for k, v in pairs(user_config) do - local p = join_path(path, k) - local elem = schema[k] - - if elem then - local value_type = type(v) - local dep = elem.deprecated - - if dep then - if dep.new_field then - local dep_text - if dep.hard then - dep_text = "deprecated and won't work anymore" - else - dep_text = "deprecated and will stop working soon" - end - - warn( - "'%s' is now %s, please use '%s'", - table.concat(p, "."), - dep_text, - table.concat(dep.new_field, ".")) - + for k,v in pairs(user_config) do + local p = join_path(path, k) + local elem = schema[k] + + if elem then + local value_type = type(v) + local dep = elem.deprecated + + if dep then + if dep.new_field then + ---@type string + local dep_text + if dep.hard then + dep_text = "deprecated and won't work anymore" + else + dep_text = "deprecated and will stop working soon" + end + + warn( + "'%s' is now %s, please use '%s'", + table.concat(p, "."), + dep_text, + table.concat(dep.new_field, ".") + ) + else + warn( + "'%s' is now deprecated, ignoring", + table.concat(p, ".") + ) + end + elseif elem.type == "section" then + if value_type == "table" then + ---@cast elem SectionSchemaElement|HiddenSectionSchemaElement + validate_schema(p, elem.fields, v) + else + warn( + "Config field '%s' was expected to be of type 'table' but was '%s', using default value.", + table.concat(p, "."), + value_type + ) + end else - warn( - "'%s' is now deprecated, ignoring", - table.concat(p, ".")) - + ---@type SchemaType[] + local elem_types + if type(elem.type) == "string" then + elem_types = { elem.type } + else + ---@type SchemaType[] + elem_types = elem.type + end + + if not vim.tbl_contains(elem_types, value_type) then + warn( + "Config field '%s' was expected to be of type '%s' but was '%s', using default value.", + table.concat(p, "."), + table.concat(elem_types, " or "), + value_type + ) + end end - elseif elem.type == "section" then + else + warn( + "Ignoring invalid config key '%s'", + table.concat(p, ".") + ) + end + end +end + +---@param schema table +---@param user_config table +---@return table +local function build_config(schema, user_config) + ---@type table + local config = {} + + for k,elem in pairs(schema) do + local v = user_config[k] + local value_type = type(v) + + if elem.type == "section" then + ---@cast elem SectionSchemaElement|HiddenSectionSchemaElement if value_type == "table" then - validate_schema(p, elem.fields, v) + config[k] = build_config(elem.fields, v) else - warn( - "Config field '%s' was expected to be of type 'table' but was '%s', using default value.", - table.concat(p, "."), - value_type) - + config[k] = build_config(elem.fields, {}) end - else + else + ---@type SchemaType[] local elem_types if type(elem.type) == "string" then - elem_types = { elem.type } + elem_types = { elem.type } else - elem_types = elem.type + ---@type SchemaType[] + elem_types = elem.type end - if not vim.tbl_contains(elem_types, value_type) then - warn( - "Config field '%s' was expected to be of type '%s' but was '%s', using default value.", - table.concat(p, "."), - table.concat(elem_types, " or "), - value_type) - + if vim.tbl_contains(elem_types, value_type) then + config[k] = v + else + config[k] = elem.default end - end - else - warn( - "Ignoring invalid config key '%s'", - table.concat(p, ".")) - - end - end -end + end + end -local function build_config(schema, user_config) - local config = {} - - for k, elem in pairs(schema) do - local v = user_config[k] - local value_type = type(v) - - if elem.type == "section" then - if value_type == "table" then - config[k] = build_config(elem.fields, v) - else - config[k] = build_config(elem.fields, {}) - end - else - local elem_types - if type(elem.type) == "string" then - elem_types = { elem.type } - else - elem_types = elem.type - end - - if vim.tbl_contains(elem_types, value_type) then - config[k] = v - else - config[k] = elem.default - end - end - end - - return config + return config end +---comment +---@param user_config table|nil +---@return Config function M.build(user_config) - user_config = user_config or {} - local config_type = type(user_config) - if config_type ~= "table" then - warn("Expected config of type 'table' found '%s'", config_type) - user_config = {} - end - - handle_deprecated({}, M.schema, user_config, user_config) - - validate_schema({}, M.schema, user_config) - - return build_config(M.schema, user_config) + user_config = user_config or {} + local config_type = type(user_config) + if config_type ~= "table" then + warn("Expected config of type 'table' found '%s'", config_type) + user_config = {} + end + + handle_deprecated({}, M.schema, user_config, user_config) + validate_schema({}, M.schema, user_config) + return build_config(M.schema, user_config) end - return M diff --git a/lua/crates/core.lua b/lua/crates/core.lua deleted file mode 100644 index b93e5ee2..00000000 --- a/lua/crates/core.lua +++ /dev/null @@ -1,222 +0,0 @@ -local M = {} - - - - - - - -local api = require("crates.api") -local async = require("crates.async") -local diagnostic = require("crates.diagnostic") -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Version = types.Version -local ui = require("crates.ui") -local util = require("crates.util") - -M.throttled_updates = {} - - -M.reload_deps = async.wrap(function(crate_name, versions, version) - local deps, cancelled = api.fetch_deps(crate_name, version.num) - if cancelled then return end - - if deps then - version.deps = deps - for _, d in ipairs(deps) do - - if d.opt and not version.features:get_feat(d.name) then - version.features:insert({ - name = d.name, - members = {}, - }) - end - end - version.features:sort() - - for b, cache in pairs(state.buf_cache) do - - for _, c in pairs(cache.crates) do - if c:package() == crate_name then - local avoid_pre = not c:vers_is_pre() - local m, p, y = util.get_newest(versions, avoid_pre, c:vers_reqs()) - local match = m or p or y - - if c.vers and match == version and vim.api.nvim_buf_is_loaded(b) then - local diagnostics = diagnostic.process_crate_deps(c, version, deps) - ui.display_diagnostics(b, diagnostics) - end - end - end - end - end -end) - -M.reload_crate = async.wrap(function(crate_name) - local crate, cancelled = api.fetch_crate(crate_name) - local versions = crate and crate.versions - if cancelled then return end - - if crate and versions[1] then - state.api_cache[crate.name] = crate - end - - for b, cache in pairs(state.buf_cache) do - - for k, c in pairs(cache.crates) do - - - - if c.dep_kind ~= "registry" or c.registry ~= nil then - goto continue - end - - if c:package() == crate_name and vim.api.nvim_buf_is_loaded(b) then - local info, diagnostics = diagnostic.process_api_crate(c, crate) - cache.info[k] = info - vim.list_extend(cache.diagnostics, diagnostics) - - ui.display_crate_info(b, info, diagnostics) - - local version = info.vers_match or info.vers_upgrade - if version then - M.reload_deps(c:package(), versions, version) - end - end - - ::continue:: - end - end -end) - -local function update(buf, reload) - buf = buf or util.current_buf() - - if reload then - state.api_cache = {} - api.cancel_jobs() - end - - local sections, crates = toml.parse_crates(buf) - local crate_cache, diagnostics = diagnostic.process_crates(sections, crates) - local cache = { - crates = crate_cache, - info = {}, - diagnostics = diagnostics, - } - state.buf_cache[buf] = cache - - ui.clear(buf) - ui.display_diagnostics(buf, diagnostics) - for k, c in pairs(crate_cache) do - - - - if c.dep_kind ~= "registry" or c.registry ~= nil then - goto continue - end - - local api_crate = state.api_cache[c:package()] - local versions = api_crate and api_crate.versions - - if not reload and api_crate then - local info, c_diagnostics = diagnostic.process_api_crate(c, api_crate) - cache.info[k] = info - vim.list_extend(cache.diagnostics, c_diagnostics) - - ui.display_crate_info(buf, info, c_diagnostics) - - local version = info.vers_match or info.vers_upgrade - if version then - if version.deps then - local d_diagnostics = diagnostic.process_crate_deps(c, version, version.deps) - vim.list_extend(cache.diagnostics, d_diagnostics) - - ui.display_diagnostics(buf, d_diagnostics) - else - M.reload_deps(c:package(), versions, version) - end - end - else - if state.cfg.loading_indicator then - ui.display_loading(buf, c) - end - - M.reload_crate(c:package()) - end - - ::continue:: - end - - local callbacks = M.throttled_updates[buf] - if callbacks then - for _, callback in ipairs(callbacks) do - callback() - end - end - M.throttled_updates[buf] = nil -end - -function M.throttled_update(buf, reload) - buf = buf or util.current_buf() - local existing = M.throttled_updates[buf] - if not existing then - M.throttled_updates[buf] = {} - end - - M.inner_throttled_update(buf, reload) -end - -function M.await_throttled_update_if_any(buf) - local existing = M.throttled_updates[buf] - if not existing then - return false - end - - coroutine.yield(function(resolve) - table.insert(existing, resolve) - end) - - return true -end - -function M.hide() - state.visible = false - for b, _ in pairs(state.buf_cache) do - ui.clear(b) - end -end - -function M.show() - state.visible = true - - - local buf = util.current_buf() - update(buf, false) - - for b, _ in pairs(state.buf_cache) do - if b ~= buf then - update(b, false) - end - end -end - -function M.toggle() - if state.visible then - M.hide() - else - M.show() - end -end - -function M.update(buf) - update(buf, false) -end - -function M.reload(buf) - update(buf, true) -end - -return M diff --git a/teal/crates/core.tl b/lua/crates/core.tl similarity index 100% rename from teal/crates/core.tl rename to lua/crates/core.tl diff --git a/lua/crates/diagnostic.lua b/lua/crates/diagnostic.lua deleted file mode 100644 index 27da1f12..00000000 --- a/lua/crates/diagnostic.lua +++ /dev/null @@ -1,360 +0,0 @@ -local M = {} - - - - - - - - - - - -local CrateScope = M.CrateScope -local SectionScope = M.SectionScope -local edit = require("crates.edit") -local semver = require("crates.semver") -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Crate = types.Crate -local CrateInfo = types.CrateInfo -local Dependency = types.Dependency -local Diagnostic = types.Diagnostic -local DiagnosticKind = types.DiagnosticKind -local Version = types.Version -local util = require("crates.util") - -local function section_diagnostic( - section, - kind, - severity, - scope, - data) - - local d = Diagnostic.new({ - lnum = section.lines.s, - end_lnum = section.lines.e - 1, - col = 0, - end_col = 999, - severity = severity, - kind = kind, - data = data, - }) - - if scope == "header" then - d.end_lnum = d.lnum + 1 - end - - return d -end - -local function crate_diagnostic( - crate, - kind, - severity, - scope, - data) - - local d = Diagnostic.new({ - lnum = crate.lines.s, - end_lnum = crate.lines.e - 1, - col = 0, - end_col = 999, - severity = severity, - kind = kind, - data = data, - }) - - if not scope then - return d - end - - if scope == "vers" then - if crate.vers then - d.lnum = crate.vers.line - d.end_lnum = crate.vers.line - d.col = crate.vers.col.s - d.end_col = crate.vers.col.e - end - elseif scope == "def" then - if crate.def then - d.lnum = crate.def.line - d.end_lnum = crate.def.line - d.col = crate.def.col.s - d.end_col = crate.def.col.e - end - elseif scope == "feat" then - if crate.feat then - d.lnum = crate.feat.line - d.end_lnum = crate.feat.line - d.col = crate.feat.col.s - d.end_col = crate.feat.col.e - end - end - - return d -end - -local function feat_diagnostic( - crate, - feat, - kind, - severity, - data) - - return Diagnostic.new({ - lnum = crate.feat.line, - end_lnum = crate.feat.line, - col = crate.feat.col.s + feat.col.s, - end_col = crate.feat.col.s + feat.col.e, - severity = severity, - kind = kind, - data = data, - }) -end - -function M.process_crates(sections, crates) - local diagnostics = {} - local s_cache = {} - local cache = {} - - for _, s in ipairs(sections) do - local key = s.text:gsub("%s+", "") - - if s.workspace and s.kind ~= "default" then - table.insert(diagnostics, section_diagnostic( - s, - "workspace_section_not_default", - vim.diagnostic.severity.WARN)) - - elseif s.workspace and s.target ~= nil then - table.insert(diagnostics, section_diagnostic( - s, - "workspace_section_has_target", - vim.diagnostic.severity.ERROR)) - - elseif s.invalid then - table.insert(diagnostics, section_diagnostic( - s, - "section_invalid", - vim.diagnostic.severity.WARN)) - - elseif s_cache[key] then - table.insert(diagnostics, section_diagnostic( - s_cache[key], - "section_dup_orig", - vim.diagnostic.severity.HINT, - "header", - { lines = s_cache[key].lines })) - - table.insert(diagnostics, section_diagnostic( - s, - "section_dup", - vim.diagnostic.severity.ERROR)) - - else - s_cache[key] = s - end - end - - for _, c in ipairs(crates) do - local key = c:cache_key() - if c.section.invalid then - goto continue - end - - if cache[key] then - table.insert(diagnostics, crate_diagnostic( - cache[key], - "crate_dup_orig", - vim.diagnostic.severity.HINT)) - - table.insert(diagnostics, crate_diagnostic( - c, - "crate_dup", - vim.diagnostic.severity.ERROR)) - - else - cache[key] = c - - if c.def then - if c.def.text ~= "false" and c.def.text ~= "true" then - table.insert(diagnostics, crate_diagnostic( - c, - "def_invalid", - vim.diagnostic.severity.ERROR, - "def")) - - end - end - - local feats = {} - for _, f in ipairs(c:feats()) do - local orig = feats[f.name] - if orig then - table.insert(diagnostics, feat_diagnostic( - c, - feats[f.name], - "feat_dup_orig", - vim.diagnostic.severity.HINT, - { feat = orig })) - - table.insert(diagnostics, feat_diagnostic( - c, - f, - "feat_dup", - vim.diagnostic.severity.WARN, - { feat = f })) - - else - feats[f.name] = f - end - end - end - - ::continue:: - end - - return cache, diagnostics -end - -function M.process_api_crate(crate, api_crate) - local avoid_pre = not crate:vers_is_pre() - local versions = api_crate and api_crate.versions - local newest, newest_pre, newest_yanked = util.get_newest(versions, avoid_pre, nil) - newest = newest or newest_pre or newest_yanked - - local info = { - lines = crate.lines, - vers_line = crate.vers and crate.vers.line or crate.lines.s, - } - local diagnostics = {} - - if crate.dep_kind == "registry" then - if api_crate then - if api_crate.name ~= crate:package() then - table.insert(diagnostics, crate_diagnostic( - crate, - "crate_name_case", - vim.diagnostic.severity.ERROR, - nil, - { crate = crate, crate_name = api_crate.name })) - - end - end - - if newest then - if semver.matches_requirements(newest.parsed, crate:vers_reqs()) then - - info.vers_match = newest - info.match_kind = "version" - - if crate.vers and crate.vers.text ~= edit.version_text(crate, newest.parsed) then - info.vers_update = newest - end - else - - local match, match_pre, match_yanked = util.get_newest(versions, avoid_pre, crate:vers_reqs()) - info.vers_match = match or match_pre or match_yanked - info.vers_upgrade = newest - - if info.vers_match then - if crate.vers and crate.vers.text ~= edit.version_text(crate, info.vers_match.parsed) then - info.vers_update = info.vers_match - end - end - - if state.cfg.enable_update_available_warning then - table.insert(diagnostics, crate_diagnostic( - crate, - "vers_upgrade", - vim.diagnostic.severity.WARN, - "vers")) - - end - - if match then - - info.match_kind = "version" - elseif match_pre then - - info.match_kind = "prerelease" - table.insert(diagnostics, crate_diagnostic( - crate, - "vers_pre", - vim.diagnostic.severity.ERROR, - "vers")) - - elseif match_yanked then - - info.match_kind = "yanked" - table.insert(diagnostics, crate_diagnostic( - crate, - "vers_yanked", - vim.diagnostic.severity.ERROR, - "vers")) - - else - - info.match_kind = "nomatch" - local kind = "vers_nomatch" - if not crate.vers then - kind = "crate_novers" - end - table.insert(diagnostics, crate_diagnostic( - crate, - kind, - vim.diagnostic.severity.ERROR, - "vers")) - - end - end - else - table.insert(diagnostics, crate_diagnostic( - crate, - "crate_error_fetching", - vim.diagnostic.severity.ERROR, - "vers")) - - end - end - - return info, diagnostics -end - -function M.process_crate_deps(crate, version, deps) - if crate.path or crate.git then - return {} - end - - local diagnostics = {} - - local valid_feats = {} - for _, f in ipairs(version.features.list) do - table.insert(valid_feats, f.name) - end - for _, d in ipairs(deps) do - if d.opt then - table.insert(valid_feats, d.name) - end - end - - if not state.cfg.disable_invalid_feature_diagnostic then - for _, f in ipairs(crate:feats()) do - if not vim.tbl_contains(valid_feats, f.name) then - table.insert(diagnostics, feat_diagnostic( - crate, - f, - "feat_invalid", - vim.diagnostic.severity.ERROR, - { feat = f })) - - end - end - end - - return diagnostics -end - -return M diff --git a/teal/crates/diagnostic.tl b/lua/crates/diagnostic.tl similarity index 100% rename from teal/crates/diagnostic.tl rename to lua/crates/diagnostic.tl diff --git a/lua/crates/edit.lua b/lua/crates/edit.lua index 7f64e91a..79f82572 100644 --- a/lua/crates/edit.lua +++ b/lua/crates/edit.lua @@ -1,524 +1,576 @@ -local M = {} - local semver = require("crates.semver") local state = require("crates.state") -local toml = require("crates.toml") local types = require("crates.types") -local CrateInfo = types.CrateInfo -local Feature = types.Feature -local Range = types.Range +local Span = types.Span local SemVer = types.SemVer -local Requirement = types.Requirement +local M = {} + +---comment +---@param buf integer +---@param crate TomlCrate +---@param name string function M.rename_crate_package(buf, crate, name) - local line, col - if crate.pkg then - line = crate.pkg.line - col = crate.pkg.col - else - line = crate.lines.s - col = crate.explicit_name_col - end - - vim.api.nvim_buf_set_text(buf, line, col.s, line, col.e, { name }) + ---@type integer, Span + local line, col + if crate.pkg then + line = crate.pkg.line + col = crate.pkg.col + else + line = crate.lines.s + col = crate.explicit_name_col + end + + vim.api.nvim_buf_set_text(buf, line, col.s, line, col.e, { name }) end +---@param buf integer +---@param crate TomlCrate +---@param text string +---@return Span local function insert_version(buf, crate, text) - if not crate.vers then - if crate.syntax == "table" then - local line = crate.lines.s + 1 - vim.api.nvim_buf_set_lines( - buf, line, line, false, - { 'version = "' .. text .. '"' }) - - return crate.lines:moved(0, 1) - elseif crate.syntax == "inline_table" then - local line = crate.lines.s - local col = math.min( - crate.pkg and crate.pkg.col.s or 999, - crate.def and crate.def.col.s or 999, - crate.feat and crate.def.col.s or 999, - crate.git and crate.git.decl_col.s or 999, - crate.path and crate.path.decl_col.s or 999) - - vim.api.nvim_buf_set_text( - buf, line, col, line, col, - { ' version = "' .. text .. '",' }) - - return Range.pos(line) - elseif crate.syntax == "plain" then - return Range.empty() - end - else - local t = text - if state.cfg.insert_closing_quote and not crate.vers.quote.e then - t = text .. crate.vers.quote.s - end - local line = crate.vers.line - - vim.api.nvim_buf_set_text( - buf, - line, - crate.vers.col.s, - line, - crate.vers.col.e, - { t }) - - return Range.pos(line) - end + if not crate.vers then + if crate.syntax == "table" then + local line = crate.lines.s + 1 + vim.api.nvim_buf_set_lines( + buf, line, line, false, + { 'version = "' .. text .. '"' } + ) + return crate.lines:moved(0, 1) + elseif crate.syntax == "inline_table" then + local line = crate.lines.s + local col = math.min( + crate.pkg and crate.pkg.col.s or 999, + crate.def and crate.def.col.s or 999, + crate.feat and crate.def.col.s or 999, + crate.git and crate.git.decl_col.s or 999, + crate.path and crate.path.decl_col.s or 999 + ) + vim.api.nvim_buf_set_text( + buf, line, col, line, col, + { ' version = "' .. text .. '",' } + ) + return Span.pos(line) + else -- crate.syntax == "plain" + return Span.empty() -- unreachable + end + else + local t = text + if state.cfg.insert_closing_quote and not crate.vers.quote.e then + t = text .. crate.vers.quote.s + end + local line = crate.vers.line + + vim.api.nvim_buf_set_text( + buf, + line, + crate.vers.col.s, + line, + crate.vers.col.e, + { t } + ) + return Span.pos(line) + end end +---@param r Requirement +---@param version SemVer +---@return SemVer local function replace_existing(r, version) - if version.pre then - return version - else - return SemVer.new({ - major = version.major, - minor = r.vers.minor and version.minor or nil, - patch = r.vers.patch and version.patch or nil, - }) - end -end - -function M.smart_version_text(crate, version) - if #crate:vers_reqs() == 0 then - return version:display() - end - - local pos = 1 - local text = "" - for _, r in ipairs(crate:vers_reqs()) do - if r.cond == "eq" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "wl" then - if version.pre then - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. version:display() - else - local v = SemVer.new({ - major = r.vers.major and version.major or nil, - minor = r.vers.minor and version.minor or nil, - }) - local before = string.sub(crate.vers.text, pos, r.vers_col.s) - local after = string.sub(crate.vers.text, r.vers_col.e + 1, r.cond_col.e) - text = text .. before .. v:display() .. after - end - elseif r.cond == "tl" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "cr" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "bl" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "lt" and not semver.matches_requirement(version, r) then - local v = SemVer.new({ + if version.pre then + return version + else + return SemVer.new({ major = version.major, minor = r.vers.minor and version.minor or nil, patch = r.vers.patch and version.patch or nil, - }) - - if v.patch then - v.patch = v.patch + 1 - elseif v.minor then - v.minor = v.minor + 1 - elseif v.major then - v.major = v.major + 1 - end - - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "le" and not semver.matches_requirement(version, r) then - local v - - if version.pre then - v = version - else - v = SemVer.new({ major = version.major }) - if r.vers.minor or version.minor and version.minor > 0 then - v.minor = version.minor + }) + end +end + +---@param crate TomlCrate +---@param version SemVer +---@return string +function M.smart_version_text(crate, version) + if #crate:vers_reqs() == 0 then + return version:display() + end + + local pos = 1 + local text = "" + for _,r in ipairs(crate:vers_reqs()) do + if r.cond == "eq" then + local v = replace_existing(r, version) + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() + elseif r.cond == "wl" then + if version.pre then + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. version:display() + else + local v = SemVer.new { + major = r.vers.major and version.major or nil, + minor = r.vers.minor and version.minor or nil, + } + local before = string.sub(crate.vers.text, pos, r.vers_col.s) + local after = string.sub(crate.vers.text, r.vers_col.e + 1, r.cond_col.e) + text = text .. before .. v:display() .. after end - if r.vers.patch or version.patch and version.patch > 0 then - v.minor = version.minor - v.patch = version.patch + elseif r.cond == "tl" then + local v = replace_existing(r, version) + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() + elseif r.cond == "cr" then + local v = replace_existing(r, version) + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() + elseif r.cond == "bl" then + local v = replace_existing(r, version) + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() + elseif r.cond == "lt" and not semver.matches_requirement(version, r) then + local v = SemVer.new { + major = version.major, + minor = r.vers.minor and version.minor or nil, + patch = r.vers.patch and version.patch or nil, + } + + if v.patch then + v.patch = v.patch + 1 + elseif v.minor then + v.minor = v.minor + 1 + elseif v.major then + v.major = v.major + 1 end - end - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "gt" and not semver.matches_requirement(version, r) then - local v = SemVer.new({ - major = r.vers.major and version.major or nil, - minor = r.vers.minor and version.minor or nil, - patch = r.vers.patch and version.patch or nil, - }) - - if v.patch then - v.patch = v.patch - 1 - if v.patch < 0 then - v.patch = 0 - v.minor = v.minor - 1 - end - elseif v.minor then - v.minor = v.minor - 1 - if v.minor < 0 then - v.minor = 0 - v.major = v.major - 1 + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() + elseif r.cond == "le" and not semver.matches_requirement(version, r) then + ---@type SemVer + local v + + if version.pre then + v = version + else + v = SemVer.new { major = version.major } + if r.vers.minor or version.minor and version.minor > 0 then + v.minor = version.minor + end + if r.vers.patch or version.patch and version.patch > 0 then + v.minor = version.minor + v.patch = version.patch + end end - elseif v.major then - v.major = v.major - 1 - if v.major < 0 then - v.major = 0 + + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() + elseif r.cond == "gt" and not semver.matches_requirement(version, r) then + local v = SemVer.new { + major = r.vers.major and version.major or nil, + minor = r.vers.minor and version.minor or nil, + patch = r.vers.patch and version.patch or nil, + } + + if v.patch then + v.patch = v.patch - 1 + if v.patch < 0 then + v.patch = 0 + v.minor = v.minor - 1 + end + elseif v.minor then + v.minor = v.minor - 1 + if v.minor < 0 then + v.minor = 0 + v.major = v.major - 1 + end + elseif v.major then + v.major = v.major - 1 + if v.major < 0 then + v.major = 0 + end end - end - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "ge" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - else - text = text .. string.sub(crate.vers.text, pos, r.vers_col.e) - end + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() + elseif r.cond == "ge" then + local v = replace_existing(r, version) + text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() + else + text = text .. string.sub(crate.vers.text, pos, r.vers_col.e) + end - pos = math.max(r.cond_col.e + 1, r.vers_col.e + 1) - end - text = text .. string.sub(crate.vers.text, pos) + pos = math.max(r.cond_col.e + 1, r.vers_col.e + 1) + end + text = text .. string.sub(crate.vers.text, pos) - return text + return text end +---@param crate TomlCrate +---@param version SemVer +---@param alt boolean|nil +---@return string function M.version_text(crate, version, alt) - local smart = alt ~= state.cfg.smart_insert - if smart then - return M.smart_version_text(crate, version) - else - return version:display() - end + local smart = alt ~= state.cfg.smart_insert + if smart then + return M.smart_version_text(crate, version) + else + return version:display() + end end +---@param buf integer +---@param crate TomlCrate +---@param version SemVer +---@param alt boolean|nil +---@return Span function M.set_version(buf, crate, version, alt) - local text = M.version_text(crate, version, alt) - return insert_version(buf, crate, text) + local text = M.version_text(crate, version, alt) + return insert_version(buf, crate, text) end +---@param buf integer +---@param crates table +---@param info table +---@param alt boolean|nil function M.upgrade_crates(buf, crates, info, alt) - for k, c in pairs(crates) do - local i = info[k] - - if i then - local version = i.vers_upgrade or i.vers_update - if version then - M.set_version(buf, c, version.parsed, alt) - end - end - end + for k,c in pairs(crates) do + local i = info[k] + + if i then + local version = i.vers_upgrade or i.vers_update + if version then + M.set_version(buf, c, version.parsed, alt) + end + end + end end +---@param buf integer +---@param crates table +---@param info table +---@param alt boolean|nil function M.update_crates(buf, crates, info, alt) - for k, c in pairs(crates) do - local i = info[k] - - if i then - local version = i.vers_update - if version then - M.set_version(buf, c, version.parsed, alt) - end - end - end + for k,c in pairs(crates) do + local i = info[k] + + if i then + local version = i.vers_update + if version then + M.set_version(buf, c, version.parsed, alt) + end + end + end end +---@param buf integer +---@param crate TomlCrate +---@param feature ApiFeature +---@return Span function M.enable_feature(buf, crate, feature) - local t = '"' .. feature.name .. '"' - if not crate.feat then - if crate.syntax == "table" then - local line = math.max( - crate.vers and crate.vers.line + 1 or 0, - crate.pkg and crate.pkg.line + 1 or 0, - crate.def and crate.def.line + 1 or 0) - - line = line ~= 0 and line or math.min( - crate.git and crate.git.line or 999, - crate.path and crate.path.line or 999) - - vim.api.nvim_buf_set_lines( - buf, line, line, false, - { "features = [" .. t .. "]" }) - - return Range.pos(line) - elseif crate.syntax == "plain" then - t = ", features = [" .. t .. "] }" - local line = crate.vers.line - local col = crate.vers.col.e - if crate.vers.quote.e then - col = col + 1 - else - t = crate.vers.quote.s .. t - end - vim.api.nvim_buf_set_text(buf, line, col, line, col, { t }) - - vim.api.nvim_buf_set_text( - buf, - line, - crate.vers.col.s - 1, - line, - crate.vers.col.s - 1, - { "{ version = " }) - - return Range.pos(line) - elseif crate.syntax == "inline_table" then - local line = crate.lines.s - local text = ", features = [" .. t .. "]" - local col = math.max( - crate.vers and crate.vers.col.e + (crate.vers.quote.e and 1 or 0) or 0, - crate.pkg and crate.pkg.col.e or 0, - crate.def and crate.def.col.e or 0) - - if col == 0 then - text = " features = [" .. t .. "]," - col = math.min( - crate.git and crate.git.decl_col.s or 999, - crate.path and crate.path.decl_col.s or 999) - - end - vim.api.nvim_buf_set_text( - buf, line, col, line, col, - { text }) - - return Range.pos(line) - end - else - local last_feat = crate.feat.items[#crate.feat.items] - if last_feat then - if not last_feat.comma then - t = ", " .. t - end - if not last_feat.quote.e then - t = last_feat.quote.s .. t - end - end - - vim.api.nvim_buf_set_text( - buf, - crate.feat.line, - crate.feat.col.e, - crate.feat.line, - crate.feat.col.e, - { t }) - - return Range.pos(crate.feat.line) - end + local t = '"' .. feature.name .. '"' + if not crate.feat then + if crate.syntax == "table" then + local line = math.max( + crate.vers and crate.vers.line + 1 or 0, + crate.pkg and crate.pkg.line + 1 or 0, + crate.def and crate.def.line + 1 or 0 + ) + line = line ~= 0 and line or math.min( + crate.git and crate.git.line or 999, + crate.path and crate.path.line or 999 + ) + vim.api.nvim_buf_set_lines( + buf, line, line, false, + { "features = [" .. t .."]" } + ) + return Span.pos(line) + elseif crate.syntax == "plain" then + t = ", features = [" .. t .. "] }" + local line = crate.vers.line + local col = crate.vers.col.e + if crate.vers.quote.e then + col = col + 1 + else + t = crate.vers.quote.s .. t + end + vim.api.nvim_buf_set_text(buf, line, col, line, col, { t }) + + vim.api.nvim_buf_set_text( + buf, + line, + crate.vers.col.s - 1, + line, + crate.vers.col.s - 1, + { "{ version = " } + ) + return Span.pos(line) + elseif crate.syntax == "inline_table" then + local line = crate.lines.s + local text = ", features = [" .. t .. "]" + local col = math.max( + crate.vers and crate.vers.col.e + (crate.vers.quote.e and 1 or 0) or 0, + crate.pkg and crate.pkg.col.e or 0, + crate.def and crate.def.col.e or 0 + ) + if col == 0 then + text = " features = [" .. t .. "]," + col = math.min( + crate.git and crate.git.decl_col.s or 999, + crate.path and crate.path.decl_col.s or 999 + ) + end + vim.api.nvim_buf_set_text( + buf, line, col, line, col, + { text } + ) + return Span.pos(line) + end + else + local last_feat = crate.feat.items[#crate.feat.items] + if last_feat then + if not last_feat.comma then + t = ", " .. t + end + if not last_feat.quote.e then + t = last_feat.quote.s .. t + end + end + + vim.api.nvim_buf_set_text( + buf, + crate.feat.line, + crate.feat.col.e, + crate.feat.line, + crate.feat.col.e, + { t } + ) + return Span.pos(crate.feat.line) + end end +---@param buf integer +---@param crate TomlCrate +---@param feature TomlFeature +---@return Span function M.disable_feature(buf, crate, feature) - - local index - for i, f in ipairs(crate.feat.items) do - if f == feature then - index = i - break - end - end - if not index then return end - - local col_start = feature.decl_col.s - local col_end = feature.decl_col.e - if index == 1 then - if #crate.feat.items > 1 then - col_end = crate.feat.items[2].col.s - 1 - elseif feature.comma then - col_end = col_end + 1 - end - else - local prev_feature = crate.feat.items[index - 1] - col_start = prev_feature.col.e + 1 - end - - vim.api.nvim_buf_set_text( - buf, - crate.feat.line, - crate.feat.col.s + col_start, - crate.feat.line, - crate.feat.col.s + col_end, - { "" }) - - return Range.pos(crate.feat.line) + -- check reference in case of duplicates + ---@type integer + local index + for i,f in ipairs(crate.feat.items) do + if f == feature then + index = i + break + end + end + assert(index) + + local col_start = feature.decl_col.s + local col_end = feature.decl_col.e + if index == 1 then + if #crate.feat.items > 1 then + col_end = crate.feat.items[2].col.s - 1 + elseif feature.comma then + col_end = col_end + 1 + end + else + local prev_feature = crate.feat.items[index - 1] + col_start = prev_feature.col.e + 1 + end + + vim.api.nvim_buf_set_text( + buf, + crate.feat.line, + crate.feat.col.s + col_start, + crate.feat.line, + crate.feat.col.s + col_end, + { "" } + ) + return Span.pos(crate.feat.line) end +---@param buf integer +---@param crate TomlCrate +---@return Span function M.enable_def_features(buf, crate) - vim.api.nvim_buf_set_text( - buf, - crate.def.line, - crate.def.col.s, - crate.def.line, - crate.def.col.e, - { "true" }) - - return Range.pos(crate.def.line) + vim.api.nvim_buf_set_text( + buf, + crate.def.line, + crate.def.col.s, + crate.def.line, + crate.def.col.e, + { "true" } + ) + return Span.pos(crate.def.line) end +---@param buf integer +---@param crate TomlCrate +---@return Span local function disable_def_features(buf, crate) - if crate.def then - local line = crate.def.line - vim.api.nvim_buf_set_text( - buf, - line, - crate.def.col.s, - line, - crate.def.col.e, - { "false" }) - - return crate.lines - else - if crate.syntax == "table" then - local line = math.max( - crate.vers and crate.vers.line + 1 or 0, - crate.pkg and crate.pkg.line + 1 or 0) - - line = line ~= 0 and line or math.min( - crate.feat and crate.feat.line or 999, - crate.git and crate.git.line or 999, - crate.path and crate.path.line or 999) - - vim.api.nvim_buf_set_lines( - buf, - line, - line, - false, - { "default-features = false" }) - - return crate.lines:moved(0, 1) - elseif crate.syntax == "plain" then - local t = ", default-features = false }" - local col = crate.vers.col.e - if crate.vers.quote.e then - col = col + 1 - else - t = crate.vers.quote.s .. t - end - local line = crate.vers.line - vim.api.nvim_buf_set_text( - buf, - line, - col, - line, - col, - { t }) - - - vim.api.nvim_buf_set_text( - buf, - line, - crate.vers.col.s - 1, - line, - crate.vers.col.s - 1, - { "{ version = " }) - - return crate.lines - elseif crate.syntax == "inline_table" then - local line = crate.lines.s - local text = ", default-features = false" - local col = math.max( - crate.vers and crate.vers.col.e + (crate.vers.quote.e and 1 or 0) or 0, - crate.pkg and crate.pkg.col.e or 0) - - if col == 0 then - text = " default-features = false," - col = math.min( - crate.feat and crate.def.col.s or 999, - crate.git and crate.git.decl_col.s or 999, - crate.path and crate.path.decl_col.s or 999) - - end - vim.api.nvim_buf_set_text( - buf, line, col, line, col, - { text }) - - return crate.lines - end - end + if crate.def then + local line = crate.def.line + vim.api.nvim_buf_set_text( + buf, + line, + crate.def.col.s, + line, + crate.def.col.e, + { "false" } + ) + return crate.lines + else + if crate.syntax == "table" then + local line = math.max( + crate.vers and crate.vers.line + 1 or 0, + crate.pkg and crate.pkg.line + 1 or 0 + ) + line = line ~= 0 and line or math.min( + crate.feat and crate.feat.line or 999, + crate.git and crate.git.line or 999, + crate.path and crate.path.line or 999 + ) + vim.api.nvim_buf_set_lines( + buf, + line, + line, + false, + { "default-features = false" } + ) + return crate.lines:moved(0, 1) + elseif crate.syntax == "plain" then + local t = ", default-features = false }" + local col = crate.vers.col.e + if crate.vers.quote.e then + col = col + 1 + else + t = crate.vers.quote.s .. t + end + local line = crate.vers.line + vim.api.nvim_buf_set_text( + buf, + line, + col, + line, + col, + { t } + ) + + vim.api.nvim_buf_set_text( + buf, + line, + crate.vers.col.s - 1, + line, + crate.vers.col.s - 1, + { "{ version = " } + ) + return crate.lines + elseif crate.syntax == "inline_table" then + local line = crate.lines.s + local text = ", default-features = false" + local col = math.max( + crate.vers and crate.vers.col.e + (crate.vers.quote.e and 1 or 0) or 0, + crate.pkg and crate.pkg.col.e or 0 + ) + if col == 0 then + text = " default-features = false," + col = math.min( + crate.feat and crate.def.col.s or 999, + crate.git and crate.git.decl_col.s or 999, + crate.path and crate.path.decl_col.s or 999 + ) + end + vim.api.nvim_buf_set_text( + buf, line, col, line, col, + { text } + ) + return crate.lines + end + end end +---@param buf integer +---@param crate TomlCrate +---@param feature TomlFeature|nil +---@return Span function M.disable_def_features(buf, crate, feature) - if feature then - if crate.def and crate.def.col.s < crate.feat.col.s then - M.disable_feature(buf, crate, feature) - return disable_def_features(buf, crate) - else - local lines = disable_def_features(buf, crate) - M.disable_feature(buf, crate, feature) - return lines - end - else - return disable_def_features(buf, crate) - end + if feature then + if crate.def and crate.def.col.s < crate.feat.col.s then + M.disable_feature(buf, crate, feature) + return disable_def_features(buf, crate) + else + local lines = disable_def_features(buf, crate) + M.disable_feature(buf, crate, feature) + return lines + end + else + return disable_def_features(buf, crate) + end end +---@param buf integer +---@param crate TomlCrate function M.expand_plain_crate_to_inline_table(buf, crate) - if crate.syntax ~= "plain" then - return - end - - local text = crate.explicit_name .. ' = { version = "' .. crate.vers.text .. '" }' - vim.api.nvim_buf_set_text( - buf, crate.lines.s, crate.vers.decl_col.s, crate.lines.s, crate.vers.decl_col.e, - { text }) - - - if state.cfg.expand_crate_moves_cursor then - local pos = { crate.lines.s + 1, #text - 2 } - vim.api.nvim_win_set_cursor(0, pos) - end + if crate.syntax ~= "plain" then + return + end + + local text = crate.explicit_name .. ' = { version = "' .. crate.vers.text .. '" }' + vim.api.nvim_buf_set_text( + buf, crate.lines.s, crate.vers.decl_col.s, crate.lines.s, crate.vers.decl_col.e, + { text } + ) + + if state.cfg.expand_crate_moves_cursor then + local pos = { crate.lines.s + 1, #text - 2 } + vim.api.nvim_win_set_cursor(0, pos) + end end +---@param buf integer +---@param crate TomlCrate function M.extract_crate_into_table(buf, crate) - if crate.syntax == "table" then - return - end - - - vim.api.nvim_buf_set_lines(buf, crate.lines.s, crate.lines.e, false, {}) - - - local lines = { - crate.section:display(crate.explicit_name), - } - if crate.vers then - table.insert(lines, "version = " .. '"' .. crate.vers.text .. '"') - end - if crate.registry then - table.insert(lines, "registry = " .. '"' .. crate.registry.text .. '"') - end - if crate.path then - table.insert(lines, "path = " .. '"' .. crate.path.text .. '"') - end - if crate.git then - table.insert(lines, "git = " .. '"' .. crate.git.text .. '"') - end - if crate.branch then - table.insert(lines, "branch = " .. '"' .. crate.branch.text .. '"') - end - if crate.rev then - table.insert(lines, "rev = " .. '"' .. crate.rev.text .. '"') - end - if crate.pkg then - table.insert(lines, "package = " .. '"' .. crate.pkg.text .. '"') - end - if crate.workspace then - table.insert(lines, "workspace = " .. '"' .. crate.workspace.text .. '"') - end - if crate.def then - table.insert(lines, "default-features = " .. '"' .. crate.def.text .. '"') - end - if crate.feat then - table.insert(lines, "features = [" .. crate.feat.text .. "]") - end - if crate.opt then - table.insert(lines, "optional = " .. '"' .. crate.opt.text .. '"') - end - - table.insert(lines, "") - - local line = crate.section.lines.e - 1 - vim.api.nvim_buf_set_lines(buf, line, line, false, lines) + if crate.syntax == "table" then + return + end + + -- remove original line + vim.api.nvim_buf_set_lines( buf, crate.lines.s, crate.lines.e, false, {}) + + -- insert table after dependency section + local lines = { + crate.section:display(crate.explicit_name), + } + if crate.vers then + table.insert(lines, "version = " .. '"' .. crate.vers.text ..'"') + end + if crate.registry then + table.insert(lines, "registry = " .. '"' .. crate.registry.text ..'"') + end + if crate.path then + table.insert(lines, "path = " .. '"' .. crate.path.text ..'"') + end + if crate.git then + table.insert(lines, "git = " .. '"' .. crate.git.text ..'"') + end + if crate.branch then + table.insert(lines, "branch = " .. '"' .. crate.branch.text ..'"') + end + if crate.rev then + table.insert(lines, "rev = " .. '"' .. crate.rev.text ..'"') + end + if crate.pkg then + table.insert(lines, "package = " .. '"' .. crate.pkg.text ..'"') + end + if crate.workspace then + table.insert(lines, "workspace = " .. '"' .. crate.workspace.text ..'"') + end + if crate.def then + table.insert(lines, "default-features = " .. '"' .. crate.def.text ..'"') + end + if crate.feat then + table.insert(lines, "features = [" .. crate.feat.text .. "]") + end + if crate.opt then + table.insert(lines, "optional = " .. '"' .. crate.opt.text ..'"') + end + + table.insert(lines, "") + + local line = crate.section.lines.e - 1 + vim.api.nvim_buf_set_lines(buf, line, line, false, lines) end return M diff --git a/lua/crates/health.lua b/lua/crates/health.lua deleted file mode 100644 index e3813b1a..00000000 --- a/lua/crates/health.lua +++ /dev/null @@ -1,40 +0,0 @@ -local M = {} - -local state = require("crates.state") -local util = require("crates.util") - -function M.check() - vim.health.start("Checking required plugins") - if util.lualib_installed("plenary") then - vim.health.ok("plenary.nvim installed") - else - vim.health.error("plenary.nvim not found") - end - if util.lualib_installed("null-ls") then - vim.health.ok("null-ls.nvim installed") - else - vim.health.info("null-ls.nvim not found") - end - - vim.health.start("Checking external dependencies") - if util.binary_installed("curl") then - vim.health.ok("curl installed") - else - vim.health.error("curl not found") - end - - local num = 0 - for _, prg in ipairs(state.cfg.open_programs) do - if util.binary_installed(prg) then - vim.health.ok(string.format("%s installed", prg)) - num = num + 1 - end - end - - if num == 0 then - local programs = table.concat(state.cfg.open_programs, " ") - vim.health.warn("none of the following are installed " .. programs) - end -end - -return M diff --git a/teal/crates/health.tl b/lua/crates/health.tl similarity index 100% rename from teal/crates/health.tl rename to lua/crates/health.tl diff --git a/lua/crates/highlight.lua b/lua/crates/highlight.lua deleted file mode 100644 index 6cc4704d..00000000 --- a/lua/crates/highlight.lua +++ /dev/null @@ -1,46 +0,0 @@ -local M = {} - - - -M.highlights = { - { "CratesNvimLoading", { default = true, link = "DiagnosticVirtualTextInfo" } }, - { "CratesNvimVersion", { default = true, link = "DiagnosticVirtualTextInfo" } }, - { "CratesNvimPreRelease", { default = true, link = "DiagnosticVirtualTextError" } }, - { "CratesNvimYanked", { default = true, link = "DiagnosticVirtualTextError" } }, - { "CratesNvimNoMatch", { default = true, link = "DiagnosticVirtualTextError" } }, - { "CratesNvimUpgrade", { default = true, link = "DiagnosticVirtualTextWarn" } }, - { "CratesNvimError", { default = true, link = "DiagnosticVirtualTextError" } }, - - { "CratesNvimPopupTitle", { default = true, link = "Title" } }, - { "CratesNvimPopupPillText", { default = true, ctermfg = 15, ctermbg = 242, fg = "#e0e0e0", bg = "#3a3a3a" } }, - { "CratesNvimPopupPillBorder", { default = true, ctermfg = 242, fg = "#3a3a3a" } }, - { "CratesNvimPopupDescription", { default = true, link = "Comment" } }, - { "CratesNvimPopupLabel", { default = true, link = "Identifier" } }, - { "CratesNvimPopupValue", { default = true, link = "String" } }, - { "CratesNvimPopupUrl", { default = true, link = "Underlined" } }, - { "CratesNvimPopupVersion", { default = true, link = "None" } }, - { "CratesNvimPopupPreRelease", { default = true, link = "DiagnosticVirtualTextWarn" } }, - { "CratesNvimPopupYanked", { default = true, link = "DiagnosticVirtualTextError" } }, - { "CratesNvimPopupVersionDate", { default = true, link = "Comment" } }, - { "CratesNvimPopupFeature", { default = true, link = "None" } }, - { "CratesNvimPopupEnabled", { default = true, ctermfg = 2, fg = "#23ab49" } }, - { "CratesNvimPopupTransitive", { default = true, ctermfg = 4, fg = "#238bb9" } }, - { "CratesNvimPopupNormalDependenciesTitle", { default = true, link = "Statement" } }, - { "CratesNvimPopupBuildDependenciesTitle", { default = true, link = "Statement" } }, - { "CratesNvimPopupDevDependenciesTitle", { default = true, link = "Statement" } }, - { "CratesNvimPopupDependency", { default = true, link = "None" } }, - { "CratesNvimPopupOptional", { default = true, link = "Comment" } }, - { "CratesNvimPopupDependencyVersion", { default = true, link = "String" } }, - { "CratesNvimPopupLoading", { default = true, link = "Special" } }, - - { "CmpItemKindVersion", { default = true, link = "Special" } }, - { "CmpItemKindFeature", { default = true, link = "Special" } }, -} - -function M.define() - for _, h in ipairs(M.highlights) do - vim.api.nvim_set_hl(0, h[1], h[2]) - end -end - -return M diff --git a/teal/crates/highlight.tl b/lua/crates/highlight.tl similarity index 100% rename from teal/crates/highlight.tl rename to lua/crates/highlight.tl diff --git a/lua/crates/init.lua b/lua/crates/init.lua deleted file mode 100644 index d34f6238..00000000 --- a/lua/crates/init.lua +++ /dev/null @@ -1,180 +0,0 @@ -local M = {} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -local actions = require("crates.actions") -local async = require("crates.async") -local command = require("crates.command") -local config = require("crates.config") -local Config = config.Config -local core = require("crates.core") -local highlight = require("crates.highlight") -local popup = require("crates.popup") -local state = require("crates.state") - - - - - - -function M.attach() - if state.cfg.src.cmp.enabled then - require("crates.src.cmp").setup() - end - - if state.cfg.lsp.enabled then - require("crates.lsp").start_server() - end - - core.update() - state.cfg.on_attach(vim.api.nvim_get_current_buf()) -end - -function M.setup(cfg) - state.cfg = config.build(cfg) - - command.register() - highlight.define() - - local group = vim.api.nvim_create_augroup("Crates", {}) - if state.cfg.autoload then - if vim.fn.expand("%:t") == "Cargo.toml" then - M.attach() - end - - vim.api.nvim_create_autocmd("BufRead", { - group = group, - pattern = "Cargo.toml", - callback = function(_) - M.attach() - end, - }) - end - - - core.inner_throttled_update = async.throttle(M.update, state.cfg.autoupdate_throttle) - - if state.cfg.autoupdate then - vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "TextChangedP" }, { - group = group, - pattern = "Cargo.toml", - callback = function() - core.throttled_update(nil, false) - end, - }) - end - - vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, { - group = group, - pattern = "Cargo.toml", - callback = function() - popup.hide() - end, - }) - - if state.cfg.src.coq.enabled then - require("crates.src.coq").setup(state.cfg.src.coq.name) - end - - if state.cfg.null_ls.enabled then - require("crates.null-ls").setup(state.cfg.null_ls.name) - end -end - -M.hide = core.hide -M.show = core.show -M.toggle = core.toggle -M.update = core.update -M.reload = core.reload - -M.upgrade_crate = actions.upgrade_crate -M.upgrade_crates = actions.upgrade_crates -M.upgrade_all_crates = actions.upgrade_all_crates -M.update_crate = actions.update_crate -M.update_crates = actions.update_crates -M.update_all_crates = actions.update_all_crates - -M.expand_plain_crate_to_inline_table = actions.expand_plain_crate_to_inline_table -M.extract_crate_into_table = actions.extract_crate_into_table - -M.open_homepage = actions.open_homepage -M.open_repository = actions.open_repository -M.open_documentation = actions.open_documentation -M.open_crates_io = actions.open_crates_io - -M.popup_available = popup.available -M.show_popup = popup.show -M.show_crate_popup = popup.show_crate -M.show_versions_popup = popup.show_versions -M.show_features_popup = popup.show_features -M.show_dependencies_popup = popup.show_dependencies -M.focus_popup = popup.focus -M.hide_popup = popup.hide - -return M diff --git a/teal/crates/init.tl b/lua/crates/init.tl similarity index 100% rename from teal/crates/init.tl rename to lua/crates/init.tl diff --git a/lua/crates/lsp.lua b/lua/crates/lsp.lua deleted file mode 100644 index a0177b76..00000000 --- a/lua/crates/lsp.lua +++ /dev/null @@ -1,143 +0,0 @@ -local M = {Server = {}, ServerOpts = {}, CodeAction = {}, Command = {}, } - - - - - - - - - - - - - - - - - - - - - - - - -local Server = M.Server -local CodeAction = M.CodeAction -local Command = M.Command - -local actions = require("crates.actions") -local util = require("crates.util") -local state = require("crates.state") -local src = require("crates.src.common") - -function M.server(opts) - opts = opts or {} - local capabilities = opts.capabilities or {} - local on_request = opts.on_request or function(_, _) end - local on_notify = opts.on_notify or function(_, _) end - local handlers = opts.handlers or {} - - return function(dispatchers) - local closing = false - local srv = {} - local request_id = 0 - - function srv.request(method, params, callback) - pcall(on_request, method, params) - local handler = handlers[method] - if handler then - handler(method, params, callback) - elseif method == "initialize" then - callback(nil, { - capabilities = capabilities, - }) - elseif method == "shutdown" then - callback(nil, nil) - end - request_id = request_id + 1 - return true, request_id - end - - function srv.notify(method, params) - pcall(on_notify, method, params) - if method == "exit" then - dispatchers.on_exit(0, 15) - end - end - - function srv.is_closing() - return closing - end - - function srv.terminate() - closing = true - end - - return srv - end -end - -function M.start_server() - local commands = { - ["crates_command"] = function(cmd, ctx) - local action = cmd.arguments[1] - if action then - vim.api.nvim_buf_call(ctx.bufnr, action) - else - util.notify(vim.log.levels.INFO, "Action not available '%s'", action) - end - end, - } - - local server = M.server({ - capabilities = { - codeActionProvider = state.cfg.lsp.actions, - completionProvider = state.cfg.lsp.completion and { - triggerCharacters = src.trigger_characters, - }, - }, - handlers = { - ["textDocument/codeAction"] = function(_, _, callback) - local code_actions = {} - for key, action in pairs(actions.get_actions()) do - local title = util.format_title(key) - table.insert(code_actions, { - title = title, - kind = "refactor.rewrite", - command = { - title = title, - command = key, - arguments = { action }, - }, - }) - end - callback(nil, code_actions) - end, - ["textDocument/completion"] = function(_, _, callback) - src.complete(function(items) - callback(nil, items) - end) - end, - }, - }) - local client_id = vim.lsp.start({ - name = state.cfg.lsp.name, - cmd = server, - commands = commands, - }) - if not client_id then - return - end - - local client = vim.lsp.get_client_by_id(client_id) - if not client then - return - end - - local buf = vim.api.nvim_get_current_buf() - state.cfg.lsp.on_attach(client, buf) -end - -return M diff --git a/teal/crates/lsp.tl b/lua/crates/lsp.tl similarity index 100% rename from teal/crates/lsp.tl rename to lua/crates/lsp.tl diff --git a/lua/crates/null-ls.lua b/lua/crates/null-ls.lua deleted file mode 100644 index 87234dda..00000000 --- a/lua/crates/null-ls.lua +++ /dev/null @@ -1,51 +0,0 @@ -local M = {} - -local util = require("crates.util") -local actions = require("crates.actions") - -local ok, null_ls = pcall(require, "null-ls") -if not ok then - util.notify(vim.log.levels.WARN, "null-ls.nvim was not found") - return { - setup = function(_) end, - } -end -local null_ls_methods = require("null-ls.methods") -local CODE_ACTION = null_ls_methods.internal.CODE_ACTION - -function M.source(name) - return { - name = name, - meta = { - url = "https://github.com/saecki/crates.nvim", - description = "Code actions for editing `Cargo.toml` files.", - }, - method = CODE_ACTION, - filetypes = { "toml" }, - generator = { - opts = { - runtime_condition = function(params) - return params.bufname:match("Cargo%.toml$") ~= nil - end, - }, - fn = function(params) - local items = {} - for key, action in pairs(actions.get_actions()) do - table.insert(items, { - title = util.format_title(key), - action = function() - vim.api.nvim_buf_call(params.bufnr, action) - end, - }) - end - return items - end, - }, - } -end - -function M.setup(name) - null_ls.register(M.source(name)) -end - -return M diff --git a/teal/crates/null-ls.tl b/lua/crates/null-ls.tl similarity index 100% rename from teal/crates/null-ls.tl rename to lua/crates/null-ls.tl diff --git a/lua/crates/popup/common.lua b/lua/crates/popup/common.lua deleted file mode 100644 index 6a3dbb80..00000000 --- a/lua/crates/popup/common.lua +++ /dev/null @@ -1,210 +0,0 @@ -local M = {WinOpts = {}, HighlightText = {}, } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -local HighlightText = M.HighlightText -local WinOpts = M.WinOpts -local state = require("crates.state") - -M.TOP_OFFSET = 2 -M.POPUP_NS = vim.api.nvim_create_namespace("crates.nvim.popup") -M.LOADING_NS = vim.api.nvim_create_namespace("crates.nvim.popup.loading") - - -function M.focus(line) - if M.win and vim.api.nvim_win_is_valid(M.win) then - vim.api.nvim_set_current_win(M.win) - local l = math.min((line or 2) + 1, vim.api.nvim_buf_line_count(M.buf)) - vim.api.nvim_win_set_cursor(M.win, { l, 0 }) - end -end - -function M.hide() - if M.win and vim.api.nvim_win_is_valid(M.win) then - vim.api.nvim_win_close(M.win, false) - end - M.win = nil - - if M.buf and vim.api.nvim_buf_is_valid(M.buf) then - vim.api.nvim_buf_delete(M.buf, {}) - end - M.buf = nil - M.type = nil - - M.transaction = nil -end - -function M.item_index(line) - return line - M.TOP_OFFSET + 1 -end - -function M.win_height(entries) - return math.min( - #entries + M.TOP_OFFSET, - state.cfg.popup.max_height) - -end - -function M.win_width(title, content_width) - return math.max( - vim.fn.strdisplaywidth(title) + vim.fn.strdisplaywidth(state.cfg.popup.text.loading), - content_width, - state.cfg.popup.min_width) + - 2 * state.cfg.popup.padding -end - -local function set_buf_body(text) - local lines = {} - for _, line in ipairs(text) do - local padding = string.rep(" ", state.cfg.popup.padding) - local line_text = padding - for _, t in ipairs(line) do - line_text = line_text .. t.text - end - line_text = line_text .. padding - table.insert(lines, line_text) - end - vim.api.nvim_buf_set_lines(M.buf, M.TOP_OFFSET, M.TOP_OFFSET + #lines, false, lines) - - for i, line in ipairs(text) do - local pos = state.cfg.popup.padding - for _, t in ipairs(line) do - vim.api.nvim_buf_add_highlight(M.buf, M.POPUP_NS, t.hl, M.TOP_OFFSET + i - 1, pos, pos + t.text:len()) - pos = pos + t.text:len() - end - end -end - -function M.update_buf_body(text) - vim.api.nvim_buf_set_option(M.buf, "modifiable", true) - set_buf_body(text) - vim.api.nvim_buf_set_option(M.buf, "modifiable", false) -end - -local function set_buf_content(buf, title, text) - vim.api.nvim_buf_set_option(buf, "modifiable", true) - - - vim.api.nvim_buf_set_lines(buf, 0, -1, false, {}) - vim.api.nvim_buf_clear_namespace(buf, M.POPUP_NS, 0, -1) - - - local padding = string.rep(" ", state.cfg.popup.padding) - local title_text = padding .. title .. padding - vim.api.nvim_buf_set_lines(buf, 0, 2, false, { title_text, "" }) - vim.api.nvim_buf_add_highlight(buf, M.POPUP_NS, state.cfg.popup.highlight.title, 0, 0, -1) - - set_buf_body(text) - - vim.api.nvim_buf_set_option(buf, "modifiable", false) - vim.api.nvim_buf_set_option(buf, "buftype", "nofile") - vim.api.nvim_buf_set_option(buf, "swapfile", false) - vim.api.nvim_buf_set_option(buf, "filetype", "crates.nvim") - vim.api.nvim_buf_set_name(buf, "crates:popup") -end - -function M.update_win(width, height, title, text, opts) - - vim.api.nvim_win_set_width(M.win, width) - vim.api.nvim_win_set_height(M.win, height) - - - set_buf_content(M.buf, title, text) - - - local l = math.min((opts.line or 2) + 1, vim.api.nvim_buf_line_count(M.buf)) - vim.api.nvim_win_set_cursor(M.win, { l, 0 }) -end - -function M.open_win(width, height, title, text, opts, configure) - M.buf = vim.api.nvim_create_buf(false, true) - - - set_buf_content(M.buf, title, text) - - - M.win = vim.api.nvim_open_win(M.buf, false, { - relative = "cursor", - col = 0, - row = 1, - width = width, - height = height, - style = state.cfg.popup.style, - border = state.cfg.popup.border, - }) - - - for _, k in ipairs(state.cfg.popup.keys.hide) do - vim.api.nvim_buf_set_keymap(M.buf, "n", k, "", { - callback = function() - M.hide() - end, - noremap = true, - silent = true, - desc = "Hide popup", - }) - end - - if configure then - configure(M.win, M.buf) - end - - - if opts and opts.focus or state.cfg.popup.autofocus then - M.focus(opts and opts.line) - end -end - - -function M.hide_loading_indicator(transaction) - if transaction and transaction ~= M.transaction then - return - end - if M.buf then - vim.api.nvim_buf_clear_namespace(M.buf, M.LOADING_NS, 0, 1) - end -end - -function M.show_loading_indicator() - if M.buf then - vim.api.nvim_buf_clear_namespace(M.buf, M.LOADING_NS, 0, 1) - vim.api.nvim_buf_set_extmark(M.buf, M.LOADING_NS, 0, -1, { - virt_text = { { state.cfg.popup.text.loading, state.cfg.popup.highlight.loading } }, - virt_text_pos = "right_align", - hl_mode = "combine", - }) - end -end - -function M.omit_loading_transaction() - M.transaction = nil - M.hide_loading_indicator() -end - -return M diff --git a/teal/crates/popup/common.tl b/lua/crates/popup/common.tl similarity index 100% rename from teal/crates/popup/common.tl rename to lua/crates/popup/common.tl diff --git a/lua/crates/popup/crate.lua b/lua/crates/popup/crate.lua deleted file mode 100644 index a5de5433..00000000 --- a/lua/crates/popup/crate.lua +++ /dev/null @@ -1,256 +0,0 @@ -local M = {CrateContext = {}, } - - - - - - - - - - - - -local CrateContext = M.CrateContext -local popup = require("crates.popup.common") -local HighlightText = popup.HighlightText -local WinOpts = popup.WinOpts -local state = require("crates.state") -local types = require("crates.types") -local Crate = types.Crate -local util = require("crates.util") - -local function copy_value(ctx, line) - local function copy(value) - vim.fn.setreg(state.cfg.popup.copy_register, value) - end - - local index = popup.item_index(line) - if ctx.created_index == index then - copy(ctx.crate.created:display(state.cfg.date_format)) - elseif ctx.downloads_index == index then - copy(ctx.crate.downloads) - elseif ctx.homepage_index == index then - copy(ctx.crate.homepage) - elseif ctx.repo_index == index then - copy(ctx.crate.repository) - elseif ctx.docs_index == index then - copy(ctx.crate.documentation or util.docs_rs_url(ctx.crate.name)) - elseif ctx.crates_io_index == index then - copy(util.crates_io_url(ctx.crate.name)) - end -end - -local function open_url(ctx, line) - local index = popup.item_index(line) - if ctx.homepage_index == index then - util.open_url(ctx.crate.homepage) - elseif ctx.repo_index == index then - util.open_url(ctx.crate.repository) - elseif ctx.docs_index == index then - util.open_url(ctx.crate.documentation or util.docs_rs_url(ctx.crate.name)) - elseif ctx.crates_io_index == index then - util.open_url(util.crates_io_url(ctx.crate.name)) - end -end - -local function pill_hl_text(items) - local hl_text = {} - for i, kw in ipairs(items) do - if i ~= 1 then - table.insert(hl_text, { text = " ", hl = "None" }) - end - table.insert(hl_text, { - text = state.cfg.popup.text.pill_left, - hl = state.cfg.popup.highlight.pill_border, - }) - table.insert(hl_text, { - text = kw, - hl = state.cfg.popup.highlight.pill_text, - }) - table.insert(hl_text, { - text = state.cfg.popup.text.pill_right, - hl = state.cfg.popup.highlight.pill_border, - }) - end - return hl_text -end - -function M.open(crate, opts) - popup.type = "crate" - - local title = string.format(state.cfg.popup.text.title, crate.name) - local text = state.cfg.popup.text - local highlight = state.cfg.popup.highlight - local info_text = {} - local ctx = { - crate = crate, - } - - if crate.description then - local desc = crate.description:gsub("\r", "\n") - local lines = vim.split(desc, "\n") - for _, l in ipairs(lines) do - if l ~= "" then - table.insert(info_text, { { - text = string.format(text.description, l), - hl = highlight.description, - }, }) - end - end - end - table.insert(info_text, { { text = "", hl = "None" } }) - - if crate.created then - table.insert(info_text, { - { - text = text.created_label, - hl = highlight.created_label, - }, { - text = string.format(text.created, crate.created:display(state.cfg.date_format)), - hl = highlight.created, - }, - }) - ctx.created_index = #info_text - end - - if crate.updated then - table.insert(info_text, { - { - text = text.updated_label, - hl = highlight.updated_label, - }, { - text = string.format(text.updated, crate.updated:display(state.cfg.date_format)), - hl = highlight.updated, - }, - }) - ctx.updated_index = #info_text - end - - if crate.downloads then - local downloads = tostring(crate.downloads) - local _end = downloads:len() - local start = math.max(0, _end - 2) - local downloads_text = downloads:sub(start, _end) - for i = start - 1, 1, -3 do - local s = math.max(0, i - 2) - downloads_text = downloads:sub(s, i) .. state.cfg.thousands_separator .. downloads_text - end - - table.insert(info_text, { - { - text = text.downloads_label, - hl = highlight.downloads_label, - }, { - text = string.format(text.downloads, downloads_text), - hl = highlight.downloads, - }, - }) - ctx.downloads_index = #info_text - end - - if crate.homepage then - table.insert(info_text, { - { - text = text.homepage_label, - hl = highlight.homepage_label, - }, { - text = string.format(text.homepage, crate.homepage), - hl = highlight.homepage, - }, - }) - ctx.homepage_index = #info_text - end - - if crate.repository then - table.insert(info_text, { - { - text = text.repository_label, - hl = highlight.repository_label, - }, { - text = string.format(text.repository, crate.repository), - hl = highlight.repository, - }, - }) - ctx.repo_index = #info_text - end - - table.insert(info_text, { - { - text = text.documentation_label, - hl = highlight.documentation_label, - }, { - text = string.format(text.documentation, crate.documentation or util.docs_rs_url(crate.name)), - hl = highlight.documentation, - }, - }) - ctx.docs_index = #info_text - - table.insert(info_text, { - { - text = text.crates_io_label, - hl = highlight.crates_io_label, - }, { - text = string.format(text.crates_io, util.crates_io_url(crate.name)), - hl = highlight.crates_io, - }, - }) - ctx.crates_io_index = #info_text - - if next(crate.categories) then - local hl_text = { { - text = text.categories_label, - hl = highlight.categories_label, - }, } - vim.list_extend(hl_text, pill_hl_text(crate.categories)) - table.insert(info_text, hl_text) - end - - if next(crate.keywords) then - local hl_text = { { - text = text.keywords_label, - hl = highlight.keywords_label, - }, } - vim.list_extend(hl_text, pill_hl_text(crate.keywords)) - table.insert(info_text, hl_text) - end - - local content_width = 0 - for _, v in ipairs(info_text) do - local w = 0 - for _, t in ipairs(v) do - w = w + vim.fn.strdisplaywidth(t.text) - end - content_width = math.max(w, content_width) - end - - local width = popup.win_width(title, content_width) - local height = popup.win_height(info_text) - popup.open_win(width, height, title, info_text, opts, function(_win, buf) - for _, k in ipairs(state.cfg.popup.keys.copy_value) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - copy_value(ctx, line) - end, - noremap = true, - silent = true, - desc = "Copy value", - }) - end - - for _, k in ipairs(state.cfg.popup.keys.open_url) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - open_url(ctx, line) - end, - noremap = true, - silent = true, - desc = "Open url", - }) - end - end) -end - -return M diff --git a/teal/crates/popup/crate.tl b/lua/crates/popup/crate.tl similarity index 100% rename from teal/crates/popup/crate.tl rename to lua/crates/popup/crate.tl diff --git a/lua/crates/popup/dependencies.lua b/lua/crates/popup/dependencies.lua deleted file mode 100644 index 60f97c7d..00000000 --- a/lua/crates/popup/dependencies.lua +++ /dev/null @@ -1,305 +0,0 @@ -local M = {DepsContext = {}, DepsHistoryEntry = {}, } - - - - - - - - - - - - - - - - -local DepsContext = M.DepsContext -local api = require("crates.api") -local async = require("crates.async") -local core = require("crates.core") -local popup = require("crates.popup.common") -local HighlightText = popup.HighlightText -local WinOpts = popup.WinOpts -local state = require("crates.state") -local types = require("crates.types") -local Dependency = types.Dependency -local Version = types.Version -local util = require("crates.util") - -local goto_dep = async.wrap(function(ctx, line) - local hist_entry = ctx.history[ctx.hist_idx] - - local selected_dependency = hist_entry.line_mapping[line] - if not selected_dependency then return end - - - hist_entry.line = line - - local transaction = math.random() - popup.transaction = transaction - - local crate_name = selected_dependency.name - local crate = state.api_cache[crate_name] - - if not crate then - popup.show_loading_indicator() - - if not api.is_fetching_crate(crate_name) then - core.reload_crate(crate_name) - end - - local cancelled - crate, cancelled = api.await_crate(crate_name) - - popup.hide_loading_indicator(transaction) - if cancelled then return end - end - - - if popup.transaction ~= transaction then - return - end - - local m, p, y = util.get_newest(crate.versions, false, selected_dependency.vers.reqs) - local version = m or p or y - - if not version.deps then - popup.show_loading_indicator() - - if not api.is_fetching_deps(crate_name, version.num) then - core.reload_deps(crate_name, crate.versions, version) - end - local _, cancelled = api.await_deps(crate_name, version.num) - - popup.hide_loading_indicator(transaction) - if cancelled then return end - end - - - if popup.transaction ~= transaction then - return - end - - ctx.hist_idx = ctx.hist_idx + 1 - for i = ctx.hist_idx, #ctx.history, 1 do - ctx.history[i] = nil - end - - ctx.history[ctx.hist_idx] = { - crate_name = crate_name, - version = version, - line = 2, - } - - M.open_deps(ctx, crate_name, version, { - focus = true, - update = true, - }) -end) - -local function jump_back_dep(ctx, line) - if ctx.hist_idx == 1 then - popup.hide() - return - end - - - local current = ctx.history[ctx.hist_idx] - current.line = line - - ctx.hist_idx = ctx.hist_idx - 1 - - local entry = ctx.history[ctx.hist_idx] - if not entry then return end - - M.open_deps(ctx, entry.crate_name, entry.version, { - focus = true, - line = entry.line, - update = true, - }) -end - -local function jump_forward_dep(ctx, line) - if ctx.hist_idx == #ctx.history then - return - end - - - local current = ctx.history[ctx.hist_idx] - current.line = line - - ctx.hist_idx = ctx.hist_idx + 1 - - local entry = ctx.history[ctx.hist_idx] - if not entry then return end - - M.open_deps(ctx, entry.crate_name, entry.version, { - focus = true, - line = entry.line, - update = true, - }) -end - -function M.open_deps(ctx, crate_name, version, opts) - popup.type = "dependencies" - - popup.omit_loading_transaction() - - local deps = version.deps - if not deps then return end - - local title = string.format(state.cfg.popup.text.title, crate_name .. " " .. version.num) - local deps_width = 0 - local deps_text_index = {} - - - - - local normal_deps_text = {} - local build_deps_text = {} - local dev_deps_text = {} - - for _, d in ipairs(deps) do - local t = {} - if d.opt then - t.text = string.format(state.cfg.popup.text.optional, d.name) - t.hl = state.cfg.popup.highlight.optional - else - t.text = string.format(state.cfg.popup.text.dependency, d.name) - t.hl = state.cfg.popup.highlight.dependency - end - - local line = { t, dep = d } - if d.kind == "normal" then - table.insert(normal_deps_text, line) - elseif d.kind == "build" then - table.insert(build_deps_text, line) - elseif d.kind == "dev" then - table.insert(dev_deps_text, line) - end - table.insert(deps_text_index, line) - deps_width = math.max(vim.fn.strdisplaywidth(t.text), deps_width) - end - - local vers_width = 0 - if state.cfg.popup.show_dependency_version then - for i, line in ipairs(deps_text_index) do - local dep_text = line[1] - local diff = deps_width - vim.fn.strdisplaywidth(dep_text.text) - local vers = deps[i].vers.text - dep_text.text = dep_text.text .. string.rep(" ", diff) - - local vers_text = { - text = string.format(state.cfg.popup.text.dependency_version, vers), - hl = state.cfg.popup.highlight.dependency_version, - } - table.insert(line, vers_text) - - vers_width = math.max(vim.fn.strdisplaywidth(vers_text.text), vers_width) - end - end - - local deps_text = {} - local line_mapping = {} - local line_idx = popup.TOP_OFFSET - if #normal_deps_text > 0 then - table.insert(deps_text, { { text = state.cfg.popup.text.normal_dependencies_title, hl = state.cfg.popup.highlight.normal_dependencies_title } }) - line_idx = line_idx + 1 - - for _, t in ipairs(normal_deps_text) do - table.insert(deps_text, t) - line_mapping[line_idx] = t.dep - line_idx = line_idx + 1 - end - end - if #build_deps_text > 0 then - if #deps_text > 0 then - table.insert(deps_text, {}) - line_idx = line_idx + 1 - end - table.insert(deps_text, { { text = state.cfg.popup.text.build_dependencies_title, hl = state.cfg.popup.highlight.build_dependencies_title } }) - line_idx = line_idx + 1 - - for _, t in ipairs(build_deps_text) do - table.insert(deps_text, t) - line_mapping[line_idx] = t.dep - line_idx = line_idx + 1 - end - end - if #dev_deps_text > 0 then - if #deps_text > 0 then - table.insert(deps_text, {}) - line_idx = line_idx + 1 - end - table.insert(deps_text, { { text = state.cfg.popup.text.dev_dependencies_title, hl = state.cfg.popup.highlight.dev_dependencies_title } }) - line_idx = line_idx + 1 - - for _, t in ipairs(dev_deps_text) do - table.insert(deps_text, t) - line_mapping[line_idx] = t.dep - line_idx = line_idx + 1 - end - end - - ctx.history[ctx.hist_idx].line_mapping = line_mapping - - local width = popup.win_width(title, deps_width + vers_width) - local height = popup.win_height(deps_text) - - if opts.update then - popup.update_win(width, height, title, deps_text, opts) - else - popup.open_win(width, height, title, deps_text, opts, function(_win, buf) - for _, k in ipairs(state.cfg.popup.keys.goto_item) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - goto_dep(ctx, line) - end, - noremap = true, - silent = true, - desc = "Goto dependency", - }) - end - - for _, k in ipairs(state.cfg.popup.keys.jump_forward) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - jump_forward_dep(ctx, line) - end, - noremap = true, - silent = true, - desc = "Jump forward", - }) - end - - for _, k in ipairs(state.cfg.popup.keys.jump_back) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - jump_back_dep(ctx, line) - end, - noremap = true, - silent = true, - desc = "Jump back", - }) - end - end) - end -end - -function M.open(crate_name, version, opts) - local ctx = { - buf = util.current_buf(), - history = { - { crate_name = crate_name, version = version, line = opts and opts.line or 2 }, - }, - hist_idx = 1, - } - M.open_deps(ctx, crate_name, version, opts) -end - -return M diff --git a/teal/crates/popup/dependencies.tl b/lua/crates/popup/dependencies.tl similarity index 100% rename from teal/crates/popup/dependencies.tl rename to lua/crates/popup/dependencies.tl diff --git a/lua/crates/popup/features.lua b/lua/crates/popup/features.lua deleted file mode 100644 index 84126b9d..00000000 --- a/lua/crates/popup/features.lua +++ /dev/null @@ -1,367 +0,0 @@ -local M = {FeatureContext = {}, FeatHistoryEntry = {}, } - - - - - - - - - - - - - - - - - -local FeatureContext = M.FeatureContext -local FeatHistoryEntry = M.FeatHistoryEntry -local edit = require("crates.edit") -local popup = require("crates.popup.common") -local HighlightText = popup.HighlightText -local WinOpts = popup.WinOpts -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Feature = types.Feature -local Range = types.Range -local Version = types.Version -local util = require("crates.util") -local FeatureInfo = util.FeatureInfo - -local function feature_text(features_info, feature) - local t = {} - local info = features_info[feature] - if info == FeatureInfo.ENABLED then - t.text = string.format(state.cfg.popup.text.enabled, feature) - t.hl = state.cfg.popup.highlight.enabled - elseif info == FeatureInfo.TRANSITIVE then - t.text = string.format(state.cfg.popup.text.transitive, feature) - t.hl = state.cfg.popup.highlight.transitive - else - t.text = string.format(state.cfg.popup.text.feature, feature) - t.hl = state.cfg.popup.highlight.feature - end - return { t } -end - -local function toggle_feature(ctx, line) - local index = popup.item_index(line) - local features = ctx.version.features - local entry = ctx.history[ctx.hist_idx] - - local selected_feature - if entry.feature then - local m = entry.feature.members[index] - if m then - selected_feature = features:get_feat(m) - end - else - selected_feature = features.list[index] - end - if not selected_feature then return end - - local line_range - local crate_feature = ctx.crate:get_feat(selected_feature.name) - if selected_feature.name == "default" then - if crate_feature ~= nil or ctx.crate:is_def_enabled() then - line_range = edit.disable_def_features(ctx.buf, ctx.crate, crate_feature) - else - line_range = edit.enable_def_features(ctx.buf, ctx.crate) - end - else - if crate_feature then - line_range = edit.disable_feature(ctx.buf, ctx.crate, crate_feature) - else - line_range = edit.enable_feature(ctx.buf, ctx.crate, selected_feature) - end - end - - - - - if ctx.crate.syntax == "table" then - for line_nr in line_range:iter() do - local text = vim.api.nvim_buf_get_lines(ctx.buf, line_nr, line_nr + 1, false)[1] - text = toml.trim_comments(text) - - local vers = toml.parse_crate_table_vers(text, line_nr) - if vers then - ctx.crate.vers = vers - end - local def = toml.parse_crate_table_def(text, line_nr) - if def then - ctx.crate.def = def - end - local feat = toml.parse_crate_table_feat(text, line_nr) - if feat then - ctx.crate.feat = feat - end - - ctx.crate = toml.Crate.new(ctx.crate) - end - elseif ctx.crate.syntax == "plain" or ctx.crate.syntax == "inline_table" then - local line_nr = line_range.s - local text = vim.api.nvim_buf_get_lines(ctx.buf, line_nr, line_nr + 1, false)[1] - text = toml.trim_comments(text) - - local crate = toml.Crate.new(toml.parse_inline_crate(text, line_nr)) - ctx.crate.syntax = crate.syntax - ctx.crate.vers = crate.vers - ctx.crate.feat = crate.feat - ctx.crate.def = crate.def - end - - - local features_text = {} - local features_info = util.features_info(ctx.crate, features) - if entry.feature then - for _, m in ipairs(entry.feature.members) do - local hi_text = feature_text(features_info, m) - table.insert(features_text, hi_text) - end - else - for _, f in ipairs(features.list) do - local hi_text = feature_text(features_info, f.name) - table.insert(features_text, hi_text) - end - end - - popup.update_buf_body(features_text) -end - -local function goto_feature(ctx, line) - local index = popup.item_index(line) - local crate = ctx.crate - local version = ctx.version - local feature = ctx.history[ctx.hist_idx].feature - - local selected_feature = nil - if feature then - local m = feature.members[index] - if m then - selected_feature = version.features:get_feat(m) - end - else - selected_feature = version.features.list[index] - end - if not selected_feature then return end - - M.open_feature_details(ctx, crate, version, selected_feature, { - focus = true, - update = true, - }) - - - local current = ctx.history[ctx.hist_idx] - current.line = line - - ctx.hist_idx = ctx.hist_idx + 1 - for i = ctx.hist_idx, #ctx.history, 1 do - ctx.history[i] = nil - end - - ctx.history[ctx.hist_idx] = { - feature = selected_feature, - line = 2, - } -end - -local function jump_back_feature(ctx, line) - local crate = ctx.crate - local version = ctx.version - - if ctx.hist_idx == 1 then - popup.hide() - return - end - - - local current = ctx.history[ctx.hist_idx] - current.line = line - - ctx.hist_idx = ctx.hist_idx - 1 - - if ctx.hist_idx == 1 then - M.open_features(ctx, crate, version, { - focus = true, - line = ctx.history[1].line, - update = true, - }) - else - local entry = ctx.history[ctx.hist_idx] - if not entry then return end - - M.open_feature_details(ctx, crate, version, entry.feature, { - focus = true, - line = entry.line, - update = true, - }) - end -end - -local function jump_forward_feature(ctx, line) - local crate = ctx.crate - local version = ctx.version - - if ctx.hist_idx == #ctx.history then - return - end - - - local current = ctx.history[ctx.hist_idx] - current.line = line - - ctx.hist_idx = ctx.hist_idx + 1 - - local entry = ctx.history[ctx.hist_idx] - if not entry then return end - - M.open_feature_details(ctx, crate, version, entry.feature, { - focus = true, - line = entry.line, - update = true, - }) -end - -local function config_feat_win(ctx) - return function(_win, buf) - for _, k in ipairs(state.cfg.popup.keys.toggle_feature) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - toggle_feature(ctx, line) - end, - noremap = true, - silent = true, - desc = "Toggle feature", - }) - end - - for _, k in ipairs(state.cfg.popup.keys.goto_item) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - goto_feature(ctx, line) - end, - noremap = true, - silent = true, - desc = "Goto feature", - }) - end - - for _, k in ipairs(state.cfg.popup.keys.jump_forward) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - jump_forward_feature(ctx, line) - end, - noremap = true, - silent = true, - desc = "Jump forward", - }) - end - - for _, k in ipairs(state.cfg.popup.keys.jump_back) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - jump_back_feature(ctx, line) - end, - noremap = true, - silent = true, - desc = "Jump back", - }) - end - end -end - -function M.open_features(ctx, crate, version, opts) - popup.type = "features" - - local features = version.features - local title = string.format(state.cfg.popup.text.title, crate:package() .. " " .. version.num) - local feat_width = 0 - local features_text = {} - - local features_info = util.features_info(crate, features) - for _, f in ipairs(features.list) do - local hl_text = feature_text(features_info, f.name) - table.insert(features_text, hl_text) - local w = 0 - for _, t in ipairs(hl_text) do - w = w + vim.fn.strdisplaywidth(t.text) - end - feat_width = math.max(w, feat_width) - end - - local width = popup.win_width(title, feat_width) - local height = popup.win_height(features.list) - - if opts.update then - popup.update_win(width, height, title, features_text, opts) - else - popup.open_win(width, height, title, features_text, opts, config_feat_win(ctx)) - end -end - -function M.open_feature_details(ctx, crate, version, feature, opts) - popup.type = "features" - - local features = version.features - local members = feature.members - local title = string.format(state.cfg.popup.text.title, crate:package() .. " " .. version.num .. " " .. feature.name) - local feat_width = 0 - local features_text = {} - - local features_info = util.features_info(crate, features) - for _, m in ipairs(members) do - local hl_text = feature_text(features_info, m) - table.insert(features_text, hl_text) - local w = 0 - for _, t in ipairs(hl_text) do - w = w + vim.fn.strdisplaywidth(t.text) - end - feat_width = math.max(w, feat_width) - end - - local width = popup.win_width(title, feat_width) - local height = popup.win_height(members) - - if opts.update then - popup.update_win(width, height, title, features_text, opts) - else - popup.open_win(width, height, title, features_text, opts, config_feat_win(ctx)) - end -end - -function M.open(crate, version, opts) - local ctx = { - buf = util.current_buf(), - crate = crate, - version = version, - history = { - { feature = nil, line = opts and opts.line or 3 }, - }, - hist_idx = 1, - } - M.open_features(ctx, crate, version, opts) -end - -function M.open_details(crate, version, feature, opts) - local ctx = { - buf = util.current_buf(), - crate = crate, - version = version, - history = { - { feature = nil, line = 2 }, - { feature = feature, line = opts and opts.line or 3 }, - }, - hist_idx = 2, - } - M.open_feature_details(ctx, crate, version, feature, opts) -end - -return M diff --git a/teal/crates/popup/features.tl b/lua/crates/popup/features.tl similarity index 100% rename from teal/crates/popup/features.tl rename to lua/crates/popup/features.tl diff --git a/lua/crates/popup/init.lua b/lua/crates/popup/init.lua deleted file mode 100644 index c43a894e..00000000 --- a/lua/crates/popup/init.lua +++ /dev/null @@ -1,215 +0,0 @@ -local M = {LineCrateInfo = {}, } - - - - - - - - - -local LineCrateInfo = M.LineCrateInfo -local popup = require("crates.popup.common") -local Type = popup.Type -local popup_crate = require("crates.popup.crate") -local popup_deps = require("crates.popup.dependencies") -local popup_feat = require("crates.popup.features") -local popup_vers = require("crates.popup.versions") -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Feature = types.Feature -local Range = types.Range -local Version = types.Version -local util = require("crates.util") - -local function line_crate_info() - local buf = util.current_buf() - local line, col = util.cursor_pos() - - local crates = util.get_line_crates(buf, Range.new(line, line + 1)) - local _, crate = next(crates) - if not crate then return end - - local api_crate = state.api_cache[crate:package()] - if not api_crate then return end - - local avoid_pre = not crate:vers_is_pre() - local newest = util.get_newest(api_crate.versions, avoid_pre, crate:vers_reqs()) - - local info = { - crate = crate, - versions = api_crate.versions, - newest = newest, - } - - local function crate_info() - info.pref = "crate" - end - - local function versions_info() - info.pref = "versions" - end - - local function features_info() - for _, cf in ipairs(crate.feat.items) do - if cf.decl_col:contains(col - crate.feat.col.s) then - info.feature = newest.features:get_feat(cf.name) - break - end - end - - if info.feature then - info.pref = "feature_details" - else - info.pref = "features" - end - end - - local function default_features_info() - info.feature = newest.features.list[1] - info.pref = "feature_details" - end - - if crate.syntax == "plain" then - if crate.vers.col:moved(-1, 1):contains(col) then - versions_info() - else - crate_info() - end - elseif crate.syntax == "table" then - if crate.vers and line == crate.vers.line then - versions_info() - elseif crate.feat and line == crate.feat.line then - features_info() - elseif crate.def and line == crate.def.line then - default_features_info() - else - crate_info() - end - elseif crate.syntax == "inline_table" then - if crate.vers and crate.vers.decl_col:contains(col) then - versions_info() - elseif crate.feat and crate.feat.decl_col:contains(col) then - features_info() - elseif crate.def and crate.def.decl_col:contains(col) then - default_features_info() - else - crate_info() - end - end - - return info -end - -function M.available() - return line_crate_info() ~= nil -end - -function M.show() - if popup.win and vim.api.nvim_win_is_valid(popup.win) then - popup.focus() - return - end - - local info = line_crate_info() - if not info then return end - - if info.pref == "crate" then - local crate = state.api_cache[info.crate:package()] - if crate then - popup_crate.open(crate) - end - elseif info.pref == "versions" then - popup_vers.open(info.crate, info.versions) - elseif info.pref == "features" then - popup_feat.open(info.crate, info.newest, {}) - elseif info.pref == "feature_details" then - popup_feat.open_details(info.crate, info.newest, info.feature, {}) - elseif info.pref == "dependencies" then - popup_deps.open(info.crate:package(), info.newest, {}) - end -end - -function M.focus() - popup.focus() -end - -function M.hide() - popup.hide() -end - -function M.show_crate() - if popup.win and vim.api.nvim_win_is_valid(popup.win) then - if popup.type == "crate" then - popup.focus() - return - else - popup.hide() - end - end - - local info = line_crate_info() - if not info then return end - - local crate = state.api_cache[info.crate:package()] - if crate then - popup_crate.open(crate) - end -end - -function M.show_versions() - if popup.win and vim.api.nvim_win_is_valid(popup.win) then - if popup.type == "versions" then - popup.focus() - return - else - popup.hide() - end - end - - local info = line_crate_info() - if not info then return end - - popup_vers.open(info.crate, info.versions) -end - -function M.show_features() - if popup.win and vim.api.nvim_win_is_valid(popup.win) then - if popup.type == "features" then - popup.focus() - return - else - popup.hide() - end - end - - local info = line_crate_info() - if not info then return end - - if info.pref == "features" then - popup_feat.open(info.crate, info.newest, {}) - elseif info.pref == "feature_details" then - popup_feat.open_details(info.crate, info.newest, info.feature, {}) - elseif info.newest then - popup_feat.open(info.crate, info.newest, {}) - end -end - -function M.show_dependencies() - if popup.win and vim.api.nvim_win_is_valid(popup.win) then - if popup.type == "dependencies" then - popup.focus() - return - else - popup.hide() - end - end - - local info = line_crate_info() - if not info then return end - - popup_deps.open(info.crate:package(), info.newest, {}) -end - -return M diff --git a/teal/crates/popup/init.tl b/lua/crates/popup/init.tl similarity index 100% rename from teal/crates/popup/init.tl rename to lua/crates/popup/init.tl diff --git a/lua/crates/popup/versions.lua b/lua/crates/popup/versions.lua deleted file mode 100644 index 57b6d0a3..00000000 --- a/lua/crates/popup/versions.lua +++ /dev/null @@ -1,162 +0,0 @@ -local M = {VersContext = {}, } - - - - - - - -local VersContext = M.VersContext -local edit = require("crates.edit") -local popup = require("crates.popup.common") -local HighlightText = popup.HighlightText -local WinOpts = popup.WinOpts -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Range = types.Range -local Version = types.Version -local util = require("crates.util") - -local function select_version(ctx, line, alt) - local index = popup.item_index(line) - local crate = ctx.crate - local version = ctx.versions[index] - if not version then return end - - local line_range - line_range = edit.set_version(ctx.buf, crate, version.parsed, alt) - - - - - if crate.syntax == "table" then - for line_nr in line_range:iter() do - local text = vim.api.nvim_buf_get_lines(ctx.buf, line_nr, line_nr + 1, false)[1] - text = toml.trim_comments(text) - - local vers = toml.parse_crate_table_vers(text, line_nr) - if vers then - crate.vers = crate.vers or vers - crate.vers.line = line_nr - crate.vers.col = vers.col - crate.vers.decl_col = vers.decl_col - crate.vers.quote = vers.quote - end - end - elseif crate.syntax == "plain" or crate.syntax == "inline_table" then - local line_nr = line_range.s - local text = vim.api.nvim_buf_get_lines(ctx.buf, line_nr, line_nr + 1, false)[1] - text = toml.trim_comments(text) - - local c = toml.parse_inline_crate(text, line_nr) - if c and c.vers then - crate.vers = crate.vers or c.vers - crate.vers.line = line_nr - crate.vers.col = c.vers.col - crate.vers.decl_col = c.vers.decl_col - crate.vers.quote = c.vers.quote - end - end - - if state.cfg.popup.hide_on_select then - popup.hide() - end -end - -local function copy_version(versions, line) - local index = popup.item_index(line) - local version = versions[index] - if not version then return end - - vim.fn.setreg(state.cfg.popup.copy_register, version.num) -end - -function M.open(crate, versions, opts) - popup.type = "versions" - - local title = string.format(state.cfg.popup.text.title, crate:package()) - local vers_width = 0 - local versions_text = {} - - for _, v in ipairs(versions) do - local t = {} - if v.yanked then - t.text = string.format(state.cfg.popup.text.yanked, v.num) - t.hl = state.cfg.popup.highlight.yanked - elseif v.parsed.pre then - t.text = string.format(state.cfg.popup.text.prerelease, v.num) - t.hl = state.cfg.popup.highlight.prerelease - else - t.text = string.format(state.cfg.popup.text.version, v.num) - t.hl = state.cfg.popup.highlight.version - end - - table.insert(versions_text, { t }) - vers_width = math.max(vim.fn.strdisplaywidth(t.text), vers_width) - end - - local date_width = 0 - if state.cfg.popup.show_version_date then - for i, line in ipairs(versions_text) do - local vers_text = line[1] - local diff = vers_width - vim.fn.strdisplaywidth(vers_text.text) - local date = versions[i].created:display(state.cfg.date_format) - vers_text.text = vers_text.text .. string.rep(" ", diff) - - local date_text = { - text = string.format(state.cfg.popup.text.version_date, date), - hl = state.cfg.popup.highlight.version_date, - } - table.insert(line, date_text) - date_width = math.max(vim.fn.strdisplaywidth(date_text.text), date_width) - end - end - - local width = popup.win_width(title, vers_width + date_width) - local height = popup.win_height(versions) - popup.open_win(width, height, title, versions_text, opts, function(_win, buf) - local ctx = { - buf = util.current_buf(), - crate = crate, - versions = versions, - } - for _, k in ipairs(state.cfg.popup.keys.select) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - select_version(ctx, line) - end, - noremap = true, - silent = true, - desc = "Select version", - }) - end - - for _, k in ipairs(state.cfg.popup.keys.select_alt) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - select_version(ctx, line, true) - end, - noremap = true, - silent = true, - desc = "Select version alt", - }) - end - - for _, k in ipairs(state.cfg.popup.keys.copy_value) do - vim.api.nvim_buf_set_keymap(buf, "n", k, "", { - callback = function() - local line = util.cursor_pos() - copy_version(versions, line) - end, - noremap = true, - silent = true, - desc = "Copy version", - }) - end - end) -end - -return M diff --git a/teal/crates/popup/versions.tl b/lua/crates/popup/versions.tl similarity index 100% rename from teal/crates/popup/versions.tl rename to lua/crates/popup/versions.tl diff --git a/lua/crates/semver.lua b/lua/crates/semver.lua index 781eca46..45eba2c1 100644 --- a/lua/crates/semver.lua +++ b/lua/crates/semver.lua @@ -1,317 +1,358 @@ -local M = {} - - local types = require("crates.types") -local Range = types.Range -local Requirement = types.Requirement local SemVer = types.SemVer +local Span = types.Span + +local M = {} +---@param str string +---@return SemVer function M.parse_version(str) - local major, minor, patch, pre, meta - - major, minor, patch, pre, meta = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)-([^%s]+)%+([^%s]+)$") - if major then - return SemVer.new({ - major = tonumber(major), - minor = tonumber(minor), - patch = tonumber(patch), - pre = pre, - meta = meta, - }) - end - - major, minor, patch, pre = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)-([^%s]+)$") - if major then - return SemVer.new({ - major = tonumber(major), - minor = tonumber(minor), - patch = tonumber(patch), - pre = pre, - }) - end - - major, minor, patch, meta = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)%+([^%s]+)$") - if major then - return SemVer.new({ - major = tonumber(major), - minor = tonumber(minor), - patch = tonumber(patch), - meta = meta, - }) - end - - major, minor, patch = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)$") - if major then - return SemVer.new({ - major = tonumber(major), - minor = tonumber(minor), - patch = tonumber(patch), - }) - end - - major, minor = str:match("^([0-9]+)%.([0-9]+)[%.]?$") - if major then - return SemVer.new({ - major = tonumber(major), - minor = tonumber(minor), - }) - end - - major = str:match("^([0-9]+)[%.]?$") - if major then - return SemVer.new({ - major = tonumber(major), - }) - end - - return SemVer.new({}) + ---@type string, string, string, string, string + local major, minor, patch, pre, meta + + major, minor, patch, pre, meta = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)-([^%s]+)%+([^%s]+)$") + if major then + return SemVer.new({ + major = tonumber(major), + minor = tonumber(minor), + patch = tonumber(patch), + pre = pre, + meta = meta, + }) + end + + major, minor, patch, pre = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)-([^%s]+)$") + if major then + return SemVer.new({ + major = tonumber(major), + minor = tonumber(minor), + patch = tonumber(patch), + pre = pre, + }) + end + + major, minor, patch, meta = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)%+([^%s]+)$") + if major then + return SemVer.new { + major = tonumber(major), + minor = tonumber(minor), + patch = tonumber(patch), + meta = meta, + } + end + + major, minor, patch = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)$") + if major then + return SemVer.new { + major = tonumber(major), + minor = tonumber(minor), + patch = tonumber(patch), + } + end + + major, minor = str:match("^([0-9]+)%.([0-9]+)[%.]?$") + if major then + return SemVer.new { + major = tonumber(major), + minor = tonumber(minor), + } + end + + major = str:match("^([0-9]+)[%.]?$") + if major then + return SemVer.new { + major = tonumber(major), + } + end + + return SemVer.new {} end +---@param str string +---@return Requirement function M.parse_requirement(str) - local vs, vers_str, ve, rs, re - - vs, vers_str, ve = str:match("^=%s*()(.+)()$") - if vs and vers_str and ve then - return { - cond = "eq", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^<=%s*()(.+)()$") - if vs and vers_str and ve then - return { - cond = "le", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^<%s*()(.+)()$") - if vs and vers_str and ve then - return { - cond = "lt", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^>=%s*()(.+)()$") - if vs and vers_str and ve then - return { - cond = "ge", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^>%s*()(.+)()$") - if vs and vers_str and ve then - return { - cond = "gt", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^%~%s*()(.+)()$") - if vs and vers_str and ve then - return { - cond = "tl", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - local wl = str:match("^%*$") - if wl then - return { - cond = "wl", - cond_col = Range.new(0, 1), - vers = SemVer.new({}), - vers_col = Range.new(0, 0), - } - end - - vers_str, rs, re = str:match("^(.+)()%.%*()$") - if vers_str and rs and re then - return { - cond = "wl", - cond_col = Range.new(rs - 1, re - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(0, rs - 1), - } - end - - vs, vers_str, ve = str:match("^%^%s*()(.+)()$") - if vs and vers_str and ve then - return { - cond = "cr", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - return { - cond = "bl", - cond_col = Range.new(0, 0), - vers = M.parse_version(str), - vers_col = Range.new(0, str:len()), - } + ---@type integer, string, integer, integer, integer + local vs, vers_str, ve, rs, re + + vs, vers_str, ve = str:match("^=%s*()(.+)()$") + if vs and vers_str and ve then + ---@type Requirement + return { + cond = "eq", + cond_col = Span.new(0, vs - 1), + vers = M.parse_version(vers_str), + vers_col = Span.new(vs - 1, ve - 1), + } + end + + vs, vers_str, ve = str:match("^<=%s*()(.+)()$") + if vs and vers_str and ve then + ---@type Requirement + return { + cond = "le", + cond_col = Span.new(0, vs - 1), + vers = M.parse_version(vers_str), + vers_col = Span.new(vs - 1, ve - 1), + } + end + + vs, vers_str, ve = str:match("^<%s*()(.+)()$") + if vs and vers_str and ve then + ---@type Requirement + return { + cond = "lt", + cond_col = Span.new(0, vs - 1), + vers = M.parse_version(vers_str), + vers_col = Span.new(vs - 1, ve - 1), + } + end + + vs, vers_str, ve = str:match("^>=%s*()(.+)()$") + if vs and vers_str and ve then + ---@type Requirement + return { + cond = "ge", + cond_col = Span.new(0, vs - 1), + vers = M.parse_version(vers_str), + vers_col = Span.new(vs - 1, ve - 1), + } + end + + vs, vers_str, ve = str:match("^>%s*()(.+)()$") + if vs and vers_str and ve then + ---@type Requirement + return { + cond = "gt", + cond_col = Span.new(0, vs - 1), + vers = M.parse_version(vers_str), + vers_col = Span.new(vs - 1, ve - 1), + } + end + + vs, vers_str, ve = str:match("^%~%s*()(.+)()$") + if vs and vers_str and ve then + ---@type Requirement + return { + cond = "tl", + cond_col = Span.new(0, vs - 1), + vers = M.parse_version(vers_str), + vers_col = Span.new(vs - 1, ve - 1), + } + end + + local wl = str:match("^%*$") + if wl then + ---@type Requirement + return { + cond = "wl", + cond_col = Span.new(0, 1), + vers = SemVer.new {}, + vers_col = Span.new(0, 0), + } + end + + vers_str, rs, re = str:match("^(.+)()%.%*()$") + if vers_str and rs and re then + ---@type Requirement + return { + cond = "wl", + cond_col = Span.new(rs - 1, re - 1), + vers = M.parse_version(vers_str), + vers_col = Span.new(0, rs - 1), + } + end + + vs, vers_str, ve = str:match("^%^%s*()(.+)()$") + if vs and vers_str and ve then + ---@type Requirement + return { + cond = "cr", + cond_col = Span.new(0, vs - 1), + vers = M.parse_version(vers_str), + vers_col = Span.new(vs - 1, ve - 1), + } + end + + ---@type Requirement + return { + cond = "bl", + cond_col = Span.new(0, 0), + vers = M.parse_version(str), + vers_col = Span.new(0, str:len()), + } end +---@param str string +---@return Requirement[] function M.parse_requirements(str) - local requirements = {} - for rs, r in str:gmatch("[,]?%s*()([^,]+)%s*[,]?") do - local s = rs - local requirement = M.parse_requirement(r) - requirement.vers_col.s = requirement.vers_col.s + s - 1 - requirement.vers_col.e = requirement.vers_col.e + s - 1 - table.insert(requirements, requirement) - end - - return requirements + ---@type Requirement[] + local requirements = {} + for s, r in str:gmatch("[,]?%s*()([^,]+)%s*[,]?") do + local requirement = M.parse_requirement(r) + requirement.vers_col.s = requirement.vers_col.s + s - 1 + requirement.vers_col.e = requirement.vers_col.e + s - 1 + table.insert(requirements, requirement) + end + + return requirements end +---@param version string +---@param req string +---@return integer local function compare_pre(version, req) - if version and req then - if version < req then - return -1 - elseif version == req then - return 0 - elseif version > req then - return 1 - end - end - - return (req and 1 or 0) - (version and 1 or 0) + if version and req then + if version < req then + return -1 + elseif version == req then + return 0 + elseif version > req then + return 1 + end + end + + return (req and 1 or 0) - (version and 1 or 0) end +---@param version SemVer +---@param req SemVer +---@return boolean local function matches_less(version, req) - if req.major and req.major ~= version.major then - return version.major < req.major - end - if req.minor and req.minor ~= version.minor then - return version.minor < req.minor - end - if req.patch and req.patch ~= version.patch then - return version.patch < req.patch - end - - return compare_pre(version.pre, req.pre) < 0 + if req.major and req.major ~= version.major then + return version.major < req.major + end + if req.minor and req.minor ~= version.minor then + return version.minor < req.minor + end + if req.patch and req.patch ~= version.patch then + return version.patch < req.patch + end + + return compare_pre(version.pre, req.pre) < 0 end +---@param version SemVer +---@param req SemVer +---@return boolean local function matches_greater(version, req) - if req.major and req.major ~= version.major then - return version.major > req.major - end - if req.minor and req.minor ~= version.minor then - return version.minor > req.minor - end - if req.patch and req.patch ~= version.patch then - return version.patch > req.patch - end - - return compare_pre(version.pre, req.pre) > 0 + if req.major and req.major ~= version.major then + return version.major > req.major + end + if req.minor and req.minor ~= version.minor then + return version.minor > req.minor + end + if req.patch and req.patch ~= version.patch then + return version.patch > req.patch + end + + return compare_pre(version.pre, req.pre) > 0 end +---@param version SemVer +---@param req SemVer +---@return boolean local function matches_exact(version, req) - if req.major and req.major ~= version.major then - return false - end - if req.minor and req.minor ~= version.minor then - return false - end - if req.patch and req.patch ~= version.patch then - return false - end - - return version.pre == req.pre + if req.major and req.major ~= version.major then + return false + end + if req.minor and req.minor ~= version.minor then + return false + end + if req.patch and req.patch ~= version.patch then + return false + end + + return version.pre == req.pre end +---@param version SemVer +---@param req SemVer +---@return boolean local function matches_caret(version, req) - if req.major and req.major ~= version.major then - return false - end - - if not req.minor then - return true - end - - if not req.patch then - if req.major > 0 then - return version.minor >= req.minor - else - return version.minor == req.minor - end - end - - if req.major > 0 then - if req.minor ~= version.minor then - return version.minor > req.minor - elseif req.patch ~= version.patch then - return version.patch > req.patch - end - elseif req.minor > 0 then - if req.minor ~= version.minor then - return false - elseif version.patch ~= req.patch then - return version.patch > req.patch - end - elseif version.minor ~= req.minor or version.patch ~= req.patch then - return false - end - - return compare_pre(version.pre, req.pre) >= 0 + if req.major and req.major ~= version.major then + return false + end + + if not req.minor then + return true + end + + if not req.patch then + if req.major > 0 then + return version.minor >= req.minor + else + return version.minor == req.minor + end + end + + if req.major > 0 then + if req.minor ~= version.minor then + return version.minor > req.minor + elseif req.patch ~= version.patch then + return version.patch > req.patch + end + elseif req.minor > 0 then + if req.minor ~= version.minor then + return false + elseif version.patch ~= req.patch then + return version.patch > req.patch + end + elseif version.minor ~= req.minor or version.patch ~= req.patch then + return false + end + + return compare_pre(version.pre, req.pre) >= 0 end +---@param version SemVer +---@param req SemVer +---@return boolean local function matches_tilde(version, req) - if req.major and req.major ~= version.major then - return false - end - if req.minor and req.minor ~= version.minor then - return false - end - if req.patch and req.patch ~= version.patch then - return version.patch > req.patch - end - - return compare_pre(version.pre, req.pre) >= 0 + if req.major and req.major ~= version.major then + return false + end + if req.minor and req.minor ~= version.minor then + return false + end + if req.patch and req.patch ~= version.patch then + return version.patch > req.patch + end + + return compare_pre(version.pre, req.pre) >= 0 end +---@param v SemVer +---@param r Requirement +---@return boolean function M.matches_requirement(v, r) - if r.cond == "cr" or r.cond == "bl" then - return matches_caret(v, r.vers) - elseif r.cond == "tl" then - return matches_tilde(v, r.vers) - elseif r.cond == "eq" or r.cond == "wl" then - return matches_exact(v, r.vers) - elseif r.cond == "lt" then - return matches_less(v, r.vers) - elseif r.cond == "le" then - return matches_exact(v, r.vers) or matches_less(v, r.vers) - elseif r.cond == "gt" then - return matches_greater(v, r.vers) - elseif r.cond == "ge" then - return matches_exact(v, r.vers) or matches_greater(v, r.vers) - end + if r.cond == "cr" or r.cond == "bl" then + return matches_caret(v, r.vers) + elseif r.cond == "tl" then + return matches_tilde(v, r.vers) + elseif r.cond == "eq" or r.cond == "wl" then + return matches_exact(v, r.vers) + elseif r.cond == "lt" then + return matches_less(v, r.vers) + elseif r.cond == "le" then + return matches_exact(v, r.vers) or matches_less(v, r.vers) + elseif r.cond == "gt" then + return matches_greater(v, r.vers) + elseif r.cond == "ge" then + return matches_exact(v, r.vers) or matches_greater(v, r.vers) + end end +---comment +---@param version SemVer +---@param requirements Requirement[] +---@return boolean function M.matches_requirements(version, requirements) - for _, r in ipairs(requirements) do - if not M.matches_requirement(version, r) then - return false - end - end - return true + for _,r in ipairs(requirements) do + if not M.matches_requirement(version, r) then + return false + end + end + return true end return M diff --git a/lua/crates/src/cmp.lua b/lua/crates/src/cmp.lua deleted file mode 100644 index 287d5b4d..00000000 --- a/lua/crates/src/cmp.lua +++ /dev/null @@ -1,127 +0,0 @@ -local M = {lsp = {CompletionItemKind = {}, MarkupKind = {}, MarkupContent = {}, CompletionItem = {}, CompletionList = {}, }, cmp = {SourceBaseApiParams = {}, SourceCompletionApiParams = {}, }, } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -local src = require("crates.src.common") - - -function M.new() - return setmetatable({}, { __index = M }) -end - - -function M.get_debug_name() - return "crates" -end - - -function M:is_available() - return vim.fn.expand("%:t") == "Cargo.toml" -end - - - - - -function M:get_keyword_pattern(_) - return [[\([^"'\%^<>=~,\s]\)*]] -end - - -function M:get_trigger_characters(_) - return src.trigger_characters -end - - - -function M:complete(_, callback) - src.complete(callback) -end - -function M.setup() - if M.registered_source then - return - end - - local cmp = package.loaded["cmp"] - if not cmp then - return - end - - cmp.register_source("crates", M.new()) - M.registered_source = true -end - -return M diff --git a/teal/crates/src/cmp.tl b/lua/crates/src/cmp.tl similarity index 100% rename from teal/crates/src/cmp.tl rename to lua/crates/src/cmp.tl diff --git a/lua/crates/src/common.lua b/lua/crates/src/common.lua deleted file mode 100644 index 75ff46d8..00000000 --- a/lua/crates/src/common.lua +++ /dev/null @@ -1,181 +0,0 @@ -local M = {CompletionList = {}, CompletionItem = {}, CmpCompletionExtension = {}, } - - - - - - - - - - - - - - - - - - - - - - - - -local CompletionItem = M.CompletionItem -local CompletionList = M.CompletionList - -local api = require("crates.api") -local async = require("crates.async") -local core = require("crates.core") -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Range = types.Range -local Version = types.Version -local util = require("crates.util") - -M.trigger_characters = { - '"', "'", ".", "<", ">", "=", "^", "~", - "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", -} - - -local VALUE_KIND = 12 - -local function complete_versions(crate, versions) - local items = {} - - for i, v in ipairs(versions) do - local r = { - label = v.num, - kind = VALUE_KIND, - sortText = string.format("%04d", i), - } - if state.cfg.src.insert_closing_quote then - if crate.vers and not crate.vers.quote.e then - r.insertText = v.num .. crate.vers.quote.s - end - end - if v.yanked then - r.deprecated = true - r.documentation = state.cfg.src.text.yanked - elseif v.parsed.pre then - r.documentation = state.cfg.src.text.prerelease - end - if state.cfg.src.cmp.use_custom_kind then - r.cmp = { - kind_text = state.cfg.src.cmp.kind_text.version, - kind_hl_group = state.cfg.src.cmp.kind_highlight.version, - } - end - - table.insert(items, r) - end - - return { - isIncomplete = false, - items = items, - } -end - -local function complete_features(crate, cf, versions) - local avoid_pre = not crate:vers_is_pre() - local newest = util.get_newest(versions, avoid_pre, crate:vers_reqs()) - - if not newest then - return { - isIncomplete = false, - items = {}, - } - end - - local items = {} - for _, f in ipairs(newest.features.list) do - local crate_feat = crate:get_feat(f.name) - if not crate_feat then - local r = { - label = f.name, - kind = VALUE_KIND, - sortText = f.name, - documentation = table.concat(f.members, "\n"), - } - if state.cfg.src.insert_closing_quote then - if not cf.quote.e then - r.insertText = f.name .. cf.quote.s - end - end - if state.cfg.src.cmp.use_custom_kind then - r.cmp = { - kind_text = state.cfg.src.cmp.kind_text.feature, - kind_hl_group = state.cfg.src.cmp.kind_highlight.feature, - } - end - - table.insert(items, r) - end - end - - return { - isIncomplete = not newest.deps, - items = items, - } -end - -local function complete() - local buf = util.current_buf() - - local awaited = core.await_throttled_update_if_any(buf) - if awaited and buf ~= util.current_buf() then - return - end - - local line, col = util.cursor_pos() - local crates = util.get_line_crates(buf, Range.new(line, line + 1)) - local _, crate = next(crates) - if not crate then - return - end - - local api_crate = state.api_cache[crate:package()] - - if not api_crate and api.is_fetching_crate(crate:package()) then - local _api_crate, cancelled = api.await_crate(crate:package()) - - if cancelled or buf ~= util.current_buf() then - return - end - - line, col = util.cursor_pos() - crates = util.get_line_crates(buf, Range.new(line, line + 1)) - _, crate = next(crates) - if not crate then - return - end - - api_crate = state.api_cache[crate:package()] - end - - if not api_crate then - return - end - - if crate.vers and crate.vers.line == line and crate.vers.col:moved(0, 1):contains(col) then - return complete_versions(crate, api_crate.versions) - elseif crate.feat and crate.feat.line == line and crate.feat.col:moved(0, 1):contains(col) then - for _, f in ipairs(crate.feat.items) do - if f.col:moved(0, 1):contains(col - crate.feat.col.s) then - return complete_features(crate, f, api_crate.versions) - end - end - end -end - -function M.complete(callback) - vim.schedule(async.wrap(function() - callback(complete()) - end)) -end - -return M diff --git a/teal/crates/src/common.tl b/lua/crates/src/common.tl similarity index 100% rename from teal/crates/src/common.tl rename to lua/crates/src/common.tl diff --git a/lua/crates/src/coq.lua b/lua/crates/src/coq.lua deleted file mode 100644 index 443957c2..00000000 --- a/lua/crates/src/coq.lua +++ /dev/null @@ -1,34 +0,0 @@ -local M = {} - - - - - -local src = require("crates.src.common") - -local function new_uid(map) - local key - repeat - key = math.floor(math.random() * 10000) - until not map[key] - return key -end - -function M.complete(_, callback) - if vim.fn.expand("%:t") ~= "Cargo.toml" then - callback(nil) - return - end - - src.complete(callback) -end - -function M.setup(name) - COQsources = COQsources or {} - COQsources[new_uid(COQsources)] = { - name = name, - fn = M.complete, - } -end - -return M diff --git a/teal/crates/src/coq.tl b/lua/crates/src/coq.tl similarity index 100% rename from teal/crates/src/coq.tl rename to lua/crates/src/coq.tl diff --git a/lua/crates/state.lua b/lua/crates/state.lua index 2f58da43..49490e27 100644 --- a/lua/crates/state.lua +++ b/lua/crates/state.lua @@ -1,25 +1,19 @@ -local State = {BufCache = {}, } +---@class State +---@field cfg Config +---@field api_cache table +---@field buf_cache table +---@field visible boolean +local State = { + api_cache = {}, + buf_cache = {}, + visible = true, +} + +---@class BufCache +---@field crates table +---@field info table +---@field diagnostics CratesDiagnostic[] - - - - - - - - - - - -local config = require("crates.config") -local Config = config.Config -local toml = require("crates.toml") -local types = require("crates.types") -local Crate = types.Crate -local CrateInfo = types.CrateInfo -local Diagnostic = types.Diagnostic - -State.cfg = {} State.api_cache = {} State.buf_cache = {} State.visible = true diff --git a/lua/crates/time.lua b/lua/crates/time.lua index ae46076c..3b431981 100644 --- a/lua/crates/time.lua +++ b/lua/crates/time.lua @@ -1,46 +1,54 @@ -local M = {DateTime = {}, } - - - - +local M = { + DateTime = {}, +} +---@class DateTime +---@field epoch integer local DateTime = M.DateTime +---@param epoch integer +---@return DateTime function DateTime.new(epoch) - return setmetatable({ epoch = epoch }, { __index = DateTime }) + return setmetatable({ epoch = epoch }, { __index = DateTime }) end +---@param str string +---@return DateTime|nil function DateTime.parse_rfc_3339(str) - - local pat = "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9])" .. - "T([0-9][0-9]):([0-9][0-9]):([0-9][0-9])%.[0-9]+" .. - "([%+%-])([0-9][0-9]):([0-9][0-9])$" - - local year, month, day, hour, minute, second, offset, offset_hour, offset_minute = str:match(pat) - if year then - local h, m - if offset == "+" then - h = tonumber(hour) + tonumber(offset_hour) - m = tonumber(minute) + tonumber(offset_minute) - elseif offset == "-" then - h = tonumber(hour) - tonumber(offset_hour) - m = tonumber(minute) - tonumber(offset_minute) - end - return DateTime.new(os.time({ - year = tonumber(year), - month = tonumber(month), - day = tonumber(day), - hour = h, - min = m, - sec = tonumber(second), - })) - end - - return nil + -- lua regex suports no {n} occurences + local pat = "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9])" -- date + .. "T([0-9][0-9]):([0-9][0-9]):([0-9][0-9])%.[0-9]+" -- time + .. "([%+%-])([0-9][0-9]):([0-9][0-9])$" -- offset + + local year, month, day, hour, minute, second, offset, offset_hour, offset_minute = str:match(pat) + if year then + ---@type integer, integer + local h, m + if offset == "+" then + h = tonumber(hour) + tonumber(offset_hour) + m = tonumber(minute) + tonumber(offset_minute) + elseif offset == "-" then + h = tonumber(hour) - tonumber(offset_hour) + m = tonumber(minute) - tonumber(offset_minute) + end + return DateTime.new(os.time({ + year = tonumber(year), + month = tonumber(month), + day = tonumber(day), + hour = h, + min = m, + sec = tonumber(second), + })) + end + + return nil end +---@param format string +---@return string function DateTime:display(format) - return os.date(format, self.epoch) + ---@type string + return os.date(format, self.epoch) end return M diff --git a/lua/crates/toml.lua b/lua/crates/toml.lua index fc8e8792..8b4993b7 100644 --- a/lua/crates/toml.lua +++ b/lua/crates/toml.lua @@ -1,468 +1,532 @@ -local M = {Section = {}, Crate = {Vers = {}, Registry = {}, Path = {}, Git = {}, Branch = {}, Rev = {}, Pkg = {}, Workspace = {}, Opt = {}, Def = {}, Feat = {}, }, Feature = {}, Quotes = {}, } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -local Section = M.Section -local Crate = M.Crate -local Feature = M.Feature local semver = require("crates.semver") local types = require("crates.types") -local Range = types.Range -local Requirement = types.Requirement - +local Span = types.Span + +local M = {} + +---@class TomlSection +---@field text string +---@field invalid boolean|nil +---@field workspace boolean|nil +---@field target string|nil +---@field kind TomlSectionKind +---@field name string|nil +---@field name_col Span +---@field lines Span +local Section = {} +M.Section = Section + +---@enum TomlSectionKind +local TomlSectionKind = { + default = "default", + dev = "dev", + build = "build", +} + +---@class TomlCrate +--- The explicit name is either the name of the package, or a rename +--- if the following syntax is used: +--- explicit_name = { package = "package" } +---@field explicit_name string +---@field explicit_name_col Span +---@field lines Span +---@field syntax TomlCrateSyntax +---@field vers TomlCrateVers +---@field registry TomlCrateRegistry +---@field path TomlCratePath +---@field git TomlCrateGit +---@field branch TomlCrateBranch +---@field rev TomlCrateRev +---@field pkg TomlCratePkg +---@field workspace TomlCrateWorkspace +---@field opt TomlCrateOpt +---@field def TomlCrateDef +---@field feat TomlCrateFeat +---@field section TomlSection +---@field dep_kind DepKind +local Crate = {} +M.Crate = Crate + +---@enum TomlCrateSyntax +M.TomlCrateSyntax = { + plain = "plain", + inline_table = "inline_table", + table = "table", +} + +---@class TomlCrateVers +---@field reqs Requirement[] +---@field text string +---@field is_pre boolean +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span +---@field quote Quotes + +---@class TomlCrateRegistry +---@field text string +---@field is_pre boolean +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span +---@field quote Quotes + +---@class TomlCratePath +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span +---@field quote Quotes + +---@class TomlCrateGit +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span +---@field quote Quotes + +---@class TomlCrateBranch +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span +---@field quote Quotes + +---@class TomlCrateRev +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span +---@field quote Quotes + +---@class TomlCratePkg +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span +---@field quote Quotes + +---@class TomlCrateWorkspace +---@field enabled boolean +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span + +---@class TomlCrateOpt +---@field enabled boolean +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span + +---@class TomlCrateDef +---@field enabled boolean +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span + +---@class TomlCrateFeat +---@field items TomlFeature[] +---@field text string +---@field line integer -- 0-indexed +---@field col Span +---@field decl_col Span + +---@enum DepKind +M.DepKind = { + registry = "registry", + path = "path", + git = "git", + workspace = "workspace", +} + +---@class TomlFeature +---@field name string +---relative to to the start of the features text +---@field col Span +---relative to to the start of the features text +---@field decl_col Span +---@field quote Quotes +---@field comma boolean +local TomlFeature = {} +M.TomlFeature = TomlFeature + +---@class Quotes +---@field s string +---@field e string|nil + + +---@param text string +---@return TomlFeature[] function M.parse_crate_features(text) - local feats = {} - for fds, qs, fs, f, fe, qe, fde, c in text:gmatch([[[,]?()%s*(["'])()([^,"']*)()(["']?)%s*()([,]?)]]) do - table.insert(feats, { - name = f, - col = Range.new(fs - 1, fe - 1), - decl_col = Range.new(fds - 1, fde - 1), - quote = { s = qs, e = qe ~= "" and qe or nil }, - comma = c == ",", - }) - end - - return feats -end - + ---@type TomlFeature[] + local feats = {} + for fds, qs, fs, f, fe, qe, fde, c in text:gmatch([[[,]?()%s*(["'])()([^,"']*)()(["']?)%s*()([,]?)]]) do + ---@type TomlFeature + local feat = { + name = f, + col = Span.new(fs - 1, fe - 1), + decl_col = Span.new(fds - 1, fde - 1), + quote = { s = qs, e = qe ~= "" and qe or nil }, + comma = c == ",", + } + table.insert(feats, feat) + end + + return feats +end + +---@param obj TomlCrate +---@return TomlCrate function Crate.new(obj) - if obj.vers then - obj.vers.reqs = semver.parse_requirements(obj.vers.text) - - obj.vers.is_pre = false - for _, r in ipairs(obj.vers.reqs) do - if r.vers.pre then - obj.vers.is_pre = true - break - end - end - end - if obj.feat then - obj.feat.items = M.parse_crate_features(obj.feat.text) - end - if obj.def then - obj.def.enabled = obj.def.text ~= "false" - end - if obj.workspace then - obj.workspace.enabled = obj.workspace.text ~= "false" - end - if obj.opt then - obj.opt.enabled = obj.opt.text ~= "false" - end - - if obj.workspace then - obj.dep_kind = "workspace" - elseif obj.path then - obj.dep_kind = "path" - elseif obj.git then - obj.dep_kind = "git" - else - obj.dep_kind = "registry" - end - - return setmetatable(obj, { __index = Crate }) -end - + if obj.vers then + obj.vers.reqs = semver.parse_requirements(obj.vers.text) + + obj.vers.is_pre = false + for _,r in ipairs(obj.vers.reqs) do + if r.vers.pre then + obj.vers.is_pre = true + break + end + end + end + if obj.feat then + obj.feat.items = M.parse_crate_features(obj.feat.text) + end + if obj.def then + obj.def.enabled = obj.def.text ~= "false" + end + if obj.workspace then + obj.workspace.enabled = obj.workspace.text ~= "false" + end + if obj.opt then + obj.opt.enabled = obj.opt.text ~= "false" + end + + if obj.workspace then + obj.dep_kind = "workspace" + elseif obj.path then + obj.dep_kind = "path" + elseif obj.git then + obj.dep_kind = "git" + else + obj.dep_kind = "registry" + end + + return setmetatable(obj, { __index = Crate }) +end + +---@return Requirement[] function Crate:vers_reqs() - return self.vers and self.vers.reqs or {} + return self.vers and self.vers.reqs or {} end +---@return boolean|nil function Crate:vers_is_pre() - return self.vers and self.vers.is_pre + return self.vers and self.vers.is_pre end +---@param name string +---@return TomlFeature|nil +---@return integer|nil function Crate:get_feat(name) - if not self.feat or not self.feat.items then - return nil - end + if not self.feat or not self.feat.items then + return nil, nil + end - for i, f in ipairs(self.feat.items) do - if f.name == name then - return f, i - end - end + for i,f in ipairs(self.feat.items) do + if f.name == name then + return f, i + end + end - return nil + return nil, nil end +---@return TomlFeature[] function Crate:feats() - return self.feat and self.feat.items or {} + return self.feat and self.feat.items or {} end +---@return boolean function Crate:is_def_enabled() - return not self.def or self.def.enabled + return not self.def or self.def.enabled end +---@return boolean function Crate:is_workspace() - return not self.workspace or self.workspace.enabled + return not self.workspace or self.workspace.enabled end +---@return string function Crate:package() - return self.pkg and self.pkg.text or self.explicit_name + return self.pkg and self.pkg.text or self.explicit_name end +---@return string function Crate:cache_key() - return string.format( - "%s:%s:%s:%s", - self.section.target or "", - self.section.workspace and "workspace" or "", - self.section.kind, - self.explicit_name) - + return string.format( + "%s:%s:%s:%s", + self.section.target or "", + self.section.workspace and "workspace" or "", + self.section.kind, + self.explicit_name + ) end +---@param obj TomlSection +---@return TomlSection function Section.new(obj) - return setmetatable(obj, { __index = Section }) + return setmetatable(obj, { __index = Section }) end +---@param override_name string|nil +---@return string function Section:display(override_name) - local text = "[" + local text = "[" - if self.target then - text = text .. self.target .. "." - end + if self.target then + text = text .. self.target .. "." + end - if self.workspace then - text = text .. "workspace." - end + if self.workspace then + text = text .. "workspace." + end - if self.kind == "default" then - text = text .. "dependencies" - elseif self.kind == "dev" then - text = text .. "dev-dependencies" - elseif self.kind == "build" then - text = text .. "build-dependencies" - end + if self.kind == "default" then + text = text .. "dependencies" + elseif self.kind == "dev" then + text = text .. "dev-dependencies" + elseif self.kind == "build" then + text = text .. "build-dependencies" + end - local name = override_name or self.name - if name then - text = text .. "." .. name - end + local name = override_name or self.name + if name then + text = text .. "." .. name + end - text = text .. "]" + text = text .. "]" - return text + return text end +---@param text string +---@param start integer +---@return TomlSection|nil function M.parse_section(text, start) - local prefix, suffix_s, suffix = text:match("^(.*)dependencies()(.*)$") - if prefix and suffix then - prefix = vim.trim(prefix) - suffix = vim.trim(suffix) - local section = { - text = text, - invalid = false, - kind = "default", - } - - local target = prefix - - local dev_target = prefix:match("^(.*)dev%-$") - if dev_target then - target = vim.trim(dev_target) - section.kind = "dev" - end - - local build_target = prefix:match("^(.*)build%-$") - if build_target then - target = vim.trim(build_target) - section.kind = "build" - end - - local workspace_target = target:match("^(.*)workspace%s*%.$") - if workspace_target then - section.workspace = true - target = vim.trim(workspace_target) - end - - if target then - local t = target:match("^target%s*%.(.+)%.$") - if t then - section.target = vim.trim(t) - target = "" - end - end - - if suffix then - local n_s, n, n_e = suffix:match("^%.%s*()(.+)()%s*$") - if n then - section.name = vim.trim(n) - local offset = start + suffix_s - 1 - section.name_col = Range.new(n_s - 1 + offset, n_e - 1 + offset) - suffix = "" - end - end - - section.invalid = (target ~= "" or suffix ~= "") or - (section.workspace and section.kind ~= "default") or - (section.workspace and section.target ~= nil) - - return Section.new(section) - end - - return nil -end + ---@type string, integer, string + local prefix, suffix_s, suffix = text:match("^(.*)dependencies()(.*)$") + if prefix and suffix then + prefix = vim.trim(prefix) + suffix = vim.trim(suffix) + ---@type TomlSection + local section = { + text = text, + invalid = false, + kind = "default", + } + + local target = prefix + + local dev_target = prefix:match("^(.*)dev%-$") + if dev_target then + target = vim.trim(dev_target) + section.kind = "dev" + end + + local build_target = prefix:match("^(.*)build%-$") + if build_target then + target = vim.trim(build_target) + section.kind = "build" + end + + local workspace_target = target:match("^(.*)workspace%s*%.$") + if workspace_target then + section.workspace = true + target = vim.trim(workspace_target) + end + + if target then + local t = target:match("^target%s*%.(.+)%.$") + if t then + section.target = vim.trim(t) + target = "" + end + end + + if suffix then + local n_s, n, n_e = suffix:match("^%.%s*()(.+)()%s*$") + if n then + section.name = vim.trim(n) + local offset = start + suffix_s - 1 + section.name_col = Span.new(n_s - 1 + offset, n_e - 1 + offset) + suffix = "" + end + end + + section.invalid = (target ~= "" or suffix ~= "") + or (section.workspace and section.kind ~= "default") + or (section.workspace and section.target ~= nil) + return Section.new(section) + end + return nil +end + +---@param line string +---@param line_nr integer +---@param pattern string +---@return table|nil local function parse_crate_table_str(line, line_nr, pattern) - local quote_s, str_s, text, str_e, quote_e = line:match(pattern) - if text then - return { - text = text, - line = line_nr, - col = Range.new(str_s - 1, str_e - 1), - decl_col = Range.new(0, line:len()), - quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, - } - end + local quote_s, str_s, text, str_e, quote_e = line:match(pattern) + if text then + return { + text = text, + line = line_nr, + col = Span.new(str_s - 1, str_e - 1), + decl_col = Span.new(0, line:len()), + quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, + } + end - return nil + return nil end +---@param line string +---@param line_nr integer +---@param pattern string +---@return table|nil local function parse_crate_table_bool(line, line_nr, pattern) - local bool_s, text, bool_e = line:match(pattern) - if text then - return { - text = text, - line = line_nr, - col = Range.new(bool_s - 1, bool_e - 1), - decl_col = Range.new(0, line:len()), - } - end + local bool_s, text, bool_e = line:match(pattern) + if text then + return { + text = text, + line = line_nr, + col = Span.new(bool_s - 1, bool_e - 1), + decl_col = Span.new(0, line:len()), + } + end - return nil + return nil end +---@param line string +---@param line_nr integer +---@return TomlCrateVers|nil function M.parse_crate_table_vers(line, line_nr) - local pat = [[^%s*version%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) + local pat = [[^%s*version%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] + return parse_crate_table_str(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCrateRegistry|nil function M.parse_crate_table_registry(line, line_nr) - local pat = [[^%s*registry%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) + local pat = [[^%s*registry%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] + return parse_crate_table_str(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCratePath|nil function M.parse_crate_table_path(line, line_nr) - local pat = [[^%s*path%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) + local pat = [[^%s*path%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] + return parse_crate_table_str(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCrateGit|nil function M.parse_crate_table_git(line, line_nr) - local pat = [[^%s*git%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) + local pat = [[^%s*git%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] + return parse_crate_table_str(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCrateBranch|nil function M.parse_crate_table_branch(line, line_nr) - local pat = [[^%s*branch%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) + local pat = [[^%s*branch%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] + return parse_crate_table_str(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCrateRev|nil function M.parse_crate_table_rev(line, line_nr) - local pat = [[^%s*rev%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) + local pat = [[^%s*rev%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] + return parse_crate_table_str(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCratePkg|nil function M.parse_crate_table_pkg(line, line_nr) - local pat = [[^%s*package%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) + local pat = [[^%s*package%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] + return parse_crate_table_str(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCrateDef|nil function M.parse_crate_table_def(line, line_nr) - local pat = "^%s*default[_-]features%s*=%s*()([^%s]*)()%s*$" - return parse_crate_table_bool(line, line_nr, pat) + local pat = "^%s*default[_-]features%s*=%s*()([^%s]*)()%s*$" + return parse_crate_table_bool(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCrateWorkspace|nil function M.parse_crate_table_workspace(line, line_nr) - local pat = "^%s*workspace%s*=%s*()([^%s]*)()%s*$" - return parse_crate_table_bool(line, line_nr, pat) + local pat = "^%s*workspace%s*=%s*()([^%s]*)()%s*$" + return parse_crate_table_bool(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCrateOpt|nil function M.parse_crate_table_opt(line, line_nr) - local pat = "^%s*optional%s*=%s*()([^%s]*)()%s*$" - return parse_crate_table_bool(line, line_nr, pat) + local pat = "^%s*optional%s*=%s*()([^%s]*)()%s*$" + return parse_crate_table_bool(line, line_nr, pat) end +---@param line string +---@param line_nr integer +---@return TomlCrateFeat|nil function M.parse_crate_table_feat(line, line_nr) - local array_s, text, array_e = line:match("%s*features%s*=%s*%[()([^%]]*)()[%]]?%s*$") - if text then - return { - text = text, - line = line_nr, - col = Range.new(array_s - 1, array_e - 1), - decl_col = Range.new(0, line:len()), - } - end + local array_s, text, array_e = line:match("%s*features%s*=%s*%[()([^%]]*)()[%]]?%s*$") + if text then + return { + text = text, + line = line_nr, + col = Span.new(array_s - 1, array_e - 1), + decl_col = Span.new(0, line:len()), + } + end - return nil + return nil end - +---@param name string +---@return string local function inline_table_bool_pattern(name) - return "^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*" .. name .. "%s*=%s*()([^%s,}]*)()%s*()[,]?.*[}]?%s*$" + return "^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*" .. name .. "%s*=%s*()([^%s,}]*)()%s*()[,]?.*[}]?%s*$" end +---@param name string +---@return string local function inline_table_str_pattern(name) - return [[^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*]] .. name .. [[%s*=%s*(["'])()([^"']*)()(["']?)%s*()[,]?.*[}]?%s*$]] + return [[^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*]] .. name .. [[%s*=%s*(["'])()([^"']*)()(["']?)%s*()[,]?.*[}]?%s*$]] end +---@param name string +---@return string local function inline_table_str_array_pattern(name) - return "^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*" .. name .. "%s*=%s*%[()([^%]]*)()[%]]?%s*()[,]?.*[}]?%s*$" + return "^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*" .. name .. "%s*=%s*%[()([^%]]*)()[%]]?%s*()[,]?.*[}]?%s*$" end local INLINE_TABLE_VERS_PATTERN = inline_table_str_pattern("version") @@ -477,311 +541,338 @@ local INLINE_TABLE_DEF_PATTERN = inline_table_bool_pattern("default[_-]features" local INLINE_TABLE_WORKSPACE_PATTERN = inline_table_bool_pattern("workspace") local INLINE_TABLE_OPT_PATTERN = inline_table_bool_pattern("optional") +---@param line string +---@param line_nr integer +---@param pattern string +---@return string|nil +---@return Span +---@return table local function parse_inline_table_str(line, line_nr, pattern) - local name_s, name, name_e, decl_s, quote_s, str_s, text, str_e, quote_e, decl_e = line:match(pattern) - if name then - local name_col = Range.new(name_s - 1, name_e - 1) - local entry = { - text = text, - line = line_nr, - col = Range.new(str_s - 1, str_e - 1), - decl_col = Range.new(decl_s - 1, decl_e - 1), - quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, - } - - return name, name_col, entry - end -end - + local name_s, name, name_e, decl_s, quote_s, str_s, text, str_e, quote_e, decl_e = line:match(pattern) + if name then + local name_col = Span.new(name_s - 1, name_e - 1) + local entry = { + text = text, + line = line_nr, + col = Span.new(str_s - 1, str_e - 1), + decl_col = Span.new(decl_s - 1, decl_e - 1), + quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, + } + + return name, name_col, entry + end +end + +---comment +---@param line string +---@param line_nr integer +---@param pattern string +---@return string|nil +---@return Span +---@return table local function parse_inline_table_bool(line, line_nr, pattern) - local name_s, name, name_e, decl_s, str_s, text, str_e, decl_e = line:match(pattern) - if name then - local name_col = Range.new(name_s - 1, name_e - 1) - local entry = { - text = text, - line = line_nr, - col = Range.new(str_s - 1, str_e - 1), - decl_col = Range.new(decl_s - 1, decl_e - 1), - } - return name, name_col, entry - end -end - -function M.parse_inline_crate(line, line_nr) - - do - local name_s, name, name_e, quote_s, str_s, text, str_e, quote_e = line:match([[^%s*()([^%s]+)()%s*=%s*(["'])()([^"']*)()(["']?)%s*$]]) - if name then - return { - explicit_name = name, - explicit_name_col = Range.new(name_s - 1, name_e - 1), - lines = Range.new(line_nr, line_nr + 1), - syntax = "plain", - vers = { - text = text, - line = line_nr, - col = Range.new(str_s - 1, str_e - 1), - decl_col = Range.new(0, line:len()), - quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, - }, - } - end - end - - - local crate = { - syntax = "inline_table", - lines = Range.new(line_nr, line_nr + 1), - } - - do - local name, name_col, vers = parse_inline_table_str(line, line_nr, INLINE_TABLE_VERS_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.vers = vers - end - end - - do - local name, name_col, registry = parse_inline_table_str(line, line_nr, INLINE_TABLE_REGISTRY_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.registry = registry - end - end - - do - local name, name_col, path = parse_inline_table_str(line, line_nr, INLINE_TABLE_PATH_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.path = path - end - end - - do - local name, name_col, git = parse_inline_table_str(line, line_nr, INLINE_TABLE_GIT_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.git = git - end - end - - do - local name, name_col, branch = parse_inline_table_str(line, line_nr, INLINE_TABLE_BRANCH_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.branch = branch - end - end - - do - local name, name_col, rev = parse_inline_table_str(line, line_nr, INLINE_TABLE_REV_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.rev = rev - end - end - - do - local name, name_col, pkg = parse_inline_table_str(line, line_nr, INLINE_TABLE_PKG_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.pkg = pkg - end - end - - do - local name, name_col, def = parse_inline_table_bool(line, line_nr, INLINE_TABLE_DEF_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.def = def - end - end - - do - local name, name_col, workspace = parse_inline_table_bool(line, line_nr, INLINE_TABLE_WORKSPACE_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.workspace = workspace - end - end - - do - local name, name_col, opt = parse_inline_table_bool(line, line_nr, INLINE_TABLE_OPT_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.opt = opt - end - end - - do - local name_s, name, name_e, decl_s, array_s, text, array_e, decl_e = line:match(INLINE_TABLE_FEAT_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = Range.new(name_s - 1, name_e - 1) - crate.feat = { + local name_s, name, name_e, decl_s, str_s, text, str_e, decl_e = line:match(pattern) + if name then + local name_col = Span.new(name_s - 1, name_e - 1) + local entry = { text = text, line = line_nr, - col = Range.new(array_s - 1, array_e - 1), - decl_col = Range.new(decl_s - 1, decl_e - 1), - } - end - end - - if crate.explicit_name then - return crate - end - - return nil + col = Span.new(str_s - 1, str_e - 1), + decl_col = Span.new(decl_s - 1, decl_e - 1), + } + return name, name_col, entry + end end +---comment +---@param line string +---@param line_nr integer +---@return TomlCrate|nil +function M.parse_inline_crate(line, line_nr) + -- plain version + do + local name_s, name, name_e, quote_s, str_s, text, str_e, quote_e = line:match([[^%s*()([^%s]+)()%s*=%s*(["'])()([^"']*)()(["']?)%s*$]]) + if name then + return { + explicit_name = name, + explicit_name_col = Span.new(name_s - 1, name_e - 1), + lines = Span.new(line_nr, line_nr + 1), + syntax = "plain", + vers = { + text = text, + line = line_nr, + col = Span.new(str_s - 1, str_e - 1), + decl_col = Span.new(0, line:len()), + quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, + } + } + end + end + + -- inline table + ---@type TomlCrate + local crate = { + syntax = "inline_table", + lines = Span.new(line_nr, line_nr + 1), + } + + do + local name, name_col, vers = parse_inline_table_str(line, line_nr, INLINE_TABLE_VERS_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.vers = vers + end + end + + do + local name, name_col, registry = parse_inline_table_str(line, line_nr, INLINE_TABLE_REGISTRY_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.registry = registry + end + end + + do + local name, name_col, path = parse_inline_table_str(line, line_nr, INLINE_TABLE_PATH_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.path = path + end + end + + do + local name, name_col, git = parse_inline_table_str(line, line_nr, INLINE_TABLE_GIT_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.git = git + end + end + + do + local name, name_col, branch = parse_inline_table_str(line, line_nr, INLINE_TABLE_BRANCH_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.branch = branch + end + end + + do + local name, name_col, rev = parse_inline_table_str(line, line_nr, INLINE_TABLE_REV_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.rev = rev + end + end + + do + local name, name_col, pkg = parse_inline_table_str(line, line_nr, INLINE_TABLE_PKG_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.pkg = pkg + end + end + + do + local name, name_col, def = parse_inline_table_bool(line, line_nr, INLINE_TABLE_DEF_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.def = def + end + end + + do + local name, name_col, workspace = parse_inline_table_bool(line, line_nr, INLINE_TABLE_WORKSPACE_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.workspace = workspace + end + end + + do + local name, name_col, opt = parse_inline_table_bool(line, line_nr, INLINE_TABLE_OPT_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = name_col + crate.opt = opt + end + end + + do + local name_s, name, name_e, decl_s, array_s, text, array_e, decl_e = line:match(INLINE_TABLE_FEAT_PATTERN) + if name then + crate.explicit_name = name + crate.explicit_name_col = Span.new(name_s - 1, name_e - 1) + crate.feat = { + text = text, + line = line_nr, + col = Span.new(array_s - 1, array_e - 1), + decl_col = Span.new(decl_s - 1, decl_e - 1), + } + end + end + + if crate.explicit_name then + return crate + end + + return nil +end + +---@param line string +---@return string function M.trim_comments(line) - local uncommented = line:match("^([^#]*)#.*$") - return uncommented or line + local uncommented = line:match("^([^#]*)#.*$") + return uncommented or line end +---comment +---@param buf integer +---@return TomlSection[] +---@return TomlCrate[] function M.parse_crates(buf) - local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + ---@type string[] + local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + + local sections = {} + local crates = {} + + ---@type TomlSection|nil + local dep_section = nil + ---@type TomlCrate|nil + local dep_section_crate = nil + + for i,line in ipairs(lines) do + line = M.trim_comments(line) + local line_nr = i - 1 - local sections = {} - local crates = {} + local section_start, section_text = line:match("^%s*%[()(.+)%]%s*$") + + if section_text then + if dep_section then + -- close line span + dep_section.lines.e = line_nr + + -- push pending crate + if dep_section_crate then + dep_section_crate.lines = dep_section.lines + table.insert(crates, Crate.new(dep_section_crate)) + end + end - local dep_section = nil - local dep_section_crate = nil + local section = M.parse_section(section_text, section_start - 1) - for i, line in ipairs(lines) do - line = M.trim_comments(line) - local line_nr = i - 1 + if section then + section.lines = Span.new(line_nr, nil) + dep_section = section + dep_section_crate = nil + table.insert(sections, dep_section) + else + dep_section = nil + dep_section_crate = nil + end + elseif dep_section and dep_section.name then + local empty_crate = { + explicit_name = dep_section.name, + explicit_name_col = dep_section.name_col, + section = dep_section, + syntax = "table", + } + + local vers = M.parse_crate_table_vers(line, line_nr) + if vers then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.vers = vers + end - local section_start, section_text = line:match("^%s*%[()(.+)%]%s*$") + local registry = M.parse_crate_table_registry(line, line_nr) + if registry then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.registry = registry + end - if section_text then - if dep_section then + local path = M.parse_crate_table_path(line, line_nr) + if path then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.path = path + end - dep_section.lines.e = line_nr + local git = M.parse_crate_table_git(line, line_nr) + if git then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.git = git + end + local branch = M.parse_crate_table_branch(line, line_nr) + if branch then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.branch = branch + end - if dep_section_crate then - dep_section_crate.lines = dep_section.lines - table.insert(crates, Crate.new(dep_section_crate)) + local rev = M.parse_crate_table_rev(line, line_nr) + if rev then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.rev = rev end - end - - local section = M.parse_section(section_text, section_start - 1) - - if section then - section.lines = Range.new(line_nr, nil) - dep_section = section - dep_section_crate = nil - table.insert(sections, dep_section) - else - dep_section = nil - dep_section_crate = nil - end - elseif dep_section and dep_section.name then - local empty_crate = { - explicit_name = dep_section.name, - explicit_name_col = dep_section.name_col, - section = dep_section, - syntax = "table", - } - - local vers = M.parse_crate_table_vers(line, line_nr) - if vers then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.vers = vers - end - - local registry = M.parse_crate_table_registry(line, line_nr) - if registry then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.registry = registry - end - - local path = M.parse_crate_table_path(line, line_nr) - if path then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.path = path - end - - local git = M.parse_crate_table_git(line, line_nr) - if git then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.git = git - end - - local branch = M.parse_crate_table_branch(line, line_nr) - if branch then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.branch = branch - end - - local rev = M.parse_crate_table_rev(line, line_nr) - if rev then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.rev = rev - end - - local pkg = M.parse_crate_table_pkg(line, line_nr) - if pkg then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.pkg = pkg - end - - local def = M.parse_crate_table_def(line, line_nr) - if def then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.def = def - end - - local workspace = M.parse_crate_table_workspace(line, line_nr) - if workspace then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.workspace = workspace - end - - local opt = M.parse_crate_table_opt(line, line_nr) - if opt then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.opt = opt - end - - local feat = M.parse_crate_table_feat(line, line_nr) - if feat then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.feat = feat - end - elseif dep_section then - local crate = M.parse_inline_crate(line, line_nr) - if crate then - crate.section = dep_section - table.insert(crates, Crate.new(crate)) - end - end - end - - if dep_section then - - dep_section.lines.e = #lines - - - if dep_section_crate then - dep_section_crate.lines = dep_section.lines - table.insert(crates, Crate.new(dep_section_crate)) - end - end - - return sections, crates + + local pkg = M.parse_crate_table_pkg(line, line_nr) + if pkg then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.pkg = pkg + end + + local def = M.parse_crate_table_def(line, line_nr) + if def then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.def = def + end + + local workspace = M.parse_crate_table_workspace(line, line_nr) + if workspace then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.workspace = workspace + end + + local opt = M.parse_crate_table_opt(line, line_nr) + if opt then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.opt = opt + end + + local feat = M.parse_crate_table_feat(line, line_nr) + if feat then + dep_section_crate = dep_section_crate or empty_crate + dep_section_crate.feat = feat + end + elseif dep_section then + local crate = M.parse_inline_crate(line, line_nr) + if crate then + crate.section = dep_section + table.insert(crates, Crate.new(crate)) + end + end + end + + if dep_section then + -- close line span + dep_section.lines.e = #lines + + -- push pending crate + if dep_section_crate then + dep_section_crate.lines = dep_section.lines + table.insert(crates, Crate.new(dep_section_crate)) + end + end + + return sections, crates end return M diff --git a/lua/crates/types.lua b/lua/crates/types.lua index db51d8f6..5ebb9b8c 100644 --- a/lua/crates/types.lua +++ b/lua/crates/types.lua @@ -1,249 +1,265 @@ -local M = {CrateInfo = {}, Diagnostic = {}, Crate = {}, Version = {}, Features = {}, Feature = {}, Dependency = {Vers = {}, }, SemVer = {}, Requirement = {}, Range = {}, } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -local Diagnostic = M.Diagnostic -local Feature = M.Feature -local Features = M.Features -local Range = M.Range -local SemVer = M.SemVer -local time = require("crates.time") -local DateTime = time.DateTime - -function Diagnostic.new(obj) - return setmetatable(obj, { __index = Diagnostic }) +local M = {} + +---@class CrateInfo +---@field lines Span +---@field vers_line integer +---@field vers_match ApiVersion +---@field vers_update ApiVersion +---@field vers_upgrade ApiVersion +---@field match_kind MatchKind + +---@enum MatchKind +M.MatchKind = { + version = 1, + yanked = 2, + prerelease = 3, + nomatch = 4, +} + +---@class ApiCrate +---@field name string +---@field description string +---@field created DateTime +---@field updated DateTime +---@field downloads integer +---@field homepage string|nil +---@field repository string|nil +---@field documentation string|nil +---@field categories string[] +---@field keywords string[] +---@field versions ApiVersion[] + +---@class ApiVersion +---@field num string +---@field features ApiFeatures +---@field yanked boolean +---@field parsed SemVer +---@field created DateTime +---@field deps ApiDependency[]|nil + +---@class ApiFeature +---@field name string +---@field members string[] + +---@class ApiDependency +---@field name string +---@field opt boolean +---@field kind ApiDependencyKind +---@field vers ApiDependencyVers + +---@enum ApiDependencyKind +M.DependencyKind = { + normal = 1, + build = 2, + dev = 3, +} + +---@class ApiDependencyVers +---@field reqs Requirement[] +---@field text string + +---@class Requirement +---@field cond Cond +---@field cond_col Span +---@field vers SemVer +---@field vers_col Span + +---@enum Cond +M.Cond = { + eq = "eq", + lt = "lt", + le = "le", + gt = "gt", + ge = "ge", + cr = "cr", + tl = "tl", + wl = "wl", + bl = "bl", +} + +---@class CratesDiagnostic +---@field lnum integer +---@field end_lnum integer +---@field col integer +---@field end_col integer +---@field severity integer +---@field kind CratesDiagnosticKind +---@field data {string:any} +local CratesDiagnostic = {} +M.CratesDiagnostic = CratesDiagnostic + +---@param obj CratesDiagnostic +---@return CratesDiagnostic +function CratesDiagnostic.new(obj) + return setmetatable(obj, { __index = CratesDiagnostic }) end -function Diagnostic:contains(line, col) - return (self.lnum < line or self.lnum == line and self.col <= col) and - (self.end_lnum > line or self.end_lnum == line and self.end_col > col) +---@param line integer +---@param col integer +---@return boolean +function CratesDiagnostic:contains(line, col) + return (self.lnum < line or self.lnum == line and self.col <= col) + and (self.end_lnum > line or self.end_lnum == line and self.end_col > col) end - -function Features.new(list) - local map = {} - for _, f in ipairs(list) do - map[f.name] = f - end - return setmetatable({ list = list, map = map }, { __index = Features }) +---keys of DiagnosticConfig +---@enum CratesDiagnosticKind +M.DiagnosticKind = { + -- error + section_invalid = "section_invalid", + workspace_section_not_default = "workspace_section_not_default", + workspace_section_has_target = "workspace_section_has_target", + section_dup = "section_dup", + crate_dup = "crate_dup", + crate_novers = "crate_novers", + crate_error_fetching = "crate_error_fetching", + crate_name_case = "crate_name_case", + vers_nomatch = "vers_nomatch", + vers_yanked = "vers_yanked", + vers_pre = "vers_pre", + def_invalid = "def_invalid", + feat_invalid = "feat_invalid", + -- warning + vers_upgrade = "vers_upgrade", + feat_dup = "feat_dup", + -- hint + section_dup_orig = "section_dup_orig", + crate_dup_orig = "crate_dup_orig", + feat_dup_orig = "feat_dup_orig", +} + +---@class ApiFeatures +---@field list ApiFeature[] +---@field map table +local ApiFeatures = {} +M.ApiFeatures = ApiFeatures + +---@param list ApiFeature[] +---@return ApiFeatures +function ApiFeatures.new(list) + ---@type table + local map = {} + for _,f in ipairs(list) do + map[f.name] = f + end + return setmetatable({ list = list, map = map }, { __index = ApiFeatures }) end -function Features:get_feat(name) - return self.map[name] +---@param name string +---@return ApiFeature|nil +function ApiFeatures:get_feat(name) + return self.map[name] end -function Features:sort() - table.sort(self.list, function(a, b) - if a.name == "default" then - return true - elseif b.name == "default" then - return false - else - return a.name < b.name - end - end) +function ApiFeatures:sort() + table.sort(self.list, function (a, b) + if a.name == "default" then + return true + elseif b.name == "default" then + return false + else + return a.name < b.name + end + end) end -function Features:insert(feat) - table.insert(self.list, feat) - self.map[feat.name] = feat +---@param feat ApiFeature +function ApiFeatures:insert(feat) + table.insert(self.list, feat) + self.map[feat.name] = feat end - +---@class SemVer +---@field major integer|nil +---@field minor integer|nil +---@field patch integer|nil +---@field pre string|nil +---@field meta string|nil +local SemVer = {} +M.SemVer = SemVer + +---@param obj SemVer +---@return SemVer function SemVer.new(obj) - return setmetatable(obj, { __index = SemVer }) + return setmetatable(obj, { __index = SemVer }) end +---@return string function SemVer:display() - local text = "" - if self.major then - text = text .. self.major - end + local text = "" + if self.major then + text = text .. self.major + end - if self.minor then - text = text .. "." .. self.minor - end + if self.minor then + text = text .. "." .. self.minor + end - if self.patch then - text = text .. "." .. self.patch - end + if self.patch then + text = text .. "." .. self.patch + end - if self.pre then - text = text .. "-" .. self.pre - end + if self.pre then + text = text .. "-" .. self.pre + end - if self.meta then - text = text .. "+" .. self.meta - end + if self.meta then + text = text .. "+" .. self.meta + end - return text + return text end - -function Range.new(s, e) - return setmetatable({ s = s, e = e }, { __index = Range }) +---@class Span +---@field s integer -- 0-indexed inclusive +---@field e integer -- 0-indexed exclusive +local Span = {} +M.Span = Span + +---@param s integer +---@param e integer +---@return Span +function Span.new(s, e) + return setmetatable({ s = s, e = e }, { __index = Span }) end -function Range.pos(p) - return Range.new(p, p + 1) +---@param p integer +---@return Span +function Span.pos(p) + return Span.new(p, p + 1) end -function Range.empty() - return Range.new(0, 0) +---@return Span +function Span.empty() + return Span.new(0, 0) end -function Range:contains(pos) - return self.s <= pos and pos < self.e +---@param pos integer +---@return boolean +function Span:contains(pos) + return self.s <= pos and pos < self.e end - -function Range:moved(s, e) - return Range.new(self.s + s, self.e + e) +---Create a new span with moved start and end bounds +---@param s integer +---@param e integer +---@return Span +function Span:moved(s, e) + return Span.new(self.s + s, self.e + e) end -function Range:iter() - local i = self.s - return function() - if i >= self.e then - return nil - end - - local val = i - i = i + 1 - return val - end +---@return fun(): integer|nil +function Span:iter() + local i = self.s + return function() + if i >= self.e then + return nil + end + + local val = i + i = i + 1 + return val + end end return M diff --git a/lua/crates/ui.lua b/lua/crates/ui.lua deleted file mode 100644 index 5ef22194..00000000 --- a/lua/crates/ui.lua +++ /dev/null @@ -1,114 +0,0 @@ -local M = {} - - - - -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local CrateInfo = types.CrateInfo -local Diagnostic = types.Diagnostic -local Version = types.Version - -local CUSTOM_NS = vim.api.nvim_create_namespace("crates.nvim") -local DIAGNOSTIC_NS = vim.api.nvim_create_namespace("crates.nvim.diagnostic") - -M.custom_diagnostics = {} -M.diagnostics = {} - -local function to_vim_diagnostic(d) - local diag = { - lnum = d.lnum, - end_lnum = d.end_lnum, - col = d.col, - end_col = d.end_col, - severity = d.severity, - message = state.cfg.diagnostic[d.kind], - source = "crates", - } - return diag -end - -function M.display_diagnostics(buf, diagnostics) - if not state.visible then return end - - local buf_diagnostics = M.diagnostics[buf] or {} - for _, d in ipairs(diagnostics) do - local vim_diagnostic = to_vim_diagnostic(d) - table.insert(buf_diagnostics, vim_diagnostic) - end - M.diagnostics[buf] = buf_diagnostics - - vim.diagnostic.set(DIAGNOSTIC_NS, buf, M.diagnostics[buf]) -end - -function M.display_crate_info(buf, info, diagnostics) - if not state.visible then return end - - local buf_diagnostics = M.custom_diagnostics[buf] or {} - for _, d in ipairs(diagnostics) do - local vim_diagnostic = to_vim_diagnostic(d) - table.insert(buf_diagnostics, vim_diagnostic) - end - M.custom_diagnostics[buf] = buf_diagnostics - - local virt_text = {} - if info.vers_match then - local match = info.vers_match - table.insert(virt_text, { - string.format(state.cfg.text[info.match_kind], match.num), - state.cfg.highlight[info.match_kind], - }) - elseif info.match_kind == "nomatch" then - table.insert(virt_text, { - state.cfg.text.nomatch, - state.cfg.highlight.nomatch, - }) - end - if info.vers_upgrade then - local upgrade = info.vers_upgrade - table.insert(virt_text, { - string.format(state.cfg.text.upgrade, upgrade.num), - state.cfg.highlight.upgrade, - }) - end - - if not (info.vers_match or info.vers_upgrade) then - table.insert(virt_text, { - state.cfg.text.error, - state.cfg.highlight.error, - }) - end - - vim.diagnostic.set(CUSTOM_NS, buf, M.custom_diagnostics[buf], { virtual_text = false }) - vim.api.nvim_buf_clear_namespace(buf, CUSTOM_NS, info.lines.s, info.lines.e) - vim.api.nvim_buf_set_extmark(buf, CUSTOM_NS, info.vers_line, -1, { - virt_text = virt_text, - virt_text_pos = "eol", - hl_mode = "combine", - }) -end - -function M.display_loading(buf, crate) - if not state.visible then return end - - local virt_text = { { state.cfg.text.loading, state.cfg.highlight.loading } } - vim.api.nvim_buf_clear_namespace(buf, CUSTOM_NS, crate.lines.s, crate.lines.e) - local vers_line = crate.vers and crate.vers.line or crate.lines.s - vim.api.nvim_buf_set_extmark(buf, CUSTOM_NS, vers_line, -1, { - virt_text = virt_text, - virt_text_pos = "eol", - hl_mode = "combine", - }) -end - -function M.clear(buf) - M.custom_diagnostics[buf] = nil - M.diagnostics[buf] = nil - - vim.api.nvim_buf_clear_namespace(buf, CUSTOM_NS, 0, -1) - vim.diagnostic.reset(CUSTOM_NS, buf) - vim.diagnostic.reset(DIAGNOSTIC_NS, buf) -end - -return M diff --git a/teal/crates/ui.tl b/lua/crates/ui.tl similarity index 100% rename from teal/crates/ui.tl rename to lua/crates/ui.tl diff --git a/lua/crates/util.lua b/lua/crates/util.lua index 47ad3e6a..9bcf36d2 100644 --- a/lua/crates/util.lua +++ b/lua/crates/util.lua @@ -1,186 +1,219 @@ -local M = {FeatureInfo = {}, } - - - - - - -M.FeatureInfo.ENABLED = 1 -M.FeatureInfo.TRANSITIVE = 2 - -local FeatureInfo = M.FeatureInfo local semver = require("crates.semver") local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Diagnostic = types.Diagnostic -local CrateInfo = types.CrateInfo -local Feature = types.Feature -local Features = types.Features -local Range = types.Range -local Requirement = types.Requirement -local Version = types.Version + +local M = {} + +---@enum FeatureInfo +M.FeatureInfo = { + ENABLED = 1, + TRANSITIVE = 2, +} local IS_WIN = vim.api.nvim_call_function("has", { "win32" }) == 1 +---@return integer function M.current_buf() - return vim.api.nvim_get_current_buf() + return vim.api.nvim_get_current_buf() end +---@return integer, integer function M.cursor_pos() - local cursor = vim.api.nvim_win_get_cursor(0) - return cursor[1] - 1, cursor[2] + ---@type integer[2] + local cursor = vim.api.nvim_win_get_cursor(0) + return cursor[1] - 1, cursor[2] end +---@param buf integer +---@return table|nil function M.get_buf_crates(buf) - local cache = state.buf_cache[buf] - return cache and cache.crates + local cache = state.buf_cache[buf] + return cache and cache.crates end +---@param buf integer +---@return table|nil function M.get_buf_info(buf) - local cache = state.buf_cache[buf] - return cache and cache.info + local cache = state.buf_cache[buf] + return cache and cache.info end +---@param buf integer +---@return Diagnostic[]|nil function M.get_buf_diagnostics(buf) - local cache = state.buf_cache[buf] - return cache and cache.diagnostics + local cache = state.buf_cache[buf] + return cache and cache.diagnostics end +---@param buf integer +---@param key string +---@return CrateInfo|nil function M.get_crate_info(buf, key) - local info = M.get_buf_info(buf) - return info[key] + local info = M.get_buf_info(buf) + return info and info[key] end +---@param buf integer +---@param lines Span +---@return table function M.get_line_crates(buf, lines) - local cache = state.buf_cache[buf] - local crates = cache and cache.crates - if not crates then - return {} - end - - local line_crates = {} - for k, c in pairs(crates) do - if lines:contains(c.lines.s) or c.lines:contains(lines.s) then - line_crates[k] = c - end - end - - return line_crates -end - + local cache = state.buf_cache[buf] + local crates = cache and cache.crates + if not crates then + return {} + end + + ---@type table + local line_crates = {} + for k,c in pairs(crates) do + if lines:contains(c.lines.s) or c.lines:contains(lines.s) then + line_crates[k] = c + end + end + + return line_crates +end + +---@param versions ApiVersion[] +---@param avoid_pre boolean +---@param reqs Requirement[]|nil +---@return ApiVersion|nil +---@return ApiVersion|nil +---@return ApiVersion|nil function M.get_newest(versions, avoid_pre, reqs) - if not versions then - return nil - end - - local newest_yanked = nil - local newest_pre = nil - local newest = nil - - for _, v in ipairs(versions) do - if not reqs or semver.matches_requirements(v.parsed, reqs) then - if not v.yanked then - if not avoid_pre or avoid_pre and not v.parsed.pre then - newest = v - break + if not versions then + return nil + end + + ---@type ApiVersion|nil, ApiVersion|nil, ApiVersion|nil + local newest_yanked, newest_pre, newest + + for _,v in ipairs(versions) do + if not reqs or semver.matches_requirements(v.parsed, reqs) then + if not v.yanked then + if not avoid_pre or avoid_pre and not v.parsed.pre then + newest = v + break + else + newest_pre = newest_pre or v + end else - newest_pre = newest_pre or v + newest_yanked = newest_yanked or v end - else - newest_yanked = newest_yanked or v - end - end - end + end + end - return newest, newest_pre, newest_yanked + return newest, newest_pre, newest_yanked end +---@param crate TomlCrate +---@param feature ApiFeature +---@return boolean function M.is_feat_enabled(crate, feature) - local enabled = crate:get_feat(feature.name) ~= nil - if feature.name == "default" then - return enabled or crate:is_def_enabled() - else - return enabled - end + local enabled = crate:get_feat(feature.name) ~= nil + if feature.name == "default" then + return enabled or crate:is_def_enabled() + else + return enabled + end end +---@param crate TomlCrate +---@param features ApiFeatures +---@return table function M.features_info(crate, features) - local info = {} - - local function update_transitive(f) - for _, m in ipairs(f.members) do - local tf = features:get_feat(m) - if tf then - local i = info[m] - if not i then - info[m] = FeatureInfo.TRANSITIVE - update_transitive(tf) + ---@type table + local info = {} + + ---@param f ApiFeature + local function update_transitive(f) + for _,m in ipairs(f.members) do + local tf = features:get_feat(m) + if tf then + local i = info[m] + if not i then + info[m] = M.FeatureInfo.TRANSITIVE + update_transitive(tf) + end end - end - end - end + end + end - if not crate.def or crate.def.enabled then - info["default"] = FeatureInfo.ENABLED - local api_feat = features.list[1] - update_transitive(api_feat) - end + if not crate.def or crate.def.enabled then + info["default"] = M.FeatureInfo.ENABLED + local api_feat = features.list[1] + update_transitive(api_feat) + end - local crate_features = crate.feat - if not crate_features then - return info - end + local crate_features = crate.feat + if not crate_features then + return info + end - for _, crate_feat in ipairs(crate_features.items) do - local api_feat = features:get_feat(crate_feat.name) - if api_feat then - info[(api_feat).name] = FeatureInfo.ENABLED - update_transitive(api_feat) - end - end + for _,crate_feat in ipairs(crate_features.items) do + local api_feat = features:get_feat(crate_feat.name) + if api_feat then + info[api_feat.name] = M.FeatureInfo.ENABLED + update_transitive(api_feat) + end + end - return info + return info end +---@param name string +---@return boolean function M.lualib_installed(name) - local ok, _ = pcall(require, name) - return ok + local ok = pcall(require, name) + return ok end +---comment +---@param name string +---@return boolean function M.binary_installed(name) - if IS_WIN then - name = name .. ".exe" - end + if IS_WIN then + name = name .. ".exe" + end - return vim.fn.executable(name) == 1 + return vim.fn.executable(name) == 1 end +---comment +---@param severity integer +---@param s string +---@param ... any function M.notify(severity, s, ...) - vim.notify(s:format(...), severity, { title = state.cfg.notification_title }) + vim.notify(s:format(...), severity, { title = state.cfg.notification_title }) end +---@param name string +---@return string function M.docs_rs_url(name) - return "https://docs.rs/" .. name + return "https://docs.rs/"..name end +---@param name string +---@return string function M.crates_io_url(name) - return "https://crates.io/crates/" .. name + return "https://crates.io/crates/"..name end +---@param url string function M.open_url(url) - for _, prg in ipairs(state.cfg.open_programs) do - if M.binary_installed(prg) then - vim.cmd(string.format("silent !%s %s", prg, url)) - return - end - end + for _, prg in ipairs(state.cfg.open_programs) do + if M.binary_installed(prg) then + vim.cmd(string.format("silent !%s %s", prg, url)) + return + end + end - M.notify(vim.log.levels.WARN, "Couldn't open url") + M.notify(vim.log.levels.WARN, "Couldn't open url") end +---@param name string +---@return string function M.format_title(name) - return name:sub(1, 1):upper() .. name:gsub("_", " "):sub(2) + return name:sub(1, 1):upper() .. name:gsub("_", " "):sub(2) end return M diff --git a/teal/crates/async.tl b/teal/crates/async.tl deleted file mode 100644 index 78f9667a..00000000 --- a/teal/crates/async.tl +++ /dev/null @@ -1,65 +0,0 @@ -local record M end - -function M.launch(f: function, ...) - local t = coroutine.create(f) - local function exec(...) - local ok, data = coroutine.resume(t, ...) as (boolean, function(function)) - if not ok then - error(debug.traceback(t as string, data as number)) - end - if coroutine.status(t) ~= "dead" then - data(exec) - end - end - exec(...) -end - -function M.wrap(f: function): function - return function(...) - M.launch(f, ...) - end -end - ----Throttle a function using tail calling -function M.throttle(f: function, timeout: integer): function - local last_call = 0; - - local timer: vim.loop.Timer = nil - - return function(...) - -- Make sure to stop any scheduled timers - if timer then - timer:stop() - end - - local rem = timeout - (vim.loop.now() - last_call) - -- Schedule a tail call - if rem > 0 then - -- Reuse timer - if timer is nil then - timer = vim.loop.new_timer() - end - - local args = { ... } - timer:start(rem, 0, vim.schedule_wrap(function() - timer:stop() - timer:close() - timer = nil - - -- Reset here to ensure timeout between the execution of the - -- tail call, and not the last call to throttle - - -- If it was reset in the throttle call, it could be a shorter - -- interval between calls to f - last_call = vim.loop.now() - - f(unpack(args)) - end)) - else - last_call = vim.loop.now() - f(...) - end - end -end - -return M diff --git a/teal/crates/config.tl b/teal/crates/config.tl deleted file mode 100644 index e7dcd3ab..00000000 --- a/teal/crates/config.tl +++ /dev/null @@ -1,1703 +0,0 @@ -local record M - schema: {string:SchemaElement} - - record Config - smart_insert: boolean - insert_closing_quote: boolean - autoload: boolean - autoupdate: boolean - autoupdate_throttle: integer - loading_indicator: boolean - date_format: string - thousands_separator: string - notification_title: string - curl_args: {string} - open_programs: {string} - max_parallel_requests: integer - expand_crate_moves_cursor: boolean - disable_invalid_feature_diagnostic: boolean - enable_update_available_warning: boolean - on_attach: function(bufnr: integer): nil - text: TextConfig - highlight: HighlightConfig - diagnostic: DiagnosticConfig - popup: PopupConfig - src: SrcConfig - null_ls: NullLsConfig - lsp: LspConfig - - record TextConfig - loading: string - version: string - prerelease: string - yanked: string - nomatch: string - upgrade: string - error: string - end - - record HighlightConfig - loading: string - version: string - prerelease: string - yanked: string - nomatch: string - upgrade: string - error: string - end - - record DiagnosticConfig - section_invalid: string - workspace_section_not_default: string - workspace_section_has_target: string - section_dup: string - section_dup_orig: string - crate_dup: string - crate_dup_orig: string - crate_novers: string - crate_error_fetching: string - crate_name_case: string - vers_upgrade: string - vers_pre: string - vers_yanked: string - vers_nomatch: string - def_invalid: string - feat_dup: string - feat_dup_orig: string - feat_invalid: string - end - - record PopupConfig - autofocus: boolean - hide_on_select: boolean - copy_register: string - style: string - border: string|{string} - show_version_date: boolean - show_dependency_version: boolean - max_height: integer - min_width: integer - padding: integer - text: PopupTextConfig - highlight: PopupHighlightConfig - keys: PopupKeyConfig - end - - record PopupTextConfig - title: string - pill_left: string - pill_right: string - - description: string - created: string - created_label: string - updated: string - updated_label: string - downloads: string - downloads_label: string - homepage: string - homepage_label: string - repository: string - repository_label: string - documentation: string - documentation_label: string - crates_io: string - crates_io_label: string - categories_label: string - keywords_label: string - - version: string - prerelease: string - yanked: string - version_date: string - - feature: string - enabled: string - transitive: string - - normal_dependencies_title: string - build_dependencies_title: string - dev_dependencies_title: string - dependency: string - optional: string - dependency_version: string - loading: string - end - - record PopupHighlightConfig - title: string - pill_text: string - pill_border: string - - created: string - created_label: string - updated: string - updated_label: string - description: string - downloads: string - downloads_label: string - homepage: string - homepage_label: string - repository: string - repository_label: string - documentation: string - documentation_label: string - crates_io: string - crates_io_label: string - categories_label: string - keywords_label: string - - version: string - prerelease: string - yanked: string - version_date: string - - feature: string - enabled: string - transitive: string - - normal_dependencies_title: string - build_dependencies_title: string - dev_dependencies_title: string - dependency: string - optional: string - dependency_version: string - loading: string - end - - record PopupKeyConfig - hide: {string} - open_url: {string} - select: {string} - select_alt: {string} - toggle_feature: {string} - copy_value: {string} - goto_item: {string} - jump_forward: {string} - jump_back: {string} - end - - record SrcConfig - insert_closing_quote: boolean - text: SrcTextConfig - coq: CoqConfig - cmp: CmpConfig - end - - record SrcTextConfig - prerelease: string - yanked: string - end - - record CoqConfig - enabled: boolean - name: string - end - - record CmpConfig - enabled: boolean - use_custom_kind: boolean - kind_text: CmpKindTextConfig - kind_highlight: CmpKindHighlightConfig - end - - record CmpKindTextConfig - version: string - feature: string - end - - record CmpKindHighlightConfig - version: string - feature: string - end - - record NullLsConfig - enabled: boolean - name: string - end - - record LspConfig - enabled: boolean - name: string - on_attach: function(client: {string:any}, bufnr: integer): nil - actions: boolean - completion: boolean - end - end - - enum SchemaType - -- A record of grouped options - "section" - -- The rest are lua types checked at runtime - "table" - "string" - "number" - "boolean" - "function" - end - - record SchemaElement - name: string - type: SchemaType|{SchemaType} - default: any - default_text: string - description: string - deprecated: Deprecated - fields: {string:SchemaElement} - hidden: boolean - - record Deprecated - new_field: {string} - hard: boolean - end - end -end - -local Config = M.Config -local SchemaElement = M.SchemaElement -local SchemaType = M.SchemaType - -local function entry(schema: {string:SchemaElement}, name: string, elem: SchemaElement) - elem.name = name - table.insert(schema as {SchemaElement}, elem) - schema[name] = elem -end - -M.schema = {} -entry(M.schema, "smart_insert", { - type = "boolean", - default = true, - description = [[ - Try to be smart about inserting versions, by respecting existing version requirements. - - Example: ~ - - Existing requirement: - `>0.8, <1.3` - - Version to insert: - `1.5.4` - - Resulting requirement: - `>0.8, <1.6` - ]], -}) -entry(M.schema, "insert_closing_quote", { - type = "boolean", - default = true, - description = [[ - Insert a closing quote when updating or upgrading a version, if there is none. - ]], -}) -entry(M.schema, "autoload", { - type = "boolean", - default = true, - description = [[ - Automatically run update when opening a Cargo.toml. - ]], -}) -entry(M.schema, "autoupdate", { - type = "boolean", - default = true, - description = [[ - Automatically update when editing text. - ]], -}) -entry(M.schema, "autoupdate_throttle", { - type = "number", - default = 250, - description = [[ - Rate limit the auto update in milliseconds - ]], -}) -entry(M.schema, "loading_indicator", { - type = "boolean", - default = true, - description = [[ - Show a loading indicator while fetching crate versions. - ]], -}) -entry(M.schema, "date_format", { - type = "string", - default = "%Y-%m-%d", - description = [[ - The date format passed to `os.date`. - ]], -}) -entry(M.schema, "thousands_separator", { - type = "string", - default = ".", - description = [[ - The separator used to separate thousands of a number: - - Example: ~ - Dot: - `14.502.265` - - Comma: - `14,502,265` - ]], -}) -entry(M.schema, "notification_title", { - type = "string", - default = "crates.nvim", - description = [[ - The title displayed in notifications. - ]], -}) -entry(M.schema, "curl_args", { - type = "table", - default = { "-sL", "--retry", "1" }, - description = [[ - A list of arguments passed to curl when fetching metadata from crates.io. - ]], -}) -entry(M.schema, "max_parallel_requests", { - type = "number", - default = 80, - description = [[ - Maximum number of parallel requests. - ]], -}) -entry(M.schema, "expand_crate_moves_cursor", { - type = "boolean", - default = true, - description = [[ - Whether to move the cursor on |crates.expand_plain_crate_to_inline_table()|. - ]], -}) -entry(M.schema, "open_programs", { - type = "table", - default = { "xdg-open", "open" }, - description = [[ - A list of programs that used to open urls. - ]], -}) --- TODO: Blocked on: https://github.com/rust-lang/crates.io/issues/1539 -entry(M.schema, "disable_invalid_feature_diagnostic", { - type = "boolean", - default = false, - description = [[ - This is a temporary solution for: - https://github.com/Saecki/crates.nvim/issues/14 - ]], -}) -entry(M.schema, "enable_update_available_warning", { - type = "boolean", - default = true, - description = [[ - Enable warnings for outdated crates. - ]], -}) -entry(M.schema, "on_attach", { - type = "function", - default = function(_bufnr) end, - default_text = "function(bufnr) end", - description = [[ - Callback to run when a `Cargo.toml` file is opened. - - NOTE: Ignored if |crates-config-autoload| is disabled. - ]], -}) --- deprecated -entry(M.schema, "avoid_prerelease", { - type = "boolean", - deprecated = { - hard = true, - }, -}) - -entry(M.schema, "text", { - type = "section", - description = [[ - Strings used to format virtual text. - ]], - fields = {}, -}) -local schema_text = M.schema.text.fields -entry(schema_text, "loading", { - type = "string", - default = "  Loading", - description = [[ - Format string used while loading crate information. - ]], -}) -entry(schema_text, "version", { - type = "string", - default = "  %s", - description = [[ - format string used for the latest compatible version - ]], -}) -entry(schema_text, "prerelease", { - type = "string", - default = "  %s", - description = [[ - Format string used for pre-release versions. - ]], -}) -entry(schema_text, "yanked", { - type = "string", - default = "  %s", - description = [[ - Format string used for yanked versions. - ]], -}) -entry(schema_text, "nomatch", { - type = "string", - default = "  No match", - description = [[ - Format string used when there is no matching version. - ]], -}) -entry(schema_text, "upgrade", { - type = "string", - default = "  %s", - description = [[ - Format string used when there is an upgrade candidate. - ]], -}) -entry(schema_text, "error", { - type = "string", - default = "  Error fetching crate", - description = [[ - Format string used when there was an error loading crate information. - ]], -}) - - -entry(M.schema, "highlight", { - type = "section", - description = [[ - Highlight groups used for virtual text. - ]], - fields = {}, -}) -local schema_hi = M.schema.highlight.fields -entry(schema_hi, "loading", { - type = "string", - default = "CratesNvimLoading", - description = [[ - Highlight group used while loading crate information. - ]], -}) -entry(schema_hi, "version", { - type = "string", - default = "CratesNvimVersion", - description = [[ - Highlight group used for the latest compatible version. - ]], -}) -entry(schema_hi, "prerelease", { - type = "string", - default = "CratesNvimPreRelease", - description = [[ - Highlight group used for pre-release versions. - ]], -}) -entry(schema_hi, "yanked", { - type = "string", - default = "CratesNvimYanked", - description = [[ - Highlight group used for yanked versions. - ]], -}) -entry(schema_hi, "nomatch", { - type = "string", - default = "CratesNvimNoMatch", - description = [[ - Highlight group used when there is no matching version. - ]], -}) -entry(schema_hi, "upgrade", { - type = "string", - default = "CratesNvimUpgrade", - description = [[ - Highlight group used when there is an upgrade candidate. - ]], -}) -entry(schema_hi, "error", { - type = "string", - default = "CratesNvimError", - description = [[ - Highlight group used when there was an error loading crate information. - ]], -}) - - -entry(M.schema, "diagnostic", { - type = "section", - fields = {}, - hidden = true, -}) -local schema_diagnostic = M.schema.diagnostic.fields -entry(schema_diagnostic, "section_invalid", { - type = "string", - default = "Invalid dependency section", - hidden = true, -}) -entry(schema_diagnostic, "workspace_section_not_default", { - type = "string", - default = "Workspace dependency sections don't support other kinds of dependencies like build or dev", - hidden = true, -}) -entry(schema_diagnostic, "workspace_section_has_target", { - type = "string", - default = "Workspace dependency sections don't support target specifiers", - hidden = true, -}) -entry(schema_diagnostic, "section_dup", { - type = "string", - default = "Duplicate dependency section", - hidden = true, -}) -entry(schema_diagnostic, "section_dup_orig", { - type = "string", - default = "Original dependency section is defined here", - hidden = true, -}) -entry(schema_diagnostic, "crate_dup", { - type = "string", - default = "Duplicate crate entry", - hidden = true, -}) -entry(schema_diagnostic, "crate_dup_orig", { - type = "string", - default = "Original crate entry is defined here", - hidden = true, -}) -entry(schema_diagnostic, "crate_novers", { - type = "string", - default = "Missing version requirement", - hidden = true, -}) -entry(schema_diagnostic, "crate_error_fetching", { - type = "string", - default = "Error fetching crate", - hidden = true, -}) -entry(schema_diagnostic, "crate_name_case", { - type = "string", - default = "Incorrect crate name casing", - hidden = true, -}) -entry(schema_diagnostic, "vers_upgrade", { - type = "string", - default = "There is an upgrade available", - hidden = true, -}) -entry(schema_diagnostic, "vers_pre", { - type = "string", - default = "Requirement only matches a pre-release version\nIf you want to use the pre-release package, it needs to be specified explicitly", - hidden = true, -}) -entry(schema_diagnostic, "vers_yanked", { - type = "string", - default = "Requirement only matches a yanked version", - hidden = true, -}) -entry(schema_diagnostic, "vers_nomatch", { - type = "string", - default = "Requirement doesn't match a version", - hidden = true, -}) -entry(schema_diagnostic, "def_invalid", { - type = "string", - default = "Invalid boolean value", - hidden = true, -}) -entry(schema_diagnostic, "feat_dup", { - type = "string", - default = "Duplicate feature entry", - hidden = true, -}) -entry(schema_diagnostic, "feat_dup_orig", { - type = "string", - default = "Original feature entry is defined here", - hidden = true, -}) -entry(schema_diagnostic, "feat_invalid", { - type = "string", - default = "Invalid feature", - hidden = true, -}) - - -entry(M.schema, "popup", { - type = "section", - description = [[ - popup config - ]], - fields = {}, -}) -local schema_popup = M.schema.popup.fields -entry(schema_popup, "autofocus", { - type = "boolean", - default = false, - description = [[ - Focus the versions popup when opening it. - ]], -}) -entry(schema_popup, "hide_on_select", { - type = "boolean", - default = false, - description = [[ - Hides the popup after selecting a version. - ]], -}) -entry(schema_popup, "copy_register", { - type = "string", - default = '"', - description = [[ - The register into which the version will be copied. - ]], -}) -entry(schema_popup, "style", { - type = "string", - default = "minimal", - description = [[ - Same as nvim_open_win config.style. - ]], -}) -entry(schema_popup, "border", { - type = { "string", "table" }, - default = "none", - description = [[ - Same as nvim_open_win config.border. - ]], -}) -entry(schema_popup, "show_version_date", { - type = "boolean", - default = false, - description = [[ - Display when a version was released. - ]], -}) -entry(schema_popup, "show_dependency_version", { - type = "boolean", - default = true, - description = [[ - Display when a version was released. - ]], -}) -entry(schema_popup, "max_height", { - type = "number", - default = 30, - description = [[ - The maximum height of the popup. - ]], -}) -entry(schema_popup, "min_width", { - type = "number", - default = 20, - description = [[ - The minimum width of the popup. - ]], -}) -entry(schema_popup, "padding", { - type = "number", - default = 1, - description = [[ - The horizontal padding of the popup. - ]], -}) --- deprecated -entry(schema_popup, "version_date", { - type = "boolean", - deprecated = { - new_field = { "popup", "show_version_date" }, - hard = true, - } -}) - - -entry(schema_popup, "text", { - type = "section", - description = [[ - Strings used to format the text inside the popup. - ]], - fields = {}, -}) -local schema_popup_text = schema_popup.text.fields -entry(schema_popup_text, "title", { - type = "string", - default = " %s", - description = [[ - Format string used for the popup title. - ]], -}) -entry(schema_popup_text, "pill_left", { - type = "string", - default = "", - description = [[ - Left border of a pill (keywords and categories). - ]], -}) -entry(schema_popup_text, "pill_right", { - type = "string", - default = "", - description = [[ - Right border of a pill (keywords and categories). - ]], -}) --- crate -entry(schema_popup_text, "description", { - type = "string", - default = "%s", - description = [[ - Format string used for the description. - ]], -}) -entry(schema_popup_text, "created_label", { - type = "string", - default = " created ", - description = [[ - Label string used for the creation date. - ]], -}) -entry(schema_popup_text, "created", { - type = "string", - default = "%s", - description = [[ - Format string used for the creation date. - ]], -}) -entry(schema_popup_text, "updated_label", { - type = "string", - default = " updated ", - description = [[ - Label string used for the updated date. - ]], -}) -entry(schema_popup_text, "updated", { - type = "string", - default = "%s", - description = [[ - Format string used for the updated date. - ]], -}) -entry(schema_popup_text, "downloads_label", { - type = "string", - default = " downloads ", - description = [[ - Label string used for the download count. - ]], -}) -entry(schema_popup_text, "downloads", { - type = "string", - default = "%s", - description = [[ - Format string used for the download count. - ]], -}) -entry(schema_popup_text, "homepage_label", { - type = "string", - default = " homepage ", - description = [[ - Label string used for the homepage url. - ]], -}) -entry(schema_popup_text, "homepage", { - type = "string", - default = "%s", - description = [[ - Format string used for the homepage url. - ]], -}) -entry(schema_popup_text, "repository_label", { - type = "string", - default = " repository ", - description = [[ - Label string used for the repository url. - ]], -}) -entry(schema_popup_text, "repository", { - type = "string", - default = "%s", - description = [[ - Format string used for the repository url. - ]], -}) -entry(schema_popup_text, "documentation_label", { - type = "string", - default = " documentation ", - description = [[ - Label string used for the documentation url. - ]], -}) -entry(schema_popup_text, "documentation", { - type = "string", - default = "%s", - description = [[ - Format string used for the documentation url. - ]], -}) -entry(schema_popup_text, "crates_io_label", { - type = "string", - default = " crates.io ", - description = [[ - Label string used for the crates.io url. - ]], -}) -entry(schema_popup_text, "crates_io", { - type = "string", - default = "%s", - description = [[ - Format string used for the crates.io url. - ]], -}) -entry(schema_popup_text, "categories_label", { - type = "string", - default = " categories ", - description = [[ - Label string used for the categories label. - ]], -}) -entry(schema_popup_text, "keywords_label", { - type = "string", - default = " keywords ", - description = [[ - Label string used for the keywords label. - ]], -}) --- versions -entry(schema_popup_text, "version", { - type = "string", - default = " %s", - description = [[ - Format string used for release versions. - ]], -}) -entry(schema_popup_text, "prerelease", { - type = "string", - default = " %s", - description = [[ - Format string used for prerelease versions. - ]], -}) -entry(schema_popup_text, "yanked", { - type = "string", - default = " %s", - description = [[ - Format string used for yanked versions. - ]], -}) -entry(schema_popup_text, "version_date", { - type = "string", - default = " %s", - description = [[ - Format string used for appending the version release date. - ]], -}) --- features -entry(schema_popup_text, "feature", { - type = "string", - default = " %s", - description = [[ - Format string used for disabled features. - ]], -}) -entry(schema_popup_text, "enabled", { - type = "string", - default = " %s", - description = [[ - Format string used for enabled features. - ]], -}) -entry(schema_popup_text, "transitive", { - type = "string", - default = " %s", - description = [[ - Format string used for transitively enabled features. - ]], -}) --- dependencies -entry(schema_popup_text, "normal_dependencies_title", { - type = "string", - default = " Dependencies", - description = [[ - Format string used for the title of the normal dependencies section. - ]], -}) -entry(schema_popup_text, "build_dependencies_title", { - type = "string", - default = " Build dependencies", - description = [[ - Format string used for the title of the build dependencies section. - ]], -}) -entry(schema_popup_text, "dev_dependencies_title", { - type = "string", - default = " Dev dependencies", - description = [[ - Format string used for the title of the dev dependencies section. - ]], -}) -entry(schema_popup_text, "dependency", { - type = "string", - default = " %s", - description = [[ - Format string used for dependencies and their version requirement. - ]], -}) -entry(schema_popup_text, "optional", { - type = "string", - default = " %s", - description = [[ - Format string used for optional dependencies and their version requirement. - ]], -}) -entry(schema_popup_text, "dependency_version", { - type = "string", - default = " %s", - description = [[ - Format string used for appending the dependency version. - ]], -}) -entry(schema_popup_text, "loading", { - type = "string", - default = "  ", - description = [[ - Format string used as a loading indicator when fetching dependencies. - ]], -}) --- deprecated -entry(schema_popup_text, "date", { - type = "string", - deprecated = { - new_field = { "popup", "text", "version_date" }, - hard = true, - } -}) - - -entry(schema_popup, "highlight", { - type = "section", - description = [[ - Highlight groups for popup elements. - ]], - fields = {}, -}) -local schema_popup_hi = schema_popup.highlight.fields -entry(schema_popup_hi, "title", { - type = "string", - default = "CratesNvimPopupTitle", - description = [[ - Highlight group used for the popup title. - ]], -}) -entry(schema_popup_hi, "pill_text", { - type = "string", - default = "CratesNvimPopupPillText", - description = [[ - Highlight group used for a pill's text (keywords and categories). - ]], -}) -entry(schema_popup_hi, "pill_border", { - type = "string", - default = "CratesNvimPopupPillBorder", - description = [[ - Highlight group used for a pill's border (keywords and categories). - ]], -}) --- crate -entry(schema_popup_hi, "description", { - type = "string", - default = "CratesNvimPopupDescription", - description = [[ - Highlight group used for the crate description. - ]], -}) -entry(schema_popup_hi, "created_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the creation date label. - ]], -}) -entry(schema_popup_hi, "created", { - type = "string", - default = "CratesNvimPopupValue", - description = [[ - Highlight group used for the creation date. - ]], -}) -entry(schema_popup_hi, "updated_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the updated date label. - ]], -}) -entry(schema_popup_hi, "updated", { - type = "string", - default = "CratesNvimPopupValue", - description = [[ - Highlight group used for the updated date. - ]], -}) -entry(schema_popup_hi, "downloads_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the download count label. - ]], -}) -entry(schema_popup_hi, "downloads", { - type = "string", - default = "CratesNvimPopupValue", - description = [[ - Highlight group used for the download count. - ]], -}) -entry(schema_popup_hi, "homepage_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the homepage url label. - ]], -}) -entry(schema_popup_hi, "homepage", { - type = "string", - default = "CratesNvimPopupUrl", - description = [[ - Highlight group used for the homepage url. - ]], -}) -entry(schema_popup_hi, "repository_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the repository url label. - ]], -}) -entry(schema_popup_hi, "repository", { - type = "string", - default = "CratesNvimPopupUrl", - description = [[ - Highlight group used for the repository url. - ]], -}) -entry(schema_popup_hi, "documentation_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the documentation url label. - ]], -}) -entry(schema_popup_hi, "documentation", { - type = "string", - default = "CratesNvimPopupUrl", - description = [[ - Highlight group used for the documentation url. - ]], -}) -entry(schema_popup_hi, "crates_io_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the crates.io url label. - ]], -}) -entry(schema_popup_hi, "crates_io", { - type = "string", - default = "CratesNvimPopupUrl", - description = [[ - Highlight group used for the crates.io url. - ]], -}) -entry(schema_popup_hi, "categories_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the categories label. - ]], -}) -entry(schema_popup_hi, "keywords_label", { - type = "string", - default = "CratesNvimPopupLabel", - description = [[ - Highlight group used for the keywords label. - ]], -}) --- versions -entry(schema_popup_hi, "version", { - type = "string", - default = "CratesNvimPopupVersion", - description = [[ - Highlight group used for versions inside the popup. - ]], -}) -entry(schema_popup_hi, "prerelease", { - type = "string", - default = "CratesNvimPopupPreRelease", - description = [[ - Highlight group used for pre-release versions inside the popup. - ]], -}) -entry(schema_popup_hi, "yanked", { - type = "string", - default = "CratesNvimPopupYanked", - description = [[ - Highlight group used for yanked versions inside the popup. - ]], -}) -entry(schema_popup_hi, "version_date", { - type = "string", - default = "CratesNvimPopupVersionDate", - description = [[ - Highlight group used for the version date inside the popup. - ]], -}) --- features -entry(schema_popup_hi, "feature", { - type = "string", - default = "CratesNvimPopupFeature", - description = [[ - Highlight group used for disabled features inside the popup. - ]], -}) -entry(schema_popup_hi, "enabled", { - type = "string", - default = "CratesNvimPopupEnabled", - description = [[ - Highlight group used for enabled features inside the popup. - ]], -}) -entry(schema_popup_hi, "transitive", { - type = "string", - default = "CratesNvimPopupTransitive", - description = [[ - Highlight group used for transitively enabled features inside the popup. - ]], -}) --- dependencies -entry(schema_popup_hi, "normal_dependencies_title", { - type = "string", - default = "CratesNvimPopupNormalDependenciesTitle", - description = [[ - Highlight group used for the title of the normal dependencies section. - ]], -}) -entry(schema_popup_hi, "build_dependencies_title", { - type = "string", - default = "CratesNvimPopupBuildDependenciesTitle", - description = [[ - Highlight group used for the title of the build dependencies section. - ]], -}) -entry(schema_popup_hi, "dev_dependencies_title", { - type = "string", - default = "CratesNvimPopupDevDependenciesTitle", - description = [[ - Highlight group used for the title of the dev dependencies section. - ]], -}) -entry(schema_popup_hi, "dependency", { - type = "string", - default = "CratesNvimPopupDependency", - description = [[ - Highlight group used for dependencies inside the popup. - ]], -}) -entry(schema_popup_hi, "optional", { - type = "string", - default = "CratesNvimPopupOptional", - description = [[ - Highlight group used for optional dependencies inside the popup. - ]], -}) -entry(schema_popup_hi, "dependency_version", { - type = "string", - default = "CratesNvimPopupDependencyVersion", - description = [[ - Highlight group used for the dependency version inside the popup. - ]], -}) -entry(schema_popup_hi, "loading", { - type = "string", - default = "CratesNvimPopupLoading", - description = [[ - Highlight group for the loading indicator inside the popup. - ]], -}) - - -entry(schema_popup, "keys", { - type = "section", - description = [[ - Key mappings inside the popup. - ]], - fields = {}, -}) -local schema_popup_keys = schema_popup.keys.fields -entry(schema_popup_keys, "hide", { - type = "table", - default = { "q", "" }, - description = [[ - Hides the popup. - ]], -}) --- crate -entry(schema_popup_keys, "open_url", { - type = "table", - default = { "" }, - description = [[ - Key mappings to open the url on the current line. - ]], -}) --- versions -entry(schema_popup_keys, "select", { - type = "table", - default = { "" }, - description = [[ - Key mappings to insert the version respecting the |crates-config-smart_insert| flag. - ]], -}) -entry(schema_popup_keys, "select_alt", { - type = "table", - default = { "s" }, - description = [[ - Key mappings to insert the version using the opposite of |crates-config-smart_insert| flag. - ]], -}) --- features -entry(schema_popup_keys, "toggle_feature", { - type = "table", - default = { "" }, - description = [[ - Key mappings to enable or disable the feature on the current line inside the popup. - ]], -}) --- common -entry(schema_popup_keys, "copy_value", { - type = "table", - default = { "yy" }, - description = [[ - Key mappings to copy the value on the current line inside the popup. - ]], -}) -entry(schema_popup_keys, "goto_item", { - type = "table", - default = { "gd", "K", "" }, - description = [[ - Key mappings to go to the item on the current line inside the popup. - ]], -}) -entry(schema_popup_keys, "jump_forward", { - type = "table", - default = { "" }, - description = [[ - Key mappings to jump forward in the popup jump history. - ]], -}) -entry(schema_popup_keys, "jump_back", { - type = "table", - default = { "", "" }, - description = [[ - Key mappings to go back in the popup jump history. - ]], -}) --- deprecated -entry(schema_popup_keys, "goto_feature", { - type = "table", - deprecated = { - new_field = { "popup", "keys", "goto_item" }, - hard = true, - } -}) -entry(schema_popup_keys, "jump_forward_feature", { - type = "table", - deprecated = { - new_field = { "popup", "keys", "jump_forward" }, - hard = true, - } -}) -entry(schema_popup_keys, "jump_back_feature", { - type = "table", - deprecated = { - new_field = { "popup", "keys", "jump_back" }, - hard = true, - } -}) -entry(schema_popup_keys, "copy_version", { - type = "table", - deprecated = { - new_field = { "popup", "keys", "copy_value" }, - hard = true, - } -}) - - -entry(M.schema, "src", { - type = "section", - description = [[ - Configuration options for completion sources. - ]], - fields = {}, -}) -local schema_src = M.schema.src.fields -entry(schema_src, "insert_closing_quote", { - type = "boolean", - default = true, - description = [[ - Insert a closing quote on completion if there is none. - ]], -}) -entry(schema_src, "text", { - type = "section", - description = [[ - Text shown in the completion source documentation preview. - ]], - fields = {}, -}) -local schema_src_text = schema_src.text.fields -entry(schema_src_text, "prerelease", { - type = "string", - default = "  pre-release ", - description = [[ - Text shown in the completion source documentation preview for pre-release versions. - ]], -}) -entry(schema_src_text, "yanked", { - type = "string", - default = "  yanked ", - description = [[ - Text shown in the completion source documentation preview for yanked versions. - ]], -}) - -entry(schema_src, "cmp", { - type = "section", - description = [[ - Configuration options for the |nvim-cmp| completion source. - ]], - fields = {}, -}) -local schema_src_cmp = schema_src.cmp.fields -entry(schema_src_cmp, "enabled", { - type = "boolean", - default = false, - description = [[ - Whether to load and register the |nvim-cmp| source. - - NOTE: Ignored if |crates-config-autoload| is disabled. - You may manually register it, after |nvim-cmp| has been loaded. - > - require("crates.src.cmp").setup() - < - ]], -}) -entry(schema_src_cmp, "use_custom_kind", { - type = "boolean", - default = true, - description = [[ - Use custom a custom kind to display inside the |nvim-cmp| completion menu. - ]], -}) - -entry(schema_src_cmp, "kind_text", { - type = "section", - description = [[ - The kind text shown in the |nvim-cmp| completion menu. - ]], - fields = {}, -}) -local schema_src_cmp_kind_text = schema_src_cmp.kind_text.fields -entry(schema_src_cmp_kind_text, "version", { - type = "string", - default = "Version", - description = [[ - The version kind text shown in the |nvim-cmp| completion menu. - ]], -}) -entry(schema_src_cmp_kind_text, "feature", { - type = "string", - default = "Feature", - description = [[ - The feature kind text shown in the |nvim-cmp| completion menu. - ]], -}) - -entry(schema_src_cmp, "kind_highlight", { - type = "section", - description = [[ - Highlight groups used for the kind text in the |nvim-cmp| completion menu. - ]], - fields = {}, -}) -local schema_src_cmp_kind_hi = schema_src_cmp.kind_highlight.fields -entry(schema_src_cmp_kind_hi, "version", { - type = "string", - default = "CmpItemKindVersion", - description = [[ - Highlight group used for the version kind text in the |nvim-cmp| completion menu. - ]], -}) -entry(schema_src_cmp_kind_hi, "feature", { - type = "string", - default = "CmpItemKindFeature", - description = [[ - Highlight group used for the feature kind text in the |nvim-cmp| completion menu. - ]], -}) - -entry(schema_src, "coq", { - type = "section", - description = [[ - Configuration options for the |coq_nvim| completion source. - ]], - fields = {}, -}) -local schema_src_coq = schema_src.coq.fields -entry(schema_src_coq, "enabled", { - type = "boolean", - default = false, - description = [[ - Whether to load and register the |coq_nvim| source. - ]], -}) -entry(schema_src_coq, "name", { - type = "string", - default = "crates.nvim", - description = [[ - The source name displayed by |coq_nvim|. - ]], -}) - - -entry(M.schema, "null_ls", { - type = "section", - description = [[ - Configuration options for null-ls.nvim actions. - ]], - fields = {}, -}) -local schema_null_ls = M.schema.null_ls.fields -entry(schema_null_ls, "enabled", { - type = "boolean", - default = false, - description = [[ - Whether to register the |null-ls.nvim| source. - ]], -}) -entry(schema_null_ls, "name", { - type = "string", - default = "crates.nvim", - description = [[ - The |null-ls.nvim| name. - ]], -}) - - -entry(M.schema, "lsp", { - type = "section", - description = [[ - Configuration options for the in-process language server. - ]], - fields = {}, -}) -local schema_lsp = M.schema.lsp.fields -entry(schema_lsp, "enabled", { - type = "boolean", - default = false, - description = [[ - Whether to enable the in-process language server. - ]], -}) -entry(schema_lsp, "name", { - type = "string", - default = "crates.nvim", - description = [[ - The lsp server name. - ]], -}) -entry(schema_lsp, "on_attach", { - type = "function", - default = function(_client, _bufnr) end, - default_text = "function(client, bufnr) end", - description = [[ - Callback to run when the in-process language server attaches to a buffer. - - NOTE: Ignored if |crates-config-autoload| is disabled. - ]], -}) -entry(schema_lsp, "actions", { - type = "boolean", - default = false, - description = [[ - Whether to enable the `codeActionProvider` capability. - ]], -}) -entry(schema_lsp, "completion", { - type = "boolean", - default = false, - description = [[ - Whether to enable the `completionProvider` capability. - ]], -}) - - -local function warn(s: string, ...:any) - vim.notify(s:format(...), vim.log.levels.WARN, { title = "crates.nvim" }) -end - -local function join_path(path: {string}, component: string): {string} - local p = {} - for i,c in ipairs(path) do - p[i] = c - end - table.insert(p, component) - return p -end - -local function table_set_path(t: {string:any}, path: {string}, value: any) - local current = t - for i,c in ipairs(path) do - if i == #path then - current[c] = value - elseif type(current[c]) == "table" then - current = current[c] as {string:any} - elseif current[c] == nil then - current[c] = {} - current = current[c] as {string:any} - else - break -- don't overwrite existing value - end - end -end - -local function handle_deprecated(path: {string}, schema: {string:SchemaElement}, root_config: {string:any}, user_config: {string:any}) - for k,v in pairs(user_config) do - local elem = schema[k] - - if elem then - local p = join_path(path, k) - local dep = elem.deprecated - - if dep then - if dep.new_field and not dep.hard then - table_set_path(root_config, dep.new_field, v) - end - elseif elem.type == "section" and type(v) == "table" then - handle_deprecated(p, elem.fields, root_config, v as {string:any}) - end - end - end -end - -local function validate_schema(path: {string}, schema: {string:SchemaElement}, user_config: {string:any}) - for k,v in pairs(user_config) do - local p = join_path(path, k) - local elem = schema[k] - - if elem then - local value_type = type(v) - local dep = elem.deprecated - - if dep then - if dep.new_field then - local dep_text: string - if dep.hard then - dep_text = "deprecated and won't work anymore" - else - dep_text = "deprecated and will stop working soon" - end - - warn( - "'%s' is now %s, please use '%s'", - table.concat(p, "."), - dep_text, - table.concat(dep.new_field, ".") - ) - else - warn( - "'%s' is now deprecated, ignoring", - table.concat(p, ".") - ) - end - elseif elem.type == "section" then - if value_type == "table" then - validate_schema(p, elem.fields, v as {string:any}) - else - warn( - "Config field '%s' was expected to be of type 'table' but was '%s', using default value.", - table.concat(p, "."), - value_type - ) - end - else - local elem_types: {SchemaType} - if type(elem.type) == "string" then - elem_types = { elem.type as SchemaType } - else - elem_types = elem.type as { SchemaType } - end - - if not vim.tbl_contains(elem_types, value_type as SchemaType) then - warn( - "Config field '%s' was expected to be of type '%s' but was '%s', using default value.", - table.concat(p, "."), - table.concat(elem_types, " or "), - value_type - ) - end - end - else - warn( - "Ignoring invalid config key '%s'", - table.concat(p, ".") - ) - end - end -end - -local function build_config(schema: {string:SchemaElement}, user_config: {string:any}): table - local config: {string:any} = {} - - for k,elem in pairs(schema) do - local v = user_config[k] - local value_type = type(v) - - if elem.type == "section" then - if value_type == "table" then - config[k] = build_config(elem.fields, v as {string:any}) - else - config[k] = build_config(elem.fields, {}) - end - else - local elem_types: {SchemaType} - if type(elem.type) == "string" then - elem_types = { elem.type as SchemaType } - else - elem_types = elem.type as { SchemaType } - end - - if vim.tbl_contains(elem_types, value_type as SchemaType) then - config[k] = v - else - config[k] = elem.default - end - end - end - - return config -end - -function M.build(user_config: {string:any}): Config - user_config = user_config or {} - local config_type = type(user_config) - if config_type ~= "table" then - warn("Expected config of type 'table' found '%s'", config_type) - user_config = {} - end - - handle_deprecated({}, M.schema, user_config, user_config) - - validate_schema({}, M.schema, user_config) - - return build_config(M.schema, user_config) as Config -end - - -return M diff --git a/teal/crates/edit.tl b/teal/crates/edit.tl deleted file mode 100644 index ecb1442b..00000000 --- a/teal/crates/edit.tl +++ /dev/null @@ -1,524 +0,0 @@ -local record M end - -local semver = require("crates.semver") -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local CrateInfo = types.CrateInfo -local Feature = types.Feature -local Range = types.Range -local SemVer = types.SemVer -local Requirement = types.Requirement - -function M.rename_crate_package(buf: integer, crate: toml.Crate, name: string) - local line, col: integer, Range - if crate.pkg then - line = crate.pkg.line - col = crate.pkg.col - else - line = crate.lines.s - col = crate.explicit_name_col - end - - vim.api.nvim_buf_set_text(buf, line, col.s, line, col.e, { name }) -end - -local function insert_version(buf: integer, crate: toml.Crate, text: string): Range - if not crate.vers then - if crate.syntax == "table" then - local line = crate.lines.s + 1 - vim.api.nvim_buf_set_lines( - buf, line, line, false, - { 'version = "' .. text .. '"' } - ) - return crate.lines:moved(0, 1) - elseif crate.syntax == "inline_table" then - local line = crate.lines.s - local col = math.min( - crate.pkg and crate.pkg.col.s or 999, - crate.def and crate.def.col.s or 999, - crate.feat and crate.def.col.s or 999, - crate.git and crate.git.decl_col.s or 999, - crate.path and crate.path.decl_col.s or 999 - ) - vim.api.nvim_buf_set_text( - buf, line, col, line, col, - { ' version = "' .. text .. '",' } - ) - return Range.pos(line) - elseif crate.syntax == "plain" then - return Range.empty() -- unreachable - end - else - local t = text - if state.cfg.insert_closing_quote and not crate.vers.quote.e then - t = text .. crate.vers.quote.s - end - local line = crate.vers.line - - vim.api.nvim_buf_set_text( - buf, - line, - crate.vers.col.s, - line, - crate.vers.col.e, - { t } - ) - return Range.pos(line) - end -end - -local function replace_existing(r: Requirement, version: SemVer): SemVer - if version.pre then - return version - else - return SemVer.new { - major = version.major, - minor = r.vers.minor and version.minor or nil, - patch = r.vers.patch and version.patch or nil, - } - end -end - -function M.smart_version_text(crate: toml.Crate, version: SemVer): string - if #crate:vers_reqs() == 0 then - return version:display() - end - - local pos = 1 - local text = "" - for _,r in ipairs(crate:vers_reqs()) do - if r.cond == "eq" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "wl" then - if version.pre then - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. version:display() - else - local v = SemVer.new { - major = r.vers.major and version.major or nil, - minor = r.vers.minor and version.minor or nil, - } - local before = string.sub(crate.vers.text, pos, r.vers_col.s) - local after = string.sub(crate.vers.text, r.vers_col.e + 1, r.cond_col.e) - text = text .. before .. v:display() .. after - end - elseif r.cond == "tl" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "cr" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "bl" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "lt" and not semver.matches_requirement(version, r) then - local v = SemVer.new { - major = version.major, - minor = r.vers.minor and version.minor or nil, - patch = r.vers.patch and version.patch or nil, - } - - if v.patch then - v.patch = v.patch + 1 - elseif v.minor then - v.minor = v.minor + 1 - elseif v.major then - v.major = v.major + 1 - end - - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "le" and not semver.matches_requirement(version, r) then - local v: SemVer - - if version.pre then - v = version - else - v = SemVer.new { major = version.major } - if r.vers.minor or version.minor and version.minor > 0 then - v.minor = version.minor - end - if r.vers.patch or version.patch and version.patch > 0 then - v.minor = version.minor - v.patch = version.patch - end - end - - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "gt" and not semver.matches_requirement(version, r) then - local v = SemVer.new { - major = r.vers.major and version.major or nil, - minor = r.vers.minor and version.minor or nil, - patch = r.vers.patch and version.patch or nil, - } - - if v.patch then - v.patch = v.patch - 1 - if v.patch < 0 then - v.patch = 0 - v.minor = v.minor - 1 - end - elseif v.minor then - v.minor = v.minor - 1 - if v.minor < 0 then - v.minor = 0 - v.major = v.major - 1 - end - elseif v.major then - v.major = v.major - 1 - if v.major < 0 then - v.major = 0 - end - end - - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - elseif r.cond == "ge" then - local v = replace_existing(r, version) - text = text .. string.sub(crate.vers.text, pos, r.vers_col.s) .. v:display() - else - text = text .. string.sub(crate.vers.text, pos, r.vers_col.e) - end - - pos = math.max(r.cond_col.e + 1, r.vers_col.e + 1) - end - text = text .. string.sub(crate.vers.text, pos) - - return text -end - -function M.version_text(crate: toml.Crate, version: SemVer, alt: boolean|nil): string - local smart = alt ~= state.cfg.smart_insert - if smart then - return M.smart_version_text(crate, version) - else - return version:display() - end -end - -function M.set_version(buf: integer, crate: toml.Crate, version: SemVer, alt: boolean|nil): Range - local text = M.version_text(crate, version, alt) - return insert_version(buf, crate, text) -end - -function M.upgrade_crates(buf: integer, crates: {string:toml.Crate}, info: {string:CrateInfo}, alt: boolean|nil) - for k,c in pairs(crates) do - local i = info[k] - - if i then - local version = i.vers_upgrade or i.vers_update - if version then - M.set_version(buf, c, version.parsed, alt) - end - end - end -end - -function M.update_crates(buf: integer, crates: {string:toml.Crate}, info: {string:CrateInfo}, alt: boolean|nil) - for k,c in pairs(crates) do - local i = info[k] - - if i then - local version = i.vers_update - if version then - M.set_version(buf, c, version.parsed, alt) - end - end - end -end - -function M.enable_feature(buf: integer, crate: toml.Crate, feature: Feature): Range - local t = '"' .. feature.name .. '"' - if not crate.feat then - if crate.syntax == "table" then - local line = math.max( - crate.vers and crate.vers.line + 1 or 0, - crate.pkg and crate.pkg.line + 1 or 0, - crate.def and crate.def.line + 1 or 0 - ) - line = line ~= 0 and line or math.min( - crate.git and crate.git.line or 999, - crate.path and crate.path.line or 999 - ) - vim.api.nvim_buf_set_lines( - buf, line, line, false, - { "features = [" .. t .."]" } - ) - return Range.pos(line) - elseif crate.syntax == "plain" then - t = ", features = [" .. t .. "] }" - local line = crate.vers.line - local col = crate.vers.col.e - if crate.vers.quote.e then - col = col + 1 - else - t = crate.vers.quote.s .. t - end - vim.api.nvim_buf_set_text(buf, line, col, line, col, { t }) - - vim.api.nvim_buf_set_text( - buf, - line, - crate.vers.col.s - 1, - line, - crate.vers.col.s - 1, - { "{ version = " } - ) - return Range.pos(line) - elseif crate.syntax == "inline_table" then - local line = crate.lines.s - local text = ", features = [" .. t .. "]" - local col = math.max( - crate.vers and crate.vers.col.e + (crate.vers.quote.e and 1 or 0) or 0, - crate.pkg and crate.pkg.col.e or 0, - crate.def and crate.def.col.e or 0 - ) - if col == 0 then - text = " features = [" .. t .. "]," - col = math.min( - crate.git and crate.git.decl_col.s or 999, - crate.path and crate.path.decl_col.s or 999 - ) - end - vim.api.nvim_buf_set_text( - buf, line, col, line, col, - { text } - ) - return Range.pos(line) - end - else - local last_feat = crate.feat.items[#crate.feat.items] - if last_feat then - if not last_feat.comma then - t = ", " .. t - end - if not last_feat.quote.e then - t = last_feat.quote.s .. t - end - end - - vim.api.nvim_buf_set_text( - buf, - crate.feat.line, - crate.feat.col.e, - crate.feat.line, - crate.feat.col.e, - { t } - ) - return Range.pos(crate.feat.line) - end -end - -function M.disable_feature(buf: integer, crate: toml.Crate, feature: toml.Feature): Range - -- check reference in case of duplicates - local index: integer - for i,f in ipairs(crate.feat.items) do - if f == feature then - index = i - break - end - end - if not index then return end - - local col_start = feature.decl_col.s - local col_end = feature.decl_col.e - if index == 1 then - if #crate.feat.items > 1 then - col_end = crate.feat.items[2].col.s - 1 - elseif feature.comma then - col_end = col_end + 1 - end - else - local prev_feature = crate.feat.items[index - 1] - col_start = prev_feature.col.e + 1 - end - - vim.api.nvim_buf_set_text( - buf, - crate.feat.line, - crate.feat.col.s + col_start, - crate.feat.line, - crate.feat.col.s + col_end, - { "" } - ) - return Range.pos(crate.feat.line) -end - -function M.enable_def_features(buf: integer, crate: toml.Crate): Range - vim.api.nvim_buf_set_text( - buf, - crate.def.line, - crate.def.col.s, - crate.def.line, - crate.def.col.e, - { "true" } - ) - return Range.pos(crate.def.line) -end - -local function disable_def_features(buf: integer, crate: toml.Crate): Range - if crate.def then - local line = crate.def.line - vim.api.nvim_buf_set_text( - buf, - line, - crate.def.col.s, - line, - crate.def.col.e, - { "false" } - ) - return crate.lines - else - if crate.syntax == "table" then - local line = math.max( - crate.vers and crate.vers.line + 1 or 0, - crate.pkg and crate.pkg.line + 1 or 0 - ) - line = line ~= 0 and line or math.min( - crate.feat and crate.feat.line or 999, - crate.git and crate.git.line or 999, - crate.path and crate.path.line or 999 - ) - vim.api.nvim_buf_set_lines( - buf, - line, - line, - false, - { "default-features = false" } - ) - return crate.lines:moved(0, 1) - elseif crate.syntax == "plain" then - local t = ", default-features = false }" - local col = crate.vers.col.e - if crate.vers.quote.e then - col = col + 1 - else - t = crate.vers.quote.s .. t - end - local line = crate.vers.line - vim.api.nvim_buf_set_text( - buf, - line, - col, - line, - col, - { t } - ) - - vim.api.nvim_buf_set_text( - buf, - line, - crate.vers.col.s - 1, - line, - crate.vers.col.s - 1, - { "{ version = " } - ) - return crate.lines - elseif crate.syntax == "inline_table" then - local line = crate.lines.s - local text = ", default-features = false" - local col = math.max( - crate.vers and crate.vers.col.e + (crate.vers.quote.e and 1 or 0) or 0, - crate.pkg and crate.pkg.col.e or 0 - ) - if col == 0 then - text = " default-features = false," - col = math.min( - crate.feat and crate.def.col.s or 999, - crate.git and crate.git.decl_col.s or 999, - crate.path and crate.path.decl_col.s or 999 - ) - end - vim.api.nvim_buf_set_text( - buf, line, col, line, col, - { text } - ) - return crate.lines - end - end -end - -function M.disable_def_features(buf: integer, crate: toml.Crate, feature: toml.Feature|nil): Range - if feature then - if crate.def and crate.def.col.s < crate.feat.col.s then - M.disable_feature(buf, crate, feature) - return disable_def_features(buf, crate) - else - local lines = disable_def_features(buf, crate) - M.disable_feature(buf, crate, feature) - return lines - end - else - return disable_def_features(buf, crate) - end -end - -function M.expand_plain_crate_to_inline_table(buf: integer, crate: toml.Crate) - if crate.syntax ~= "plain" then - return - end - - local text = crate.explicit_name .. ' = { version = "' .. crate.vers.text .. '" }' - vim.api.nvim_buf_set_text( - buf, crate.lines.s, crate.vers.decl_col.s, crate.lines.s, crate.vers.decl_col.e, - { text } - ) - - if state.cfg.expand_crate_moves_cursor then - local pos = { crate.lines.s + 1, #text - 2 } - vim.api.nvim_win_set_cursor(0, pos) - end -end - -function M.extract_crate_into_table(buf: integer, crate: toml.Crate) - if crate.syntax == "table" then - return - end - - -- remove original line - vim.api.nvim_buf_set_lines( buf, crate.lines.s, crate.lines.e, false, {}) - - -- insert table after dependency section - local lines = { - crate.section:display(crate.explicit_name), - } - if crate.vers then - table.insert(lines, "version = " .. '"' .. crate.vers.text ..'"') - end - if crate.registry then - table.insert(lines, "registry = " .. '"' .. crate.registry.text ..'"') - end - if crate.path then - table.insert(lines, "path = " .. '"' .. crate.path.text ..'"') - end - if crate.git then - table.insert(lines, "git = " .. '"' .. crate.git.text ..'"') - end - if crate.branch then - table.insert(lines, "branch = " .. '"' .. crate.branch.text ..'"') - end - if crate.rev then - table.insert(lines, "rev = " .. '"' .. crate.rev.text ..'"') - end - if crate.pkg then - table.insert(lines, "package = " .. '"' .. crate.pkg.text ..'"') - end - if crate.workspace then - table.insert(lines, "workspace = " .. '"' .. crate.workspace.text ..'"') - end - if crate.def then - table.insert(lines, "default-features = " .. '"' .. crate.def.text ..'"') - end - if crate.feat then - table.insert(lines, "features = [" .. crate.feat.text .. "]") - end - if crate.opt then - table.insert(lines, "optional = " .. '"' .. crate.opt.text ..'"') - end - - table.insert(lines, "") - - local line = crate.section.lines.e - 1 - vim.api.nvim_buf_set_lines(buf, line, line, false, lines) -end - -return M diff --git a/teal/crates/semver.tl b/teal/crates/semver.tl deleted file mode 100644 index 1b87f06f..00000000 --- a/teal/crates/semver.tl +++ /dev/null @@ -1,317 +0,0 @@ -local record M -end - -local types = require("crates.types") -local Range = types.Range -local Requirement = types.Requirement -local SemVer = types.SemVer - -function M.parse_version(str: string): SemVer - local major, minor, patch, pre, meta: string, string, string, string, string - - major, minor, patch, pre, meta = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)-([^%s]+)%+([^%s]+)$") - if major then - return SemVer.new { - major = tonumber(major) as integer, - minor = tonumber(minor) as integer, - patch = tonumber(patch) as integer, - pre = pre, - meta = meta, - } - end - - major, minor, patch, pre = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)-([^%s]+)$") - if major then - return SemVer.new { - major = tonumber(major) as integer, - minor = tonumber(minor) as integer, - patch = tonumber(patch) as integer, - pre = pre, - } - end - - major, minor, patch, meta = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)%+([^%s]+)$") - if major then - return SemVer.new { - major = tonumber(major) as integer, - minor = tonumber(minor) as integer, - patch = tonumber(patch) as integer, - meta = meta, - } - end - - major, minor, patch = str:match("^([0-9]+)%.([0-9]+)%.([0-9]+)$") - if major then - return SemVer.new { - major = tonumber(major) as integer, - minor = tonumber(minor) as integer, - patch = tonumber(patch) as integer, - } - end - - major, minor = str:match("^([0-9]+)%.([0-9]+)[%.]?$") - if major then - return SemVer.new { - major = tonumber(major) as integer, - minor = tonumber(minor) as integer, - } - end - - major = str:match("^([0-9]+)[%.]?$") - if major then - return SemVer.new { - major = tonumber(major) as integer, - } - end - - return SemVer.new {} -end - -function M.parse_requirement(str: string): Requirement - local vs, vers_str, ve, rs, re: integer, string, integer, integer, integer - - vs, vers_str, ve = str:match("^=%s*()(.+)()$") as (integer, string, integer) - if vs and vers_str and ve then - return { - cond = "eq", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^<=%s*()(.+)()$") as (integer, string, integer) - if vs and vers_str and ve then - return { - cond = "le", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^<%s*()(.+)()$") as (integer, string, integer) - if vs and vers_str and ve then - return { - cond = "lt", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^>=%s*()(.+)()$") as (integer, string, integer) - if vs and vers_str and ve then - return { - cond = "ge", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^>%s*()(.+)()$") as (integer, string, integer) - if vs and vers_str and ve then - return { - cond = "gt", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - vs, vers_str, ve = str:match("^%~%s*()(.+)()$") as (integer, string, integer) - if vs and vers_str and ve then - return { - cond = "tl", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - local wl = str:match("^%*$") as (string, integer, integer) - if wl then - return { - cond = "wl", - cond_col = Range.new(0, 1), - vers = SemVer.new {}, - vers_col = Range.new(0, 0), - } - end - - vers_str, rs, re = str:match("^(.+)()%.%*()$") as (string, integer, integer) - if vers_str and rs and re then - return { - cond = "wl", - cond_col = Range.new(rs - 1, re - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(0, rs - 1), - } - end - - vs, vers_str, ve = str:match("^%^%s*()(.+)()$") as (integer, string, integer) - if vs and vers_str and ve then - return { - cond = "cr", - cond_col = Range.new(0, vs - 1), - vers = M.parse_version(vers_str), - vers_col = Range.new(vs - 1, ve - 1), - } - end - - return { - cond = "bl", - cond_col = Range.new(0, 0), - vers = M.parse_version(str), - vers_col = Range.new(0, str:len()), - } -end - -function M.parse_requirements(str: string): {Requirement} - local requirements = {} - for rs, r in str:gmatch("[,]?%s*()([^,]+)%s*[,]?") do - local s = rs as integer - local requirement = M.parse_requirement(r) - requirement.vers_col.s = requirement.vers_col.s + s - 1 - requirement.vers_col.e = requirement.vers_col.e + s - 1 - table.insert(requirements, requirement) - end - - return requirements -end - -local function compare_pre(version: string, req: string): integer - if version and req then - if version < req then - return -1 - elseif version == req then - return 0 - elseif version > req then - return 1 - end - end - - return (req and 1 or 0) - (version and 1 or 0) -end - -local function matches_less(version: SemVer, req: SemVer): boolean - if req.major and req.major ~= version.major then - return version.major < req.major - end - if req.minor and req.minor ~= version.minor then - return version.minor < req.minor - end - if req.patch and req.patch ~= version.patch then - return version.patch < req.patch - end - - return compare_pre(version.pre, req.pre) < 0 -end - -local function matches_greater(version: SemVer, req: SemVer): boolean - if req.major and req.major ~= version.major then - return version.major > req.major - end - if req.minor and req.minor ~= version.minor then - return version.minor > req.minor - end - if req.patch and req.patch ~= version.patch then - return version.patch > req.patch - end - - return compare_pre(version.pre, req.pre) > 0 -end - -local function matches_exact(version: SemVer, req: SemVer): boolean - if req.major and req.major ~= version.major then - return false - end - if req.minor and req.minor ~= version.minor then - return false - end - if req.patch and req.patch ~= version.patch then - return false - end - - return version.pre == req.pre -end - -local function matches_caret(version: SemVer, req: SemVer): boolean - if req.major and req.major ~= version.major then - return false - end - - if not req.minor then - return true - end - - if not req.patch then - if req.major > 0 then - return version.minor >= req.minor - else - return version.minor == req.minor - end - end - - if req.major > 0 then - if req.minor ~= version.minor then - return version.minor > req.minor - elseif req.patch ~= version.patch then - return version.patch > req.patch - end - elseif req.minor > 0 then - if req.minor ~= version.minor then - return false - elseif version.patch ~= req.patch then - return version.patch > req.patch - end - elseif version.minor ~= req.minor or version.patch ~= req.patch then - return false - end - - return compare_pre(version.pre, req.pre) >= 0 -end - -local function matches_tilde(version: SemVer, req: SemVer): boolean - if req.major and req.major ~= version.major then - return false - end - if req.minor and req.minor ~= version.minor then - return false - end - if req.patch and req.patch ~= version.patch then - return version.patch > req.patch - end - - return compare_pre(version.pre, req.pre) >= 0 -end - -function M.matches_requirement(v: SemVer, r: Requirement): boolean - if r.cond == "cr" or r.cond == "bl" then - return matches_caret(v, r.vers) - elseif r.cond == "tl" then - return matches_tilde(v, r.vers) - elseif r.cond == "eq" or r.cond == "wl" then - return matches_exact(v, r.vers) - elseif r.cond == "lt" then - return matches_less(v, r.vers) - elseif r.cond == "le" then - return matches_exact(v, r.vers) or matches_less(v, r.vers) - elseif r.cond == "gt" then - return matches_greater(v, r.vers) - elseif r.cond == "ge" then - return matches_exact(v, r.vers) or matches_greater(v, r.vers) - end -end - -function M.matches_requirements(version: SemVer, requirements: {Requirement}): boolean - for _,r in ipairs(requirements) do - if not M.matches_requirement(version, r) then - return false - end - end - return true -end - -return M diff --git a/teal/crates/state.tl b/teal/crates/state.tl deleted file mode 100644 index b2035792..00000000 --- a/teal/crates/state.tl +++ /dev/null @@ -1,27 +0,0 @@ -local record State - cfg: Config - api_cache: {string:Crate} - buf_cache: {integer:BufCache} - visible: boolean - - record BufCache - crates: {string:toml.Crate} - info: {string:CrateInfo} - diagnostics: {Diagnostic} - end -end - -local config = require("crates.config") -local Config = config.Config -local toml = require("crates.toml") -local types = require("crates.types") -local Crate = types.Crate -local CrateInfo = types.CrateInfo -local Diagnostic = types.Diagnostic - -State.cfg = {} -State.api_cache = {} -State.buf_cache = {} -State.visible = true - -return State diff --git a/teal/crates/time.tl b/teal/crates/time.tl deleted file mode 100644 index c35a0bbe..00000000 --- a/teal/crates/time.tl +++ /dev/null @@ -1,46 +0,0 @@ -local record M - record DateTime - epoch: integer - end -end - -local DateTime = M.DateTime - -function DateTime.new(epoch: integer): DateTime - return setmetatable({ epoch = epoch }, { __index = DateTime }) -end - -function DateTime.parse_rfc_3339(str: string): DateTime - -- lua regex suports no {n} occurences - local pat = "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9])" -- date - .."T([0-9][0-9]):([0-9][0-9]):([0-9][0-9])%.[0-9]+" -- time - .."([%+%-])([0-9][0-9]):([0-9][0-9])$" -- offset - - local year, month, day, hour, minute, second, offset, offset_hour, offset_minute = str:match(pat) - if year then - local h, m: number, number - if offset == "+" then - h = tonumber(hour) + tonumber(offset_hour) - m = tonumber(minute) + tonumber(offset_minute) - elseif offset == "-" then - h = tonumber(hour) - tonumber(offset_hour) - m = tonumber(minute) - tonumber(offset_minute) - end - return DateTime.new(os.time { - year = tonumber(year) as integer, - month = tonumber(month) as integer, - day = tonumber(day) as integer, - hour = h as integer, - min = m as integer, - sec = tonumber(second) as integer, - }) - end - - return nil -end - -function DateTime:display(format: string): string - return os.date(format, self.epoch) -end - -return M diff --git a/teal/crates/toml.tl b/teal/crates/toml.tl deleted file mode 100644 index 49930ad6..00000000 --- a/teal/crates/toml.tl +++ /dev/null @@ -1,787 +0,0 @@ -local record M - record Section - text: string - invalid: boolean - workspace: boolean - target: string|nil - kind: Kind - name: string|nil - name_col: Range - lines: Range - - enum Kind - "default" - "dev" - "build" - end - end - - record Crate - -- The explicit name is either the name of the package, or a rename - -- if the following syntax is used: - -- explicit_name = { package = "package" } - explicit_name: string - explicit_name_col: Range - lines: Range - syntax: Syntax - vers: Vers - registry: Registry - path: Path - git: Git - branch: Branch - rev: Rev - pkg: Pkg - workspace: Workspace - opt: Opt - def: Def - feat: Feat - section: Section - dep_kind: DepKind - - enum Syntax - "plain" - "inline_table" - "table" - end - - record Vers - reqs: {Requirement} - text: string - is_pre: boolean - line: integer -- 0-indexed - col: Range - decl_col: Range - quote: Quotes - end - - record Registry - text: string - is_pre: boolean - line: integer -- 0-indexed - col: Range - decl_col: Range - quote: Quotes - end - - record Path - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - quote: Quotes - end - - record Git - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - quote: Quotes - end - - record Branch - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - quote: Quotes - end - - record Rev - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - quote: Quotes - end - - record Pkg - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - quote: Quotes - end - - record Workspace - enabled: boolean - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - end - - record Opt - enabled: boolean - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - end - - record Def - enabled: boolean - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - end - - record Feat - items: {Feature} - text: string - line: integer -- 0-indexed - col: Range - decl_col: Range - end - end - - enum DepKind - "registry" - "path" - "git" - "workspace" - end - - record Feature - name: string - col: Range -- relative to to the start of the features text - decl_col: Range -- relative to to the start of the features text - quote: Quotes - comma: boolean - end - - record Quotes - s: string - e: string|nil - end -end - -local Section = M.Section -local Crate = M.Crate -local Feature = M.Feature -local semver = require("crates.semver") -local types = require("crates.types") -local Range = types.Range -local Requirement = types.Requirement - -function M.parse_crate_features(text: string): {Feature} - local feats: {Feature} = {} - for fds, qs, fs, f, fe, qe, fde, c in text:gmatch([[[,]?()%s*(["'])()([^,"']*)()(["']?)%s*()([,]?)]]) do - table.insert(feats, { - name = f, - col = Range.new(fs as integer - 1, fe as integer - 1), - decl_col = Range.new(fds as integer - 1, fde as integer - 1), - quote = { s = qs, e = qe ~= "" and qe or nil }, - comma = c == ",", - }) - end - - return feats -end - -function Crate.new(obj: Crate): Crate - if obj.vers then - obj.vers.reqs = semver.parse_requirements(obj.vers.text) - - obj.vers.is_pre = false - for _,r in ipairs(obj.vers.reqs) do - if r.vers.pre then - obj.vers.is_pre = true - break - end - end - end - if obj.feat then - obj.feat.items = M.parse_crate_features(obj.feat.text) - end - if obj.def then - obj.def.enabled = obj.def.text ~= "false" - end - if obj.workspace then - obj.workspace.enabled = obj.workspace.text ~= "false" - end - if obj.opt then - obj.opt.enabled = obj.opt.text ~= "false" - end - - if obj.workspace then - obj.dep_kind = "workspace" - elseif obj.path then - obj.dep_kind = "path" - elseif obj.git then - obj.dep_kind = "git" - else - obj.dep_kind = "registry" - end - - return setmetatable(obj, { __index = Crate }) -end - -function Crate:vers_reqs(): {Requirement} - return self.vers and self.vers.reqs or {} -end - -function Crate:vers_is_pre(): boolean|nil - return self.vers and self.vers.is_pre -end - -function Crate:get_feat(name: string): Feature|nil, integer - if not self.feat or not self.feat.items then - return nil - end - - for i,f in ipairs(self.feat.items) do - if f.name == name then - return f, i - end - end - - return nil -end - -function Crate:feats(): {Feature} - return self.feat and self.feat.items or {} -end - -function Crate:is_def_enabled(): boolean - return not self.def or self.def.enabled -end - -function Crate:is_workspace(): boolean - return not self.workspace or self.workspace.enabled -end - -function Crate:package(): string - return self.pkg and self.pkg.text or self.explicit_name -end - -function Crate:cache_key(): string - return string.format( - "%s:%s:%s:%s", - self.section.target or "", - self.section.workspace and "workspace" or "", - self.section.kind, - self.explicit_name - ) -end - -function Section.new(obj: Section): Section - return setmetatable(obj, { __index = Section }) -end - -function Section:display(override_name: string|nil): string - local text = "[" - - if self.target then - text = text .. self.target .. "." - end - - if self.workspace then - text = text .. "workspace." - end - - if self.kind == "default" then - text = text .. "dependencies" - elseif self.kind == "dev" then - text = text .. "dev-dependencies" - elseif self.kind == "build" then - text = text .. "build-dependencies" - end - - local name = override_name or self.name - if name then - text = text .. "." .. name - end - - text = text .. "]" - - return text -end - -function M.parse_section(text: string, start: integer): Section - local prefix, suffix_s, suffix = text:match("^(.*)dependencies()(.*)$") - if prefix and suffix then - prefix = vim.trim(prefix) - suffix = vim.trim(suffix) - local section: Section = { - text = text, - invalid = false, - kind = "default", - } - - local target = prefix - - local dev_target = prefix:match("^(.*)dev%-$") - if dev_target then - target = vim.trim(dev_target) - section.kind = "dev" - end - - local build_target = prefix:match("^(.*)build%-$") - if build_target then - target = vim.trim(build_target) - section.kind = "build" - end - - local workspace_target = target:match("^(.*)workspace%s*%.$") - if workspace_target then - section.workspace = true - target = vim.trim(workspace_target) - end - - if target then - local t = target:match("^target%s*%.(.+)%.$") - if t then - section.target = vim.trim(t) - target = "" - end - end - - if suffix then - local n_s, n, n_e = suffix:match("^%.%s*()(.+)()%s*$") - if n then - section.name = vim.trim(n) - local offset = start + suffix_s as integer - 1 - section.name_col = Range.new(n_s as integer - 1 + offset, n_e as integer - 1 + offset) - suffix = "" - end - end - - section.invalid = (target ~= "" or suffix ~= "") - or (section.workspace and section.kind ~= "default") - or (section.workspace and section.target ~= nil) - - return Section.new(section) - end - - return nil -end - - -local function parse_crate_table_str(line: string, line_nr: integer, pattern: string): table|nil - local quote_s, str_s, text, str_e, quote_e = line:match(pattern) - if text then - return { - text = text, - line = line_nr, - col = Range.new(str_s as integer - 1, str_e as integer - 1), - decl_col = Range.new(0, line:len()), - quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, - } - end - - return nil -end - -local function parse_crate_table_bool(line: string, line_nr: integer, pattern: string): table|nil - local bool_s, text, bool_e = line:match(pattern) - if text then - return { - text = text, - line = line_nr, - col = Range.new(bool_s as integer - 1, bool_e as integer - 1), - decl_col = Range.new(0, line:len()), - } - end - - return nil -end - -function M.parse_crate_table_vers(line: string, line_nr: integer): Crate.Vers - local pat = [[^%s*version%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) as Crate.Vers -end - -function M.parse_crate_table_registry(line: string, line_nr: integer): Crate.Registry - local pat = [[^%s*registry%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) as Crate.Registry -end - -function M.parse_crate_table_path(line: string, line_nr: integer): Crate.Path - local pat = [[^%s*path%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) as Crate.Path -end - -function M.parse_crate_table_git(line: string, line_nr: integer): Crate.Git - local pat = [[^%s*git%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) as Crate.Git -end - -function M.parse_crate_table_branch(line: string, line_nr: integer): Crate.Branch - local pat = [[^%s*branch%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) as Crate.Branch -end - -function M.parse_crate_table_rev(line: string, line_nr: integer): Crate.Rev - local pat = [[^%s*rev%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) as Crate.Rev -end - -function M.parse_crate_table_pkg(line: string, line_nr: integer): Crate.Pkg - local pat = [[^%s*package%s*=%s*(["'])()([^"']*)()(["']?)%s*$]] - return parse_crate_table_str(line, line_nr, pat) as Crate.Pkg -end - -function M.parse_crate_table_def(line: string, line_nr: integer): Crate.Def - local pat = "^%s*default[_-]features%s*=%s*()([^%s]*)()%s*$" - return parse_crate_table_bool(line, line_nr, pat) as Crate.Def -end - -function M.parse_crate_table_workspace(line: string, line_nr: integer): Crate.Workspace - local pat = "^%s*workspace%s*=%s*()([^%s]*)()%s*$" - return parse_crate_table_bool(line, line_nr, pat) as Crate.Workspace -end - -function M.parse_crate_table_opt(line: string, line_nr: integer): Crate.Opt - local pat = "^%s*optional%s*=%s*()([^%s]*)()%s*$" - return parse_crate_table_bool(line, line_nr, pat) as Crate.Opt -end - -function M.parse_crate_table_feat(line: string, line_nr: integer): Crate.Feat - local array_s, text, array_e = line:match("%s*features%s*=%s*%[()([^%]]*)()[%]]?%s*$") - if text then - return { - text = text, - line = line_nr, - col = Range.new(array_s as integer - 1, array_e as integer - 1), - decl_col = Range.new(0, line:len()), - } - end - - return nil -end - - -local function inline_table_bool_pattern(name: string): string - return "^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*" .. name .. "%s*=%s*()([^%s,}]*)()%s*()[,]?.*[}]?%s*$" -end - -local function inline_table_str_pattern(name: string): string - return [[^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*]] .. name .. [[%s*=%s*(["'])()([^"']*)()(["']?)%s*()[,]?.*[}]?%s*$]] -end - -local function inline_table_str_array_pattern(name: string): string - return "^%s*()([^%s]+)()%s*=%s*{.-[,]?()%s*" .. name .. "%s*=%s*%[()([^%]]*)()[%]]?%s*()[,]?.*[}]?%s*$" -end - -local INLINE_TABLE_VERS_PATTERN = inline_table_str_pattern("version") -local INLINE_TABLE_REGISTRY_PATTERN = inline_table_str_pattern("registry") -local INLINE_TABLE_PATH_PATTERN = inline_table_str_pattern("path") -local INLINE_TABLE_GIT_PATTERN = inline_table_str_pattern("git") -local INLINE_TABLE_BRANCH_PATTERN = inline_table_str_pattern("branch") -local INLINE_TABLE_REV_PATTERN = inline_table_str_pattern("rev") -local INLINE_TABLE_PKG_PATTERN = inline_table_str_pattern("package") -local INLINE_TABLE_FEAT_PATTERN = inline_table_str_array_pattern("features") -local INLINE_TABLE_DEF_PATTERN = inline_table_bool_pattern("default[_-]features") -local INLINE_TABLE_WORKSPACE_PATTERN = inline_table_bool_pattern("workspace") -local INLINE_TABLE_OPT_PATTERN = inline_table_bool_pattern("optional") - -local function parse_inline_table_str(line: string, line_nr: integer, pattern: string): string|nil, Range|nil, {string:any}|nil - local name_s, name, name_e, decl_s, quote_s, str_s, text, str_e, quote_e, decl_e = line:match(pattern) - if name then - local name_col = Range.new(name_s as integer - 1, name_e as integer - 1) - local entry = { - text = text, - line = line_nr, - col = Range.new(str_s as integer - 1, str_e as integer - 1), - decl_col = Range.new(decl_s as integer - 1, decl_e as integer - 1), - quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, - } - - return name, name_col, entry - end -end - -local function parse_inline_table_bool(line: string, line_nr: integer, pattern: string): string|nil, Range|nil, {string:any}|nil - local name_s, name, name_e, decl_s, str_s, text, str_e, decl_e = line:match(pattern) - if name then - local name_col = Range.new(name_s as integer - 1, name_e as integer - 1) - local entry = { - text = text, - line = line_nr, - col = Range.new(str_s as integer - 1, str_e as integer - 1), - decl_col = Range.new(decl_s as integer - 1, decl_e as integer - 1), - } - return name, name_col, entry - end -end - -function M.parse_inline_crate(line: string, line_nr: integer): Crate - -- plain version - do - local name_s, name, name_e, quote_s, str_s, text, str_e, quote_e = line:match([[^%s*()([^%s]+)()%s*=%s*(["'])()([^"']*)()(["']?)%s*$]]) - if name then - return { - explicit_name = name, - explicit_name_col = Range.new(name_s as integer - 1, name_e as integer - 1), - lines = Range.new(line_nr, line_nr + 1), - syntax = "plain", - vers = { - text = text, - line = line_nr, - col = Range.new(str_s as integer - 1, str_e as integer - 1), - decl_col = Range.new(0, line:len()), - quote = { s = quote_s, e = quote_e ~= "" and quote_e or nil }, - } - } - end - end - - -- inline table - local crate: Crate = { - syntax = "inline_table", - lines = Range.new(line_nr, line_nr + 1), - } - - do - local name, name_col, vers = parse_inline_table_str(line, line_nr, INLINE_TABLE_VERS_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.vers = vers as Crate.Vers - end - end - - do - local name, name_col, registry = parse_inline_table_str(line, line_nr, INLINE_TABLE_REGISTRY_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.registry = registry as Crate.Registry - end - end - - do - local name, name_col, path = parse_inline_table_str(line, line_nr, INLINE_TABLE_PATH_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.path = path as Crate.Path - end - end - - do - local name, name_col, git = parse_inline_table_str(line, line_nr, INLINE_TABLE_GIT_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.git = git as Crate.Git - end - end - - do - local name, name_col, branch = parse_inline_table_str(line, line_nr, INLINE_TABLE_BRANCH_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.branch = branch as Crate.Branch - end - end - - do - local name, name_col, rev = parse_inline_table_str(line, line_nr, INLINE_TABLE_REV_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.rev = rev as Crate.Rev - end - end - - do - local name, name_col, pkg = parse_inline_table_str(line, line_nr, INLINE_TABLE_PKG_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.pkg = pkg as Crate.Pkg - end - end - - do - local name, name_col, def = parse_inline_table_bool(line, line_nr, INLINE_TABLE_DEF_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.def = def as Crate.Def - end - end - - do - local name, name_col, workspace = parse_inline_table_bool(line, line_nr, INLINE_TABLE_WORKSPACE_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.workspace = workspace as Crate.Workspace - end - end - - do - local name, name_col, opt = parse_inline_table_bool(line, line_nr, INLINE_TABLE_OPT_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = name_col - crate.opt = opt as Crate.Opt - end - end - - do - local name_s, name, name_e, decl_s, array_s, text, array_e, decl_e = line:match(INLINE_TABLE_FEAT_PATTERN) - if name then - crate.explicit_name = name - crate.explicit_name_col = Range.new(name_s as integer - 1, name_e as integer - 1) - crate.feat = { - text = text, - line = line_nr, - col = Range.new(array_s as integer - 1, array_e as integer - 1), - decl_col = Range.new(decl_s as integer - 1, decl_e as integer - 1), - } - end - end - - if crate.explicit_name then - return crate - end - - return nil -end - -function M.trim_comments(line: string): string - local uncommented = line:match("^([^#]*)#.*$") - return uncommented or line -end - -function M.parse_crates(buf: integer): {Section}, {Crate} - local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) - - local sections = {} - local crates = {} - - local dep_section: Section = nil - local dep_section_crate: Crate = nil - - for i,line in ipairs(lines) do - line = M.trim_comments(line) - local line_nr = i - 1 - - local section_start, section_text = line:match("^%s*%[()(.+)%]%s*$") - - if section_text then - if dep_section then - -- close line range - dep_section.lines.e = line_nr - - -- push pending crate - if dep_section_crate then - dep_section_crate.lines = dep_section.lines - table.insert(crates, Crate.new(dep_section_crate)) - end - end - - local section = M.parse_section(section_text, section_start as integer - 1) - - if section then - section.lines = Range.new(line_nr, nil) - dep_section = section - dep_section_crate = nil - table.insert(sections, dep_section) - else - dep_section = nil - dep_section_crate = nil - end - elseif dep_section and dep_section.name then - local empty_crate = { - explicit_name = dep_section.name, - explicit_name_col = dep_section.name_col, - section = dep_section, - syntax = "table", - } - - local vers = M.parse_crate_table_vers(line, line_nr) - if vers then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.vers = vers - end - - local registry = M.parse_crate_table_registry(line, line_nr) - if registry then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.registry = registry - end - - local path = M.parse_crate_table_path(line, line_nr) - if path then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.path = path - end - - local git = M.parse_crate_table_git(line, line_nr) - if git then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.git = git - end - - local branch = M.parse_crate_table_branch(line, line_nr) - if branch then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.branch = branch - end - - local rev = M.parse_crate_table_rev(line, line_nr) - if rev then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.rev = rev - end - - local pkg = M.parse_crate_table_pkg(line, line_nr) - if pkg then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.pkg = pkg - end - - local def = M.parse_crate_table_def(line, line_nr) - if def then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.def = def - end - - local workspace = M.parse_crate_table_workspace(line, line_nr) - if workspace then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.workspace = workspace - end - - local opt = M.parse_crate_table_opt(line, line_nr) - if opt then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.opt = opt - end - - local feat = M.parse_crate_table_feat(line, line_nr) - if feat then - dep_section_crate = dep_section_crate or empty_crate - dep_section_crate.feat = feat - end - elseif dep_section then - local crate = M.parse_inline_crate(line, line_nr) - if crate then - crate.section = dep_section - table.insert(crates, Crate.new(crate)) - end - end - end - - if dep_section then - -- close line range - dep_section.lines.e = #lines - - -- push pending crate - if dep_section_crate then - dep_section_crate.lines = dep_section.lines - table.insert(crates, Crate.new(dep_section_crate)) - end - end - - return sections, crates -end - -return M diff --git a/teal/crates/types.tl b/teal/crates/types.tl deleted file mode 100644 index 3dc969c9..00000000 --- a/teal/crates/types.tl +++ /dev/null @@ -1,249 +0,0 @@ -local record M - record CrateInfo - lines: Range - vers_line: integer - vers_match: Version - vers_update: Version - vers_upgrade: Version - match_kind: MatchKind - end - - enum MatchKind - "version" - "yanked" - "prerelease" - "nomatch" - end - - record Diagnostic - lnum: integer - end_lnum: integer - col: integer - end_col: integer - severity: integer - kind: DiagnosticKind - data: {string:any} - end - - -- keys of DiagnosticConfig - enum DiagnosticKind - -- error - "section_invalid" - "workspace_section_not_default" - "workspace_section_has_target" - "section_dup" - "crate_dup" - "crate_novers" - "crate_error_fetching" - "crate_name_case" - "vers_nomatch" - "vers_yanked" - "vers_pre" - "def_invalid" - "feat_invalid" - -- warning - "vers_upgrade" - "feat_dup" - -- hint - "section_dup_orig" - "crate_dup_orig" - "feat_dup_orig" - end - - record Crate - name: string - description: string - created: DateTime - updated: DateTime - downloads: integer - homepage: string|nil - repository: string|nil - documentation: string|nil - categories: {string} - keywords: {string} - versions: {Version} - end - - record Version - num: string - features: Features - yanked: boolean - parsed: SemVer - created: DateTime - deps: {Dependency}|nil - end - - record Features - list: {Feature} - map: {string:Feature} - end - - record Feature - name: string - members: {string} - end - - record Dependency - name: string - opt: boolean - kind: Kind - vers: Vers - - enum Kind - "normal" - "build" - "dev" - end - - record Vers - reqs: {Requirement} - text: string - end - end - - record SemVer - major: integer - minor: integer - patch: integer - pre: string - meta: string - end - - record Requirement - cond: Cond - cond_col: Range -- relative to to the start of the requirement text - vers: SemVer - vers_col: Range -- relative to to the start of the requirement text - end - - enum Cond - "eq" - "lt" - "le" - "gt" - "ge" - "cr" - "tl" - "wl" - "bl" - end - - record Range - s: integer -- 0-indexed inclusive - e: integer -- 0-indexed exclusive - end -end - -local Diagnostic = M.Diagnostic -local Feature = M.Feature -local Features = M.Features -local Range = M.Range -local SemVer = M.SemVer -local time = require("crates.time") -local DateTime = time.DateTime - -function Diagnostic.new(obj: Diagnostic): Diagnostic - return setmetatable(obj, { __index = Diagnostic }) -end - -function Diagnostic:contains(line: integer, col: integer): boolean - return (self.lnum < line or self.lnum == line and self.col <= col) - and (self.end_lnum > line or self.end_lnum == line and self.end_col > col) -end - - -function Features.new(list: {Feature}): Features - local map = {} - for _,f in ipairs(list) do - map[f.name] = f - end - return setmetatable({ list = list, map = map }, { __index = Features }) -end - -function Features:get_feat(name: string): Feature|nil - return self.map[name] -end - -function Features:sort() - table.sort(self.list, function (a: Feature, b: Feature): boolean - if a.name == "default" then - return true - elseif b.name == "default" then - return false - else - return a.name < b.name - end - end) -end - -function Features:insert(feat: Feature) - table.insert(self.list, feat) - self.map[feat.name] = feat -end - - -function SemVer.new(obj: SemVer): SemVer - return setmetatable(obj, { __index = SemVer }) -end - -function SemVer:display(): string - local text = "" - if self.major then - text = text .. self.major - end - - if self.minor then - text = text .. "." .. self.minor - end - - if self.patch then - text = text .. "." .. self.patch - end - - if self.pre then - text = text .. "-" .. self.pre - end - - if self.meta then - text = text .. "+" .. self.meta - end - - return text -end - - -function Range.new(s: integer, e: integer): Range - return setmetatable({ s = s, e = e }, { __index = Range }) -end - -function Range.pos(p: integer): Range - return Range.new(p, p + 1) -end - -function Range.empty(): Range - return Range.new(0, 0) -end - -function Range:contains(pos: integer): boolean - return self.s <= pos and pos < self.e -end - --- Create a new range with moved start and end bounds -function Range:moved(s: integer, e: integer): Range - return Range.new(self.s + s, self.e + e) -end - -function Range:iter(): function(): integer - local i = self.s - return function(): integer - if i >= self.e then - return nil - end - - local val = i - i = i + 1 - return val - end -end - -return M diff --git a/teal/crates/util.tl b/teal/crates/util.tl deleted file mode 100644 index 1343c360..00000000 --- a/teal/crates/util.tl +++ /dev/null @@ -1,186 +0,0 @@ -local record M - record FeatureInfo - ENABLED: integer - TRANSITIVE: integer - end -end - -M.FeatureInfo.ENABLED = 1 -M.FeatureInfo.TRANSITIVE = 2 - -local FeatureInfo = M.FeatureInfo -local semver = require("crates.semver") -local state = require("crates.state") -local toml = require("crates.toml") -local types = require("crates.types") -local Diagnostic = types.Diagnostic -local CrateInfo = types.CrateInfo -local Feature = types.Feature -local Features = types.Features -local Range = types.Range -local Requirement = types.Requirement -local Version = types.Version - -local IS_WIN = vim.api.nvim_call_function("has", { "win32" }) == 1 - -function M.current_buf(): integer - return vim.api.nvim_get_current_buf() as integer -end - -function M.cursor_pos(): integer, integer - local cursor = vim.api.nvim_win_get_cursor(0) - return cursor[1] - 1, cursor[2] -end - -function M.get_buf_crates(buf: integer): {string:toml.Crate} - local cache = state.buf_cache[buf] - return cache and cache.crates -end - -function M.get_buf_info(buf: integer): {string:CrateInfo} - local cache = state.buf_cache[buf] - return cache and cache.info -end - -function M.get_buf_diagnostics(buf: integer): {Diagnostic} - local cache = state.buf_cache[buf] - return cache and cache.diagnostics -end - -function M.get_crate_info(buf: integer, key: string): CrateInfo - local info = M.get_buf_info(buf) - return info[key] -end - -function M.get_line_crates(buf: integer, lines: Range): {string:toml.Crate} - local cache = state.buf_cache[buf] - local crates = cache and cache.crates - if not crates then - return {} - end - - local line_crates = {} - for k,c in pairs(crates) do - if lines:contains(c.lines.s) or c.lines:contains(lines.s) then - line_crates[k] = c - end - end - - return line_crates -end - -function M.get_newest(versions: {Version}, avoid_pre: boolean, reqs: {Requirement}|nil): Version, Version, Version - if not versions then - return nil - end - - local newest_yanked: Version|nil = nil - local newest_pre: Version|nil = nil - local newest: Version|nil = nil - - for _,v in ipairs(versions) do - if not reqs or semver.matches_requirements(v.parsed, reqs) then - if not v.yanked then - if not avoid_pre or avoid_pre and not v.parsed.pre then - newest = v - break - else - newest_pre = newest_pre or v - end - else - newest_yanked = newest_yanked or v - end - end - end - - return newest, newest_pre, newest_yanked -end - -function M.is_feat_enabled(crate: toml.Crate, feature: Feature): boolean - local enabled = crate:get_feat(feature.name) ~= nil - if feature.name == "default" then - return enabled or crate:is_def_enabled() - else - return enabled - end -end - -function M.features_info(crate: toml.Crate, features: Features): {string:integer} - local info: {string:integer} = {} - - local function update_transitive(f: Feature) - for _,m in ipairs(f.members) do - local tf = features:get_feat(m) - if tf then - local i = info[m] - if not i then - info[m] = FeatureInfo.TRANSITIVE - update_transitive(tf) - end - end - end - end - - if not crate.def or crate.def.enabled then - info["default"] = FeatureInfo.ENABLED - local api_feat = features.list[1] - update_transitive(api_feat) - end - - local crate_features = crate.feat - if not crate_features then - return info - end - - for _,crate_feat in ipairs(crate_features.items) do - local api_feat = features:get_feat(crate_feat.name) - if api_feat then - info[(api_feat as Feature).name] = FeatureInfo.ENABLED - update_transitive(api_feat) - end - end - - return info -end - -function M.lualib_installed(name: string): boolean - local ok, _ = pcall(require as function(string): (any), name) - return ok -end - -function M.binary_installed(name: string): boolean - if IS_WIN then - name = name .. ".exe" - end - - return vim.fn.executable(name) == 1 -end - -function M.notify(severity: integer, s: string, ...:any) - vim.notify(s:format(...), severity, { title = state.cfg.notification_title }) -end - -function M.docs_rs_url(name: string): string - return "https://docs.rs/"..name -end - -function M.crates_io_url(name: string): string - return "https://crates.io/crates/"..name -end - -function M.open_url(url: string) - for _, prg in ipairs(state.cfg.open_programs) do - if M.binary_installed(prg) then - vim.cmd(string.format("silent !%s %s", prg, url)) - return - end - end - - M.notify(vim.log.levels.WARN, "Couldn't open url") -end - -function M.format_title(name: string): string - return name:sub(1, 1):upper() .. name:gsub("_", " "):sub(2) -end - -return M