blink.cmp icon indicating copy to clipboard operation
blink.cmp copied to clipboard

Tailwindcss/emmet_ls is breaking vtsls suggestions

Open AlejandroSanchez90 opened this issue 1 month ago • 2 comments

Make sure you have done the following

  • [x] Updated to the latest version of blink.cmp
  • [x] Searched for existing issues and documentation (try <C-k> on https://cmp.saghen.dev)

Bug Description

hey just started using vtsls, and found that after i place (/{/[ and type anything i would only get snippets from the cmp menu, i have to go to normal and back to insert mode and type to be able to see my other context, i disabled tailwindcss, and it did fix the issue, is there a workaround for this? edit: also emmet_ls

Relevant configuration

-- Run with `nvim -u repro.lua`
--
-- Please update the code below to reproduce your issue and send the updated code, with reproduction
--  steps, in your issue report
--
-- If you get warnings about prebuilt binaries, you may use `fuzzy.implementation = 'lua'`
--  but note this has caveats: https://cmp.saghen.dev/configuration/fuzzy#rust-vs-lua-implementation
vim.env.LAZY_STDPATH = '.repro'
load(vim.fn.system 'curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua')()
---@diagnostic disable-next-line: missing-fields
require('lazy.minit').repro {
  spec = {
    {
      'saghen/blink.cmp',
      version = '1.*',
      dependencies = {
        {
          'L3MON4D3/LuaSnip',
          version = '2.*',
          build = function()
            if vim.fn.has 'win32' == 1 or vim.fn.executable 'make' == 0 then
              return
            end
            return 'make install_jsregexp'
          end,
          dependencies = {
            'rafamadriz/friendly-snippets',
          },
        },
      },
      opts = {
        keymap = {
          preset = 'none',
          ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' },
          ['<C-e>'] = { 'hide', 'fallback' },
          ['<C-y>'] = { 'accept', 'fallback' },
          ['<Tab>'] = { 'snippet_forward', 'fallback' },
          ['<S-Tab>'] = { 'snippet_backward', 'fallback' },
          ['<Up>'] = { 'select_prev', 'fallback' },
          ['<Down>'] = { 'select_next', 'fallback' },
          ['<C-p>'] = { 'select_prev', 'fallback_to_mappings' },
          ['<C-n>'] = { 'select_next', 'fallback_to_mappings' },
          ['<C-b>'] = { 'scroll_documentation_up', 'fallback' },
          ['<C-f>'] = { 'scroll_documentation_down', 'fallback' },
          ['<C-k>'] = { 'show_signature', 'hide_signature', 'fallback' },
        },
        appearance = {
          nerd_font_variant = 'mono',
        },
        completion = {
          menu = {
            draw = {
              treesitter = { 'lsp' },
              columns = {
                { 'label', gap = 2 },
                { 'kind_icon', gap = 1, 'kind' },
              },
            },
          },
          documentation = {
            auto_show = true,
            auto_show_delay_ms = 200,
            window = {
              max_width = math.floor(vim.o.columns * 0.4),
              max_height = math.floor(vim.o.lines * 0.5),
            },
          },
          accept = {
            auto_brackets = {
              enabled = false,
            },
          },
        },
        signature = { enabled = true },
        snippets = { preset = 'luasnip' },
        sources = {
          default = { 'lsp', 'path', 'snippets', 'buffer' },
        },
        fuzzy = { implementation = 'prefer_rust_with_warning' },
      },
    },
    {
      'neovim/nvim-lspconfig',
      dependencies = {
        'j-hui/fidget.nvim',
      },
      config = function()
        -- LSP Attach autocmd with your keymaps
        vim.api.nvim_create_autocmd('LspAttach', {
          group = vim.api.nvim_create_augroup('lsp-attach', { clear = true }),
          callback = function(event)
            local map = function(keys, func, desc, mode)
              mode = mode or 'n'
              vim.keymap.set(mode, keys, func, { buffer = event.buf, desc = 'LSP: ' .. desc })
            end
            map('<leader>rn', vim.lsp.buf.rename, '[R]e[n]ame')
            map('<leader>d', vim.diagnostic.open_float, 'Show Line Diagnostic')
            map('[d', function()
              vim.diagnostic.jump { count = -1, float = true }
            end, 'Previous Diagnostic')
            map(']d', function()
              vim.diagnostic.jump { count = 1, float = true }
            end, 'Next Diagnostic')
            map('K', vim.lsp.buf.hover, 'Show hover')
            map('<leader>ca', vim.lsp.buf.code_action, '[C]ode [A]ction', { 'n', 'x' })
          end,
        })
        -- Diagnostic config
        vim.diagnostic.config {
          severity_sort = true,
          float = { border = 'rounded', source = 'if_many' },
          underline = { severity = vim.diagnostic.severity.ERROR },
          virtual_text = false,
        }
        -- Setup lua_ls with your config
        require('lspconfig').lua_ls.setup {
          settings = {
            Lua = {
              completion = {
                callSnippet = 'Replace',
              },
            },
          },
        }
      end,
    },
    {
      'mason-org/mason.nvim',
      build = ':MasonUpdate',
      opts = {
        ui = {
          icons = {
            package_installed = '✓',
            package_pending = '➜',
            package_uninstalled = '✗',
          },
        },
      },
    },
    {
      'mason-org/mason-lspconfig.nvim',
      dependencies = { 'mason-org/mason.nvim', 'neovim/nvim-lspconfig' },
      opts = {
        ensure_installed = { 'lua_ls', 'emmet_language_server', 'tailwindcss', 'vtsls' },
      },
    },
  },
}

neovim version

0.11.4

blink.cmp version

1.*

AlejandroSanchez90 avatar Nov 25 '25 14:11 AlejandroSanchez90

Please provide a repro. Or at least some materials like repro steps, video or code source to see what's going on.

soifou avatar Nov 25 '25 16:11 soifou

Please provide a repro. Or at least some materials like repro steps, video or code source to see what's going on.

sorry i didnt see that you asked for repro, just updated my post with it

AlejandroSanchez90 avatar Nov 26 '25 00:11 AlejandroSanchez90

any updates on this ?

AlejandroSanchez90 avatar Dec 03 '25 00:12 AlejandroSanchez90

Actually could you share a video and the source file as an example so I can try to reproduce the issue? I'm not sure I understand the problem :smile:, thanks!

soifou avatar Dec 03 '25 08:12 soifou

Actually could you share a video and the source file as an example so I can try to reproduce the issue? I'm not sure I understand the problem :smile:, thanks!

Using the config above, create a .ts file, then inside create a variable lets say const test = 'testing'; then in a new line Open parenthesis, or any symbol will do it, and try to type part of the variable 'te', you wont get any suggestions, if you try this with no symbol at the start it does give you correct suggestions, also i have noticed that signatures are not appearing too

AlejandroSanchez90 avatar Dec 03 '25 11:12 AlejandroSanchez90

It's likely due to vtsls returning no results on ( and then we don't query the language server again afterwards (while nvim-cmp for example will query again on the next char). This issue only seems to affect javascript-related LSPs (vtsls, vue, svelte, ...). We should probably change the default behavior just to close a bunch of these bugs, but in v2, I'd like to make this a per-LSP setting since the spec makes no mention of this afaict.

saghen avatar Dec 03 '25 19:12 saghen

When using tailwindcss+vtsls, we merge the trigger chars from both LSPs:

{
  client = "tailwindcss",
  trigger_chars = { '"', "'", "`", " ", ".", "(", "[", "]", "!", "/", "-", ":" }
}
{
  client = "vtsls",
  trigger_chars = { ".", '"', "'", "`", "/", "@", "<", "#", " ", "*" }
}

That's why we request completion on ( or [ for vtsls using this combo although not explicitly specified by vstls itself. And it explains why it works as single use.

I'm not sure how to tackle this, not the best solution, but a logical workaround would be:

diff --git a/lua/blink/cmp/sources/lsp/cache.lua b/lua/blink/cmp/sources/lsp/cache.lua
index 839d99b..e09dc6b 100644
--- a/lua/blink/cmp/sources/lsp/cache.lua
+++ b/lua/blink/cmp/sources/lsp/cache.lua
@@ -12,7 +12,7 @@ function cache.get(context, client)
   local entry = cache.entries[client.id]
   if entry == nil then return end
 
-  if context.id ~= entry.context.id then return end
+  if context.id ~= entry.context.id or context.line ~= entry.context.line then return end
   if entry.response.is_incomplete_forward and entry.context.cursor[2] ~= context.cursor[2] then return end
   if not entry.response.is_incomplete_forward and entry.context.cursor[2] > context.cursor[2] then return end
 

soifou avatar Dec 03 '25 20:12 soifou

It's likely due to vtsls returning no results on ( and then we don't query the language server again afterwards (while nvim-cmp for example will query again on the next char). This issue only seems to affect javascript-related LSPs (vtsls, vue, svelte, ...). We should probably change the default behavior just to close a bunch of these bugs, but in v2, I'd like to make this a per-LSP setting since the spec makes no mention of this afaict.

why does it work fine when i use typescript-tools ? is there a diference ?

AlejandroSanchez90 avatar Dec 04 '25 16:12 AlejandroSanchez90