Skip to content

Commit

Permalink
feat(statusline): add section_lsp() to show attached LSP servers
Browse files Browse the repository at this point in the history
  • Loading branch information
echasnovski committed May 23, 2024
1 parent f536bae commit 3fc7872
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
- Fallback icon is "Diag" instead of "LSP".
- BREAKING FEATURE: `section_git()` now prefers using data from 'mini.git' with fallback on pure HEAD data from 'lewis6991/gistigns.nvim'.
- FEATURE: add `section_diff()` to show data from 'mini.diff' with fallback on diff data from 'lewis6991/gistigns.nvim'.
- FEATURE: add `section_lsp()` to show indicator of LSP servers attached to the buffer.
- BREAKING FEATURE: update default active content to use both `section_git()` and `section_diff()`.

## mini.tabline
Expand Down
15 changes: 15 additions & 0 deletions doc/mini-statusline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,21 @@ Parameters ~
Return ~
`(string)` Section string.

------------------------------------------------------------------------------
*MiniStatusline.section_lsp()*
`MiniStatusline.section_lsp`({args})
Section for attached LSP servers

Shows number of LSP servers (each as separate "+" character) attached to
current buffer or nothing if none is attached.
Nothing is shown if window width is lower than `args.trunc_width`.

Parameters ~
{args} `(table)` Section arguments. Use `args.icon` to supply your own icon.

Return ~
`(string)` Section string.

------------------------------------------------------------------------------
*MiniStatusline.section_filename()*
`MiniStatusline.section_filename`({args})
Expand Down
89 changes: 62 additions & 27 deletions lua/mini/statusline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,26 @@ MiniStatusline.section_diagnostics = function(args)
return icon .. table.concat(t, '')
end

--- Section for attached LSP servers
---
--- Shows number of LSP servers (each as separate "+" character) attached to
--- current buffer or nothing if none is attached.
--- Nothing is shown if window width is lower than `args.trunc_width`.
---
---@param args __statusline_args Use `args.icon` to supply your own icon.
---
---@return __statusline_section
MiniStatusline.section_lsp = function(args)
if MiniStatusline.is_truncated(args.trunc_width) then return '' end

local attached = H.get_attached_lsp()
if attached == '' then return '' end

local use_icons = H.use_icons or H.get_config().use_icons
local icon = args.icon or (use_icons and '󰰎' or 'LSP')
return icon .. ' ' .. attached
end

--- Section for file name
---
--- Show full file name or relative in short output.
Expand Down Expand Up @@ -447,8 +467,8 @@ H.diagnostic_levels = {
{ name = 'HINT', sign = 'H' },
}

-- Count of attached LSP clients per buffer id
H.n_attached_lsp = {}
-- String representation of attached LSP clients per buffer id
H.attached_lsp = {}

-- Helper functionality =======================================================
-- Settings -------------------------------------------------------------------
Expand Down Expand Up @@ -497,11 +517,12 @@ H.create_autocommands = function()
au({ 'WinEnter', 'BufWinEnter' }, '*', H.ensure_content, 'Ensure statusline content')

if vim.fn.has('nvim-0.8') == 1 then
local make_track_lsp = function(increment)
return function(data) H.n_attached_lsp[data.buf] = (H.n_attached_lsp[data.buf] or 0) + increment end
end
au('LspAttach', '*', make_track_lsp(1), 'Track LSP clients')
au('LspDetach', '*', make_track_lsp(-1), 'Track LSP clients')
-- Use `schedule_wrap()` because at `LspDetach` server is still present
local track_lsp = vim.schedule_wrap(function(data)
H.attached_lsp[data.buf] = H.compute_attached_lsp(data.buf)
vim.cmd('redrawstatus')
end)
au({ 'LspAttach', 'LspDetach' }, '*', track_lsp, 'Track LSP clients')
end
end

Expand Down Expand Up @@ -601,6 +622,40 @@ end

H.default_content_inactive = function() return '%#MiniStatuslineInactive#%F%=' end

-- LSP ------------------------------------------------------------------------
H.get_attached_lsp = function() return H.attached_lsp[vim.api.nvim_get_current_buf()] or '' end
if vim.fn.has('nvim-0.8') == 0 then
H.get_attached_lsp = function() return H.compute_attached_lsp(vim.api.nvim_get_current_buf()) end
end

H.compute_attached_lsp = function(buf_id)
local names = vim.tbl_map(function() return '+' end, H.get_buf_lsp_clients(buf_id))
return table.concat(names, '')
end

H.get_buf_lsp_clients = function(buf_id) return vim.lsp.get_clients({ bufnr = buf_id }) end
if vim.fn.has('nvim-0.10') == 0 then
H.get_buf_lsp_clients = function(buf_id) return vim.lsp.buf_get_clients(buf_id) end
end

-- Diagnostics ----------------------------------------------------------------
H.diagnostic_get_count = function()
local res = {}
for _, d in ipairs(vim.diagnostic.get(0)) do
res[d.severity] = (res[d.severity] or 0) + 1
end
return res
end
if vim.fn.has('nvim-0.10') == 1 then H.diagnostic_get_count = function() return vim.diagnostic.count(0) end end

if vim.fn.has('nvim-0.10') == 1 then
H.diagnostic_is_disabled = function(_) return not vim.diagnostic.is_enabled({ bufnr = 0 }) end
elseif vim.fn.has('nvim-0.9') == 1 then
H.diagnostic_is_disabled = function(_) return vim.diagnostic.is_disabled(0) end
else
H.diagnostic_is_disabled = function(_) return false end
end

-- Utilities ------------------------------------------------------------------
H.get_filesize = function()
local size = vim.fn.getfsize(vim.fn.getreg('%'))
Expand All @@ -625,24 +680,4 @@ H.ensure_get_icon = function()
end
end

H.has_no_lsp_attached = function() return (H.n_attached_lsp[vim.api.nvim_get_current_buf()] or 0) == 0 end
if vim.fn.has('nvim-0.8') == 0 then H.has_no_lsp_attached = function() return #vim.lsp.buf_get_clients() == 0 end end

H.diagnostic_get_count = function()
local res = {}
for _, d in ipairs(vim.diagnostic.get(0)) do
res[d.severity] = (res[d.severity] or 0) + 1
end
return res
end
if vim.fn.has('nvim-0.10') == 1 then H.diagnostic_get_count = function() return vim.diagnostic.count(0) end end

if vim.fn.has('nvim-0.10') == 1 then
H.diagnostic_is_disabled = function(_) return not vim.diagnostic.is_enabled({ bufnr = 0 }) end
elseif vim.fn.has('nvim-0.9') == 1 then
H.diagnostic_is_disabled = function(_) return vim.diagnostic.is_disabled(0) end
else
H.diagnostic_is_disabled = function(_) return false end
end

return MiniStatusline
6 changes: 0 additions & 6 deletions tests/dir-statusline/mock-diagnostics.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
vim.lsp.buf_get_clients = function() return { 'mock client' } end
if vim.fn.has('nvim-0.8') == 1 then
vim.api.nvim_exec_autocmds('LspAttach', { data = { client_id = 1 } })
_G.detach_lsp = function() vim.api.nvim_exec_autocmds('LspDetach', { data = { client_id = 1 } }) end
end

vim.diagnostic.get = function(_, _)
local s = vim.diagnostic.severity
return {
Expand Down
27 changes: 27 additions & 0 deletions tests/dir-statusline/mock-lsp.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
_G.n_lsp_clients = 0
local mock_buf_clients = function()
local res = {}
for i = 1, _G.n_lsp_clients do
res[i] = { id = i }
end
return res
end
vim.lsp.buf_get_clients = mock_buf_clients
vim.lsp.get_clients = mock_buf_clients

_G.attach_lsp = function()
_G.n_lsp_clients = _G.n_lsp_clients + 1
if vim.fn.has('nvim-0.8') == 1 then
vim.api.nvim_exec_autocmds('LspAttach', { data = { client_id = _G.n_lsp_clients } })
end
end

_G.detach_lsp = function()
local n = _G.n_lsp_clients
if n == 0 then return end

_G.n_lsp_clients = _G.n_lsp_clients - 1
if vim.fn.has('nvim-0.8') == 1 then vim.api.nvim_exec_autocmds('LspDetach', { data = { client_id = n } }) end
end

_G.attach_lsp()
46 changes: 44 additions & 2 deletions tests/test_statusline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ local mock_minidiff = function() child.b.minidiff_summary_string = '#4 +3 ~2 -1'

local mock_diagnostics = function() child.cmd('luafile tests/dir-statusline/mock-diagnostics.lua') end

local mock_lsp = function() child.cmd('luafile tests/dir-statusline/mock-lsp.lua') end

local mocked_filepath = vim.fn.fnamemodify('tests/dir-statusline/mocked.lua', ':p')
local mock_file = function(bytes)
-- Reduce one byte for '\n' at end
Expand Down Expand Up @@ -308,8 +310,12 @@ T['section_diagnostics()']['works'] = function()
eq(child.lua_get('MiniStatusline.section_diagnostics({})'), ' E4 W3 I2 H1')

-- Should not depend on LSP server attached
child.lua('vim.lsp.buf_get_clients = function() return {} end')
if child.fn.has('nvim-0.8') == 1 then child.lua('_G.detach_lsp()') end
mock_lsp()
eq(child.lua_get('_G.n_lsp_clients'), 1)
eq(child.lua_get('MiniStatusline.section_diagnostics({})'), ' E4 W3 I2 H1')

child.lua('_G.detach_lsp()')
eq(child.lua_get('_G.n_lsp_clients'), 0)
eq(child.lua_get('MiniStatusline.section_diagnostics({})'), ' E4 W3 I2 H1')

-- Should return empty string if no diagnostic entries defined
Expand Down Expand Up @@ -363,6 +369,42 @@ T['section_diagnostics()']['is not shown if diagnostics is disabled'] = function
eq(child.lua_get('MiniStatusline.section_diagnostics({})'), '')
end

T['section_lsp()'] = new_set({ hooks = { pre_case = mock_lsp } })

T['section_lsp()']['works'] = function()
eq(child.lua_get('MiniStatusline.section_lsp({})'), '󰰎 +')

-- Should show number of attached LSP servers
child.lua('_G.attach_lsp()')
eq(child.lua_get('MiniStatusline.section_lsp({})'), '󰰎 ++')

-- Should show empty string if no attached LSP servers
child.lua('_G.detach_lsp()')
child.lua('_G.detach_lsp()')
eq(child.lua_get('MiniStatusline.section_lsp({})'), '')
end

T['section_lsp()']['respects `args.trunc_width`'] = function()
set_width(100)
eq(child.lua_get('MiniStatusline.section_lsp({ trunc_width = 100 })'), '󰰎 +')
set_width(99)
eq(child.lua_get('MiniStatusline.section_lsp({ trunc_width = 100 })'), '')
end

T['section_lsp()']['respects `args.icon`'] = function()
eq(child.lua_get([[MiniStatusline.section_lsp({icon = 'A'})]]), 'A +')
eq(child.lua_get([[MiniStatusline.section_lsp({icon = 'AAA'})]]), 'AAA +')
end

T['section_lsp()']['respects `config.use_icons`'] = function()
child.lua('MiniStatusline.config.use_icons = false')
eq(child.lua_get([[MiniStatusline.section_lsp({})]]), 'LSP +')

-- Should also use buffer local config
child.b.ministatusline_config = { use_icons = true }
eq(child.lua_get([[MiniStatusline.section_lsp({})]]), '󰰎 +')
end

T['section_fileinfo()'] = new_set({ hooks = { pre_case = mock_devicons, post_case = unmock_file } })

local validate_fileinfo = function(args, pattern)
Expand Down

0 comments on commit 3fc7872

Please sign in to comment.