Skip to content

Commit

Permalink
feat: added proper api
Browse files Browse the repository at this point in the history
  • Loading branch information
folke committed May 30, 2024
1 parent 4d31d77 commit a327003
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 61 deletions.
125 changes: 125 additions & 0 deletions lua/trouble/api.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
local Actions = require("trouble.config.actions")
local Config = require("trouble.config")
local Util = require("trouble.util")
local View = require("trouble.view")

---@alias trouble.ApiFn fun(opts?: trouble.Config|string): trouble.View
---@alias trouble.Open trouble.Config|{focus?:boolean, new?:boolean}

---@class trouble.api: trouble.actions
local M = {}
M.last_mode = nil ---@type string?

--- Finds all open views matching the filter.
---@param opts? trouble.Config|string
---@param filter? trouble.View.filter
---@return trouble.View[], trouble.Config
function M.find(opts, filter)
opts = Config.get(opts)
if opts.mode == "last" then
opts.mode = M.last_mode
opts = Config.get(opts)
end
M.last_mode = opts.mode or M.last_mode
filter = filter or { is_open = true, mode = opts.mode }
return vim.tbl_map(function(v)
return v.view
end, View.get(filter)), opts
end

--- Finds the last open view matching the filter.
---@param opts? trouble.Open|string
---@param filter? trouble.View.filter
---@return trouble.View?, trouble.Open
function M.find_last(opts, filter)
local views, _opts = M.find(opts, filter)
return views[#views], _opts
end

--- Gets the last open view matching the filter or creates a new one.
---@param opts? trouble.Config|string
---@param filter? trouble.View.filter
---@return trouble.View, trouble.Open
function M.get(opts, filter)
local view, _opts = M.find_last(opts, filter)
if not view or _opts.new then
if not _opts.mode then
error("No mode specified")
end
view = View.new(_opts)
end
return view, _opts
end

---@param opts? trouble.Open|string
function M.open(opts)
local view, _opts = M.get(opts)
if view then
view:open()
if _opts.focus then
view.win:focus()
end
return view, _opts
end
end

--- Returns true if there is an open view matching the filter.
---@param opts? trouble.Config|string
function M.is_open(opts)
return M.find_last(opts) ~= nil
end

---@param opts? trouble.Config|string
function M.close(opts)
local view = M.find_last(opts)
if view then
view:close()
end
end

---@param opts? trouble.Open|string
function M.toggle(opts)
if M.is_open(opts) then
M.close(opts)
else
M.open(opts)
end
end

--- Special case for refresh. Refresh all open views.
---@param opts? trouble.Config|string
function M.refresh(opts)
for _, view in ipairs(M.find(opts)) do
view:refresh()
end
end

--- Proxy to last view's action.
---@param action trouble.Action|string
function M.action(action)
action = type(action) == "string" and Actions[action] or action
---@cast action trouble.Action
return function(opts)
local view = M.open(opts)
view:action(action, opts)
return view
end
end

---@param opts? trouble.Config|string
function M.get_items(opts)
local view = M.find_last(opts)
local ret = {} ---@type trouble.Item[]
if view then
for _, items in pairs(view.items) do
vim.list_extend(ret, items)
end
end
return ret
end

return setmetatable(M, {
__index = function(_, k)
return M.action(k)
end,
})
113 changes: 82 additions & 31 deletions lua/trouble/command.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,92 @@ local Util = require("trouble.util")

local M = {}

---@param input string
function M.parse(input)
local source, args = input:match("%s*(%S+)%s*(.*)$")
if not source then
Util.error("Invalid arguments: " .. input)
return
end
return source, Parser.parse(args)
end

---@param prefix string
---@param line string
---@param col number
function M.complete(_, line, col)
line = line:sub(1, col)
function M.complete(prefix, line, col)
line = line:sub(1, col):match("Trouble%s*(.*)$")
local parsed = M.parse(line)
local candidates = {} ---@type string[]
local prefix = ""
local source = line:match("Trouble%s+(%S*)$")
if source then
prefix = source
candidates = Config.modes()
else
local args = line:match("Trouble%s+%S+%s*.*%s+(%S*)$")
if args then
prefix = args
candidates = vim.tbl_keys(Config.get())
candidates = vim.tbl_map(function(x)
return x .. "="
end, candidates)
if vim.tbl_isempty(parsed.opts) then
if not parsed.mode then
vim.list_extend(candidates, Config.modes())
else
if not parsed.action then
vim.list_extend(candidates, M.actions())
end
vim.list_extend(candidates, M.complete_opts())
end
else
vim.list_extend(candidates, M.complete_opts())
end

candidates = vim.tbl_filter(function(x)
return tostring(x):find(prefix, 1, true) ~= nil
return tostring(x):find(prefix, 1, true) == 1
end, candidates)
table.sort(candidates)
return candidates
end

function M.complete_opts()
local candidates = {} ---@type string[]
local stack = { { k = "", t = Config.get() } }
while #stack > 0 do
local top = table.remove(stack)
for k, v in pairs(top.t) do
if k:match("^[a-z_]+$") then
k = "." .. k
else
k = ("[%q]"):format(k)
end
local kk = top.k .. k
candidates[#candidates + 1] = kk:gsub("^%.", "") .. "="
if type(v) == "table" and not vim.tbl_islist(v) then
table.insert(stack, { k = kk, t = v })
end
end
end
vim.list_extend(candidates, {
"focus=true",
"jump=true",
"win.type=float",
"win.type=split",
"win.position=top",
"win.position=bottom",
"win.position=left",
"win.position=right",
"win.relative=editor",
"win.relative=win",
})
return candidates
end

function M.actions()
local actions = vim.tbl_keys(require("trouble.api"))
vim.list_extend(actions, vim.tbl_keys(require("trouble.config.actions")))
return actions
end

---@param input string
function M.parse(input)
---@type {mode: string, action: string, opts: trouble.Config, errors: string[], args: string[]}
local ret = Parser.parse(input)
local modes = Config.modes()
local actions = M.actions()

-- Args can be mode and/or action
for _, a in ipairs(ret.args) do
if vim.tbl_contains(modes, a) then
ret.mode = a
elseif vim.tbl_contains(actions, a) then
ret.action = a
else
table.insert(ret.errors, "Unknown argument: " .. a)
end
end

return ret
end

function M.execute(input)
if input.args:match("^%s*$") then
vim.ui.select(Config.modes(), { prompt = "Select Trouble Mode:" }, function(mode)
Expand All @@ -50,11 +98,14 @@ function M.execute(input)
end
end)
else
local mode, opts = M.parse(input.args)
if mode and opts then
opts.mode = mode
require("trouble").open(opts)
local ret = M.parse(input.args)
ret.action = ret.action or "open"
ret.opts.mode = ret.opts.mode or ret.mode
if #ret.errors > 0 then
Util.error("Error parsing command:\n- input: `" .. input.args .. "`\nErrors:\n" .. table.concat(ret.errors, "\n"))
return
end
require("trouble")[ret.action](ret.opts)
end
end

Expand Down
26 changes: 24 additions & 2 deletions lua/trouble/config/actions.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---@alias trouble.Action.ctx {item?: trouble.Item, node?: trouble.Node}
---@alias trouble.Action.ctx {item?: trouble.Item, node?: trouble.Node, opts?: table}
---@alias trouble.ActionFn fun(view:trouble.View, ctx:trouble.Action.ctx)
---@alias trouble.Action trouble.ActionFn|{action:trouble.ActionFn, desc?:string}

---@type table<string, trouble.Action>
---@class trouble.actions: {[string]: trouble.Action}
local M = {
refresh = function(self)
self:refresh()
Expand All @@ -13,6 +13,9 @@ local M = {
cancel = function(self)
self:goto_main()
end,
focus = function(self)
self.win:focus()
end,
preview = function(self, ctx)
local Preview = require("trouble.view.preview")
if Preview.preview then
Expand All @@ -30,9 +33,24 @@ local M = {
Preview.close()
end
end,
toggle_auto_refresh = function(self)
self.opts.auto_refresh = not self.opts.auto_refresh
end,
help = function(self)
self:help()
end,
next = function(self, ctx)
self:move({ down = vim.v.count1, jump = ctx.opts.jump })
end,
prev = function(self, ctx)
self:move({ up = vim.v.count1, jump = ctx.opts.jump })
end,
first = function(self, ctx)
self:move({ idx = vim.v.count1, jump = ctx.opts.jump })
end,
last = function(self, ctx)
self:move({ idx = -vim.v.count1, jump = ctx.opts.jump })
end,
jump_only = function(self, ctx)
if ctx.item then
self:jump(ctx.item)
Expand Down Expand Up @@ -97,6 +115,10 @@ local M = {
end,
}

-- FIXME: make deprecation warnings instead
-- backward compatibility with Trouble v2
M.previous = M.prev

for _, fold_action in ipairs({ "toggle", "open", "close" }) do
for _, recursive in ipairs({ true, false }) do
local desc = "Fold " .. fold_action .. " " .. (recursive and "recursive" or "")
Expand Down
14 changes: 13 additions & 1 deletion lua/trouble/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ local defaults = {
auto_open = false,
auto_close = false,
auto_preview = true,
auto_refresh = true,
pinned = false,
multiline = true, -- render multi-line messages
---@type table<string, trouble.Formatter>
Expand All @@ -34,13 +35,22 @@ local defaults = {
keys = {
["?"] = "help",
r = "refresh",
R = "toggle_auto_refresh",
q = "close",
o = "jump_close",
["<esc>"] = "cancel",
["<cr>"] = "jump",
["<2-leftmouse>"] = "jump",
["<c-s>"] = "jump_split",
["<c-v>"] = "jump_vsplit",
-- go down to next item (accepts count)
-- j = "next",
["}"] = "next",
["]]"] = "next",
-- go up to prev item (accepts count)
-- k = "prev",
["{"] = "prev",
["[["] = "prev",
i = "inspect",
p = "preview",
P = "toggle_auto_preview",
Expand Down Expand Up @@ -191,6 +201,7 @@ function M.get(...)

---@type table<string, boolean>
local modes = {}
local first_mode ---@type string?

for i = 1, select("#", ...) do
---@type trouble.Config?
Expand All @@ -202,6 +213,7 @@ function M.get(...)
table.insert(all, opts)
local idx = #all
while opts.mode and not modes[opts.mode] do
first_mode = first_mode or opts.mode
modes[opts.mode or ""] = true
opts = options.modes[opts.mode] or {}
table.insert(all, idx, opts)
Expand All @@ -214,7 +226,7 @@ function M.get(...)
if type(ret.config) == "function" then
ret.config(ret)
end

ret.mode = first_mode
if ret.mode then
ret.modes = {}
end
Expand Down
13 changes: 6 additions & 7 deletions lua/trouble/init.lua
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
---@class trouble: trouble.api
local M = {}

---@param opts? trouble.Config
function M.setup(opts)
require("trouble.config").setup(opts)
end

---@param opts trouble.Config|string
function M.open(opts)
opts = require("trouble.config").get(opts)
require("trouble.view").new(opts):open()
end

return M
return setmetatable(M, {
__index = function(_, k)
return require("trouble.api")[k]
end,
})
Loading

0 comments on commit a327003

Please sign in to comment.