hrsh7th / cmp-nvim-lsp

nvim-cmp source for neovim builtin LSP client
MIT License
1.23k stars 48 forks source link

Filter out suggestion based on current typing characters #52

Open BlueDruddigon opened 1 year ago

BlueDruddigon commented 1 year ago

I'm currently using latest release version of neovim (0.9.0)

I'm follow up this channel to setup lsp servers and completion with nvim-lspconfig, mason-lspconfig.nvim, mason.nvim and nvim-cmp comes up with cmp-nvim-lsp.

The LSP server i've setup is pyright. Current behavior is, when i type down isin, expected by isinstance builtin function in python. but the completion item suggest the np.isinf in the first place in completion menu.

Expected behavior is how to filter out the completion item depends on which user typed, such as the completion items start with isin in the previous examples.

BlueDruddigon commented 1 year ago

Here is my config of lsp with lazy.nvim package manager

return {
    -- lspconfig
    {
        "neovim/nvim-lspconfig",
        dependencies = {
            { "folke/neoconf.nvim", cmd = "Neoconf", config = true },
            { "folke/neodev.nvim", opts = { experimental = { pathStrict = true } } },
            "mason.nvim",
            "williamboman/mason-lspconfig.nvim",
            {
                "tamago324/nlsp-settings.nvim",
                cmd = "LspSettings",
                lazy = true,
                opts = {
                    config_home = vim.fn.stdpath("config") .. "/lsp-settings",
                    local_settings_dir = ".lsp-settings",
                    local_settings_root_markers_fallback = { ".git" },
                    append_default_schemas = true,
                    loader = "json",
                },
            },
            { "b0o/SchemaStore.nvim", lazy = true },
            "nvim-navic",
        },
        opts = {
            -- options for vim.diagnostic.config()
            diagnostics = {
                underline = true,
                update_in_insert = false,
                virtual_text = { spacing = 4, prefix = "●" },
                severity_sort = true,
            },
            -- lsp servers
            servers = {
                jsonls = {},
                lua_ls = {
                    settings = {
                        Lua = {
                            telemetry = { enable = false },
                            runtime = { version = "LuaJIT" },
                            diagnostics = { globals = { "vim" } },
                            workspace = {
                                library = vim.api.nvim_get_runtime_file("", true),
                                checkThirdParty = false,
                            },
                        },
                    },
                },
                -- python
                jedi = {},
                -- js, jsx, ts, tsx
                tsserver = {},
                cssls = {},
                tailwindcss = {},
                astro = {},
            },
        },
        config = function(_, opts)
            -- setup format on save
            local format_on_save = function(buf)
                vim.lsp.buf.format({
                    bufnr = buf,
                    timeout_ms = 2000,
                    filter = function(client)
                        return client.name == "null-ls"
                    end,
                })
            end

            -- on attach
            local navic = require("nvim-navic")
            local augroup_format = vim.api.nvim_create_augroup("LspFormatting", {})
            vim.api.nvim_create_autocmd("LspAttach", {
                group = vim.api.nvim_create_augroup("UserLspConfig", {}),
                callback = function(args)
                    local bufnr = args.buf
                    local client = vim.lsp.get_client_by_id(args.data.client_id)

                    -- enable completion triggered by <c-x><c-o>
                    vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc"

                    -- navic lsp symbol
                    if client.server_capabilities.documentSymbolProvider then
                        navic.attach(client, bufnr)
                    end

                    -- keymappings
                    local buf_opts = { buffer = bufnr }
                    vim.keymap.set("n", "gd", vim.lsp.buf.definition, buf_opts)
                    vim.keymap.set("n", "gD", vim.lsp.buf.declaration, buf_opts)
                    vim.keymap.set("n", "K", vim.lsp.buf.hover, buf_opts)
                    vim.keymap.set("n", "gI", vim.lsp.buf.implementation, buf_opts)
                    vim.keymap.set("n", "gr", vim.lsp.buf.references, buf_opts)
                    vim.keymap.set("n", "<leader>D", vim.lsp.buf.type_definition, buf_opts)
                    vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, buf_opts)

                    if client.supports_method("textDocument/formatting") then
                        vim.api.nvim_clear_autocmds({ group = augroup_format, buffer = bufnr })
                        vim.api.nvim_create_autocmd("BufWritePre", {
                            group = augroup_format,
                            buffer = bufnr,
                            callback = function()
                                format_on_save(bufnr)
                            end,
                        })
                    end
                end,
            })

            -- diagnostics
            for name, icon in pairs(require("user.icons").diagnostics) do
                name = "DiagnosticSign" .. name
                vim.fn.sign_define(name, { text = icon, texthl = name, numhl = "" })
            end
            vim.diagnostic.config(opts.diagnostics)

            local servers = opts.servers
            local capabilities = require("cmp_nvim_lsp").default_capabilities()
            capabilities.textDocument.completion.completionItem.snippetSupport = true
            capabilities.textDocument.completion.completionItem.resolveSupport = {
                properties = {
                    "documentation",
                    "detail",
                    "additionalTextEdits",
                },
            }

            local function setup(server)
                local server_opts = {}
                if server == "jsonls" then
                    server_opts = vim.tbl_deep_extend("force", {
                        capabilities = vim.deepcopy(capabilities),
                        settings = {
                            json = {
                                schemas = require("schemastore").json.schemas(),
                                format = { enable = true },
                                validate = { enable = true },
                            },
                        },
                    }, servers["jsonls"])
                else
                    server_opts = vim.tbl_deep_extend("force", {
                        capabilities = vim.deepcopy(capabilities),
                    }, servers[server] or {})
                end
                require("lspconfig")[server].setup(server_opts)
            end

            -- get all the servers that are available through mason-lspconfig
            local have_mason, mslp = pcall(require, "mason-lspconfig")
            local all_mslp_servers = {}
            if have_mason then
                all_mslp_servers = vim.tbl_keys(require("mason-lspconfig.mappings.server").lspconfig_to_package)
            end

            local ensure_installed = {}
            for server, server_opts in pairs(servers) do
                setup(server)
                if vim.tbl_contains(all_mslp_servers, server) then
                    ensure_installed[#ensure_installed + 1] = server
                end
            end

            if have_mason then
                mslp.setup({ ensure_installed = ensure_installed })
                mslp.setup_handlers({ setup })
            end
        end,
    },

    -- formatters
    {
        "jose-elias-alvarez/null-ls.nvim",
        event = { "BufReadPre", "BufNewFile" },
        lazy = true,
        opts = function()
            local nls = require("null-ls")
            return {
                sources = {
                    -- python
                    nls.builtins.diagnostics.pylint,
                    nls.builtins.diagnostics.flake8,
                    nls.builtins.formatting.isort,
                    nls.builtins.formatting.yapf,
                    nls.builtins.formatting.autopep8,
                    -- lua
                    nls.builtins.formatting.stylua,
                    -- js, jsx, ts, tsx
                    nls.builtins.diagnostics.eslint,
                    nls.builtins.formatting.prettierd,
                    -- markdown
                    nls.builtins.diagnostics.write_good,
                    nls.builtins.code_actions.cspell,
                    nls.builtins.code_actions.proselint,
                },
            }
        end,
    },

    -- cmdline tools and lsp servers
    {
        "williamboman/mason.nvim",
        opts = {
            ensure_installed = {
                -- lua
                "stylua",
                -- python
                "pylint",
                "flake8",
                "yapf",
                "isort",
                "autopep8",
                -- markdown
                "write-good",
                "cspell",
                "proselint",
                -- js, jsx, ts, tsx
                "eslint-lsp",
                "prettierd",
            },
            pip = {
                upgrade_pip = true,
            },
        },
        config = function(_, opts)
            require("mason").setup(opts)
            local mr = require("mason-registry")
            local function ensure_installed()
                for _, tool in ipairs(opts.ensure_installed) do
                    local p = mr.get_package(tool)
                    if not p:is_installed() then
                        p:install()
                    end
                end
            end
            if mr.refresh then
                mr.refresh(ensure_installed)
            else
                ensure_installed()
            end
        end,
    },
}

And here is my config of completion

return {
    -- snippets
    {
        "L3MON4D3/LuaSnip",
        event = "InsertEnter",
        dependencies = {
            "rafamadriz/friendly-snippets",
        },
        config = function()
            local user_snippets = vim.fn.stdpath("config") .. "/snippets"
            require("luasnip.loaders.from_vscode").lazy_load({ paths = user_snippets })
        end,
    },

    -- auto completion
    {
        "hrsh7th/nvim-cmp",
        version = false,
        event = "InsertEnter",
        dependencies = {
            "hrsh7th/cmp-nvim-lsp",
            "hrsh7th/cmp-buffer",
            "hrsh7th/cmp-path",
            "saadparwaiz1/cmp_luasnip",
        },
        opts = function()
            local luasnip = require("luasnip")
            local cmp = require("cmp")

            local has_word_before = function()
                unpack = unpack or table.unpack
                local line, col = unpack(vim.api.nvim_win_get_cursor(0))
                return col ~= 0
                    and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
            end
            return {
                snippet = {
                    expand = function(args)
                        require("luasnip").lsp_expand(args.body)
                    end,
                },
                sources = {
                    { name = "luasnip" },
                    { name = "nvim_lsp" },
                    { name = "buffer" },
                    { name = "path" },
                },
                window = {
                    completion = cmp.config.window.bordered(),
                    documentation = cmp.config.window.bordered(),
                },
                formatting = {
                    fields = { "kind", "abbr", "menu" },
                    format = function(entry, item)
                        item.kind = require("user.icons").kinds[item.kind]
                        item.menu = ({
                            nvim_lsp = "[LSP]",
                            path = "[Path]",
                            luasnip = "[Snippet]",
                            buffer = "[Buffer]",
                        })[entry.source.name]
                        item.dup = ({
                            nvim_lsp = 0,
                            path = 1,
                            luasnip = 1,
                            buffer = 1,
                        })[entry.source.name]
                        return item
                    end,
                },
                mapping = cmp.mapping.preset.insert({
                    ["<C-k>"] = cmp.mapping(cmp.mapping.select_prev_item(), { "i", "c" }),
                    ["<C-j>"] = cmp.mapping(cmp.mapping.select_next_item(), { "i", "c" }),
                    ["<C-b>"] = cmp.mapping.scroll_docs(-4),
                    ["<C-f>"] = cmp.mapping.scroll_docs(4),
                    ["<Tab>"] = cmp.mapping(function(fallback)
                        if cmp.visible() then
                            cmp.select_next_item()
                        elseif luasnip.expand_or_jumpable() then
                            luasnip.expand_or_jump()
                        elseif has_word_before() then
                            cmp.complete()
                        else
                            fallback()
                        end
                    end, { "i", "s" }),
                    ["<S-Tab>"] = cmp.mapping(function(fallback)
                        if cmp.visible() then
                            cmp.select_prev_item()
                        elseif luasnip.jumpable(-1) then
                            luasnip.jump(-1)
                        else
                            fallback()
                        end
                    end, { "i", "s" }),
                    ["<C-Space>"] = cmp.mapping.complete(),
                    ["<C-e>"] = cmp.mapping.abort(),
                    ["<CR>"] = cmp.mapping.confirm({ select = true }),
                }),
            }
        end,
    },
}