Skip to content

linrongbin16/lsp-progress.nvim

Repository files navigation

lsp-progress.nvim

Neovim luarocks ci.yml codecov

A performant lsp progress status for Neovim.

default

Click here to see how to configure
require("lsp-progress").setup()

client-names

Click here to see how to configure
require("lsp-progress").setup({
  client_format = function(client_name, spinner, series_messages)
    if #series_messages == 0 then
      return nil
    end
    return {
      name = client_name,
      body = spinner .. " " .. table.concat(series_messages, ", "),
    }
  end,
  format = function(client_messages)
    --- @param name string
    --- @param msg string?
    --- @return string
    local function stringify(name, msg)
      return msg and string.format("%s %s", name, msg) or name
    end

    local sign = "" -- nf-fa-gear \uf013
    local lsp_clients = vim.lsp.get_active_clients()
    local messages_map = {}
    for _, climsg in ipairs(client_messages) do
      messages_map[climsg.name] = climsg.body
    end

    if #lsp_clients > 0 then
      table.sort(lsp_clients, function(a, b)
        return a.name < b.name
      end)
      local builder = {}
      for _, cli in ipairs(lsp_clients) do
        if
          type(cli) == "table"
          and type(cli.name) == "string"
          and string.len(cli.name) > 0
        then
          if messages_map[cli.name] then
            table.insert(builder, stringify(cli.name, messages_map[cli.name]))
          else
            table.insert(builder, stringify(cli.name))
          end
        end
      end
      if #builder > 0 then
        return sign .. " " .. table.concat(builder, ", ")
      end
    end
    return ""
  end,
})

green-check

Click here to see how to configure
require("lsp-progress").setup({
  decay = 1200,
  series_format = function(title, message, percentage, done)
    local builder = {}
    local has_title = false
    local has_message = false
    if type(title) == "string" and string.len(title) > 0 then
      table.insert(builder, title)
      has_title = true
    end
    if type(message) == "string" and string.len(message) > 0 then
      table.insert(builder, message)
      has_message = true
    end
    if percentage and (has_title or has_message) then
      table.insert(builder, string.format("(%.0f%%)", percentage))
    end
    return { msg = table.concat(builder, " "), done = done }
  end,
  client_format = function(client_name, spinner, series_messages)
    if #series_messages == 0 then
      return nil
    end
    local builder = {}
    local done = true
    for _, series in ipairs(series_messages) do
      if not series.done then
        done = false
      end
      table.insert(builder, series.msg)
    end
    if done then
      spinner = "" -- replace your check mark
    end
    return "["
      .. client_name
      .. "] "
      .. spinner
      .. " "
      .. table.concat(builder, ", ")
  end,
})

Table of contents

Performance

I use a 2-layer map to cache all lsp progress messages, thus split the O(N * M) time complexity calculation into almost O(1) on every LSP progress update.

N is active lsp clients count, M is token count of each lsp client.

For more details, please see Design & Technologies.

Requirement

Install

With packer.nvim
-- lua
return require('packer').startup(function(use)
  use {
    'linrongbin16/lsp-progress.nvim',
    config = function()
      require('lsp-progress').setup()
    end
  }
end)
With lazy.nvim
-- lua
require("lazy").setup({
  {
    'linrongbin16/lsp-progress.nvim',
    config = function()
      require('lsp-progress').setup()
    end
  }
})
With vim-plug
" vim
call plug#begin()

Plug 'linrongbin16/lsp-progress.nvim'

call plug#end()

lua require('lsp-progress').setup()

Usage

  • LspProgressStatusUpdated: user event to notify new status, and trigger statusline refresh.

  • require('lsp-progress').progress(opts): get lsp progress status, parameter opts is an optional lua table:

    require('lsp-progress').progress({
      format = ...,
      max_size = ...,
    })

    The fields are the same value passing to setup (see Configuration) for more dynamic abilities.

Integration

Important

Don't directly put require('lsp-progress').progress as lualine component or heirline's component provider, wrap it with a function to avoid the lazy dependency issue, see #131.

require("lualine").setup({
  sections = {
    -- Other Status Line components
    lualine_a = { ... },
    lualine_b = { ... },

    lualine_c = {
      function()
        -- invoke `progress` here.
        return require('lsp-progress').progress()
      end,
    },
    ...
  }
})

-- listen lsp-progress event and refresh lualine
vim.api.nvim_create_augroup("lualine_augroup", { clear = true })
vim.api.nvim_create_autocmd("User", {
  group = "lualine_augroup",
  pattern = "LspProgressStatusUpdated",
  callback = require("lualine").refresh,
})
local LspProgress = {
  provider = function()
    return require('lsp-progress').progress()
  end,
  update = {
    'User',
    pattern = 'LspProgressStatusUpdated',
    callback = vim.schedule_wrap(function()
      vim.cmd('redrawstatus')
    end),
  }
}

local StatusLine = {
  -- Other StatusLine components
  { ... },

  -- Lsp progress status component here
  LspProgress,
}

require('heirline').setup({
  statusline = StatusLine
})

Configuration

To configure options, please use:

require('lsp-progress').setup(opts)

The opts is an optional lua table that overwrite the default options.

For complete options and defaults, please check defaults.lua.

For more advanced configurations, please see Advanced Configuration.

Alternatives

  • lsp-status.nvim: Utility functions for getting diagnostic status and progress messages from LSP servers, for use in the Neovim statusline.
  • fidget.nvim: Standalone UI for nvim-lsp progress.

Contribute

Please open issue/PR for anything about lsp-progress.nvim.

Like lsp-progress.nvim? Consider

Github Sponsor Wechat Pay Alipay