143 lines
6.2 KiB
Lua
143 lines
6.2 KiB
Lua
-- builtin/menus/default.lua --- default right-click context menu (Q#CM2/Q#CM3).
|
|
--
|
|
-- Items are registered with `pmacs.menu.item` (Rust registry). Each
|
|
-- names a command to invoke and declares visibility via a coarse
|
|
-- `context` tag (sugar) or a full `predicate`, evaluated against a
|
|
-- context table when the menu opens. `group`/`order` drive layout;
|
|
-- separators fall between groups.
|
|
--
|
|
-- `pmacs.menu.build()` (called from Rust at right-click) does the
|
|
-- resolution: filter visible items, group/sort, and return the rows.
|
|
|
|
local ed = pmacs.editor
|
|
|
|
-- The active buffer's live LSP attachment record, or nil --- guarded so
|
|
-- the menu never errors when LSP isn't configured/loaded, and never
|
|
-- triggers an attach just by opening.
|
|
local function lsp_attachment()
|
|
if pmacs.lsp == nil or pmacs.lsp.active_attachment == nil then
|
|
return nil
|
|
end
|
|
local ok, rec = pcall(pmacs.lsp.active_attachment)
|
|
if ok then return rec end
|
|
return nil
|
|
end
|
|
|
|
-- Whether the 0-based (line, col) falls within diagnostic `d`'s range.
|
|
local function diag_contains(d, line, col)
|
|
if line < d.start_line or line > d.end_line then return false end
|
|
if line == d.start_line and col < d.start_col then return false end
|
|
if line == d.end_line and col > d.end_col then return false end
|
|
return true
|
|
end
|
|
|
|
-- Evaluate a coarse `context` tag against the live context table
|
|
-- (Q#CM3). Sugar for a predicate; `symbol` needs a word-under-point and
|
|
-- an attached server, `diagnostic` needs a published diagnostic
|
|
-- spanning the cursor.
|
|
function pmacs.menu._context_eval(tag, cx)
|
|
if tag == "always" then
|
|
return true
|
|
elseif tag == "selection" then
|
|
return cx.has_selection
|
|
elseif tag == "symbol" then
|
|
return cx.word ~= nil and cx.attachment ~= nil
|
|
elseif tag == "diagnostic" then
|
|
if cx.attachment == nil then return false end
|
|
for _, d in ipairs(pmacs.diag.list(cx.attachment.uri)) do
|
|
if diag_contains(d, cx.line, cx.col) then return true end
|
|
end
|
|
return false
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Whether `it` is visible in context `cx`. A failing predicate hides
|
|
-- the item rather than aborting the whole menu.
|
|
local function item_visible(it, cx)
|
|
if it.predicate ~= nil then
|
|
local ok, vis = pcall(it.predicate, cx)
|
|
return ok and vis and true or false
|
|
elseif it.context ~= nil then
|
|
return pmacs.menu._context_eval(it.context, cx) and true or false
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- Build the resolved, grouped, visibility-filtered rows for an open
|
|
-- menu (Q#CM3). Returns an array where each element is either
|
|
-- `{ separator = true }` or `{ label = ..., command = ... }`.
|
|
function pmacs.menu.build()
|
|
local cx = {
|
|
has_selection = ed.region() ~= nil,
|
|
word = ed.word_at_cursor(),
|
|
line = ed.cursor_line(),
|
|
col = ed.cursor_col(),
|
|
attachment = lsp_attachment(),
|
|
}
|
|
|
|
-- Filter to visible items, tagging insertion order for a stable sort.
|
|
local visible = {}
|
|
for i, it in ipairs(pmacs.menu._raw()) do
|
|
if item_visible(it, cx) then
|
|
it.__i = i
|
|
visible[#visible + 1] = it
|
|
end
|
|
end
|
|
|
|
-- Group order = first appearance in the registry; within a group,
|
|
-- sort by `order`, then by insertion for ties.
|
|
local gidx, next_g = {}, 1
|
|
for _, it in ipairs(visible) do
|
|
local g = it.group or ""
|
|
if gidx[g] == nil then
|
|
gidx[g] = next_g
|
|
next_g = next_g + 1
|
|
end
|
|
end
|
|
table.sort(visible, function(a, b)
|
|
local ga, gb = gidx[a.group or ""], gidx[b.group or ""]
|
|
if ga ~= gb then return ga < gb end
|
|
local oa, ob = a.order or 0, b.order or 0
|
|
if oa ~= ob then return oa < ob end
|
|
return a.__i < b.__i
|
|
end)
|
|
|
|
-- Emit rows, inserting a separator between distinct groups.
|
|
local rows, last_group = {}, nil
|
|
for _, it in ipairs(visible) do
|
|
local g = it.group or ""
|
|
if last_group ~= nil and g ~= last_group then
|
|
rows[#rows + 1] = { separator = true }
|
|
end
|
|
rows[#rows + 1] = { label = it.label, command = it.command }
|
|
last_group = g
|
|
end
|
|
return rows
|
|
end
|
|
|
|
-- Default items. The edit group adapts to the selection; the symbol
|
|
-- group appears on an identifier with a server attached; the diagnostic
|
|
-- group appears when a diagnostic spans the cursor; history is always
|
|
-- available. Group order follows registration order.
|
|
pmacs.menu.item { id = "edit.cut", label = "Cut", command = "edit.cut", context = "selection", group = "edit", order = 10 }
|
|
pmacs.menu.item { id = "edit.copy", label = "Copy", command = "edit.copy", context = "selection", group = "edit", order = 20 }
|
|
pmacs.menu.item { id = "edit.paste", label = "Paste", command = "edit.paste", context = "always", group = "edit", order = 30 }
|
|
pmacs.menu.item { id = "edit.select-all", label = "Select All", command = "edit.select-all", context = "always", group = "edit", order = 40 }
|
|
|
|
-- Symbol group (LSP). Shown when the cursor is on an identifier and a
|
|
-- language server is attached. Each invokes the existing async command,
|
|
-- which acts at the cursor (the right-click anchored it there).
|
|
pmacs.menu.item { id = "lsp.go-to-definition", label = "Go to Definition", command = "lsp.go-to-definition", context = "symbol", group = "symbol", order = 10 }
|
|
pmacs.menu.item { id = "lsp.find-references", label = "Find References", command = "lsp.find-references", context = "symbol", group = "symbol", order = 20 }
|
|
pmacs.menu.item { id = "lsp.rename", label = "Rename", command = "lsp.rename", context = "symbol", group = "symbol", order = 30 }
|
|
pmacs.menu.item { id = "lsp.hover", label = "Hover", command = "lsp.hover", context = "symbol", group = "symbol", order = 40 }
|
|
|
|
-- Diagnostic group (LSP). Shown when a diagnostic spans the cursor.
|
|
-- "Quick Fix" runs code actions at the point (Q#CM10 defers streaming
|
|
-- the individual fix titles into the menu).
|
|
pmacs.menu.item { id = "lsp.quick-fix", label = "Quick Fix", command = "lsp.code-actions", context = "diagnostic", group = "diagnostic", order = 10 }
|
|
|
|
pmacs.menu.item { id = "buffer.undo", label = "Undo", command = "buffer.undo", context = "always", group = "history", order = 10 }
|
|
pmacs.menu.item { id = "buffer.redo", label = "Redo", command = "buffer.redo", context = "always", group = "history", order = 20 }
|