xzbdmw/colorful-menu.nvim

github github
color
stars 237
issues 1
subscribers 3
forks 9
CREATED

2025-01-03

UPDATED

yesterday


README

colorful-menu.nvim

Out of box, this plugin reconstructs completion item and applies treesitter highlight queries to produce richly colorized completion items with variable-size highlight ranges.

Has built-in support for

For other languages, it defaults to use highlight group of item's kind. (feel free to open feature request for more languages)

Currently supports nvim-cmp and blink.cmp.

Installation

With lazy.nvim:

return {
    "xzbdmw/colorful-menu.nvim",
    config = function()
        -- You don't need to set these options.
        require("colorful-menu").setup({
            ls = {
                lua_ls = {
                    -- Maybe you want to dim arguments a bit.
                    arguments_hl = "@comment",
                },
                gopls = {
                    -- By default, we render variable/function's type in the right most side,
                    -- to make them not to crowd together with the original label.

                    -- when true:
                    -- foo             *Foo
                    -- ast         "go/ast"

                    -- when false:
                    -- foo *Foo
                    -- ast "go/ast"
                    align_type_to_right = true,
                    -- When true, label for field and variable will format like "foo: Foo"
                    -- instead of go's original syntax "foo Foo". If align_type_to_right is
                    -- true, this option has no effect.
                    add_colon_before_type = false,
                    -- See https://github.com/xzbdmw/colorful-menu.nvim/pull/36
                    preserve_type_when_truncate = true,
                },
                -- for lsp_config or typescript-tools
                ts_ls = {
                    extra_info_hl = "@comment",
                },
                vtsls = {
                    extra_info_hl = "@comment",
                },
                ["rust-analyzer"] = {
                    -- Such as (as Iterator), (use std::io).
                    extra_info_hl = "@comment",
                    -- Similar to the same setting of gopls.
                    align_type_to_right = true,
                    -- See https://github.com/xzbdmw/colorful-menu.nvim/pull/36
                    preserve_type_when_truncate = true,
                },
                clangd = {
                    -- Such as "From <stdio.h>".
                    extra_info_hl = "@comment",
                    -- Similar to the same setting of gopls.
                    align_type_to_right = true,
                    -- the hl group of leading dot of "•std::filesystem::permissions(..)"
                    import_dot_hl = "@comment",
                    -- See https://github.com/xzbdmw/colorful-menu.nvim/pull/36
                    preserve_type_when_truncate = true,
                },
                zls = {
                    -- Similar to the same setting of gopls.
                    align_type_to_right = true,
                },
                roslyn = {
                    extra_info_hl = "@comment",
                },
                dartls = {
                    extra_info_hl = "@comment",
                },
                -- The same applies to pyright/pylance
                basedpyright = {
                    -- It is usually import path such as "os"
                    extra_info_hl = "@comment",
                },

                -- If true, try to highlight "not supported" languages.
                fallback = true,
            },
            -- If the built-in logic fails to find a suitable highlight group,
            -- this highlight is applied to the label.
            fallback_highlight = "@variable",
            -- If provided, the plugin truncates the final displayed text to
            -- this width (measured in display cells). Any highlights that extend
            -- beyond the truncation point are ignored. When set to a float
            -- between 0 and 1, it'll be treated as percentage of the width of
            -- the window: math.floor(max_width * vim.api.nvim_win_get_width(0))
            -- Default 60.
            max_width = 60,
        })
    end,
}

use it in nvim-cmp:

require("cmp").setup({
    formatting = {
        format = function(entry, vim_item)
            local highlights_info = require("colorful-menu").cmp_highlights(entry)

            -- highlight_info is nil means we are missing the ts parser, it's
            -- better to fallback to use default `vim_item.abbr`. What this plugin
            -- offers is two fields: `vim_item.abbr_hl_group` and `vim_item.abbr`.
            if highlights_info ~= nil then
                vim_item.abbr_hl_group = highlights_info.highlights
                vim_item.abbr = highlights_info.text
            end

            return vim_item
        end,
    },
})

use it in blink.cmp:

config = function()
    require("blink.cmp").setup({
        completion = {
            menu = {
                draw = {
                    -- We don't need label_description now because label and label_description are already
                    -- combined together in label by colorful-menu.nvim.
                    columns = { { "kind_icon" }, { "label", gap = 1 } },
                    components = {
                        label = {
                            text = function(ctx)
                                return require("colorful-menu").blink_components_text(ctx)
                            end,
                            highlight = function(ctx)
                                return require("colorful-menu").blink_components_highlight(ctx)
                            end,
                        },
                    },
                },
            },
        },
    })
end

If you want to customize a bit more in those options, here is a more granular control approach:

config = function()
    require("blink.cmp").setup({
        completion = {
            menu = {
                draw = {
                    -- We don't need label_description now because label and label_description are already
                    -- combined together in label by colorful-menu.nvim.
                    columns = { { "kind_icon" }, { "label", gap = 1 } },
                    components = {
                        label = {
                            width = { fill = true, max = 60 },
                            text = function(ctx)
                                local highlights_info = require("colorful-menu").blink_highlights(ctx)
                                if highlights_info ~= nil then
                                    -- Or you want to add more item to label
                                    return highlights_info.label
                                else
                                    return ctx.label
                                end
                            end,
                            highlight = function(ctx)
                                local highlights = {}
                                local highlights_info = require("colorful-menu").blink_highlights(ctx)
                                if highlights_info ~= nil then
                                    highlights = highlights_info.highlights
                                end
                                for _, idx in ipairs(ctx.label_matched_indices) do
                                    table.insert(highlights, { idx, idx + 1, group = "BlinkCmpLabelMatch" })
                                end
                                -- Do something else
                                return highlights
                            end,
                        },
                    },
                },
            },
        },
    })
end

Custom configuration with lspkind.nvim

You can configure your completion engine (below is for nvim-cmp with both colorful-menu.nvim and lspkind.nvim to get completion menu like those in Screen section.

formatting = {
  fields = { "kind", "abbr", "menu" },

  format = function(entry, vim_item)
    local kind = require("lspkind").cmp_format({
        mode = "symbol_text",
    })(entry, vim.deepcopy(vim_item))
    local highlights_info = require("colorful-menu").cmp_highlights(entry)

    -- highlight_info is nil means we are missing the ts parser, it's
    -- better to fallback to use default `vim_item.abbr`. What this plugin
    -- offers is two fields: `vim_item.abbr_hl_group` and `vim_item.abbr`.
    if highlights_info ~= nil then
        vim_item.abbr_hl_group = highlights_info.highlights
        vim_item.abbr = highlights_info.text
    end
    local strings = vim.split(kind.kind, "%s", { trimempty = true })
    vim_item.kind = " " .. (strings[1] or "") .. " "
    vim_item.menu = ""

    return vim_item
  end,
}

Screen

*� Note: You may want to set CmpItemAbbrMatch or BlinkCmpLabelMatch to only have bold style (without fg) to achieve a similar effect as the images below.

gopls

before:

after:

rust-analyzer

before:

after:

clangd

before:

after:

lua_ls

before:

after:

typescript-language-server

before:

after:

intelephense

Thanks to @pnx

before:

image

after:

image

zls

before:

after:

roslyn

Thanks to @seblj

before:

image

after:

image

basedpyright

before:

after:

dartls

before:

after:

Contributing

Feel free to open issues or submit pull requests if you encounter any bugs or have feature requests.

License

MIT License.

Credit

Zed for the initial idea of colorize.

@David van Munster for the pr which make this plugin possible.

JetBrains for its right-aligned type layout