Regenerate nvim config
This commit is contained in:
@ -0,0 +1,34 @@
|
||||
local util = require("trouble.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
local links = {
|
||||
TextError = "TroubleText",
|
||||
TextWarning = "TroubleText",
|
||||
TextInformation = "TroubleText",
|
||||
TextHint = "TroubleText",
|
||||
Text = "Normal",
|
||||
File = "Directory",
|
||||
Source = "Comment",
|
||||
Code = "Comment",
|
||||
Location = "LineNr",
|
||||
FoldIcon = "CursorLineNr",
|
||||
Normal = "Normal",
|
||||
Count = "TabLineSel",
|
||||
Preview = "Search",
|
||||
Indent = "LineNr",
|
||||
SignOther = "TroubleSignInformation",
|
||||
}
|
||||
|
||||
function M.setup()
|
||||
for k, v in pairs(links) do
|
||||
vim.api.nvim_command("hi def link Trouble" .. k .. " " .. v)
|
||||
end
|
||||
|
||||
for _, severity in pairs(util.severity) do
|
||||
vim.api.nvim_command("hi def link Trouble" .. severity .. " " .. util.get_severity_label(severity))
|
||||
vim.api.nvim_command("hi def link TroubleSign" .. severity .. " " .. util.get_severity_label(severity, "Sign"))
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,83 @@
|
||||
local M = {}
|
||||
|
||||
M.namespace = vim.api.nvim_create_namespace("Trouble")
|
||||
|
||||
---@class TroubleOptions
|
||||
---@field buf number|nil
|
||||
---@field win number|nil
|
||||
---@field severity lsp.DiagnosticSeverity|nil
|
||||
-- TODO: make some options configurable per mode
|
||||
-- TODO: make it possible to have multiple trouble lists open at the same time
|
||||
local defaults = {
|
||||
debug = false,
|
||||
cmd_options = {},
|
||||
group = true, -- group results by file
|
||||
padding = true, -- add an extra new line on top of the list
|
||||
position = "bottom", -- position of the list can be: bottom, top, left, right
|
||||
height = 10, -- height of the trouble list when position is top or bottom
|
||||
width = 50, -- width of the list when position is left or right
|
||||
icons = true, -- use devicons for filenames
|
||||
mode = "workspace_diagnostics", -- "workspace_diagnostics", "document_diagnostics", "quickfix", "lsp_references", "loclist"
|
||||
severity = nil, -- nil (ALL) or vim.diagnostic.severity.ERROR | WARN | INFO | HINT
|
||||
fold_open = "", -- icon used for open folds
|
||||
fold_closed = "", -- icon used for closed folds
|
||||
cycle_results = true, -- cycle item list when reaching beginning or end of list
|
||||
action_keys = { -- key mappings for actions in the trouble list
|
||||
close = "q", -- close the list
|
||||
cancel = "<esc>", -- cancel the preview and get back to your last window / buffer / cursor
|
||||
refresh = "r", -- manually refresh
|
||||
jump = { "<cr>", "<tab>", "<2-leftmouse>" }, -- jump to the diagnostic or open / close folds
|
||||
open_split = { "<c-x>" }, -- open buffer in new split
|
||||
open_vsplit = { "<c-v>" }, -- open buffer in new vsplit
|
||||
open_tab = { "<c-t>" }, -- open buffer in new tab
|
||||
jump_close = { "o" }, -- jump to the diagnostic and close the list
|
||||
toggle_mode = "m", -- toggle between "workspace" and "document" mode
|
||||
switch_severity = "s", -- switch "diagnostics" severity filter level to ALL / HINT / INFO / WARN / ERROR
|
||||
toggle_preview = "P", -- toggle auto_preview
|
||||
hover = "K", -- opens a small popup with the full multiline message
|
||||
preview = "p", -- preview the diagnostic location
|
||||
open_code_href = "c", -- if present, open a URI with more information about the diagnostic error
|
||||
close_folds = { "zM", "zm" }, -- close all folds
|
||||
open_folds = { "zR", "zr" }, -- open all folds
|
||||
toggle_fold = { "zA", "za" }, -- toggle fold of current file
|
||||
previous = "k", -- preview item
|
||||
next = "j", -- next item
|
||||
help = "?", -- help menu
|
||||
},
|
||||
multiline = true, -- render multi-line messages
|
||||
indent_lines = true, -- add an indent guide below the fold icons
|
||||
win_config = { border = "single" }, -- window configuration for floating windows. See |nvim_open_win()|.
|
||||
auto_open = false, -- automatically open the list when you have diagnostics
|
||||
auto_close = false, -- automatically close the list when you have no diagnostics
|
||||
auto_preview = true, -- automatically preview the location of the diagnostic. <esc> to close preview and go back to last window
|
||||
auto_fold = false, -- automatically fold a file trouble list at creation
|
||||
auto_jump = { "lsp_definitions" }, -- for the given modes, automatically jump if there is only a single result
|
||||
include_declaration = { "lsp_references", "lsp_implementations", "lsp_definitions" }, -- for the given modes, include the declaration of the current symbol in the results
|
||||
signs = {
|
||||
-- icons / text used for a diagnostic
|
||||
error = "",
|
||||
warning = "",
|
||||
hint = "",
|
||||
information = "",
|
||||
other = "",
|
||||
},
|
||||
use_diagnostic_signs = false, -- enabling this will use the signs defined in your lsp client
|
||||
sort_keys = {
|
||||
"severity",
|
||||
"filename",
|
||||
"lnum",
|
||||
"col",
|
||||
},
|
||||
}
|
||||
|
||||
---@type TroubleOptions
|
||||
M.options = {}
|
||||
---@return TroubleOptions
|
||||
|
||||
function M.setup(options)
|
||||
M.options = vim.tbl_deep_extend("force", {}, defaults, options or {})
|
||||
end
|
||||
|
||||
M.setup()
|
||||
|
||||
return M
|
||||
@ -0,0 +1,24 @@
|
||||
local config = require("trouble.config")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.folded = {}
|
||||
|
||||
function M.is_folded(filename)
|
||||
local fold = M.folded[filename]
|
||||
return (fold == nil and config.options.auto_fold == true) or (fold == true)
|
||||
end
|
||||
|
||||
function M.toggle(filename)
|
||||
M.folded[filename] = not M.is_folded(filename)
|
||||
end
|
||||
|
||||
function M.close(filename)
|
||||
M.folded[filename] = true
|
||||
end
|
||||
|
||||
function M.open(filename)
|
||||
M.folded[filename] = false
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,300 @@
|
||||
local View = require("trouble.view")
|
||||
local config = require("trouble.config")
|
||||
local colors = require("trouble.colors")
|
||||
local util = require("trouble.util")
|
||||
|
||||
colors.setup()
|
||||
|
||||
local Trouble = {}
|
||||
|
||||
local view
|
||||
|
||||
function Trouble.is_open()
|
||||
return view and view:is_valid() or false
|
||||
end
|
||||
|
||||
function Trouble.setup(options)
|
||||
if vim.fn.has("nvim-0.7.2") == 0 then
|
||||
util.error("Trouble needs Neovim >= 0.7.2")
|
||||
return
|
||||
end
|
||||
config.setup(options)
|
||||
util.fix_mode(config.options)
|
||||
colors.setup()
|
||||
end
|
||||
|
||||
function Trouble.close()
|
||||
if Trouble.is_open() then
|
||||
view:close()
|
||||
end
|
||||
end
|
||||
|
||||
local function get_opts(...)
|
||||
local args = { ... }
|
||||
if (vim.islist or vim.tbl_islist)(args) and #args == 1 and type(args[1]) == "table" then
|
||||
args = args[1]
|
||||
end
|
||||
local opts = {}
|
||||
for key, value in pairs(args) do
|
||||
if type(key) == "number" then
|
||||
local k, v = value:match("^(.*)=(.*)$")
|
||||
if k then
|
||||
opts[k] = v
|
||||
elseif opts.mode then
|
||||
util.error("unknown option " .. value)
|
||||
else
|
||||
opts.mode = value
|
||||
end
|
||||
else
|
||||
opts[key] = value
|
||||
end
|
||||
end
|
||||
opts = opts or {}
|
||||
util.fix_mode(opts)
|
||||
config.options.cmd_options = opts
|
||||
return opts
|
||||
end
|
||||
|
||||
function Trouble.open(...)
|
||||
local opts = get_opts(...)
|
||||
if opts.mode and (opts.mode ~= config.options.mode) then
|
||||
config.options.mode = opts.mode
|
||||
end
|
||||
|
||||
if opts.severity and (opts.severity ~= config.options.severity) then
|
||||
config.options.severity = opts.severity
|
||||
end
|
||||
|
||||
opts.focus = (opts.focus == nil and not opts.auto) and true or opts.focus
|
||||
opts.on_open = true
|
||||
|
||||
if Trouble.is_open() then
|
||||
Trouble.refresh(opts)
|
||||
elseif not opts.auto and vim.tbl_contains(config.options.auto_jump, opts.mode) then
|
||||
require("trouble.providers").get(vim.api.nvim_get_current_win(), vim.api.nvim_get_current_buf(), function(results)
|
||||
if #results == 1 then
|
||||
util.jump_to_item(opts.win, opts.precmd, results[1])
|
||||
elseif #results > 0 then
|
||||
view = View.create(opts)
|
||||
end
|
||||
end, config.options)
|
||||
else
|
||||
view = View.create(opts)
|
||||
end
|
||||
end
|
||||
|
||||
function Trouble.toggle(...)
|
||||
local opts = get_opts(...)
|
||||
|
||||
if opts.mode and (opts.mode ~= config.options.mode) then
|
||||
config.options.mode = opts.mode
|
||||
Trouble.open(...)
|
||||
return
|
||||
end
|
||||
|
||||
if Trouble.is_open() then
|
||||
Trouble.close()
|
||||
else
|
||||
Trouble.open(...)
|
||||
end
|
||||
end
|
||||
|
||||
function Trouble.help()
|
||||
local lines = { "# Key Bindings" }
|
||||
local height = 1
|
||||
for command, key in pairs(config.options.action_keys) do
|
||||
if type(key) == "table" then
|
||||
key = table.concat(key, " | ")
|
||||
end
|
||||
table.insert(lines, " * **" .. key .. "** " .. command:gsub("_", " "))
|
||||
height = height + 1
|
||||
end
|
||||
-- help
|
||||
vim.lsp.util.open_floating_preview(lines, "markdown", config.options.win_config)
|
||||
end
|
||||
|
||||
local updater = util.debounce(100, function()
|
||||
-- buff might have been closed during the debounce
|
||||
if not Trouble.is_open() then
|
||||
util.debug("refresh: not open anymore")
|
||||
return
|
||||
end
|
||||
|
||||
util.debug("refresh: auto")
|
||||
view:update({ auto = true })
|
||||
end)
|
||||
|
||||
function Trouble.refresh(opts)
|
||||
opts = opts or {}
|
||||
|
||||
-- dont do an update if this is an automated refresh from a different provider
|
||||
if opts.auto then
|
||||
if opts.provider == "diagnostics" and config.options.mode == "document_diagnostics" then
|
||||
opts.provider = "document_diagnostics"
|
||||
elseif opts.provider == "diagnostics" and config.options.mode == "workspace_diagnostics" then
|
||||
opts.provider = "workspace_diagnostics"
|
||||
elseif opts.provider == "qf" and config.options.mode == "quickfix" then
|
||||
opts.provider = "quickfix"
|
||||
elseif opts.provider == "qf" and config.options.mode == "loclist" then
|
||||
opts.provider = "loclist"
|
||||
end
|
||||
if opts.provider ~= config.options.mode then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if Trouble.is_open() then
|
||||
if opts.auto then
|
||||
updater()
|
||||
else
|
||||
util.debug("refresh")
|
||||
view:update(opts)
|
||||
end
|
||||
elseif opts.auto and config.options.auto_open and opts.provider == config.options.mode then
|
||||
require("trouble.providers").get(vim.api.nvim_get_current_win(), vim.api.nvim_get_current_buf(), function(results)
|
||||
if #results > 0 then
|
||||
Trouble.open(opts)
|
||||
end
|
||||
end, config.options)
|
||||
end
|
||||
end
|
||||
|
||||
function Trouble.action(action)
|
||||
if action == "toggle_mode" then
|
||||
if config.options.mode == "document_diagnostics" then
|
||||
config.options.mode = "workspace_diagnostics"
|
||||
elseif config.options.mode == "workspace_diagnostics" then
|
||||
config.options.mode = "document_diagnostics"
|
||||
end
|
||||
action = "refresh"
|
||||
end
|
||||
|
||||
if action == "switch_severity" then
|
||||
if config.options.severity == nil then
|
||||
config.options.severity = vim.diagnostic.severity.ERROR
|
||||
elseif config.options.severity < 4 then
|
||||
config.options.severity = config.options.severity + 1
|
||||
else
|
||||
config.options.severity = nil
|
||||
end
|
||||
action = "refresh"
|
||||
end
|
||||
|
||||
if view and action == "on_win_enter" then
|
||||
view:on_win_enter()
|
||||
end
|
||||
if not Trouble.is_open() then
|
||||
return Trouble
|
||||
end
|
||||
if action == "hover" then
|
||||
view:hover()
|
||||
end
|
||||
if action == "jump" then
|
||||
view:jump()
|
||||
elseif action == "open_split" then
|
||||
view:jump({ precmd = "split" })
|
||||
elseif action == "open_vsplit" then
|
||||
view:jump({ precmd = "vsplit" })
|
||||
elseif action == "open_tab" then
|
||||
view:jump({ precmd = "tabe" })
|
||||
end
|
||||
if action == "jump_close" then
|
||||
view:jump()
|
||||
Trouble.close()
|
||||
end
|
||||
if action == "open_folds" then
|
||||
Trouble.refresh({ open_folds = true })
|
||||
end
|
||||
if action == "close_folds" then
|
||||
Trouble.refresh({ close_folds = true })
|
||||
end
|
||||
if action == "toggle_fold" then
|
||||
view:toggle_fold()
|
||||
end
|
||||
if action == "on_enter" then
|
||||
view:on_enter()
|
||||
end
|
||||
if action == "on_leave" then
|
||||
view:on_leave()
|
||||
end
|
||||
if action == "cancel" then
|
||||
view:switch_to_parent()
|
||||
end
|
||||
if action == "next" then
|
||||
view:next_item()
|
||||
return Trouble
|
||||
end
|
||||
if action == "previous" then
|
||||
view:previous_item()
|
||||
return Trouble
|
||||
end
|
||||
if action == "first" then
|
||||
view:first_item()
|
||||
return Trouble
|
||||
end
|
||||
if action == "last" then
|
||||
view:last_item()
|
||||
return Trouble
|
||||
end
|
||||
|
||||
if action == "toggle_preview" then
|
||||
config.options.auto_preview = not config.options.auto_preview
|
||||
if not config.options.auto_preview then
|
||||
view:close_preview()
|
||||
else
|
||||
action = "preview"
|
||||
end
|
||||
end
|
||||
if action == "auto_preview" and config.options.auto_preview then
|
||||
action = "preview"
|
||||
end
|
||||
if action == "preview" then
|
||||
view:preview()
|
||||
end
|
||||
if action == "open_code_href" then
|
||||
view:open_code_href()
|
||||
end
|
||||
|
||||
if Trouble[action] then
|
||||
Trouble[action]()
|
||||
end
|
||||
return Trouble
|
||||
end
|
||||
|
||||
function Trouble.next(opts)
|
||||
util.fix_mode(opts)
|
||||
if view then
|
||||
view:next_item(opts)
|
||||
end
|
||||
end
|
||||
|
||||
function Trouble.previous(opts)
|
||||
util.fix_mode(opts)
|
||||
if view then
|
||||
view:previous_item(opts)
|
||||
end
|
||||
end
|
||||
|
||||
function Trouble.first(opts)
|
||||
util.fix_mode(opts)
|
||||
if view then
|
||||
view:first_item(opts)
|
||||
end
|
||||
end
|
||||
|
||||
function Trouble.last(opts)
|
||||
util.fix_mode(opts)
|
||||
if view then
|
||||
view:last_item(opts)
|
||||
end
|
||||
end
|
||||
|
||||
function Trouble.get_items()
|
||||
if view ~= nil then
|
||||
return view.items
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
return Trouble
|
||||
@ -0,0 +1,59 @@
|
||||
local util = require("trouble.util")
|
||||
|
||||
---@class Lsp
|
||||
local M = {}
|
||||
|
||||
local severity = {
|
||||
[1] = "ERROR",
|
||||
[2] = "WARN",
|
||||
[3] = "INFO",
|
||||
[4] = "HINT",
|
||||
}
|
||||
|
||||
---@param options TroubleOptions
|
||||
---@return Item[]
|
||||
function M.diagnostics(_, buf, cb, options)
|
||||
if options.mode == "workspace_diagnostics" then
|
||||
buf = nil
|
||||
end
|
||||
|
||||
local items = {}
|
||||
|
||||
if vim.diagnostic then
|
||||
local diags = vim.diagnostic.get(buf, { severity = options.severity })
|
||||
for _, item in ipairs(diags) do
|
||||
table.insert(items, util.process_item(item))
|
||||
end
|
||||
else
|
||||
---@diagnostic disable-next-line: deprecated
|
||||
local diags = buf and { [buf] = vim.lsp.diagnostic.get(buf) } or vim.lsp.diagnostic.get_all()
|
||||
items = util.locations_to_items(diags, 1)
|
||||
end
|
||||
|
||||
local messages = {}
|
||||
if severity[options.severity] then
|
||||
table.insert(messages, { text = "filter:", group = "Information" })
|
||||
table.insert(messages, { text = severity[options.severity], group = "Sign" .. util.severity[options.severity] })
|
||||
end
|
||||
|
||||
cb(items, messages)
|
||||
end
|
||||
|
||||
function M.get_signs()
|
||||
local signs = {}
|
||||
for _, v in pairs(util.severity) do
|
||||
if v ~= "Other" then
|
||||
-- pcall to catch entirely unbound or cleared out sign hl group
|
||||
local status, sign = pcall(function()
|
||||
return vim.trim(vim.fn.sign_getdefined(util.get_severity_label(v, "Sign"))[1].text)
|
||||
end)
|
||||
if not status then
|
||||
sign = v:sub(1, 1)
|
||||
end
|
||||
signs[string.lower(v)] = sign
|
||||
end
|
||||
end
|
||||
return signs
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,94 @@
|
||||
local util = require("trouble.util")
|
||||
local qf = require("trouble.providers.qf")
|
||||
local telescope = require("trouble.providers.telescope")
|
||||
local lsp = require("trouble.providers.lsp")
|
||||
local diagnostic = require("trouble.providers.diagnostic")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.providers = {
|
||||
workspace_diagnostics = diagnostic.diagnostics,
|
||||
document_diagnostics = diagnostic.diagnostics,
|
||||
lsp_references = lsp.references,
|
||||
lsp_implementations = lsp.implementations,
|
||||
lsp_definitions = lsp.definitions,
|
||||
lsp_type_definitions = lsp.type_definitions,
|
||||
quickfix = qf.qflist,
|
||||
loclist = qf.loclist,
|
||||
telescope = telescope.telescope,
|
||||
}
|
||||
|
||||
---@param options TroubleOptions
|
||||
function M.get(win, buf, cb, options)
|
||||
local name = options.mode
|
||||
local provider = M.providers[name]
|
||||
|
||||
if not provider then
|
||||
local ok, mod = pcall(require, "trouble.providers." .. name)
|
||||
if ok then
|
||||
M.providers[name] = mod
|
||||
provider = mod
|
||||
end
|
||||
end
|
||||
|
||||
if not provider then
|
||||
util.error(("invalid provider %q"):format(name))
|
||||
return {}
|
||||
end
|
||||
|
||||
local sort_keys = vim.list_extend({
|
||||
function(item)
|
||||
local cwd = vim.loop.fs_realpath(vim.fn.getcwd())
|
||||
local path = vim.loop.fs_realpath(item.filename)
|
||||
if not path then
|
||||
return 200
|
||||
end
|
||||
local ret = string.find(path, cwd, 1, true) == 1 and 10 or 100
|
||||
-- prefer non-hidden files
|
||||
if string.find(path, ".") then
|
||||
ret = ret + 1
|
||||
end
|
||||
return ret
|
||||
end,
|
||||
}, options.sort_keys)
|
||||
|
||||
provider(win, buf, function(items, messages)
|
||||
table.sort(items, function(a, b)
|
||||
for _, key in ipairs(sort_keys) do
|
||||
local ak = type(key) == "string" and a[key] or key(a)
|
||||
local bk = type(key) == "string" and b[key] or key(b)
|
||||
if ak ~= bk then
|
||||
return ak < bk
|
||||
end
|
||||
end
|
||||
end)
|
||||
cb(items, messages)
|
||||
end, options)
|
||||
end
|
||||
|
||||
---@param items Item[]
|
||||
---@return table<string, Item[]>
|
||||
function M.group(items)
|
||||
local keys = {}
|
||||
local keyid = 0
|
||||
local groups = {}
|
||||
for _, item in ipairs(items) do
|
||||
if groups[item.filename] == nil then
|
||||
groups[item.filename] = { filename = item.filename, items = {} }
|
||||
keys[item.filename] = keyid
|
||||
keyid = keyid + 1
|
||||
end
|
||||
table.insert(groups[item.filename].items, item)
|
||||
end
|
||||
|
||||
local ret = {}
|
||||
for _, group in pairs(groups) do
|
||||
table.insert(ret, group)
|
||||
end
|
||||
table.sort(ret, function(a, b)
|
||||
return keys[a.filename] < keys[b.filename]
|
||||
end)
|
||||
return ret
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,93 @@
|
||||
local lsp = require("vim.lsp")
|
||||
local util = require("trouble.util")
|
||||
|
||||
---@class Lsp
|
||||
local M = {}
|
||||
|
||||
local function lsp_buf_request(buf, method, params, handler)
|
||||
lsp.buf_request(buf, method, params, function(err, m, result)
|
||||
handler(err, method == m and result or m)
|
||||
end)
|
||||
end
|
||||
|
||||
---@return Item[]
|
||||
function M.references(win, buf, cb, options)
|
||||
local method = "textDocument/references"
|
||||
local params = util.make_position_params(win, buf)
|
||||
params.context = { includeDeclaration = vim.tbl_contains(options.include_declaration, options.mode) }
|
||||
|
||||
lsp_buf_request(buf, method, params, function(err, result)
|
||||
if err then
|
||||
util.error("an error happened getting references: " .. err.message)
|
||||
return cb({})
|
||||
end
|
||||
if result == nil or #result == 0 then
|
||||
return cb({})
|
||||
end
|
||||
local ret = util.locations_to_items({ result }, 0)
|
||||
cb(ret)
|
||||
end)
|
||||
end
|
||||
|
||||
---@return Item[]
|
||||
function M.implementations(win, buf, cb, options)
|
||||
local method = "textDocument/implementation"
|
||||
local params = util.make_position_params(win, buf)
|
||||
params.context = { includeDeclaration = vim.tbl_contains(options.include_declaration, options.mode) }
|
||||
lsp_buf_request(buf, method, params, function(err, result)
|
||||
if err then
|
||||
util.error("an error happened getting implementation: " .. err.message)
|
||||
return cb({})
|
||||
end
|
||||
if result == nil or #result == 0 then
|
||||
return cb({})
|
||||
end
|
||||
local ret = util.locations_to_items({ result }, 0)
|
||||
cb(ret)
|
||||
end)
|
||||
end
|
||||
|
||||
---@return Item[]
|
||||
function M.definitions(win, buf, cb, options)
|
||||
local method = "textDocument/definition"
|
||||
local params = util.make_position_params(win, buf)
|
||||
params.context = { includeDeclaration = vim.tbl_contains(options.include_declaration, options.mode) }
|
||||
lsp_buf_request(buf, method, params, function(err, result)
|
||||
if err then
|
||||
util.error("an error happened getting definitions: " .. err.message)
|
||||
return cb({})
|
||||
end
|
||||
if result == nil or #result == 0 then
|
||||
return cb({})
|
||||
end
|
||||
for _, value in ipairs(result) do
|
||||
value.uri = value.targetUri or value.uri
|
||||
value.range = value.targetSelectionRange or value.range
|
||||
end
|
||||
local ret = util.locations_to_items({ result }, 0)
|
||||
cb(ret)
|
||||
end)
|
||||
end
|
||||
|
||||
---@return Item[]
|
||||
function M.type_definitions(win, buf, cb, _options)
|
||||
local method = "textDocument/typeDefinition"
|
||||
local params = util.make_position_params(win, buf)
|
||||
lsp_buf_request(buf, method, params, function(err, result)
|
||||
if err then
|
||||
util.error("an error happened getting type definitions: " .. err.message)
|
||||
return cb({})
|
||||
end
|
||||
if result == nil or #result == 0 then
|
||||
return cb({})
|
||||
end
|
||||
for _, value in ipairs(result) do
|
||||
value.uri = value.targetUri or value.uri
|
||||
value.range = value.targetSelectionRange or value.range
|
||||
end
|
||||
local ret = util.locations_to_items({ result }, 0)
|
||||
cb(ret)
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,47 @@
|
||||
local util = require("trouble.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
local severities = { E = 1, W = 2, I = 3, H = 4 }
|
||||
|
||||
function M.get_list(winid)
|
||||
local list = winid == nil and vim.fn.getqflist({ all = true }) or vim.fn.getloclist(winid, { all = true })
|
||||
|
||||
local ret = {}
|
||||
for _, item in pairs(list.items) do
|
||||
local row = (item.lnum == 0 and 1 or item.lnum) - 1
|
||||
local col = (item.col == 0 and 1 or item.col) - 1
|
||||
|
||||
if item.valid == 1 then
|
||||
ret[#ret + 1] = {
|
||||
row = row,
|
||||
col = col,
|
||||
message = item.text,
|
||||
severity = severities[item.type] or 0,
|
||||
bufnr = item.bufnr,
|
||||
range = {
|
||||
start = { line = row, character = col },
|
||||
["end"] = { line = row, character = -1 },
|
||||
},
|
||||
}
|
||||
elseif #ret > 0 then
|
||||
ret[#ret].message = ret[#ret].message .. "\n" .. item.text
|
||||
end
|
||||
end
|
||||
|
||||
for i, item in ipairs(ret) do
|
||||
ret[i] = util.process_item(item)
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.loclist(win, _buf, cb, _options)
|
||||
return cb(M.get_list(win))
|
||||
end
|
||||
|
||||
function M.qflist(_win, _buf, cb, _options)
|
||||
return cb(M.get_list())
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,86 @@
|
||||
local util = require("trouble.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.results = {}
|
||||
|
||||
--- Turns a Telescope item into a Trouble item.
|
||||
local function item_to_result(item)
|
||||
local row = (item.lnum or 1) - 1
|
||||
local col = (item.col or 1) - 1
|
||||
|
||||
if not item.bufnr then
|
||||
local fname = vim.fn.fnamemodify(item.filename, ":p")
|
||||
if vim.fn.filereadable(fname) == 0 and item.cwd then
|
||||
fname = vim.fn.fnamemodify(item.cwd .. "/" .. item.filename, ":p")
|
||||
end
|
||||
item.bufnr = vim.fn.bufnr(fname, true)
|
||||
end
|
||||
|
||||
local pitem = {
|
||||
row = row,
|
||||
col = col,
|
||||
message = item.text,
|
||||
severity = 0,
|
||||
range = {
|
||||
start = { line = row, character = col },
|
||||
["end"] = { line = row, character = -1 },
|
||||
},
|
||||
}
|
||||
|
||||
return util.process_item(pitem, item.bufnr)
|
||||
end
|
||||
|
||||
--- Shows all Telescope results in Trouble.
|
||||
function M.open_with_trouble(prompt_bufnr, _mode)
|
||||
local action_state = require("telescope.actions.state")
|
||||
local actions = require("telescope.actions")
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
local manager = picker.manager
|
||||
|
||||
M.results = {}
|
||||
for item in manager:iter() do
|
||||
table.insert(M.results, item_to_result(item))
|
||||
end
|
||||
|
||||
actions.close(prompt_bufnr)
|
||||
require("trouble").open("telescope")
|
||||
end
|
||||
|
||||
--- Shows the selected Telescope results in Trouble.
|
||||
function M.open_selected_with_trouble(prompt_bufnr, _mode)
|
||||
local action_state = require("telescope.actions.state")
|
||||
local actions = require("telescope.actions")
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
|
||||
M.results = {}
|
||||
for _, item in ipairs(picker:get_multi_selection()) do
|
||||
table.insert(M.results, item_to_result(item))
|
||||
end
|
||||
|
||||
actions.close(prompt_bufnr)
|
||||
require("trouble").open("telescope")
|
||||
end
|
||||
|
||||
--- Shows the selected Telescope results in Trouble.
|
||||
--- If no results are currently selected, shows all of them.
|
||||
function M.smart_open_with_trouble(prompt_bufnr, _mode)
|
||||
local action_state = require("telescope.actions.state")
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
if #picker:get_multi_selection() > 0 then
|
||||
M.open_selected_with_trouble(prompt_bufnr, _mode)
|
||||
else
|
||||
M.open_with_trouble(prompt_bufnr, _mode)
|
||||
end
|
||||
end
|
||||
|
||||
function M.telescope(_win, _buf, cb, _options)
|
||||
if #M.results == 0 then
|
||||
util.warn(
|
||||
"No Telescope results found. Open Telescope and send results to Trouble first. Refer to the documentation for more info."
|
||||
)
|
||||
end
|
||||
cb(M.results)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,173 @@
|
||||
local providers = require("trouble.providers")
|
||||
local util = require("trouble.util")
|
||||
local config = require("trouble.config")
|
||||
local Text = require("trouble.text")
|
||||
local folds = require("trouble.folds")
|
||||
|
||||
---@class Renderer
|
||||
local renderer = {}
|
||||
|
||||
renderer.signs = {}
|
||||
|
||||
local function get_icon(file)
|
||||
local ok, icons = pcall(require, "nvim-web-devicons")
|
||||
if not ok then
|
||||
util.warn(
|
||||
"'nvim-web-devicons' is not installed. Install it, or set icons=false in your configuration to disable this message"
|
||||
)
|
||||
return ""
|
||||
end
|
||||
local fname = vim.fn.fnamemodify(file, ":t")
|
||||
local ext = vim.fn.fnamemodify(file, ":e")
|
||||
return icons.get_icon(fname, ext, { default = true })
|
||||
end
|
||||
|
||||
local function update_signs()
|
||||
renderer.signs = config.options.signs
|
||||
if config.options.use_diagnostic_signs then
|
||||
local lsp_signs = require("trouble.providers.diagnostic").get_signs()
|
||||
renderer.signs = vim.tbl_deep_extend("force", {}, renderer.signs, lsp_signs)
|
||||
end
|
||||
end
|
||||
|
||||
---@param view TroubleView
|
||||
function renderer.render(view, opts)
|
||||
opts = opts or {}
|
||||
local buf = vim.api.nvim_win_get_buf(view.parent)
|
||||
providers.get(view.parent, buf, function(items, messages)
|
||||
local auto_jump = vim.tbl_contains(config.options.auto_jump, opts.mode)
|
||||
if opts.on_open and #items == 1 and auto_jump and not opts.auto then
|
||||
view:close()
|
||||
util.jump_to_item(opts.win, opts.precmd, items[1])
|
||||
return
|
||||
end
|
||||
|
||||
local grouped = providers.group(items)
|
||||
local count = util.count(grouped)
|
||||
|
||||
-- check for auto close
|
||||
if opts.auto and config.options.auto_close then
|
||||
if count == 0 then
|
||||
view:close()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Update lsp signs
|
||||
update_signs()
|
||||
|
||||
local text = Text:new()
|
||||
view.items = {}
|
||||
|
||||
if config.options.padding then
|
||||
if messages ~= nil then
|
||||
for _, msg in ipairs(messages) do
|
||||
text:render(" " .. msg.text, msg.group, { append = " " })
|
||||
end
|
||||
end
|
||||
text:nl()
|
||||
end
|
||||
|
||||
-- render file groups
|
||||
for _, group in ipairs(grouped) do
|
||||
if opts.open_folds then
|
||||
folds.open(group.filename)
|
||||
end
|
||||
if opts.close_folds then
|
||||
folds.close(group.filename)
|
||||
end
|
||||
renderer.render_file(view, text, group.filename, group.items)
|
||||
end
|
||||
|
||||
view:render(text)
|
||||
if opts.focus then
|
||||
view:focus()
|
||||
end
|
||||
end, config.options)
|
||||
end
|
||||
|
||||
---@param view TroubleView
|
||||
---@param text Text
|
||||
---@param items Item[]
|
||||
---@param filename string
|
||||
function renderer.render_file(view, text, filename, items)
|
||||
view.items[text.lineNr + 1] = { filename = filename, is_file = true }
|
||||
|
||||
if view.group == true then
|
||||
local count = util.count(items)
|
||||
|
||||
text:render(" ")
|
||||
|
||||
if folds.is_folded(filename) then
|
||||
text:render(config.options.fold_closed, "FoldIcon", " ")
|
||||
else
|
||||
text:render(config.options.fold_open, "FoldIcon", " ")
|
||||
end
|
||||
|
||||
if config.options.icons then
|
||||
local icon, icon_hl = get_icon(filename)
|
||||
text:render(icon, icon_hl, { exact = true, append = " " })
|
||||
end
|
||||
|
||||
text:render(vim.fn.fnamemodify(filename, ":p:."), "File", " ")
|
||||
text:render(" " .. count .. " ", "Count")
|
||||
text:nl()
|
||||
end
|
||||
|
||||
if not folds.is_folded(filename) then
|
||||
renderer.render_diagnostics(view, text, items)
|
||||
end
|
||||
end
|
||||
|
||||
---@param view TroubleView
|
||||
---@param text Text
|
||||
---@param items Item[]
|
||||
function renderer.render_diagnostics(view, text, items)
|
||||
for _, diag in ipairs(items) do
|
||||
view.items[text.lineNr + 1] = diag
|
||||
|
||||
local sign = diag.sign or renderer.signs[string.lower(diag.type)]
|
||||
if not sign then
|
||||
sign = diag.type
|
||||
end
|
||||
|
||||
local indent = " "
|
||||
if config.options.indent_lines then
|
||||
indent = " │ "
|
||||
elseif config.options.group == false then
|
||||
indent = " "
|
||||
end
|
||||
|
||||
local sign_hl = diag.sign_hl or ("TroubleSign" .. diag.type)
|
||||
|
||||
text:render(indent, "Indent")
|
||||
text:render(sign .. " ", sign_hl, { exact = true })
|
||||
|
||||
local lines = config.options.multiline and vim.split(diag.full_text, "\n") or { diag.text }
|
||||
|
||||
text:render(lines[1], "Text" .. diag.type, " ")
|
||||
|
||||
if diag.source then
|
||||
text:render(diag.source, "Source")
|
||||
end
|
||||
if diag.code and diag.code ~= vim.NIL then
|
||||
text:render(" (" .. diag.code .. ")", "Code")
|
||||
end
|
||||
|
||||
text:render(" ")
|
||||
|
||||
text:render("[" .. diag.lnum .. ", " .. diag.col .. "]", "Location")
|
||||
|
||||
for l = 2, #lines do
|
||||
local str = lines[l]
|
||||
text:nl()
|
||||
view.items[text.lineNr + 1] = diag
|
||||
text:render(indent .. " ", "Indent")
|
||||
text:render(str, "Text" .. diag.type, " ")
|
||||
end
|
||||
|
||||
text:nl()
|
||||
end
|
||||
end
|
||||
|
||||
return renderer
|
||||
@ -0,0 +1,52 @@
|
||||
---@class Text
|
||||
---@field lines string[]
|
||||
---@field hl Highlight[]
|
||||
---@field lineNr number
|
||||
---@field current string
|
||||
local Text = {}
|
||||
Text.__index = Text
|
||||
|
||||
function Text:new()
|
||||
local this = { lines = {}, hl = {}, lineNr = 0, current = "" }
|
||||
setmetatable(this, self)
|
||||
return this
|
||||
end
|
||||
|
||||
function Text:nl()
|
||||
table.insert(self.lines, self.current)
|
||||
self.current = ""
|
||||
self.lineNr = self.lineNr + 1
|
||||
end
|
||||
|
||||
function Text:render(str, group, opts)
|
||||
str = str:gsub("[\n]", " ")
|
||||
if type(opts) == "string" then
|
||||
opts = { append = opts }
|
||||
end
|
||||
opts = opts or {}
|
||||
|
||||
if group then
|
||||
if opts.exact ~= true then
|
||||
group = "Trouble" .. group
|
||||
end
|
||||
local from = string.len(self.current)
|
||||
---@class Highlight
|
||||
local hl
|
||||
hl = {
|
||||
line = self.lineNr,
|
||||
from = from,
|
||||
to = from + string.len(str),
|
||||
group = group,
|
||||
}
|
||||
table.insert(self.hl, hl)
|
||||
end
|
||||
self.current = self.current .. str
|
||||
if opts.append then
|
||||
self.current = self.current .. opts.append
|
||||
end
|
||||
if opts.nl then
|
||||
self:nl()
|
||||
end
|
||||
end
|
||||
|
||||
return Text
|
||||
@ -0,0 +1,242 @@
|
||||
local config = require("trouble.config")
|
||||
local uv = vim.loop
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.jump_to_item(win, precmd, item)
|
||||
-- requiring here, as otherwise we run into a circular dependency
|
||||
local View = require("trouble.view")
|
||||
|
||||
-- save position in jump list
|
||||
vim.cmd("normal! m'")
|
||||
|
||||
View.switch_to(win)
|
||||
if precmd then
|
||||
vim.cmd(precmd)
|
||||
end
|
||||
if not vim.bo[item.bufnr].buflisted then
|
||||
vim.bo[item.bufnr].buflisted = true
|
||||
end
|
||||
if not vim.api.nvim_buf_is_loaded(item.bufnr) then
|
||||
vim.fn.bufload(item.bufnr)
|
||||
end
|
||||
vim.api.nvim_set_current_buf(item.bufnr)
|
||||
vim.api.nvim_win_set_cursor(win or 0, { item.start.line + 1, item.start.character })
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "TroubleJump", modeline = false })
|
||||
end
|
||||
|
||||
function M.fix_mode(opts)
|
||||
if opts.use_lsp_diagnostic_signs then
|
||||
opts.use_diagnostic_signs = opts.use_lsp_diagnostic_signs
|
||||
M.warn("The Trouble option use_lsp_diagnostic_signs has been renamed to use_diagnostic_signs")
|
||||
end
|
||||
local replace = {
|
||||
lsp_workspace_diagnostics = "workspace_diagnostics",
|
||||
lsp_document_diagnostics = "document_diagnostics",
|
||||
workspace = "workspace_diagnostics",
|
||||
document = "document_diagnostics",
|
||||
}
|
||||
|
||||
for old, new in pairs(replace) do
|
||||
if opts.mode == old then
|
||||
opts.mode = new
|
||||
M.warn("Using " .. old .. " for Trouble is deprecated. Please use " .. new .. " instead.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@return number
|
||||
function M.count(tab)
|
||||
local count = 0
|
||||
for _ in pairs(tab) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
function M.warn(msg)
|
||||
vim.notify(msg, vim.log.levels.WARN, { title = "Trouble" })
|
||||
end
|
||||
|
||||
function M.error(msg)
|
||||
vim.notify(msg, vim.log.levels.ERROR, { title = "Trouble" })
|
||||
end
|
||||
|
||||
function M.debug(msg)
|
||||
if config.options.debug then
|
||||
vim.notify(msg, vim.log.levels.DEBUG, { title = "Trouble" })
|
||||
end
|
||||
end
|
||||
|
||||
function M.debounce(ms, fn)
|
||||
local timer = vim.loop.new_timer()
|
||||
return function(...)
|
||||
local argv = { ... }
|
||||
timer:start(ms, 0, function()
|
||||
timer:stop()
|
||||
vim.schedule_wrap(fn)(unpack(argv))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function M.throttle(ms, fn)
|
||||
local timer = vim.loop.new_timer()
|
||||
local running = false
|
||||
return function(...)
|
||||
if not running then
|
||||
local argv = { ... }
|
||||
local argc = select("#", ...)
|
||||
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc))
|
||||
end)
|
||||
running = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.severity = {
|
||||
[0] = "Other",
|
||||
[1] = "Error",
|
||||
[2] = "Warning",
|
||||
[3] = "Information",
|
||||
[4] = "Hint",
|
||||
}
|
||||
|
||||
-- returns a hl or sign label for the givin severity and type
|
||||
-- correctly handles new names introduced in vim.diagnostic
|
||||
function M.get_severity_label(severity, type)
|
||||
local label = severity
|
||||
local prefix = "LspDiagnostics" .. (type or "Default")
|
||||
|
||||
if vim.diagnostic then
|
||||
prefix = type and ("Diagnostic" .. type) or "Diagnostic"
|
||||
label = ({
|
||||
Warning = "Warn",
|
||||
Information = "Info",
|
||||
})[severity] or severity
|
||||
end
|
||||
|
||||
return prefix .. label
|
||||
end
|
||||
|
||||
-- based on the Telescope diagnostics code
|
||||
-- see https://github.com/nvim-telescope/telescope.nvim/blob/0d6cd47990781ea760dd3db578015c140c7b9fa7/lua/telescope/utils.lua#L85
|
||||
function M.process_item(item, bufnr)
|
||||
bufnr = bufnr or item.bufnr
|
||||
local filename = vim.api.nvim_buf_get_name(bufnr)
|
||||
local range = item.range or item.targetSelectionRange
|
||||
|
||||
local start = {
|
||||
line = range and vim.tbl_get(range, "start", "line") or item.lnum,
|
||||
character = range and vim.tbl_get(range, "start", "character") or item.col,
|
||||
}
|
||||
local finish = {
|
||||
line = range and vim.tbl_get(range, "end", "line") or item.end_lnum,
|
||||
character = range and vim.tbl_get(range, "end", "character") or item.end_col,
|
||||
}
|
||||
|
||||
if start.character == nil or start.line == nil then
|
||||
M.error("Found an item for Trouble without start range " .. vim.inspect(start))
|
||||
end
|
||||
if finish.character == nil or finish.line == nil then
|
||||
M.error("Found an item for Trouble without finish range " .. vim.inspect(finish))
|
||||
end
|
||||
local row = start.line ---@type number
|
||||
local col = start.character ---@type number
|
||||
|
||||
if not item.message and filename then
|
||||
-- check if the filename is a uri
|
||||
if string.match(filename, "^%w+://") ~= nil then
|
||||
if not vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
vim.fn.bufload(bufnr)
|
||||
end
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)
|
||||
item.message = lines[1] or ""
|
||||
else
|
||||
local fd = assert(uv.fs_open(filename, "r", 438))
|
||||
local stat = assert(uv.fs_fstat(fd))
|
||||
local data = assert(uv.fs_read(fd, stat.size, 0))
|
||||
assert(uv.fs_close(fd))
|
||||
|
||||
item.message = vim.split(data, "\n", { plain = true })[row + 1] or ""
|
||||
end
|
||||
end
|
||||
|
||||
---@class Item
|
||||
---@field is_file boolean
|
||||
---@field fixed boolean
|
||||
local ret = {
|
||||
bufnr = bufnr,
|
||||
filename = filename,
|
||||
lnum = row + 1,
|
||||
col = col + 1,
|
||||
start = start,
|
||||
finish = finish,
|
||||
sign = item.sign, ---@type string?
|
||||
sign_hl = item.sign_hl, ---@type string?
|
||||
-- remove line break to avoid display issues
|
||||
text = vim.trim(item.message:gsub("[\n]+", "")):sub(0, vim.o.columns),
|
||||
full_text = vim.trim(item.message),
|
||||
type = M.severity[item.severity] or M.severity[0],
|
||||
code = item.code or (item.user_data and item.user_data.lsp and item.user_data.lsp.code), ---@type string?
|
||||
code_href = (item.codeDescription and item.codeDescription.href)
|
||||
or (
|
||||
item.user_data
|
||||
and item.user_data.lsp
|
||||
and item.user_data.lsp.codeDescription
|
||||
and item.user_data.lsp.codeDescription.href
|
||||
), ---@type string?
|
||||
source = item.source, ---@type string?
|
||||
severity = item.severity or 0,
|
||||
}
|
||||
return ret
|
||||
end
|
||||
|
||||
-- takes either a table indexed by bufnr, or an lsp result with uri
|
||||
---@return Item[]
|
||||
function M.locations_to_items(results, default_severity)
|
||||
default_severity = default_severity or 0
|
||||
local ret = {}
|
||||
for bufnr, locs in pairs(results or {}) do
|
||||
for _, loc in pairs(locs.result or locs) do
|
||||
if not vim.tbl_isempty(loc) then
|
||||
local uri = loc.uri or loc.targetUri
|
||||
local buf = uri and vim.uri_to_bufnr(uri) or bufnr
|
||||
loc.severity = loc.severity or default_severity
|
||||
table.insert(ret, M.process_item(loc, buf))
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
-- @private
|
||||
local function make_position_param(win, buf)
|
||||
local row, col = unpack(vim.api.nvim_win_get_cursor(win))
|
||||
row = row - 1
|
||||
local line = vim.api.nvim_buf_get_lines(buf, row, row + 1, true)[1]
|
||||
if not line then
|
||||
return { line = 0, character = 0 }
|
||||
end
|
||||
col = vim.str_utfindex(line, col)
|
||||
return { line = row, character = col }
|
||||
end
|
||||
|
||||
function M.make_text_document_params(buf)
|
||||
return { uri = vim.uri_from_bufnr(buf) }
|
||||
end
|
||||
|
||||
--- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position.
|
||||
---
|
||||
-- @returns `TextDocumentPositionParams` object
|
||||
-- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams
|
||||
function M.make_position_params(win, buf)
|
||||
return {
|
||||
textDocument = M.make_text_document_params(buf),
|
||||
position = make_position_param(win, buf),
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,581 @@
|
||||
local renderer = require("trouble.renderer")
|
||||
local config = require("trouble.config")
|
||||
local folds = require("trouble.folds")
|
||||
local util = require("trouble.util")
|
||||
|
||||
local highlight = vim.api.nvim_buf_add_highlight
|
||||
|
||||
---@class TroubleView
|
||||
---@field buf number
|
||||
---@field win number
|
||||
---@field group boolean
|
||||
---@field items Item[]
|
||||
---@field folded table<string, boolean>
|
||||
---@field parent number
|
||||
---@field float number
|
||||
local View = {}
|
||||
View.__index = View
|
||||
|
||||
-- keep track of buffers with added highlights
|
||||
-- highlights are cleared on BufLeave of Trouble
|
||||
local hl_bufs = {}
|
||||
|
||||
local function clear_hl(bufnr)
|
||||
if vim.api.nvim_buf_is_valid(bufnr) then
|
||||
vim.api.nvim_buf_clear_namespace(bufnr, config.namespace, 0, -1)
|
||||
end
|
||||
end
|
||||
|
||||
---Find a rogue Trouble buffer that might have been spawned by i.e. a session.
|
||||
local function find_rogue_buffer()
|
||||
for _, v in ipairs(vim.api.nvim_list_bufs()) do
|
||||
if vim.fn.bufname(v) == "Trouble" then
|
||||
return v
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Find pre-existing Trouble buffer, delete its windows then wipe it.
|
||||
---@private
|
||||
local function wipe_rogue_buffer()
|
||||
local bn = find_rogue_buffer()
|
||||
if bn then
|
||||
local win_ids = vim.fn.win_findbuf(bn)
|
||||
for _, id in ipairs(win_ids) do
|
||||
if vim.fn.win_gettype(id) ~= "autocmd" and vim.api.nvim_win_is_valid(id) then
|
||||
vim.api.nvim_win_close(id, true)
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_name(bn, "")
|
||||
vim.schedule(function()
|
||||
pcall(vim.api.nvim_buf_delete, bn, {})
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---@return TroubleView
|
||||
function View:new(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local group
|
||||
if opts.group ~= nil then
|
||||
group = opts.group
|
||||
else
|
||||
group = config.options.group
|
||||
end
|
||||
|
||||
local this = {
|
||||
buf = vim.api.nvim_get_current_buf(),
|
||||
win = opts.win or vim.api.nvim_get_current_win(),
|
||||
parent = opts.parent,
|
||||
items = {},
|
||||
group = group,
|
||||
}
|
||||
setmetatable(this, self)
|
||||
return this
|
||||
end
|
||||
|
||||
function View:set_option(name, value, win)
|
||||
if win then
|
||||
return vim.api.nvim_set_option_value(name, value, { win = self.win, scope = "local" })
|
||||
else
|
||||
return vim.api.nvim_set_option_value(name, value, { buf = self.buf })
|
||||
end
|
||||
end
|
||||
|
||||
---@param text Text
|
||||
function View:render(text)
|
||||
if not self:is_valid() then
|
||||
return
|
||||
end
|
||||
|
||||
self:unlock()
|
||||
self:set_lines(text.lines)
|
||||
self:lock()
|
||||
clear_hl(self.buf)
|
||||
for _, data in ipairs(text.hl) do
|
||||
highlight(self.buf, config.namespace, data.group, data.line, data.from, data.to)
|
||||
end
|
||||
end
|
||||
|
||||
function View:clear()
|
||||
return vim.api.nvim_buf_set_lines(self.buf, 0, -1, false, {})
|
||||
end
|
||||
|
||||
function View:unlock()
|
||||
self:set_option("modifiable", true)
|
||||
self:set_option("readonly", false)
|
||||
end
|
||||
|
||||
function View:lock()
|
||||
self:set_option("readonly", true)
|
||||
self:set_option("modifiable", false)
|
||||
end
|
||||
|
||||
function View:set_lines(lines, first, last, strict)
|
||||
first = first or 0
|
||||
last = last or -1
|
||||
strict = strict or false
|
||||
return vim.api.nvim_buf_set_lines(self.buf, first, last, strict, lines)
|
||||
end
|
||||
|
||||
function View:is_valid()
|
||||
return vim.api.nvim_buf_is_valid(self.buf) and vim.api.nvim_buf_is_loaded(self.buf)
|
||||
end
|
||||
|
||||
function View:update(opts)
|
||||
util.debug("update")
|
||||
renderer.render(self, opts)
|
||||
end
|
||||
|
||||
function View:setup(opts)
|
||||
util.debug("setup")
|
||||
opts = opts or {}
|
||||
vim.cmd("setlocal nonu")
|
||||
vim.cmd("setlocal nornu")
|
||||
if not pcall(vim.api.nvim_buf_set_name, self.buf, "Trouble") then
|
||||
wipe_rogue_buffer()
|
||||
vim.api.nvim_buf_set_name(self.buf, "Trouble")
|
||||
end
|
||||
self:set_option("bufhidden", "wipe")
|
||||
self:set_option("buftype", "nofile")
|
||||
self:set_option("swapfile", false)
|
||||
self:set_option("cursorline", true, true)
|
||||
self:set_option("buflisted", false)
|
||||
self:set_option("winfixwidth", true, true)
|
||||
self:set_option("wrap", false, true)
|
||||
self:set_option("spell", false, true)
|
||||
self:set_option("list", false, true)
|
||||
self:set_option("winfixheight", true, true)
|
||||
self:set_option("signcolumn", "no", true)
|
||||
self:set_option("foldmethod", "manual", true)
|
||||
self:set_option("foldcolumn", "0", true)
|
||||
self:set_option("foldlevel", 3, true)
|
||||
self:set_option("foldenable", false, true)
|
||||
self:set_option("winhighlight", "Normal:TroubleNormal,EndOfBuffer:TroubleNormal,SignColumn:TroubleNormal", true)
|
||||
self:set_option("fcs", "eob: ", true)
|
||||
|
||||
for action, keys in pairs(config.options.action_keys) do
|
||||
if type(keys) == "string" then
|
||||
keys = { keys }
|
||||
end
|
||||
for _, key in pairs(keys) do
|
||||
vim.api.nvim_buf_set_keymap(
|
||||
self.buf,
|
||||
"n",
|
||||
key,
|
||||
[[<cmd>lua require("trouble").action("]] .. action .. [[")<cr>]],
|
||||
{
|
||||
silent = true,
|
||||
noremap = true,
|
||||
nowait = true,
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if config.options.position == "top" or config.options.position == "bottom" then
|
||||
vim.api.nvim_win_set_height(self.win, config.options.height)
|
||||
else
|
||||
vim.api.nvim_win_set_width(self.win, config.options.width)
|
||||
end
|
||||
|
||||
self:set_option("filetype", "Trouble")
|
||||
|
||||
vim.api.nvim_exec(
|
||||
[[
|
||||
augroup TroubleHighlights
|
||||
autocmd! * <buffer>
|
||||
autocmd BufEnter <buffer> lua require("trouble").action("on_enter")
|
||||
autocmd CursorMoved <buffer> lua require("trouble").action("auto_preview")
|
||||
autocmd BufLeave <buffer> lua require("trouble").action("on_leave")
|
||||
augroup END
|
||||
]],
|
||||
false
|
||||
)
|
||||
|
||||
if not opts.parent then
|
||||
self:on_enter()
|
||||
end
|
||||
self:lock()
|
||||
self:update(opts)
|
||||
end
|
||||
|
||||
function View:on_enter()
|
||||
util.debug("on_enter")
|
||||
|
||||
self.parent = self.parent or vim.fn.win_getid(vim.fn.winnr("#"))
|
||||
|
||||
if (not self:is_valid_parent(self.parent)) or self.parent == self.win then
|
||||
util.debug("not valid parent")
|
||||
for _, win in pairs(vim.api.nvim_list_wins()) do
|
||||
if self:is_valid_parent(win) and win ~= self.win then
|
||||
self.parent = win
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not vim.api.nvim_win_is_valid(self.parent) then
|
||||
return self:close()
|
||||
end
|
||||
|
||||
self.parent_state = {
|
||||
buf = vim.api.nvim_win_get_buf(self.parent),
|
||||
cursor = vim.api.nvim_win_get_cursor(self.parent),
|
||||
}
|
||||
end
|
||||
|
||||
function View:on_leave()
|
||||
util.debug("on_leave")
|
||||
self:close_preview()
|
||||
end
|
||||
|
||||
function View:close_preview()
|
||||
-- Clear preview highlights
|
||||
for buf, _ in pairs(hl_bufs) do
|
||||
clear_hl(buf)
|
||||
end
|
||||
hl_bufs = {}
|
||||
|
||||
-- Reset parent state
|
||||
local valid_win = vim.api.nvim_win_is_valid(self.parent)
|
||||
local valid_buf = self.parent_state and vim.api.nvim_buf_is_valid(self.parent_state.buf)
|
||||
|
||||
if self.parent_state and valid_buf and valid_win then
|
||||
vim.api.nvim_win_set_buf(self.parent, self.parent_state.buf)
|
||||
vim.api.nvim_win_set_cursor(self.parent, self.parent_state.cursor)
|
||||
end
|
||||
|
||||
self.parent_state = nil
|
||||
end
|
||||
|
||||
function View:is_float(win)
|
||||
local opts = vim.api.nvim_win_get_config(win)
|
||||
return opts and opts.relative and opts.relative ~= ""
|
||||
end
|
||||
|
||||
function View:is_valid_parent(win)
|
||||
if not vim.api.nvim_win_is_valid(win) then
|
||||
return false
|
||||
end
|
||||
-- dont do anything for floating windows
|
||||
if View:is_float(win) then
|
||||
return false
|
||||
end
|
||||
local buf = vim.api.nvim_win_get_buf(win)
|
||||
-- Skip special buffers
|
||||
if vim.api.nvim_buf_get_option(buf, "buftype") ~= "" then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function View:on_win_enter()
|
||||
util.debug("on_win_enter")
|
||||
|
||||
local current_win = vim.api.nvim_get_current_win()
|
||||
|
||||
if vim.fn.winnr("$") == 1 and current_win == self.win then
|
||||
vim.cmd([[q]])
|
||||
return
|
||||
end
|
||||
|
||||
if not self:is_valid_parent(current_win) then
|
||||
return
|
||||
end
|
||||
|
||||
local current_buf = vim.api.nvim_get_current_buf()
|
||||
|
||||
-- update parent when needed
|
||||
if current_win ~= self.parent and current_win ~= self.win then
|
||||
self.parent = current_win
|
||||
-- update diagnostics to match the window we are viewing
|
||||
if self:is_valid() then
|
||||
vim.defer_fn(function()
|
||||
util.debug("update_on_win_enter")
|
||||
self:update()
|
||||
end, 100)
|
||||
end
|
||||
end
|
||||
|
||||
-- check if another buffer took over our window
|
||||
local parent = self.parent
|
||||
if current_win == self.win and current_buf ~= self.buf then
|
||||
-- open the buffer in the parent
|
||||
vim.api.nvim_win_set_buf(parent, current_buf)
|
||||
-- HACK: some window local settings need to be reset
|
||||
vim.api.nvim_win_set_option(parent, "winhl", "")
|
||||
-- close the current trouble window
|
||||
vim.api.nvim_win_close(self.win, false)
|
||||
-- open a new trouble window
|
||||
require("trouble").open()
|
||||
-- switch back to the opened window / buffer
|
||||
View.switch_to(parent, current_buf)
|
||||
-- util.warn("win_enter pro")
|
||||
end
|
||||
end
|
||||
|
||||
function View:focus()
|
||||
if not self:is_valid() then
|
||||
return
|
||||
end
|
||||
|
||||
View.switch_to(self.win, self.buf)
|
||||
local line = self:get_line()
|
||||
if line == 1 then
|
||||
self:next_item()
|
||||
if config.options.padding then
|
||||
self:next_item()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function View.switch_to(win, buf)
|
||||
if win then
|
||||
vim.api.nvim_set_current_win(win)
|
||||
if buf then
|
||||
vim.api.nvim_win_set_buf(win, buf)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function View:switch_to_parent()
|
||||
-- vim.cmd("wincmd p")
|
||||
View.switch_to(self.parent)
|
||||
end
|
||||
|
||||
function View:close()
|
||||
util.debug("close")
|
||||
if vim.api.nvim_win_is_valid(self.win) then
|
||||
if vim.api.nvim_win_is_valid(self.parent) then
|
||||
vim.api.nvim_set_current_win(self.parent)
|
||||
end
|
||||
vim.api.nvim_win_close(self.win, {})
|
||||
end
|
||||
if vim.api.nvim_buf_is_valid(self.buf) then
|
||||
vim.api.nvim_buf_delete(self.buf, {})
|
||||
end
|
||||
end
|
||||
|
||||
function View.create(opts)
|
||||
opts = opts or {}
|
||||
---@type TroubleView
|
||||
local view
|
||||
vim.api.nvim_win_call(0, function()
|
||||
if opts.win then
|
||||
View.switch_to(opts.win)
|
||||
vim.cmd("enew")
|
||||
else
|
||||
vim.cmd("below new")
|
||||
local pos = { bottom = "J", top = "K", left = "H", right = "L" }
|
||||
vim.cmd("wincmd " .. (pos[config.options.position] or "K"))
|
||||
end
|
||||
view = View:new(opts)
|
||||
view:setup(opts)
|
||||
end)
|
||||
|
||||
if opts.focus == true then
|
||||
view:focus()
|
||||
end
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
function View:get_cursor()
|
||||
return vim.api.nvim_win_get_cursor(self.win)
|
||||
end
|
||||
function View:get_line()
|
||||
return self:get_cursor()[1]
|
||||
end
|
||||
function View:get_col()
|
||||
return self:get_cursor()[2]
|
||||
end
|
||||
|
||||
function View:current_item()
|
||||
local line = self:get_line()
|
||||
local item = self.items[line]
|
||||
return item
|
||||
end
|
||||
|
||||
function View:next_item(opts)
|
||||
opts = opts or { skip_groups = false }
|
||||
local line = opts.first and 0 or self:get_line() + 1
|
||||
|
||||
if line > #self.items then
|
||||
if config.options.cycle_results then
|
||||
self:first_item(opts)
|
||||
end
|
||||
else
|
||||
for i = line, vim.api.nvim_buf_line_count(self.buf), 1 do
|
||||
if self.items[i] and not (opts.skip_groups and self.items[i].is_file) then
|
||||
vim.api.nvim_win_set_cursor(self.win, { i, self:get_col() })
|
||||
if opts.jump then
|
||||
self:jump()
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function View:previous_item(opts)
|
||||
opts = opts or { skip_groups = false }
|
||||
local line = opts.last and vim.api.nvim_buf_line_count(self.buf) or self:get_line() - 1
|
||||
|
||||
for i = 0, vim.api.nvim_buf_line_count(self.buf), 1 do
|
||||
if self.items[i] then
|
||||
if line < i + (opts.skip_groups and 1 or 0) then
|
||||
if config.options.cycle_results then
|
||||
self:last_item(opts)
|
||||
end
|
||||
return
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for i = line, 0, -1 do
|
||||
if self.items[i] and not (opts.skip_groups and self.items[i].is_file) then
|
||||
vim.api.nvim_win_set_cursor(self.win, { i, self:get_col() })
|
||||
if opts.jump then
|
||||
self:jump()
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function View:first_item(opts)
|
||||
opts = opts or {}
|
||||
opts.first = true
|
||||
return self:next_item(opts)
|
||||
end
|
||||
|
||||
function View:last_item(opts)
|
||||
opts = opts or {}
|
||||
opts.last = true
|
||||
return self:previous_item(opts)
|
||||
end
|
||||
|
||||
function View:hover(opts)
|
||||
opts = opts or {}
|
||||
local item = opts.item or self:current_item()
|
||||
if not (item and item.full_text) then
|
||||
return
|
||||
end
|
||||
vim.lsp.util.open_floating_preview(vim.split(item.full_text, "\n"), "markdown", config.options.win_config)
|
||||
end
|
||||
|
||||
function View:jump(opts)
|
||||
opts = opts or {}
|
||||
local item = opts.item or self:current_item()
|
||||
if not item then
|
||||
return
|
||||
end
|
||||
|
||||
if item.is_file == true then
|
||||
folds.toggle(item.filename)
|
||||
self:update()
|
||||
else
|
||||
util.jump_to_item(opts.win or self.parent, opts.precmd, item)
|
||||
end
|
||||
end
|
||||
|
||||
function View:toggle_fold()
|
||||
folds.toggle(self:current_item().filename)
|
||||
self:update()
|
||||
end
|
||||
|
||||
function View:_preview()
|
||||
if not vim.api.nvim_win_is_valid(self.parent) then
|
||||
return
|
||||
end
|
||||
if not vim.api.nvim_win_is_valid(self.win) then
|
||||
return
|
||||
end
|
||||
|
||||
local item = self:current_item()
|
||||
if not item then
|
||||
return
|
||||
end
|
||||
if item.bufnr == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
util.debug("preview")
|
||||
|
||||
if item.is_file ~= true then
|
||||
vim.api.nvim_win_set_buf(self.parent, item.bufnr)
|
||||
local pos = { item.start.line + 1, item.start.character }
|
||||
local line_count = vim.api.nvim_buf_line_count(item.bufnr)
|
||||
pos[1] = math.min(pos[1], line_count)
|
||||
vim.api.nvim_win_set_cursor(self.parent, pos)
|
||||
|
||||
vim.api.nvim_buf_call(item.bufnr, function()
|
||||
-- Center preview line on screen and open enough folds to show it
|
||||
vim.cmd("norm! zz zv")
|
||||
if not vim.api.nvim_buf_is_loaded(item.bufnr) then
|
||||
vim.fn.bufload(item.bufnr)
|
||||
end
|
||||
end)
|
||||
|
||||
clear_hl(item.bufnr)
|
||||
hl_bufs[item.bufnr] = true
|
||||
for row = item.start.line, item.finish.line, 1 do
|
||||
local col_start = 0
|
||||
local col_end = -1
|
||||
if row == item.start.line then
|
||||
col_start = item.start.character
|
||||
end
|
||||
if row == item.finish.line then
|
||||
col_end = item.finish.character
|
||||
end
|
||||
highlight(item.bufnr, config.namespace, "TroublePreview", row, col_start, col_end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- View.preview = View._preview
|
||||
|
||||
View.preview = util.throttle(50, View._preview)
|
||||
|
||||
function View:open_code_href()
|
||||
if not vim.api.nvim_win_is_valid(self.parent) then
|
||||
return
|
||||
end
|
||||
|
||||
local item = self:current_item()
|
||||
if not item then
|
||||
return
|
||||
end
|
||||
util.debug("open code href")
|
||||
|
||||
if item.is_file ~= true and item.code_href then
|
||||
local cmd
|
||||
if vim.fn.has("win32") == 1 then
|
||||
cmd = "explorer"
|
||||
elseif vim.fn.executable("xdg-open") == 1 then
|
||||
cmd = "xdg-open"
|
||||
elseif vim.fn.executable("wslview") == 1 then
|
||||
cmd = "wslview"
|
||||
else
|
||||
cmd = "open"
|
||||
end
|
||||
|
||||
local ret = vim.fn.jobstart({ cmd, item.code_href }, { detach = true })
|
||||
if ret <= 0 then
|
||||
local msg = {
|
||||
"Failed to open code href",
|
||||
ret,
|
||||
vim.inspect(cmd),
|
||||
}
|
||||
vim.notify(table.concat(msg, "\n"), vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return View
|
||||
Reference in New Issue
Block a user