blink.cmp

repository·main·Indexed 27 days ago

https://github.com/saghen/blink.cmp

A high-performance, feature-rich completion plugin for Neovim supporting LSPs, snippets, and command-line completion. It features typo-resistant fuzzy matching via the frizbee SIMD matcher (Rust/Lua), native vim.snippet support, and an extensible architecture with a compatibility layer for nvim-cmp sources. Key capabilities include auto-bracket support, ghost text, and highly customizable completion menus with Treesitter highlighting.

Tokens
32K
Snippets
95
Records
112
Agent score
91%

What's inside blink.cmp

  1. Overview of blink.cmp features

    main

    blink.cmp is a performant, batteries-included completion plugin for Neovim. Key features include:

    • High Performance: Updates on every keystroke (0.5-4ms async, single core).
    • Fuzzy Matching: Typo-resistant fuzzy matching using frizbee with frecency and proximity bonuses.
    • Extensive LSP Support: Includes an LSP tracker for deep integration.
    • Snippet Support: Supports native vim.snippet (including friendly-snippets), LuaSnip, and mini.snippets.
    • Extensibility: Supports external sources via community sources and a compatibility layer for nvim-cmp sources (blink.compat).
    • Advanced Completion: Includes auto-bracket support (based on semantic tokens), signature help (experimental), command line completion, and terminal completion (Neovim 0.11+ only).
  2. Understand the blink.cmp completion pipeline

    main

    The plugin operates using a 4-stage pipeline to process completion requests:

    1. Trigger: Determines when to request items. It provides context downstream, including the current query (e.g., for hello.wo|, the query is wo) and the Treesitter object under the cursor. It also respects and includes trigger characters provided by the LSP.
    2. Sources: A common interface that merges results from various providers. It handles completion items, trigger characters, additional information resolution, and cancellation. Built-in sources include LSP, buffer, path, and snippets.
    3. Fuzzy: A Rust <-> Lua FFI layer responsible for filtering and sorting items.
      • Filtering: Uses a SIMD-accelerated Smith-Waterman algorithm (similar to FZF) for high-performance fuzzy matching that supports typos. It prioritizes prefix matches, capital letters (for camelCase/PascalCase), and matches after delimiters (for snake_case).
      • Sorting: Combines fuzzy matching scores with frecency and proximity bonuses. Completion items can include a score_offset to adjust their rank (e.g., the snippets source uses this to avoid outranking the LSP source).
    4. Windows: Manages the rendering of the completion menu, documentation, and function parameter windows. It uses Neovim's window decoration provider for efficient highlighting and allows for rendering overrides using a syntax similar to incline.nvim.
  3. Build blink.cmp 2.0.0

    main

    The build process and library paths have changed in version 2.0.0:

    • Build command: Instead of running cargo build --release manually, use the Lua API: require('blink.cmp').build():pwait().
    • Rust library path: The library path has moved from target/release/ to lib/.
    require('blink.cmp').build():pwait()
  4. Configure keymaps in blink.cmp

    main

    blink.cmp uses a per-mode keymap system. You can define mappings for Insert/Select mode (top-level keymap), Command-line mode (cmdline.keymap), and Terminal mode (terminal.keymap).

    Mappings consist of a key sequence mapped to a list of actions. Actions are executed sequentially. If an action returns false, nil, or '', execution continues to the next action. Any other return value stops execution.

    -- Insert/Select mode
    keymap = {
      preset = 'default',
    },
    -- Command-line mode
    cmdline = {
        keymap = {
            preset = 'cmdline'
        }
    }
    -- Terminal mode
    terminal = {
        keymap = {
            preset = 'terminal'
        }
    }
  5. Hide Copilot suggestions when blink.cmp menu is open

    main

    Use Neovim autocmds on BlinkCmpMenuOpen and BlinkCmpMenuClose to dismiss copilot.suggestion and toggle a visibility flag.

    vim.api.nvim_create_autocmd('User', {
      pattern = 'BlinkCmpMenuOpen',
      callback = function()
        require("copilot.suggestion").dismiss()
        vim.b.copilot_suggestion_hidden = true
      end,
    })
    
    vim.api.nvim_create_autocmd('User', {
      pattern = 'BlinkCmpMenuClose',
      callback = function()
        vim.b.copilot_suggestion_hidden = false
      end,
    })
  6. Install blink.cmp using lazy.nvim

    main

    To install blink.cmp using lazy.nvim, add it to your plugin list with saghen/blink.lib as a dependency. You must include a build function that calls require('blink.cmp').build():pwait() to compile the fuzzy matcher. You can optionally add rafamadriz/friendly-snippets for snippet support.

    Note: You can use the gb command within :Lazy to rebuild the plugin if necessary.

    {
      'saghen/blink.cmp',
      dependencies = {
        'saghen/blink.lib',
        -- optional: provides snippets for the snippet source
        'rafamadriz/friendly-snippets',
      },
      build = function()
        -- build the fuzzy matcher, optionally add a timeout to `pwait(timeout_ms)`
        -- you can use `gb` in `:Lazy` to rebuild the plugin as needed
        require('blink.cmp').build():pwait()
      end,
    
      ---@module 'blink.cmp'
      ---@type blink.cmp.Config
      opts = {
        -- 'default' (recommended) for mappings similar to built-in completions (C-y to accept)
        -- 'super-tab' for mappings similar to vscode (tab to accept)
        -- 'enter' for enter to accept
        -- 'none' for no mappings
        keymap = { preset = 'default' },
    
        -- (Default) Only show the documentation popup when manually triggered
        completion = { documentation = { auto_show = false } },
    
        -- (Default) list of enabled providers defined so that you can extend it
        sources = { default = { 'lsp', 'path', 'snippets', 'buffer' } },
    
        -- (Default) Rust fuzzy matcher for typo resistance and significantly better performance
        fuzzy = { implementation = 'rust' }
      },
    }
  7. Use mini.snippets with blink.cmp

    main

    To use mini.snippets as your snippet engine, set the snippets.preset to 'mini_snippets' and ensure mini.snippets is a dependency.

    {
      'saghen/blink.cmp',
      dependencies = { 'echasnovski/mini.snippets' },
      opts = {
        snippets = { preset = 'mini_snippets' },
        -- ensure you have the `snippets` source (enabled by default)
        sources = {
          default = { 'lsp', 'path', 'snippets', 'buffer' },
        },
      }
    }
  8. Avoid multi-line completion ghost text overlap

    main

    If completion.ghost_text.enabled is true, you can prevent the menu from overlapping multi-line ghost text by providing a custom completion.menu.direction_priority function that checks if the selected item's text contains a newline.

    completion = {
      menu = {
        direction_priority = function()
          local ctx = require('blink.cmp').get_context()
          local item = require('blink.cmp').get_selected_item()
          if ctx == nil or item == nil then return { 's', 'n' } end
    
          local item_text = item.textEdit ~= nil and item.textEdit.newText or item.insertText or item.label
          local is_multi_line = item_text:find('\n') ~= nil
    
          if is_multi_line or vim.g.blink_cmp_upwards_ctx_id == ctx.id then
            vim.g.blink_cmp_upwards_ctx_id = ctx.id
            return { 'n', 's' }
          end
          return { 's', 'n' }
        end,
      },
    },
  9. Show the cmdline completion menu automatically

    main

    By default, the completion menu does not show automatically in cmdline mode. You can enable it using cmdline.completion.menu.auto_show = true.

    It is recommended to update your <Tab> keymap to {'show', 'accept'} when enabling auto-show to ensure smooth interaction.

    cmdline = {
      keymap = {
        -- recommended, as the default keymap will only show and select the next item
        ['<Tab>'] = { 'show', 'accept' },
      },
      completion = { menu = { auto_show = true } },
    }
  10. Disable all snippets in blink.cmp

    main

    To completely remove snippets from your completion results, you can use sources.transform_items to filter out items where the kind matches CompletionItemKind.Snippet.

    Additionally, you should inform your LSP that you do not support snippets by configuring capabilities via blink.cmp.get_lsp_capabilities.

    -- Filter items in the completion menu
    sources.transform_items = function(_, items)
      return vim.tbl_filter(function(item)
        return item.kind ~= require('blink.cmp.types').CompletionItemKind.Snippet
      end, items)
    end
    
    -- Inform LSP of no snippet support
    capabilities = require('blink.cmp').get_lsp_capabilities({
      textDocument = { completion = { completionItem = { snippetSupport = false } } },
    })
  11. Add custom VSCode-style snippets

    main

    Blink automatically searches ~/.config/nvim/snippets/ for custom VSCode-style snippets. To add additional directories, use sources.providers.snippets.opts.search_paths.

    Custom snippets require a package.json in your snippets directory to map languages to JSON files.

    Example structure:

    • ~/.config/nvim/snippets/package.json (defines the mapping)
    • ~/.config/nvim/snippets/lua.json (contains the actual snippet definitions)
    // ~/.config/nvim/snippets/package.json
    {
      "name": "personal-snippets",
      "contributes": {
        "snippets": [
          { "language": "lua", "path": "./lua.json" }
          { "language": ["typescriptreact", "javascriptreact"], "path": "./react.json" }
          { "language": "all", "path": "./all.json" }
        ]
      }
    }
    
    // ~/.config/nvim/snippets/lua.json
    {
      "foo": {
        "prefix": "foo",
        "body": [
          "local ${1:foo} = ${2:bar}",
          "return ${3:baz}"
        ]
      }
    }