zbirenbaum / copilot-cmp

Lua plugin to turn github copilot into a cmp source
MIT License
1.13k stars 41 forks source link

Accepting copilot suggestion puts it at the beginning of line (no indentation) #45

Closed strdr4605 closed 1 year ago

strdr4605 commented 1 year ago

https://user-images.githubusercontent.com/16056918/215777757-445f3e49-7ca6-4c46-9bd1-9a601719c343.mov

Packer:

  use({
    "zbirenbaum/copilot-cmp",
    after = { "copilot.lua" },
    config = function()
      require("copilot_cmp").setup()
    end,
  })
  use({
    "zbirenbaum/copilot.lua",
    cmd = "Copilot",
    event = "InsertEnter",
    config = function()
      require("copilot").setup({
        suggestion = { enabled = false },
        panel = { enabled = false },
      })
    end,
  })

CMP config:

local cmp = require("cmp")
local luasnip = require("luasnip")

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

local has_words_before = function()
  if vim.api.nvim_buf_get_option(0, "buftype") == "prompt" then
    return false
  end
  local line, col = unpack(vim.api.nvim_win_get_cursor(0))
  return col ~= 0
      and vim.api
      .nvim_buf_get_text(0, line - 1, 0, line - 1, col, {})[1]
      :match("^%s*$")
      == nil
end

cmp.setup({
  snippet = {
    expand = function(args)
      luasnip.lsp_expand(args.body) -- For `luasnip` users.
    end,
  },
  mapping = {
    ["<C-Space>"] = cmp.mapping.complete(),
      -- config = { sources = { { name = "copilot", keyword_length = 0 } } },
    -- }),
    ["<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 = true }),
    ["<Tab>"] = vim.schedule_wrap(function(fallback)
      if cmp.visible() and has_words_before() then
        cmp.select_next_item({ behavior = cmp.SelectBehavior })
      else
        fallback()
      end
    end),
    ["<S-Tab>"] = vim.schedule_wrap(function(fallback)
      if cmp.visible() and has_words_before() then
        cmp.select_prev_item({ behavior = cmp.SelectBehavior })
      else
        fallback()
      end
    end),
  },
  sources = {
    { name = "copilot", keyword_length = 0 },
    { name = "luasnip" },
    { name = "nvim_lsp" },
    { name = "nvim_lua" },
    { name = "buffer" },
    { name = "path" },
  },
})

-- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
-- `/` cmdline setup.
cmp.setup.cmdline("/", {
  mapping = cmp.mapping.preset.cmdline(),
  sources = {
    { name = "buffer" },
  },
})
-- `:` cmdline setup.
cmp.setup.cmdline(":", {
  mapping = cmp.mapping.preset.cmdline(),
  sources = cmp.config.sources({
    { name = "path" },
  }, {
    { name = "cmdline" },
  }),
})
fjchen7 commented 1 year ago

Same issue here.

georgi commented 1 year ago

same for me

dongdongbh commented 1 year ago

Same here

lysandroc commented 1 year ago

Try to use cmp.SelectBehavior.Insert instead of cmp.SelectBehavior

dongdongbh commented 1 year ago

Tested both behavior = cmp.ConfirmBehavior.Replace and behavior = cmp.SelectBehavior.Insert not work.

ttbug commented 1 year ago

fixed with behavior = cmp.ConfirmBehavior.Replace, for me

Vinni-Cedraz commented 1 year ago

image As you can see in the picture above I was having the same issue. The first line of the suggestion was losing indentation and starting at the very beginning of the line.

behavior = cmp.Confirmbehavior.Replace works indeed but I had a hard time figuring out where to put it, so this here code here might be helpful to someone else. This is where to put it:

cmp.setup({
    snippet = {
        expand = function() end,
    },
    mapping = cmp.mapping.preset.insert({
        ["<C-right>"] = cmp.mapping.confirm({
            select = true,
            behavior = cmp.ConfirmBehavior.Replace,
        }),

How it looks like when fixed:

image

zbirenbaum commented 1 year ago

Did behavior = cmp.ConfirmBehavior.Replace work for everyone here? Indentation is extremely hard to get right, as there are so many different ways to configure indenting in neovim. If anyone is still having this issue and has confirmed they have behavior = cmp.ConfirmBehavior.Replace set properly in their cmp config, then please post the values of all indent related settings including:

strdr4605 commented 1 year ago

cmp.SelectBehavior.Replace not working for me

https://user-images.githubusercontent.com/16056918/221146769-e7a383f8-4d87-42b1-a669-970cc95fd2e3.mov


all indent related settings:

vim.opt.shiftwidth = 2 -- the number of spaces inserted for each indentation
vim.opt.expandtab = true -- convert tabs to spaces
vim.opt.tabstop = 2 -- insert 2 spaces for a tab
vim.opt.smartindent = true -- make indenting smarter again
dongdongbh commented 1 year ago

cmp.SelectBehavior.Replace not working for me

vim.opt.expandtab      = true                     
vim.opt.shiftwidth     = 2                        
vim.opt.tabstop        = 2                     
vim.opt.smarttab       = true
vim.opt.softtabstop    = 2
vim.opt.smartindent    = true            
indentexpr=nvim_treesitter#indent()
cindent=nocindent
zbirenbaum commented 1 year ago

cmp.SelectBehavior.Replace not working for me

vim.opt.expandtab      = true                     
vim.opt.shiftwidth     = 2                        
vim.opt.tabstop        = 2                     
vim.opt.smarttab       = true
vim.opt.softtabstop    = 2
vim.opt.smartindent    = true            
indentexpr=nvim_treesitter#indent()
cindent=nocindent

treesitter indentation is notoriously buggy and is very likely the problem here. I would definitely turn it off as a first step if you are having issues. If it's still anything like was when I last used it, I really wouldn't ever recommend turning it on.

dongdongbh commented 1 year ago

I disable treesitter indentation, then in a lua file, my indentexpr is indentexpr=GetLuaIndent(), and the problem still there; in a cpp file indentexpr=, and the problem persist

thallada commented 1 year ago

Also experiencing the issue even with indentexpr= set and with all variations of expandtab, smarttab, smartindent, and cindent on an off. Setting behavior = cmp.ConfirmBehavior.Replace or behavior = cmp.ConfirmBehavior.Insert does not affect it either.

accacin commented 1 year ago

Thanks for that code snippet, @Vinni-Cedraz. I'm using lazyvim and adding the following under lua/plugins/copilot.lua solved the indentation issue for me also. Pasting it below for anyone else who is having trouble with this issue.

return {
  -- copilot
  {
    "zbirenbaum/copilot.lua",
    cmd = "Copilot",
    build = ":Copilot auth",
    opts = {
      suggestion = { enabled = false },
      panel = { enabled = false },
    },
  },

  -- copilot cmp source
  {
    "nvim-cmp",
    dependencies = {
      {
        "zbirenbaum/copilot-cmp",
        dependencies = "copilot.lua",
        config = function(_, opts)
          local copilot_cmp = require("copilot_cmp")
          copilot_cmp.setup(opts)
          -- attach cmp source whenever copilot attaches
          -- fixes lazy-loading issues with the copilot cmp source
          require("lazyvim.util").on_attach(function(client)
            if client.name == "copilot" then
              copilot_cmp._on_insert_enter()
            end
          end)
        end,
      },
    },
    ---@param opts cmp.ConfigSchema
    opts = function(_, opts)
      local cmp = require("cmp")
      opts.snippet = vim.tbl_extend("force", opts.snippet, {
        expand = function() end,
      })
      opts.mapping = vim.tbl_extend("force", opts.mapping, {
        ["<CR>"] = cmp.mapping.confirm({
          select = true,
          behavior = cmp.ConfirmBehavior.Replace,
        }),
      })
      opts.sources = cmp.config.sources(vim.list_extend(opts.sources, { { name = "copilot" } }))
    end,
  },
}
thallada commented 1 year ago

@danbrownlow thanks for posting that snippet. Made me realize I was specifying cmp.ConfirmBehavior.Replace in the wrong place in my config. It needs to be on the <CR> mapping:

local cmp = require('cmp')

cmp.setup({
  ...
  mapping = cmp.mapping.preset.insert({
    ...
    ['<CR>'] = cmp.mapping.confirm({ select = true, behavior = cmp.ConfirmBehavior.Replace }),
    ...
  }),
})

^ adding behavior = cmp.ConfirmBehavior.Replace there fixes it for me, I was trying to add it to the <Tab> mapping before.

folke commented 1 year ago

@danbrownlow I just pushed an update to LazyVim for the copilot extra that does Replace but only for the confirming a completion from the copilot source.

See https://github.com/LazyVim/LazyVim/commit/079d3967d03d47b95bdec10bb2621db1c4a07dce

dongdongbh commented 1 year ago

I tried everything here and didn't solve my problem, here is my config copilot

require("copilot").setup({
  suggestion = {
    enabled = false,
    auto_trigger = false,
  },
  filetypes = {
    markdown = true, -- overrides default
    terraform = false, -- disallow specific filetype
  },
})

require("copilot_cmp").setup {
  method = "getCompletionsCycling",
  formatters = {
    insert_text = require("copilot_cmp.format").remove_existing
  },
}

cmp

cmp.setup({
  snippet = {
    expand = function(args)
      luasnip.lsp_expand(args.body) -- For `luasnip` users.
    end,
  },

  mapping = cmp.mapping.preset.insert({
    ["<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-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 = true, behavior = cmp.ConfirmBehavior.Replace }),
    ["<Tab>"] = cmp.mapping(function(fallback)
      -- if require("copilot.suggestion").is_visible() then
      --   require("copilot.suggestion").accept()
      if cmp.visible() and has_words_before() then
        cmp.select_next_item({ behavior = cmp.SelectBehavior.Replace })
      elseif 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()
      elseif luasnip.jumpable(-1) then
        luasnip.jump(-1)
      else
        fallback()
      end
    end, {
        "i",
        "s",
    }),
  }),
  formatting = {
    fields = { "kind", "abbr", "menu" },
    format = function(entry, vim_item)
      vim_item.kind = kind_icons[vim_item.kind]
      vim_item.menu = ({
        nvim_lsp = "",
        nvim_lua = "",
        luasnip = "",
        buffer = "",
        path = "",
        emoji = "",
      })[entry.source.name]
      return vim_item
    end,
  },
  sources = {
    {
      name = "copilot",
      max_item_count = 3,
      group_index = 2,
    },
    { name = "nvim_lsp" },
    { name = "neorg" },
    { name = "nvim_lua" },
    { name = "luasnip" },
    { name = "buffer" },
    { name = "path" },
  },
  sorting = {
    priority_weight = 2,
    comparators = {
      require("copilot_cmp.comparators").prioritize,
      require("copilot_cmp.comparators").score,

      -- Below is the default comparitor list and order for nvim-cmp
      cmp.config.compare.offset,
      -- cmp.config.compare.scopes, --this is commented in nvim-cmp too
      cmp.config.compare.exact,
      cmp.config.compare.score,
      cmp.config.compare.recently_used,
      cmp.config.compare.locality,
      cmp.config.compare.kind,
      cmp.config.compare.sort_text,
      cmp.config.compare.length,
      cmp.config.compare.order,
    },
  },
  -- confirm_opts = {
  --   behavior = cmp.ConfirmBehavior.Replace,
  --   select = false,
  -- },
  window = {
    completion = cmp.config.window.bordered(),
    documentation = cmp.config.window.bordered(),
  },
  experimental = {
    ghost_text = true,
  },
})
dongdongbh commented 1 year ago

Oh, I fixed it by upgrading the copilot-cmp and nvim-cmp plugin, I'm not sure which upgrading fixed it.

sqwxl commented 1 year ago

I've tried everything here, but I still have this issue.

Here's the relevant setup:

vim.opt.smartindent = true
vim.opt.breakindent = true
vim.opt.smarttab = true
vim.opt.expandtab = true
vim.opt.shiftwidth = 2
vim.opt.tabstop = 2
vim.opt.softtabstop = 2
local cmp_mappings = cmp.mapping.preset.insert{
      ["<C-p>"] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
      ["<C-n>"] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
      ["<C-u>"] = cmp.mapping.scroll_docs(-4),
      ["<C-d>"] = cmp.mapping.scroll_docs(4),
      ["<C-Space>"] = cmp.mapping.complete(),
      ["<C-e>"] = cmp.mapping.close(),
      ["<CR>"] = cmp.mapping.confirm({ behavior = cmp.ConfirmBehavior.Replace, select = true }),
      ["<Tab>"] = cmp.mapping(function(fallback)
      if cmp.visible() and has_words_before() then
        cmp.select_next_item({ behavior = cmp.SelectBehavior.Replace })
      elseif luasnip.expand_or_jumpable() then
        luasnip.expand_or_jump()
      else
        fallback() -- The fallback function sends a already mapped key. In this case, it's probably `<Tab>`.
      end
    end,
    { "i", "s" }),
      ["<S-Tab>"] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_prev_item({ behavior = cmp.SelectBehavior.Select })
      elseif luasnip.jumpable(-1) then
        luasnip.jump(-1)
      else
        fallback()
      end
    end,
    { "i", "s" }),
}

lazy.nvim plugin spec:

{
  "zbirenbaum/copilot-cmp",
  config = true,
  dependencies = {
    {
      "zbirenbaum/copilot.lua",
      event = "InsertEnter",
      opts = {
        suggestion = { enabled = false },
        panel = { enabled = false },
      },
    }
  }
}
patricklewis commented 1 year ago

I made the update suggested by @thallada and it fixed this issue for me.

Including behavior = cmp.ConfirmBehavior.Replace in the <CR> mapping of my nvim-cmp configuration was all I needed to do to fix this issue.

Thanks!

gshpychka commented 11 months ago

@zbirenbaum I see that you closed this issue, but I still have the problem with the following config:

["<CR>"] = cmp.mapping({
  i = function(fallback)
    if cmp.visible() and cmp.get_active_entry() then
      cmp.confirm({ behavior = cmp.ConfirmBehavior.Replace, select = false })
    else
      fallback()
    end
  end,
}),
zhimsel commented 5 months ago

@zbirenbaum (and anyone else), I'm also seeing the same behavior, even with the most recent versions of copilot-cmp and copilot.lua.

Relevant bits of my config:

xlucn commented 1 month ago

@zbirenbaum I believe I found the reason for at least some of the cases. It's related to shiftwidth.

In vim/neovim, shiftwidth=0 will make vim use the value of tabstop. Although I haven't written any vim/neovim plugins, it seems that the developer has to take care of the case shiftwidth=0 if they want to use this option. Currently, if shiftwidth=0, the following code: https://github.com/zbirenbaum/copilot-cmp/blob/b6e5286b3d74b04256d0a7e3bd2908eabec34b44/lua/copilot_cmp/format.lua#L91-L94 results in

user_indent = ""
indent_level = inf

Something like this works for me (replacing the code above):

  if user_indent == ' ' then
    local shiftwidth = vim.o.shiftwidth
    if shiftwidth == 0 then
      shiftwidth = vim.o.tabstop
    end
    user_indent = string.rep(' ', shiftwidth)
    indent_level = math.floor(indent_offset/shiftwidth)
  end

For those who still have the issue, if your current shiftwidth value is 0 (use :set shiftwidth? to check instead of looking into your config), and the indent is corrected after setting it to a positive value, then this must be the reason.