diff --git a/docs/.nojekyll b/docs/.nojekyll
new file mode 100644
index 0000000..e69de29
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..077b87e
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,7 @@
+
+
+# Welcome to lsp-progress.nvim's Documentation!
+
+- [Design & Technologies](/design_and_technologies.md)
+- [Advanced Configurations](/advanced_configurations.md)
+- [Sponsor](/sponsor.md)
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
new file mode 100644
index 0000000..0cca329
--- /dev/null
+++ b/docs/_sidebar.md
@@ -0,0 +1,4 @@
+- [Home](/)
+- [Design & Technologies](/design_and_technologies.md)
+- [Advanced Configurations](/advanced_configurations.md)
+- [Sponsor](/sponsor.md)
diff --git a/docs/advanced_configurations.md b/docs/advanced_configurations.md
new file mode 100644
index 0000000..6d8c86e
--- /dev/null
+++ b/docs/advanced_configurations.md
@@ -0,0 +1,367 @@
+# Advanced Configurations
+
+## Show LSP Client Names
+
+
+
+Configurations:
+
+```lua
+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,
+})
+```
+
+## Show LSP Client Counts & Names
+
+
+
+Configurations:
+
+```lua
+require("lsp-progress").setup()
+```
+
+```lua
+return require("lsp-progress").progress({
+ format = function(messages)
+ local active_clients = vim.lsp.get_active_clients()
+ local client_count = #active_clients
+ if #messages > 0 then
+ return " LSP:"
+ .. client_count
+ .. " "
+ .. table.concat(messages, " ")
+ end
+ if #active_clients <= 0 then
+ return " LSP:" .. client_count
+ else
+ local client_names = {}
+ for i, client in ipairs(active_clients) do
+ if client and client.name ~= "" then
+ table.insert(client_names, "[" .. client.name .. "]")
+ print(
+ "client[" .. i .. "]:" .. vim.inspect(client.name)
+ )
+ end
+ end
+ return " LSP:"
+ .. client_count
+ .. " "
+ .. table.concat(client_names, " ")
+ end
+ end,
+})
+```
+
+## Use A Check Mark `✓` On Message Complete
+
+Use a green check mark `✓` on lsp message complete, follow the [fidget.nvim](https://github.com/j-hui/fidget.nvim) style.
+
+?> Credit: [@ryanmsnyder](https://github.com/ryanmsnyder), see: .
+
+
+
+Configurations:
+
+```lua
+-- Create a highlighting group with green color
+vim.cmd([[ hi LspProgressMessageCompleted ctermfg=Green guifg=Green ]])
+
+require("lsp-progress").setup({
+ series_format = function(title, message, percentage, done)
+ local builder = {}
+ local has_title = false
+ local has_message = false
+ if title and title ~= "" then
+ table.insert(builder, title)
+ has_title = true
+ end
+ if message and message ~= "" 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
+ if done and (has_title or has_message) then
+ table.insert(builder, "- done")
+ end
+ -- return table.concat(builder, " ")
+ 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
+ -- replace the check mark once done
+ spinner = "%#LspProgressMessageCompleted#✓%*"
+ end
+ return "["
+ .. client_name
+ .. "] "
+ .. spinner
+ .. " "
+ .. table.concat(builder, ", ")
+ end,
+})
+```
+
+```lua
+local function LspIcon()
+ local active_clients_count = #vim.lsp.get_active_clients()
+ return active_clients_count > 0 and " LSP" or ""
+end
+
+local function LspStatus()
+ return require("lsp-progress").progress({
+ format = function(messages)
+ return #messages > 0 and table.concat(messages, " ") or ""
+ end,
+ })
+end
+
+require('lualine').setup({
+ sections = {
+ lualine_a = { "mode" },
+ lualine_b = {
+ "branch",
+ "diff",
+ },
+ lualine_c = {
+ "filename",
+ "diagnostics",
+ LspIcon,
+ LspStatus,
+ },
+ ...
+ }
+})
+
+vim.api.nvim_create_augroup("lualine_augroup", { clear = true })
+vim.api.nvim_create_autocmd("User LspProgressStatusUpdated", {
+ group = "lualine_augroup",
+ callback = require("lualine").refresh,
+})
+```
+
+## Put Progress On The Right Side
+
+Put lsp progress messages on the right side of lualine.
+
+?> Credit: [@daephx](https://github.com/daephx), see: .
+
+
+
+Minimal `init.lua` (Windows 10 x86_64, Neovim v0.9.2):
+
+```lua
+local root = vim.fn.stdpath("data")
+
+-- bootstrap lazy
+local lazypath = root .. "/lazy/plugins/lazy.nvim"
+if not vim.loop.fs_stat(lazypath) then
+ vim.fn.system({
+ "git",
+ "clone",
+ "--filter=blob:none",
+ "https://github.com/folke/lazy.nvim.git",
+ lazypath,
+ })
+end
+vim.opt.runtimepath:prepend(lazypath)
+
+-- install plugins
+local plugins = {
+ { -- Initialize language server configuration
+ "neovim/nvim-lspconfig",
+ cmd = { "LspInfo", "LspInstall", "LspUninstall" },
+ event = { "BufReadPost", "BufNewFile" },
+ dependencies = {
+ { "williamboman/mason.nvim", config = true },
+ { "folke/neodev.nvim", config = true },
+ },
+ config = function()
+ require("lspconfig")["lua_ls"].setup({
+ settings = {
+ Lua = {
+ diagnostics = {
+ enable = true,
+ globals = { "vim" },
+ },
+ workspace = {
+ checkThirdParty = false,
+ },
+ },
+ },
+ })
+ end,
+ },
+ {
+ "jose-elias-alvarez/null-ls.nvim",
+ event = { "BufReadPost", "BufNewFile" },
+ dependencies = { "nvim-lua/plenary.nvim" },
+ config = function()
+ local null_ls = require("null-ls")
+ null_ls.setup({
+ sources = { null_ls.builtins.formatting.stylua },
+ })
+ end,
+ },
+ {
+ "nvim-lualine/lualine.nvim",
+ event = "UIEnter",
+ dependencies = {
+ -- Lua fork of vim-web-devicons for neovim
+ { "nvim-tree/nvim-web-devicons" },
+ -- A performant lsp progress status for Neovim.
+ {
+ "linrongbin16/lsp-progress.nvim",
+ config = true,
+ -- dev = true,
+ -- dir = "~/github/linrongbin16/lsp-progress.nvim",
+ },
+ },
+ config = function(_, opts)
+ require("lualine").setup(opts)
+
+ vim.api.nvim_create_augroup("lualine_augroup", { clear = true })
+ vim.api.nvim_create_autocmd("User LspProgressStatusUpdated", {
+ group = "lualine_augroup",
+ callback = require("lualine").refresh,
+ })
+ end,
+ opts = {
+ sections = {
+ lualine_a = { "mode" },
+ lualine_b = {},
+ lualine_c = { "filename" },
+ lualine_x = {
+ { -- Setup lsp-progress component
+ function()
+ return require("lsp-progress").progress({
+ max_size = 80,
+ format = function(messages)
+ local active_clients =
+ vim.lsp.get_active_clients()
+ if #messages > 0 then
+ return table.concat(messages, " ")
+ end
+ local client_names = {}
+ for _, client in ipairs(active_clients) do
+ if client and client.name ~= "" then
+ table.insert(
+ client_names,
+ 1,
+ client.name
+ )
+ end
+ end
+ return table.concat(client_names, " ")
+ end,
+ })
+ end,
+ icon = { "", align = "right" },
+ },
+ "diagnostics",
+ },
+ lualine_y = { "filetype", "encoding", "fileformat" },
+ lualine_z = { "location" },
+ },
+ },
+ },
+}
+
+-- Attach autocmd to enable auto-formatting on save
+vim.api.nvim_create_autocmd({ "LspAttach" }, {
+ callback = function(ev)
+ -- Apply autocmd if client supports formatting
+ vim.api.nvim_create_autocmd("BufWritePre", {
+ buffer = ev.buf,
+ desc = "Apply Auto-formatting for to document on save",
+ group = vim.api.nvim_create_augroup("LspFormat." .. ev.buf, {}),
+ callback = function()
+ vim.lsp.buf.format({
+ bufnr = ev.buf,
+ filter = function(client)
+ return client.name == "null-ls"
+ end,
+ })
+ end,
+ })
+ end,
+})
+
+-- Setup lazy.nvim
+require("lazy").setup(plugins, {
+ root = root .. "/lazy/plugins",
+})
+
+```
diff --git a/docs/design_and_technologies.md b/docs/design_and_technologies.md
new file mode 100644
index 0000000..6beae39
--- /dev/null
+++ b/docs/design_and_technologies.md
@@ -0,0 +1,139 @@
+# Design & Technologies
+
+## The [`$/progress`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#progress) Method
+
+This plugin registers the `$/progress` method into Neovim's [vim.lsp.handlers](https://neovim.io/doc/user/lsp.html#vim.lsp.handlers) table (or use the `LspProgress` event for Neovim v0.10+). Once there's any lsp progress messages, the registered callback function will be invoked.
+
+Here I use the [Producer-Consumer Pattern](https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem) to split the data processing and UI rendering into two parts:
+
+- Producer: The lua function that registers on `$/progress` method (or `LspProgress` event). It receives lsp progress messages and saves in plugin context, then produces a user event to notify.
+- Consumer: A Vim `autocommand` that listens to the user event, consumes the lsp progress messages, prints to UI component, for example the statusline.
+
+That's why I provide two APIs:
+
+- `setup`: It registers a lua callback `function(err, msg, ctx)` on `$/progress` method (or `LspProgress` event).
+- `progress`: It consumes the lsp progress messages, and returns the final rendered text contents.
+
+And one user event:
+
+- `User LspProgressStatusUpdated`: When there's a lsp progress message, this plugin will emit this event to notify users.
+
+## Data Structures
+
+A Vim buffer (the file you're editing) can have multiple lsp cilents, each lsp client can have multiple progress messages (each message has a unique token), each message is a time-based data series from beginning to end, i.e. the _**progress**_.
+
+Based on this scenario, we have a 2-layer hash table:
+
+
+
+The layer-1 maps from a client ID to a client instance, the layer-2 maps from a unique token to a message instance, i.e. the _**series**_.
+
+## Performance
+
+Formatting the 2-layer hash table is a **O(N \* M)** time complexity calculation.
+
+?> **N** is the active client count, **M** is the unique token count.
+
+If we format the messages right before the statusline refreshing, it can lead to editor blocking if the data is too big. It actually happened to me, since I installed over 10+ lsp servers and 5+ code formatters, linters, etc through none-ls, for the super large git monorepo (much bigger than [linux kernel](https://github.com/torvalds/linux)) that belongs to the company I'm working for, it contains over 10+ programming languages.
+
+There're 3 steps to optimize:
+
+1. Add format cache on both message instances and client instances (the _**red**_ parts):
+
+
+
+2. Split the **O(N \* M)** calculation into each message's updating. Every time a message receives updates, it will:
+
+ 1. On message instance, it invokes the `series_format` function to format the lsp message and cache to its `formatted` variable.
+ 2. On client instance, it invokes the `client_format` function to concatenate multiple `formatted` caches and cache to its `formatted` variable.
+
+3. When statusline refreshing, the `progress` API invokes the `format` function to concatenate multiple client instances `formatted` caches and returns a final result, which is quite cheap and fast.
+
+## Customization
+
+There're 3 formatting hook functions that maximize the customization. From bottom to top they are:
+
+### `series_format`
+
+```lua
+--- @param title string?
+--- Message title.
+--- @param message string?
+--- Message body.
+--- @param percentage integer?
+--- Progress in percentage numbers: 0-100.
+--- @param done boolean
+--- Indicate whether this series is the last one in progress.
+--- @return lsp_progress.SeriesFormatResult
+--- The returned value will be passed to function `client_format` as
+--- one of the `series_messages` array, or ignored if return nil.
+series_format = function(title, message, percentage, done)
+```
+
+It formats the message level data. The returned value will be passed to next level `client_format` function, as one of `series_messages` array parameter.
+
+By default the result looks like:
+
+```
+formatting isort (100%) - done
+formatting black (50%)
+```
+
+### `client_format`
+
+```lua
+--- @param client_name string
+--- Client name.
+--- @param spinner string
+--- Spinner icon.
+--- @param series_messages string[]|table[]
+--- Messages array.
+--- @return lsp_progress.ClientFormatResult
+--- The returned value will be passed to function `format` as one of the
+--- `client_messages` array, or ignored if return nil.
+client_format = function(client_name, spinner, series_messages)
+```
+
+It formats the client level data, the parameter `series_messages` is an array, each of them is returned from `series_format`. The returned value will be passed to next level `format` function, as one of `client_messages` array parameter.
+
+By default the result looks like:
+
+```
+[null-ls] ⣷ formatting isort (100%) - done, formatting black (50%)
+```
+
+### `format`
+
+```lua
+--- @param client_messages string[]|table[]
+--- Client messages array.
+--- @return string
+--- The returned value will be returned as the result of `progress` API.
+format = function(client_messages)
+```
+
+It formats the top level data, the parameter `client_messages` is an array, each of them is returned from `client_format`. The returned value will be passed as the result of `progress` API.
+
+By default the result looks like:
+
+```
+ LSP [null-ls] ⣷ formatting isort (100%) - done, formatting black (50%)
+```
+
+!> There's no such requirements that these formatting functions have to return a `string` type. Actually you can return any type, for example `table`, `array`, `number`. But `nil` value will be ignored and throw away.
+
+## Other Enhancements
+
+### Spinning Animation
+
+The `$/progress` method doesn't guarantee when to update, but user may want an accurate spinning animation, i.e. the `⣷` icon keeps spinning in a fixed rate. A background job is scheduled and runs within a fixed interval time to update the spin icon.
+
+### Delayed Disappearance
+
+A lsp progress message can be really fast, appears and disappears in an instant, and people cannot even see it. A delayed timeout is been set to cache the last message for a while. During this while, the spinning animation still needs to keep running!
+
+### Message Deduplication
+
+Again in the super large git monorepo, I have seen 4+ duplicated `formatting (100%)` and `diagnostics (100%)` messages showing at the same time, when I work with `eslint`, `prettier` (via `none-ls`) and `flow`. Turns out they come from the multiple processes launched by the `flow-cli` in background, which is quite noisy.
+
+So I introduced another hash table (maps `title+message` to `token`) to detect the duplicates and reduce them.
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..d981d5c
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/sponsor.md b/docs/sponsor.md
new file mode 100644
index 0000000..d544364
--- /dev/null
+++ b/docs/sponsor.md
@@ -0,0 +1,15 @@
+# Sponsor
+
+Please sponsor me by:
+
+## GitHub Sponsor
+
+
+
+## Alipay
+
+
+
+## Wechat Pay
+
+