Browse Source

Add LunarVim info panel (Experimental) (#1241)

* feat: lunarvim info (Experimental)

* Add missing providers info

* Use nvim api directly to create the popup

* width tweaks
kylo252 3 years ago
parent
commit
47ebd70817
5 changed files with 268 additions and 0 deletions
  1. 180 0
      lua/core/info.lua
  2. 4 0
      lua/core/which-key.lua
  3. 30 0
      lua/lsp/init.lua
  4. 26 0
      lua/lsp/null-ls.lua
  5. 28 0
      lua/utils/init.lua

+ 180 - 0
lua/core/info.lua

@@ -0,0 +1,180 @@
+local M = {}
+local u = require "utils"
+local null_ls_handler = require "lsp.null-ls"
+local indent = "  "
+
+M.banner = {
+  "",
+  "",
+  "⠀⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀   ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀  ⠀⠀     ⠀⠀⠀   ⠀⠀ ⣺⡿⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀",
+  "⠀⣿⠇⠀⠀⠀⠀⠀⣤⡄⠀⠀⢠⣤⡄⠀.⣠⣤⣤⣤⡀⠀⠀⢀⣤⣤⣤⣤⡄⠀⠀⠀⣤⣄⣤⣤⣤⠀⠀ ⣿⣯  ⣿⡟⠀   ⣤⣤⠀⠀⠀⠀⣠⣤⣤⣤⣄⣤⣤",
+  "⢠⣿⠀⠀⠀⠀⠀⠀⣿⠃⠀⠀⣸⣿⠁⠀⣿⣿⠉⠀⠈⣿⡇⠀⠀⠛⠋⠀⠀⢹⣿⠀⠀⠀⣿⠏⠀⠸⠿⠃⠀⣿⣿⠀⣰⡟⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⣿⡟⢸⣿⡇⢀⣿",
+  "⣸⡇⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⣿⡟⠀⢠⣿⡇⠀⠀⢰⣿⡇⠀⣰⣾⠟⠛⠛⣻⡇⠀⠀⢸⡿⠀⠀⠀⠀⠀⠀⢻⣿⢰⣿⠀⠀⠀⠀⠀⠀⣾⡇⠀⠀⠀⢸⣿⠇⢸⣿⠀⢸⡏",
+  "⣿⣧⣤⣤⣤⡄⠀⠘⣿⣤⣤⡤⣿⠇⠀⢸⣿⠁⠀⠀⣼⣿⠀⠀⢿⣿⣤⣤⠔⣿⠃⠀⠀⣾⡇⠀⠀⠀⠀⠀⠀⢸⣿⣿⠋⠀⠀⠀⢠⣤⣤⣿⣥⣤⡄⠀⣼⣿⠀⣸⡏⠀⣿⠃",
+  "⠉⠉⠉⠉⠉⠁⠀⠀⠈⠉⠉⠀⠉⠀⠀⠈⠉⠀⠀⠀⠉⠉⠀⠀⠀⠉⠉⠁⠈⠉⠀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠁⠀⠉⠁⠀⠉⠁⠀⠉⠀",
+  "",
+  "",
+}
+
+local function str_list(list)
+  return "[ " .. table.concat(list, ", ") .. " ]"
+end
+
+local function get_formatter_suggestion_msg(ft)
+  local supported_formatters = u.get_supported_formatters_by_filetype(ft)
+  return {
+    "-------------------------------------------------------------------",
+    "",
+    "  HINT ",
+    "",
+    indent .. "* List of supported formatters: " .. str_list(supported_formatters),
+    "",
+    indent .. "You can enable a supported formatter by adding this to your config.lua",
+    "",
+    indent
+      .. "lvim.lang."
+      .. tostring(ft)
+      .. [[.formatting = { { exe = ']]
+      .. table.concat(supported_formatters, "|")
+      .. [[' } }]],
+    "",
+    "-------------------------------------------------------------------",
+  }
+end
+
+local function get_linter_suggestion_msg(ft)
+  local supported_linters = u.get_supported_linters_by_filetype(ft)
+  return {
+    "-------------------------------------------------------------------",
+    "",
+    "  HINT ",
+    "",
+    indent .. "* List of supported linters: " .. str_list(supported_linters),
+    "",
+    indent .. "You can enable a supported linter by adding this to your config.lua",
+    "",
+    indent
+      .. "lvim.lang."
+      .. tostring(ft)
+      .. [[.linters = { { exe = ']]
+      .. table.concat(supported_linters, "|")
+      .. [[' } }]],
+    "",
+    "-------------------------------------------------------------------",
+  }
+end
+
+---creates an average size popup
+---@param buf_lines a list of lines to print
+---@param callback could be used to set syntax highlighting rules for example
+---@return bufnr buffer number of the created buffer
+---@return win_id window ID of the created popup
+function M.create_simple_popup(buf_lines, callback)
+  -- runtime/lua/vim/lsp/util.lua
+  local bufnr = vim.api.nvim_create_buf(false, true)
+  local height_percentage = 0.7
+  local width_percentage = 0.8
+  local row_start_percentage = (1 - height_percentage) / 2
+  local col_start_percentage = (1 - width_percentage) / 2
+  local opts = {}
+  opts.relative = "editor"
+  opts.height = math.ceil(vim.o.lines * height_percentage)
+  opts.row = math.ceil(vim.o.lines * row_start_percentage)
+  opts.col = math.floor(vim.o.columns * col_start_percentage)
+  opts.width = math.floor(vim.o.columns * width_percentage)
+  opts.border = {
+    "┌",
+    "-",
+    "┐",
+    "|",
+    "┘",
+    "-",
+    "└",
+    "|",
+  }
+
+  local win_id = vim.api.nvim_open_win(bufnr, true, opts)
+
+  vim.api.nvim_win_set_buf(win_id, bufnr)
+  -- this needs to be window option!
+  vim.api.nvim_win_set_option(win_id, "number", false)
+  vim.cmd "setlocal nocursorcolumn"
+  vim.cmd "setlocal wrap"
+  -- set buffer options
+  vim.api.nvim_buf_set_option(bufnr, "filetype", "lspinfo")
+  vim.lsp.util.close_preview_autocmd({ "BufHidden", "BufLeave" }, win_id)
+  buf_lines = vim.lsp.util._trim(buf_lines, {})
+  vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, buf_lines)
+  vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
+  if type(callback) == "function" then
+    callback()
+  end
+  return bufnr, win_id
+end
+
+function M.toggle_popup(ft)
+  local client = u.get_active_client_by_ft(ft)
+  local is_client_active = not client.is_stopped()
+  local client_enabled_caps = require("lsp").get_ls_capabilities(client.id)
+  local num_caps = vim.tbl_count(client_enabled_caps)
+  local null_ls_providers = null_ls_handler.get_registered_providers_by_filetype(ft)
+
+  local missing_linters = lvim.lang[ft].linters._failed_requests or {}
+  local missing_formatters = lvim.lang[ft].formatters._failed_requests or {}
+
+  local buf_lines = {}
+  vim.list_extend(buf_lines, M.banner)
+
+  local header = {
+    "Detected filetype is: " .. tostring(ft),
+    "",
+    "Treesitter active: " .. tostring(next(vim.treesitter.highlighter.active) ~= nil),
+    "",
+    "",
+  }
+  vim.list_extend(buf_lines, header)
+
+  local lsp_info = {
+    "Associated language-server: " .. client.name,
+    indent .. "* Active: " .. tostring(is_client_active) .. ", id: " .. tostring(client.id),
+    indent .. "* Formatting support: " .. tostring(client.resolved_capabilities.document_formatting),
+    indent .. "* Capabilities list: " .. table.concat(vim.list_slice(client_enabled_caps, 1, num_caps / 2), ", "),
+    indent .. indent .. indent .. table.concat(vim.list_slice(client_enabled_caps, ((num_caps / 2) + 1)), ", "),
+    "",
+  }
+  vim.list_extend(buf_lines, lsp_info)
+
+  local null_ls_info = {
+    "Configured providers: " .. table.concat(null_ls_providers, "  , ") .. "  ",
+    "",
+  }
+  vim.list_extend(buf_lines, null_ls_info)
+
+  local missing_formatters_status
+  if vim.tbl_count(missing_formatters) > 0 then
+    missing_formatters_status = { "Missing formatters: " .. table.concat(missing_formatters, "  , ") .. "  ", "" }
+    vim.list_extend(buf_lines, missing_formatters_status)
+  end
+
+  local missing_linters_status
+  if vim.tbl_count(missing_linters) > 0 then
+    missing_linters_status = { "Missing linters: " .. table.concat(missing_linters, "  , ") .. "  ", "" }
+    vim.list_extend(buf_lines, missing_linters_status)
+  end
+
+  vim.list_extend(buf_lines, get_formatter_suggestion_msg(ft))
+
+  vim.list_extend(buf_lines, get_linter_suggestion_msg(ft))
+
+  local function set_syntax_hl()
+    --TODO: highlighting is either inconsistent or not working :\
+    vim.cmd("syntax match Identifier /filetype is: .*\\zs\\<" .. ft .. "\\>/")
+    vim.cmd("syntax match Identifier /server: .*\\zs\\<" .. client.name .. "\\>/")
+    vim.cmd("syntax match Identifier /providers: .*\\zs\\<" .. table.concat(null_ls_providers, ", ") .. "\\>/")
+    vim.cmd("syntax match Identifier /formatters: .*\\zs\\<" .. table.concat(missing_formatters, ", ") .. "\\>/")
+    vim.cmd("syntax match Identifier /linters: .*\\zs\\<" .. table.concat(missing_linters, ", ") .. "\\>/")
+  end
+
+  return M.create_simple_popup(buf_lines, set_syntax_hl)
+end
+return M

+ 4 - 0
lua/core/which-key.lua

@@ -169,6 +169,10 @@ M.config = function()
       L = {
       L = {
         name = "+LunarVim",
         name = "+LunarVim",
         k = { "<cmd>lua require('keymappings').print()<cr>", "View LunarVim's default keymappings" },
         k = { "<cmd>lua require('keymappings').print()<cr>", "View LunarVim's default keymappings" },
+        i = {
+          "<cmd>lua require('core.info').toggle_popup(vim.bo.filetype)<cr>",
+          "Toggle LunarVim Info",
+        },
       },
       },
 
 
       s = {
       s = {

+ 30 - 0
lua/lsp/init.lua

@@ -64,6 +64,36 @@ function M.common_capabilities()
   return capabilities
   return capabilities
 end
 end
 
 
+function M.get_ls_capabilities(client_id)
+  local client
+  if not client_id then
+    local buf_clients = vim.lsp.buf_get_clients()
+    for _, buf_client in ipairs(buf_clients) do
+      if buf_client.name ~= "null-ls" then
+        client_id = buf_client.id
+        break
+      end
+    end
+  end
+  if not client_id then
+    error "Unable to determine client_id"
+  end
+
+  client = vim.lsp.get_client_by_id(tonumber(client_id))
+
+  local enabled_caps = {}
+
+  for k, v in pairs(client.resolved_capabilities) do
+    if v == true then
+      -- print("got cap: ", vim.inspect(caps))
+      table.insert(enabled_caps, k)
+      -- vim.list_extend(enabled_caps, cap)
+    end
+  end
+
+  return enabled_caps
+end
+
 function M.common_on_init(client, bufnr)
 function M.common_on_init(client, bufnr)
   if lvim.lsp.on_init_callback then
   if lvim.lsp.on_init_callback then
     lvim.lsp.on_init_callback(client, bufnr)
     lvim.lsp.on_init_callback(client, bufnr)

+ 26 - 0
lua/lsp/null-ls.lua

@@ -23,6 +23,26 @@ function M.get_registered_providers_by_filetype(ft)
   return matches
   return matches
 end
 end
 
 
+function M.get_missing_providers_by_filetype(ft)
+  local matches = {}
+  for _, provider in pairs(M.requested_providers) do
+    if vim.tbl_contains(provider.filetypes, ft) then
+      local provider_name = provider.name
+
+      table.insert(matches, provider_name)
+    end
+  end
+
+  return matches
+end
+
+local function register_failed_request(ft, provider, operation)
+  if not lvim.lang[ft][operation]._failed_requests then
+    lvim.lang[ft][operation]._failed_requests = {}
+  end
+  table.insert(lvim.lang[ft][operation]._failed_requests, provider)
+end
+
 local function validate_nodejs_provider(provider)
 local function validate_nodejs_provider(provider)
   local command_path
   local command_path
   local root_dir
   local root_dir
@@ -80,6 +100,9 @@ function M.setup(filetype)
         builtin_formatter._opts.command = resolved_path
         builtin_formatter._opts.command = resolved_path
         table.insert(M.requested_providers, builtin_formatter)
         table.insert(M.requested_providers, builtin_formatter)
         u.lvim_log(string.format("Using format provider: [%s]", builtin_formatter.name))
         u.lvim_log(string.format("Using format provider: [%s]", builtin_formatter.name))
+      else
+        -- mark it here to avoid re-doing the lookup again
+        register_failed_request(filetype, formatter.exe, "formatters")
       end
       end
     end
     end
   end
   end
@@ -101,6 +124,9 @@ function M.setup(filetype)
         builtin_diagnoser._opts.command = resolved_path
         builtin_diagnoser._opts.command = resolved_path
         table.insert(M.requested_providers, builtin_diagnoser)
         table.insert(M.requested_providers, builtin_diagnoser)
         u.lvim_log(string.format("Using linter provider: [%s]", builtin_diagnoser.name))
         u.lvim_log(string.format("Using linter provider: [%s]", builtin_diagnoser.name))
+      else
+        -- mark it here to avoid re-doing the lookup again
+        register_failed_request(filetype, linter.exe, "linters")
       end
       end
     end
     end
   end
   end

+ 28 - 0
lua/utils/init.lua

@@ -113,6 +113,34 @@ function utils.get_active_client_by_ft(filetype)
   return nil
   return nil
 end
 end
 
 
+-- TODO: consider porting this logic to null-ls instead
+function utils.get_supported_linters_by_filetype(filetype)
+  local null_ls = require "null-ls"
+  local matches = {}
+  for _, provider in pairs(null_ls.builtins.diagnostics) do
+    if vim.tbl_contains(provider.filetypes, filetype) then
+      local provider_name = provider.name
+
+      table.insert(matches, provider_name)
+    end
+  end
+
+  return matches
+end
+
+function utils.get_supported_formatters_by_filetype(filetype)
+  local null_ls = require "null-ls"
+  local matches = {}
+  for _, provider in pairs(null_ls.builtins.formatting) do
+    if provider.filetypes and vim.tbl_contains(provider.filetypes, filetype) then
+      -- table.insert(matches, { provider.name, ft })
+      table.insert(matches, provider.name)
+    end
+  end
+
+  return matches
+end
+
 function utils.unrequire(m)
 function utils.unrequire(m)
   package.loaded[m] = nil
   package.loaded[m] = nil
   _G[m] = nil
   _G[m] = nil