Skip to content

Commit

Permalink
feat(ui): lspinfo: better layout neovim#1184)
Browse files Browse the repository at this point in the history
  • Loading branch information
kylo252 authored Aug 25, 2021
1 parent 62b9e1a commit d6e78ef
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 113 deletions.
273 changes: 171 additions & 102 deletions lua/lspconfig/ui/lspinfo.lua
Original file line number Diff line number Diff line change
@@ -1,13 +1,124 @@
local configs = require 'lspconfig/configs'
local windows = require 'lspconfig/ui/windows'
local util = require 'lspconfig/util'

local indent = ' '
local cmd_not_found_msg = 'False. Please check your path and ensure the server is installed'

local function trim_whitespace(cmd)
local trimmed_cmd = {}
for _, str in pairs(cmd) do
table.insert(trimmed_cmd, str:match '^%s*(.*)')
end
return trimmed_cmd
end

local function indent_lines(lines, offset)
return vim.tbl_map(function(val)
return offset .. val
end, lines)
end

local function remove_newlines(cmd)
cmd = trim_whitespace(cmd)
cmd = table.concat(cmd, ' ')
cmd = vim.split(cmd, '\n')
cmd = trim_whitespace(cmd)
cmd = table.concat(cmd, ' ')
return cmd
end

local function autostart_to_str(autostart)
local autostart_status = 'true'
if autostart == false then
autostart_status = 'false'
end
return autostart_status
end

local function make_config_info(config)
local config_info = {}
config_info.name = config.name
if config.cmd then
config_info.cmd = remove_newlines(config.cmd)
if vim.fn.executable(config.cmd[1]) == 1 then
config_info.cmd_is_executable = 'true'
else
config_info.cmd_is_executable = cmd_not_found_msg
end
config_info.cmd = config.cmd[1]
config_info.cmd_args = '[' .. table.concat(vim.list_slice(config.cmd, 2, #config.cmd), ', ') .. ']'
else
config_info.cmd = 'cmd not defined'
config_info.cmd_is_executable = 'NA'
config_info.cmd_args = 'NA'
end
local buffer_dir = vim.fn.expand '%:p:h'
config_info.root_dir = config.get_root_dir(buffer_dir) or 'NA'
config_info.autostart = autostart_to_str(config._autostart)
config_info.handlers = table.concat(vim.tbl_keys(config.handlers), ', ')
config_info.filetypes = '[' .. table.concat(config.filetypes or {}, ', ') .. ']'

local lines = {
'',
'Config: ' .. config_info.name,
'\tfiletypes: ' .. config_info.filetypes,
'\troot directory: ' .. config_info.root_dir,
'\tcommand: ' .. config_info.cmd,
'\targ[s]: ' .. config_info.cmd_args,
'\tcmd is executable: ' .. config_info.cmd_is_executable,
'\tautostart: ' .. config_info.autostart,
'\tcustom handlers: ' .. config_info.handlers,
}
return lines
end

local function get_other_matching_providers(filetype)
local active_clients_list = util.get_active_client_by_ft(filetype)
local other_matching_configs = {}
for _, config in pairs(configs) do
if not vim.tbl_contains(active_clients_list, config.name) then
table.insert(other_matching_configs, config)
end
end
return other_matching_configs
end

local function make_client_info(client)
local client_info = {}

client_info.cmd = client.config.cmd[1]
client_info.cmd_args = '[' .. table.concat(vim.list_slice(client.config.cmd, 2, #client.config.cmd), ', ') .. ']'
client_info.root_dir = client.workspaceFolders[1].name
client_info.filetypes = table.concat(client.config.filetypes or {}, ', ')
client_info.autostart = autostart_to_str(client._autostart)

local lines = {
'',
'Client: ' .. client.name .. ' (id: ' .. tostring(client.id) .. ' pid: ' .. tostring(client.rpc.pid) .. ')',
'\tfiletypes: ' .. client_info.filetypes,
'\tautostart: ' .. client_info.autostart,
'\troot directory: ' .. client_info.root_dir,
'\tcommand: ' .. client_info.cmd,
'\targ[s]: ' .. client_info.cmd_args,
}
if client.config.lspinfo then
local server_specific_info = client.config.lspinfo(client.config)
server_specific_info = vim.tbl_map(function(val)
return indent .. '\t' .. val
end, server_specific_info)
lines = vim.list_extend(lines, server_specific_info)
end

return lines
end

return function()
-- These options need to be cached before switching to the floating
-- buffer.
local buf_clients = vim.lsp.buf_get_clients()
local clients = vim.lsp.get_active_clients()
local buffer_filetype = vim.bo.filetype
local buffer_dir = vim.fn.expand '%:p:h'

local win_info = windows.percentage_range_window(0.8, 0.7)
local bufnr, win_id = win_info.bufnr, win_info.win_id
Expand All @@ -20,8 +131,13 @@ return function()
end

local header = {
'',
'',
'Language Server Protocol (LSP) info',
'',
'Detected filetype: ' .. buffer_filetype,
'Default LSP log: ' .. (vim.lsp.get_log_path()),
'Configured servers: ' .. table.concat(vim.tbl_keys(configs), ', '),
'Neovim logs at: ' .. (vim.lsp.get_log_path()),
'',
tostring(#vim.tbl_keys(buf_clients)) .. ' client(s) attached to this buffer: ' .. table.concat(
buf_client_names,
Expand All @@ -30,63 +146,6 @@ return function()
}
vim.list_extend(buf_lines, header)

local function trim_whitespace(cmd)
local trimmed_cmd = {}
for _, str in pairs(cmd) do
table.insert(trimmed_cmd, str:match '^%s*(.*)')
end
return trimmed_cmd
end

local function remove_newlines(cmd)
cmd = trim_whitespace(cmd)
cmd = table.concat(cmd, ' ')
cmd = vim.split(cmd, '\n')
cmd = trim_whitespace(cmd)
cmd = table.concat(cmd, ' ')
return cmd
end

local function autostart_to_str(autostart)
local autostart_status = 'True'
if autostart == false then
autostart_status = 'Disabled'
end
return autostart_status
end
local indent = ' '
local function make_client_info(client)
local lines = {
'',
indent
.. 'Client: '
.. client.name
.. ' (id: '
.. tostring(client.id)
.. ' pid: '
.. tostring(client.rpc.pid)
.. ')',
indent .. '\troot: ' .. client.workspaceFolders[1].name,
indent .. '\tfiletypes: ' .. table.concat(client.config.filetypes or {}, ', '),
indent .. '\tcmd: ' .. remove_newlines(client.config.cmd),
indent .. '\tautostart: ' .. autostart_to_str(client._autostart),
}
if client.config.lspinfo then
local server_specific_info = client.config.lspinfo(client.config)
server_specific_info = vim.tbl_map(function(val)
return indent .. '\t' .. val
end, server_specific_info)
lines = vim.list_extend(lines, server_specific_info)
end

return lines
end

for _, client in pairs(buf_clients) do
local client_info = make_client_info(client)
vim.list_extend(buf_lines, client_info)
end

local active_section_header = {
'',
tostring(#clients) .. ' active client(s): ',
Expand All @@ -96,60 +155,70 @@ return function()
local client_info = make_client_info(client)
vim.list_extend(buf_lines, client_info)
end

local matching_config_header = {
'',
'Clients that match the filetype ' .. buffer_filetype .. ':',
'',
'Other clients that match the filetype ' .. buffer_filetype .. ':',
}
local cmd_not_found_msg = 'False. Please check your path and ensure the server is installed'
vim.list_extend(buf_lines, matching_config_header)
for _, config in pairs(configs) do
local cmd_is_executable, cmd
if config.cmd then
cmd = remove_newlines(config.cmd)
if vim.fn.executable(config.cmd[1]) == 1 then
cmd_is_executable = 'True'
else
cmd_is_executable = cmd_not_found_msg
end
else
cmd = 'cmd not defined'
cmd_is_executable = cmd
end
if config.filetypes then
for _, filetype_match in pairs(config.filetypes) do
if buffer_filetype == filetype_match then
local matching_config_info = {
indent .. '',
indent .. 'Config: ' .. config.name,
indent .. '\tcmd: ' .. cmd,
indent .. '\tcmd is executable: ' .. cmd_is_executable,
indent .. '\tidentified root: ' .. (config.get_root_dir(buffer_dir) or 'None'),
indent .. '\tautostart: ' .. autostart_to_str(config._autostart),
indent .. '\tcustom handlers: ' .. table.concat(vim.tbl_keys(config.handlers), ', '),
}
vim.list_extend(buf_lines, matching_config_info)

local all_matching_configs_attached_header = {
'',
'All the clients that can be configured for the current buffer filetype are currently active.',
'',
}

local other_matching_configs = get_other_matching_providers(buffer_filetype)

if vim.tbl_isempty(other_matching_configs) then
vim.list_extend(buf_lines, all_matching_configs_attached_header)
else
vim.list_extend(buf_lines, matching_config_header)
for _, config in pairs(other_matching_configs) do
if config.filetypes then
for _, filetype_match in pairs(config.filetypes) do
if filetype_match == buffer_filetype then
vim.list_extend(buf_lines, make_config_info(config))
end
end
else
local matching_config_info = {
'',
'Config: ' .. config.name,
'\tfiletype: ' .. 'No filetypes defined, please define filetypes in setup().',
}
vim.list_extend(buf_lines, matching_config_info)
end
else
local matching_config_info = {
'',
'Config: ' .. config.name,
'\tfiletype: ' .. 'No filetypes defined, please define filetypes in setup().',
}
vim.list_extend(buf_lines, matching_config_info)
end
end
buf_lines = vim.lsp.util._trim(buf_lines, {})
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, buf_lines)

local fmt_buf_lines = indent_lines(buf_lines, indent .. indent)

fmt_buf_lines = vim.lsp.util._trim(fmt_buf_lines, {})

vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, fmt_buf_lines)
vim.api.nvim_buf_set_option(bufnr, 'modifiable', false)
vim.api.nvim_buf_set_option(bufnr, 'filetype', 'lspinfo')
local configs_pattern = '\\%(' .. table.concat(vim.tbl_keys(configs), '\\|') .. '\\)'
vim.cmd('syntax match Title /\\%(Client\\|Config\\):.*\\zs' .. configs_pattern .. '/')
vim.cmd('syntax match Identifier /filetypes:.*\\zs\\<' .. buffer_filetype .. '\\>/')

vim.api.nvim_buf_set_keymap(bufnr, 'n', '<esc>', '<cmd>bd<CR>', { noremap = true })
vim.lsp.util.close_preview_autocmd({ 'BufHidden', 'BufLeave' }, win_id)

vim.fn.matchadd(
'Error',
'No filetypes defined, please define filetypes in setup().\\|' .. 'cmd not defined\\|' .. cmd_not_found_msg
)
vim.api.nvim_buf_set_keymap(bufnr, 'n', '<esc>', '<cmd>bd<CR>', { noremap = true })
vim.lsp.util.close_preview_autocmd({ 'BufHidden', 'BufLeave' }, win_id)
local configs_pattern = '\\%(' .. table.concat(vim.tbl_keys(configs), '\\|') .. '\\)'

vim.cmd [[highlight LspInfoIdentifier gui=bold]]
vim.cmd [[highlight link LspInfoFileTypes Type]]
vim.cmd [[highlight link LspInfoHeader Identifier]]
vim.cmd [[let m=matchadd("LspInfoHeader", "Language Server Protocol (LSP) info")]]
vim.cmd('let m=matchadd("LspInfoFileTypes", " ' .. buffer_filetype .. '$")')
vim.cmd 'let m=matchadd("string", "true")'
vim.cmd 'let m=matchadd("error", "false")'
for _, name in ipairs(buf_client_names) do
vim.cmd('let m=matchadd("LspInfoIdentifier", "' .. name .. '.*[ ,|\']")')
end

vim.cmd('syntax match Title /\\%(Client\\|Config\\):.*\\zs' .. configs_pattern .. '/')
end
14 changes: 3 additions & 11 deletions lua/lspconfig/ui/windows.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- The following is extracted and modified from plenary.vnim by
-- The following is extracted and modified from popup.nvim by
-- TJ Devries. It is not a stable API, and is expected to change
--
local function apply_defaults(original, defaults)
Expand Down Expand Up @@ -40,10 +40,12 @@ function win_float.default_opts(options)
width = width,
height = height,
style = 'minimal',
border = 'rounded',
}

return opts
end

--- Create window that takes up certain percentags of the current screen.
---
--- Works regardless of current buffers, tabs, splits, etc.
Expand Down Expand Up @@ -90,16 +92,6 @@ function win_float.percentage_range_window(col_range, row_range, options)

win_opts.col = math.floor(vim.o.columns * col_start_percentage)
win_opts.width = math.floor(vim.o.columns * width_percentage)
win_opts.border = {
{ ' ', 'NormalFloat' },
{ ' ', 'NormalFloat' },
{ ' ', 'NormalFloat' },
{ ' ', 'NormalFloat' },
{ ' ', 'NormalFloat' },
{ ' ', 'NormalFloat' },
{ ' ', 'NormalFloat' },
{ ' ', 'NormalFloat' },
}

local bufnr = options.bufnr or vim.api.nvim_create_buf(false, true)
local win_id = vim.api.nvim_open_win(bufnr, true, win_opts)
Expand Down
11 changes: 11 additions & 0 deletions lua/lspconfig/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,15 @@ function M.find_package_json_ancestor(startpath)
end)
end

function M.get_active_client_by_ft(filetype)
local clients = vim.lsp.get_active_clients()
local clients_list = {}
for _, client in pairs(clients) do
if vim.tbl_contains(client.config.filetypes, filetype) then
table.insert(clients_list, client.name)
end
end
return clients_list
end

return M

0 comments on commit d6e78ef

Please sign in to comment.