Skip to content

Commit

Permalink
feat: inital version
Browse files Browse the repository at this point in the history
  • Loading branch information
folke committed Apr 21, 2021
0 parents commit 980fb07
Show file tree
Hide file tree
Showing 10 changed files with 617 additions and 0 deletions.
33 changes: 33 additions & 0 deletions lua/trouble/colors.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
local M = {}

local links = {
Error = "LspDiagnosticsDefaultError",
Warning = "LspDiagnosticsDefaultWarning",
Information = "LspDiagnosticsDefaultInformation",
Hint = "LspDiagnosticsDefaultHint",
SignError = "LspDiagnosticsSignError",
SignWarning = "LspDiagnosticsSignWarning",
SignInformation = "LspDiagnosticsSignInformation",
SignHint = "LspDiagnosticsSignHint",
TextError = "LspTroubleText",
TextWarning = "LspTroubleText",
TextInformation = "LspTroubleText",
TextHint = "LspTroubleText",
Text = "Normal",
File = "Directory",
Source = "Comment",
Code = "Comment",
Location = "LineNr",
FoldIcon = "CursorLineNr",
Normal = "Normal",
Count = "TabLineSel",
Preview = "Search"
}

function M.setup()
for k, v in pairs(links) do
vim.api.nvim_command('hi def link LspTrouble' .. k .. ' ' .. v)
end
end

return M
35 changes: 35 additions & 0 deletions lua/trouble/config.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local M = {}

M.namespace = vim.api.nvim_create_namespace('LspTrouble')

---@class Options
local defaults = {
height = 10,
icons = true,
fold_open = "",
fold_closed = "",
actions = {
["<cr>"] = "jump",
q = "close",
r = "refresh",
zR = "open_folds",
zM = "close_folds",
p = "preview",
P = "toggle_preview"
},
auto_open = false,
auto_close = true,
auto_preview = false
}

---@type Options
M.options = {}

---@return Options
function M.setup(options)
M.options = vim.tbl_deep_extend("force", {}, defaults, options or {})
end

M.setup()

return M
13 changes: 13 additions & 0 deletions lua/trouble/folds.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
local M = {}

M.folded = {}

function M.is_folded(filename) return M.folded[filename] == 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
65 changes: 65 additions & 0 deletions lua/trouble/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
local View = require("trouble.view")
local config = require("trouble.config")
local colors = require("trouble.colors")
local util = require("trouble.util")
local lsp = require("trouble.lsp")

colors.setup()

local Trouble = {}

local view

local function is_open() return view and view:is_valid() end

function Trouble.setup(options)
config.setup(options)
colors.setup()
end

function Trouble.close() if is_open() then view:close() end end

function Trouble.open(opts)
if is_open() then
view:focus()
else
view = View.create(opts)
end
end

function Trouble.toggle()
if is_open() then
Trouble.close()
else
Trouble.open()
end
end

function Trouble.refresh(opts)
if is_open() then
view:update(opts)
elseif opts.auto and config.options.auto_open then
local count = util.count(lsp.diagnostics())
if count > 0 then Trouble.open(opts) end
end
end

function Trouble.action(action)
if not is_open() then return end
if action == "jump" then view:jump() 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_preview" then
config.options.auto_preview = not config.options.auto_preview
if config.options.auto_preview then action = "auto_preview" end
end

if action == "preview" or
(action == "auto_preview" and config.options.auto_preview) then
view:preview()
end
if Trouble[action] then Trouble[action]() end
end

return Trouble
91 changes: 91 additions & 0 deletions lua/trouble/lsp.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---@class Lsp
local M = {}

local lsp_type_diagnostic = {
[1] = "Error",
[2] = "Warning",
[3] = "Information",
[4] = "Hint"
}

local function preprocess_diag(diag, bufnr)
local filename = vim.api.nvim_buf_get_name(bufnr)
local start = diag.range['start']
local finish = diag.range['end']
local row = start.line
local col = start.character

---@class Diagnostics
---@field is_file boolean
local ret
ret = {
bufnr = bufnr,
filename = filename,
lnum = row + 1,
col = col + 1,
start = start,
finish = finish,
-- remove line break to avoid display issues
text = vim.trim(diag.message:gsub("[\n]", "")),
type = lsp_type_diagnostic[diag.severity] or lsp_type_diagnostic[1],
code = diag.code,
source = diag.source,
severity = diag.severity or 1
}
return ret
end

---@return Diagnostics[]
function M.diagnostics(opts)
opts = opts or {}
local items = {}

local buffer_diags = opts.bufnr and
{
[opts.bufnr] = vim.lsp.diagnostic.get(opts.bufnr, nil)
} or vim.lsp.diagnostic.get_all()

for bufnr, diags in pairs(buffer_diags) do
for _, diag in pairs(diags) do
-- workspace diagnostics may include empty tables for unused bufnr
if not vim.tbl_isempty(diag) then
table.insert(items, preprocess_diag(diag, bufnr))
end
end
end
table.sort(items, function(a, b)
if a.severity == b.severity then
return a.lnum < b.lnum
else
return a.severity < b.severity
end
end)
return items
end

---@param items Diagnostics[]
---@return table<string, Diagnostics[]>
function M.group(items)
local ret = {}
for _, item in ipairs(items) do
if ret[item.filename] == nil then ret[item.filename] = {} end
table.insert(ret[item.filename], item)
end
return ret
end

function M.get_signs()
local signs = {}
for _, v in pairs(lsp_type_diagnostic) do
-- pcall to catch entirely unbound or cleared out sign hl group
local status, sign = pcall(function()
return vim.trim(vim.fn.sign_getdefined("LspDiagnosticsSign" .. v)[1]
.text)
end)
if not status then sign = v:sub(1, 1) end
signs[v] = sign
end
return signs
end

return M
105 changes: 105 additions & 0 deletions lua/trouble/renderer.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
local lsp = require("trouble.lsp")
local util = require("trouble.util")
local config = require("trouble.config")
local Text = require("trouble.text")
local folds = require("trouble.folds")

---@class Renderer
local renderer = {}

local signs = {}

local function get_icon(file)
local icons, _ = require('nvim-web-devicons')
local fname = vim.fn.fnamemodify(file, ":t")
local ext = vim.fn.fnamemodify(file, ":e")
return icons.get_icon(fname, ext, {default = true})
end

---@param view View
function renderer.render(view, opts)
opts = opts or {}
local diagnostics = lsp.diagnostics(config.options)
local grouped = lsp.group(diagnostics)

-- check for auto close
if opts.auto and config.options.auto_close then
local count = util.count(grouped)
if count == 0 then
view:close()
return
end
end

-- Update lsp signs
signs = lsp.get_signs()

local text = Text:new()
view.items = {}

-- render file groups
for filename, items in pairs(grouped) do
if opts.open_folds then folds.open(filename) end
if opts.close_folds then folds.close(filename) end
renderer.render_file(view, text, filename, items)
end

view:render(text)
end

---@param view View
---@param text Text
---@param items Diagnostics[]
---@param filename string
function renderer.render_file(view, text, filename, items)
view.items[text.lineNr] = {filename = filename, is_file = true}

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()

if not folds.is_folded(filename) then
renderer.render_diagnostics(view, text, items)
end
end

---@param view View
---@param text Text
---@param items Diagnostics[]
function renderer.render_diagnostics(view, text, items)
for _, diag in ipairs(items) do
view.items[text.lineNr] = diag

local sign = signs[diag.type]
if not sign then sign = diag.type end

text:render(" " .. sign .. " ", "Sign" .. diag.type)
text:render(diag.text, "Text" .. diag.type, " ")
text:render(diag.type, diag.type, " ")

if diag.source then text:render(diag.source, "Source", " ") end
if diag.code then
text:render("(" .. diag.code .. ")", "Code", " ")
end

text:render("[" .. diag.lnum .. ", " .. diag.col .. "]", "Location")
text:nl()
end
end

return renderer
43 changes: 43 additions & 0 deletions lua/trouble/text.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---@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)
if type(opts) == "string" then opts = {append = opts} end
opts = opts or {}

if group then
if opts.exact ~= true then group = "LspTrouble" .. 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
Loading

0 comments on commit 980fb07

Please sign in to comment.