Skip to content

Commit

Permalink
feat: correctly handle dep:<crate_name> features in popup
Browse files Browse the repository at this point in the history
  • Loading branch information
saecki committed Jun 29, 2024
1 parent 06dc38e commit ada726a
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 44 deletions.
18 changes: 18 additions & 0 deletions lua/crates/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,19 @@ local function remove_feature_action(buf, crate, feat)
end
end

---@param buf integer
---@param crate TomlCrate
---@param feat TomlFeature
---@return fun()
local function remove_feature_dep_prefix_action(buf, crate, feat)
return function()
local line = crate.feat.line
local col_start = crate.feat.col.s + feat.col.s
local col_end = col_start + 4
vim.api.nvim_buf_set_text(buf, line, col_start, line, col_end, {})
end
end

---@return CratesAction[]
function M.get_actions()
---@type CratesAction[]
Expand Down Expand Up @@ -270,6 +283,11 @@ function M.get_actions()
name = "remove_invalid_feature",
action = remove_feature_action(buf, crate, d.data["feat"]),
})
elseif crate and d.kind == CratesDiagnosticKind.FEAT_EXPLICIT_DEP then
table.insert(actions, {
name = "remove_`dep:`_prefix",
action = remove_feature_dep_prefix_action(buf, crate, d.data["feat"]),
})
end

::continue::
Expand Down
86 changes: 67 additions & 19 deletions lua/crates/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -322,18 +322,6 @@ function M.parse_crate(json_str)
-- created = assert(DateTime.parse_rfc_3339(v.created_at)),
}

-- TODO: handle `features2` syntaxes
-- - explicit `dep:<crate_name>`
-- - weak dependencies `pkg?/feat`
---@diagnostic disable-next-line: no-unknown
for n, m in pairs(json.features) do
table.sort(m)
version.features:insert({
name = n,
members = m,
})
end

---@diagnostic disable-next-line: no-unknown
for _, d in ipairs(json.deps) do
if d.name then
Expand All @@ -349,18 +337,78 @@ function M.parse_crate(json_str)
},
}
table.insert(version.deps, dependency)
end
end

---@param name string
---@param members string[]
for name, members in pairs(json.features) do
for i, m in ipairs(members) do
if json.features[m] then
goto continue
end

-- enforce explicit `dep:<crate_name>` syntax
for _,d in ipairs(version.deps) do
if d.name == m then
members[i] = "dep:" .. m
break
end
end

if dependency.opt and not version.features.map[dependency.name] then
version.features:insert({
name = dependency.name,
members = {},
})
::continue::
end

table.sort(members, function(a, b)
if string.sub(a, 1, 4) == "dep:" then
return false
elseif string.sub(b, 1, 4) == "dep:" then
return true
else
return a < b
end
end)

version.features:insert({
name = name,
members = members,
})
end
if json.features2 then
---@diagnostic disable-next-line: no-unknown
for n, m in pairs(json.features2) do
-- TODO: handle `features2` syntaxes
-- - explicit `dep:<crate_name>`
-- - weak dependencies `pkg?/feat`
table.sort(m)
version.features:insert({
name = n,
members = m,
})
end
end

-- sort features alphabetically
version.features:sort()
-- sort features
table.sort(version.features.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)

-- add optional dependencies as features
for _, d in ipairs(version.deps) do
if d.opt then
version.features:insert({
name = "dep:" .. d.name,
members = {},
dep = true,
})
end
end

-- add missing default feature
if not version.features.list[1] or not (version.features.list[1].name == "default") then
Expand Down
6 changes: 6 additions & 0 deletions lua/crates/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ entry(schema_diagnostic, {
default = "Invalid feature",
hidden = true,
})
entry(schema_diagnostic, {
name = "feat_explicit_dep",
type = STRING_TYPE,
default = "Explicit `dep:` prefix for optional dependencies is not allowed",
hidden = true,
})


local schema_popup = section_entry(M.schema, {
Expand Down
1 change: 1 addition & 0 deletions lua/crates/config/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
---@field feat_dup string
---@field feat_dup_orig string
---@field feat_invalid string
---@field feat_explicit_dep string

---@class PopupConfig
---@field autofocus boolean
Expand Down
10 changes: 9 additions & 1 deletion lua/crates/diagnostic.lua
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,15 @@ function M.process_api_crate(crate, api_crate)
-- invalid features diagnostics
if info.vers_match then
for _, f in ipairs(crate:feats()) do
if not info.vers_match.features.map[f.name] then
if string.sub(f.name, 1, 4) == "dep:" then
table.insert(diagnostics, feat_diagnostic(
crate,
f,
CratesDiagnosticKind.FEAT_EXPLICIT_DEP,
vim.diagnostic.severity.ERROR,
{ feat = f }
))
elseif not info.vers_match.features:get_feat(f.name) then
table.insert(diagnostics, feat_diagnostic(
crate,
f,
Expand Down
7 changes: 6 additions & 1 deletion lua/crates/edit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,12 @@ end
---@param feature ApiFeature
---@return Span
function M.enable_feature(buf, crate, feature)
local t = '"' .. feature.name .. '"'
local name = feature.name
if feature.dep then
name = string.sub(name, 5)
end
local t = '"' .. name .. '"'

if crate.feat then
local last_feat = crate.feat.items[#crate.feat.items]
if last_feat then
Expand Down
39 changes: 34 additions & 5 deletions lua/crates/popup/features.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,39 @@ local function toggle_feature(ctx, line)
return
end

local feat_name = selected_feature.name
if selected_feature.dep then
local parent_name = string.sub(feat_name, 5)
local parent_feat = features.map[parent_name]

if parent_feat and vim.tbl_contains(parent_feat.members, feat_name) then
if #parent_feat.members > 1 then
util.notify(vim.log.levels.INFO, "Cannot enable/disable '%s' directly; instead toggle its parent feature '%s'", feat_name, parent_name)
return
end
elseif not parent_feat then
-- no direct explicit parent feature, so just toggle the implicit feature
else
-- the parent feature named like the dependency, doesn't include the `dep:` feature,
-- so find other features, that include it.
local parents = {}
for _, f in ipairs(features.list) do
if vim.tbl_contains(f.members, feat_name) then
table.insert(parents, string.format("'%s'", f.name))
end
end

local parent_names = table.concat(parents, ", ")
util.notify(vim.log.levels.INFO, "Cannot enable/disable '%s' directly; instead toggle a parent feature: %s", feat_name, parent_names)
return
end

feat_name = parent_name
end

---@type Span
local line_span
local crate_feature = ctx.crate:get_feat(selected_feature.name)
local crate_feature = ctx.crate:get_feat(feat_name)
if selected_feature.name == "default" then
if crate_feature ~= nil or ctx.crate:is_def_enabled() then
line_span = edit.disable_def_features(ctx.buf, ctx.crate, crate_feature)
Expand Down Expand Up @@ -143,7 +173,7 @@ local function goto_feature(ctx, line)
if feature then
local m = feature.members[index]
if m then
selected_feature = version.features:get_feat(m)
selected_feature = version.features.map[m]
end
else
selected_feature = version.features.list[index]
Expand Down Expand Up @@ -373,8 +403,7 @@ function M.open(crate, version, opts)
crate = crate,
version = version,
history = {
--- TODO: 3?
{ feature = nil, line = opts.line or 3 },
{ feature = nil, line = opts.line or 2 },
},
hist_idx = 1,
}
Expand All @@ -392,7 +421,7 @@ function M.open_details(crate, version, feature, opts)
version = version,
history = {
{ feature = nil, line = 2 },
{ feature = feature, line = opts.line or 3 },
{ feature = feature, line = opts.line or 2 },
},
hist_idx = 2,
}
Expand Down
9 changes: 4 additions & 5 deletions lua/crates/toml.lua
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,18 @@ 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, nil
return nil
end

for i, f in ipairs(self.feat.items) do
for _, f in ipairs(self.feat.items) do
if f.name == name then
return f, i
return f
end
end

return nil, nil
return nil
end

---@return TomlFeature[]
Expand Down
17 changes: 4 additions & 13 deletions lua/crates/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ M.MatchKind = {
---@class ApiFeature
---@field name string
---@field members string[]
---@field dep boolean?

---@class ApiDependency
---@field name string
Expand Down Expand Up @@ -129,6 +130,7 @@ M.CratesDiagnosticKind = {
VERS_PRE = "vers_pre",
DEF_INVALID = "def_invalid",
FEAT_INVALID = "feat_invalid",
FEAT_EXPLICIT_DEP = "feat_explicit_dep",
-- warning
VERS_UPGRADE = "vers_upgrade",
FEAT_DUP = "feat_dup",
Expand All @@ -155,22 +157,11 @@ function ApiFeatures.new(list)
return setmetatable({ list = list, map = map }, { __index = ApiFeatures })
end

---Returns the feature directly matching `name` or alternatively in `dep:name` syntax.
---@param name string
---@return ApiFeature|nil
function ApiFeatures:get_feat(name)
return self.map[name]
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)
return self.map[name] or self.map["dep:" .. name]
end

---@param feat ApiFeature
Expand Down

0 comments on commit ada726a

Please sign in to comment.