Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to trigger autocomplete on InsertEnter only for a single source (e.g., Copilot)? #1197

Closed
serhez opened this issue Sep 27, 2022 · 7 comments

Comments

@serhez
Copy link

serhez commented Sep 27, 2022

I am trying to use Copilot with nvim-cmp and my current problem is that, after writing a comment suggesting what I want Copilot to do, I still have to enter a new line and write at least one character to trigger the nvim-cmp autocompletion engine which allows me to autocomplete with Copilot.

To solve this, I have attempted to use this bit of config:

completion = {
	autocomplete = {
		cmp.TriggerEvent.TextChanged,
		cmp.TriggerEvent.InsertEnter,
	},
	completeopt = "menuone,noinsert,noselect",
	keyword_length = 0,
}

However, this triggers autocompletion on InsertEnter for all sources, so it is quite annoying. Is there any way to only trigger on the InsertEnter event when Copilot has suggestions?

Thanks!

@fnix2
Copy link

fnix2 commented Sep 29, 2022

Something like that:

sources = cmp.config.sources(
  {
    { name = 'nvim_lsp', keyword_length = 3 },
    { name = 'buffer', keyword_length = 5 },
  }),

@serhez
Copy link
Author

serhez commented Oct 1, 2022

Thank for the suggestion @fnix2! However, this is not working for me. For example, when I write a comment (which Copilot uses to generate completion suggestions) and I start a new line below, the completion is not triggered. If I then write a letter, then the completion is triggered for all completion sources; if next, I remove that letter and have an empty line again, then only Copilot suggestions are shown. Hence, I have some indication that setting keyword_length = 0 for a single source works in some cases, but it seems that you have to still write something before to trigger it.

Here are some relevant parts of my config:

completion = {
  autocomplete = {
    cmp.TriggerEvent.TextChanged,
    cmp.TriggerEvent.InsertEnter,
  },
  completeopt = "menuone,noinsert,noselect",
  keyword_length = 1,
}
sources = {
  { name = "copilot", keyword_length = 0 },
  { name = "luasnip" },
  { name = "nvim_lsp" },
  { name = "buffer" },
  { name = "path" },
  { name = "dap" },
},

I suppose this is not working as intended?

@Shougo
Copy link

Shougo commented Oct 2, 2022

Hm... It is reproduced for me. I don't know why.

@hrsh7th
Copy link
Owner

hrsh7th commented Oct 2, 2022

If you only want to enable single source and perform completion in InsertEnter , you'll have to do it yourself.

nvim-cmp does not implement features for all special situations. Instead, it provides an API.

And nvim-cmp does not support keyword_length=0 now. PR welcome.

autocmd InsertEnter * call s:on_insert_enter()
function! s:on_insert_enter()
lua <<EOF
  vim.schedule(function()
    local cmp = require('cmp')
    cmp.complete({
      config = {
        sources = {
          { name = 'buffer' }
        }
      }
    })
  end)
EOF
endfunction

@hrsh7th hrsh7th closed this as completed Oct 2, 2022
@benbrastmckie
Copy link

...when I write a comment (which Copilot uses to generate completion suggestions) and I start a new line below, the completion is not triggered. If I then write a letter, then the completion is triggered for all completion sources

I'm jealous, this is just the behaviour that I'm looking for, but can't seem to achieve even after lots of searching.

What I'd like is for cmp to only autocomplete after I start typing the first character, and not immediately upon creating a new line. It would seem that all I need to do is to include the following:

cmp.setup {
  completion = {
    autocomplete = {
      cmp.TriggerEvent.TextChanged,
      cmp.TriggerEvent.InsertEnter,
    },
    completeopt = "menuone,noinsert,noselect",
    keyword_length = 1,
  },
}

Right now, everything works great with one exception: if I create a new line, then the autocomplete menu opens even before I start typing. This only happens if I'm inside a function like cmp.setup above. If I comment out the TextChanged line, then there is no autocompletion at all. Any help would be much appreciated.

@serhez
Copy link
Author

serhez commented Nov 22, 2022

@benbrastmckie is this black magic? I have the same configuration as you mention, yet my problem is the opposite: it does not trigger on new lines. Would you mind sharing your complete cmp config to compare?

@benbrastmckie
Copy link

Sure thing. Would love any thoughts if you can places to make improvements.

local cmp_status_ok, cmp = pcall(require, "cmp")
if not cmp_status_ok then
  return
end

local snip_status_ok, luasnip = pcall(require, "luasnip")
if not snip_status_ok then
  return
end

require("luasnip/loaders/from_vscode").lazy_load()

local check_backspace = function()
  local col = vim.fn.col "." - 1
  return col == 0 or vim.fn.getline("."):sub(col, col):match "%s"
end

--   פּ ﯟ   some other good icons
local kind_icons = {
  Text = "",
  Method = "m",
  Function = "",
  Constructor = "",
  Field = "",
  Variable = "",
  Class = "",
  Interface = "",
  Module = "",
  Property = "",
  Unit = "",
  Value = "",
  Enum = "",
  Keyword = "",
  Snippet = "",
  Color = "",
  File = "",
  Reference = "",
  Folder = "",
  EnumMember = "",
  Constant = "",
  Struct = "",
  Event = "",
  Operator = "",
  TypeParameter = "",
}
-- find more here: https://www.nerdfonts.com/cheat-sheet

cmp.setup {
	-- preselect = cmp.PreselectMode.None,
  completion = {
    autocomplete = {
      cmp.TriggerEvent.TextChanged,
      cmp.TriggerEvent.InsertEnter,
    },
    completeopt = "menuone,noinsert,noselect",
    keyword_length = 1,
  },
  snippet = {
    expand = function(args)
      luasnip.lsp_expand(args.body) -- For `luasnip` users.
    end,
  },
  mapping = {
    ["<C-k>"] = cmp.mapping.select_prev_item(),
		["<C-j>"] = cmp.mapping.select_next_item(),
    ["<C-b>"] = cmp.mapping(cmp.mapping.scroll_docs(-1), { "i", "c" }),
    ["<C-f>"] = cmp.mapping(cmp.mapping.scroll_docs(1), { "i", "c" }),
    -- ["<C-Space>"] = cmp.mapping(cmp.mapping.complete(), { "i", "c" }),
    -- ["<C-y>"] = cmp.config.disable, -- Specify `cmp.config.disable` if you want to remove the default `<C-y>` mapping.
    ["<C-e>"] = cmp.mapping {
      i = cmp.mapping.abort(),
      c = cmp.mapping.close(),
    },

    -- Accept currently selected item. If none selected, `select` first item.
    -- Set `select` to `false` to only confirm explicitly selected items.
    ["<CR>"] = cmp.mapping.confirm { select = false },
    ["<Tab>"] = cmp.mapping(function(fallback)
      -- if cmp.visible() then
      --   cmp.select_next_item()
      if luasnip.expandable() then
        luasnip.expand()
      elseif luasnip.expand_or_jumpable() then
        luasnip.expand_or_jump()
      elseif check_backspace() then
        fallback()
      else
        fallback()
      end
    end, {
      "i",
      "s",
    }),
    ["<S-Tab>"] = cmp.mapping(function(fallback)
      -- if cmp.visible() then
      --   cmp.select_prev_item()
      if luasnip.jumpable(-1) then
        luasnip.jump(-1)
      else
        fallback()
      end
    end, {
      "i",
      "s",
    }),
  },
  formatting = {
    fields = { "kind", "abbr", "menu" },
    format = function(entry, vim_item)
      -- Kind icons
      vim_item.kind = string.format("%s", kind_icons[vim_item.kind])
      -- vim_item.kind = string.format('%s %s', kind_icons[vim_item.kind], vim_item.kind) -- This concatonates the icons with the name of the item kind
      vim_item.menu = ({
        nvim_lsp = "[LSP]",
        luasnip = "[Snippet]",
        buffer = "[Buffer]",
        path = "[Path]",
        cmdline = "[LSP]",
      })[entry.source.name]
      return vim_item
    end,
  },
  sources = {
    { name = "nvim_lsp" },
    { name = "luasnip" },
    { name = "buffer" },
    -- { name = "dictionary" },
    { name = "path" },
    { name = "cmdline" },
    { name = "lua-latex-symbols",
      option = { cache = true },
      filetype = { "tex", "latex" },
    }
      -- The `cache` option is used to determine whether to generate the list of symbols every time you start Neovim, or if it should be stored in a cache file to save time. I strongly do not advise changing this option because the data used for this plugin has not been updated since 2011.
    -- { name = "dictionary" },
  },
  confirm_opts = {
    behavior = cmp.ConfirmBehavior.Replace,
    select = false,
  },
  window = {
    documentation = {
      border = { "╭", "─", "╮", "│", "╯", "─", "╰", "│" },
    },
  },
  -- experimental = {
  --   ghost_text = true,
  --   native_menu = false,
  -- },
}


-- TODO was trying to get <C-j>, <C-k> to work in the command line
-- cmp-cmdline
-- `/` cmdline setup.
cmp.setup.cmdline('/', {
  mapping = cmp.mapping.preset.cmdline({
    -- ['<C-j>'] = cmp.mapping(cmp.mapping.select_next_item()),
    -- ['<C-k>'] = cmp.mapping(cmp.mapping.select_prev_item()),
    -- ['<C-y>'] = cmp.config.disable, -- Specify `cmp.config.disable` if you want to remove the default `<C-y>` mapping.
  }),
  sources = {
    { name = 'buffer' }
  }
})

-- `:` cmdline setup.
cmp.setup.cmdline(':', {
  mapping = cmp.mapping.preset.cmdline({
    -- ['<C-j>'] = cmp.mapping(cmp.mapping.select_next_item()),
    -- ['<C-k>'] = cmp.mapping(cmp.mapping.select_prev_item()),
    -- ['<C-y>'] = cmp.config.disable, -- Specify `cmp.config.disable` if you want to remove the default `<C-y>` mapping.
  }),
  sources = cmp.config.sources({
    { name = 'path' }
  }, {
    { name = 'cmdline',
      option = {
        ignore_cmds = { 'Man', '!' }
      }
    },
    mapping = cmp.mapping.preset.cmdline({}), -- fixes supertab
  }),
})

I also came across some code that I haven't got to work yet:

api.nvim_create_autocmd(
  {"TextChangedI", "TextChangedP"},
  {
    callback = function()
      local line = vim.api.nvim_get_current_line()
      local cursor = vim.api.nvim_win_get_cursor(0)[2]

      local current = string.sub(line, cursor, cursor + 1)
      if current == "." or current == "," or current == " " then
        require('cmp').close()
      end

      local before_line = string.sub(line, 1, cursor + 1)
      local after_line = string.sub(line, cursor + 1, -1)
      if not string.match(before_line, '^%s+$') then
        if after_line == "" or string.match(before_line, " $") or string.match(before_line, "%.$") then
          require('cmp').complete()
        end
      end
  end,
  pattern = "*"
})

Hope that helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants