cmp-cmdline icon indicating copy to clipboard operation
cmp-cmdline copied to clipboard

Expansion of % in cmdline has an extraneous % prepended

Open dlee opened this issue 2 years ago • 9 comments

Normally, when you type :e % and press tab, the % will expand to the current buffer's path. However, with cmp-cmdline, the % will expand to the path but with a % prepended to it.

Example:

touch myfile
nvim myfile
:e %<TAB>

Should expand to :e myfile but instead expands to :e %myfile.

dlee avatar Feb 17 '22 04:02 dlee

I can reproduce this. Also, sometimes, expanding % doesn't even do anything. I'm not sure how to reproduce that one because it doesn't happen all the time.

fuadsaud avatar Mar 11 '22 11:03 fuadsaud

I found that this keyword pattern can help prevent cmp from messing with the % sign:

[^[:blank:]%]*

 { name = 'cmdline', keyword_pattern = [=[[^[:blank:]%]*]=] },

It's similar to the solution to a different issue suggested by @RnYi in https://github.com/hrsh7th/cmp-cmdline/issues/24#issuecomment-1005662931

https://user-images.githubusercontent.com/551858/166122850-1f9bf2a2-8bc2-45cc-80bf-801c1775ac3b.mp4

It doesn't work however with %:h I suspect a different approach may be necessary. I played with an alternative: a positive regex (as in list the things that DO trigger a completion instead of those that shouldn't) with a negative lookbehind (to ensure the string didn't start with something we don't want to complete).

For example (?<!%:|%)[a-zA-Z0-9#_\.]+, but I'm not sure how to get this to work in Lua and Neovim

image

https://regex101.com/r/dCXdz7/1

This is the closest thing I could find for implementing the regex in vim https://stackoverflow.com/a/20409575

So it would look something like this in verymagic:

(\%)@<!([a-zA-Z0-9_\-# ]+)

Maybe I'm overthinking this and going about it all wrong though..

@hrsh7th any thoughts?

dkarter avatar May 01 '22 00:05 dkarter

Here's one alternative way to handle this, that also takes care of "%:h" and similar constructs. I use this function in cmp.setup.cmdline(":", ...)

local function delay(fn, time)
    local timer = vim.loop.new_timer()
    timer:start(time, 0, vim.schedule_wrap(function()
        fn()
        timer:stop()
    end))
end

function M.on_tab(fallback)
    local cmp = require"cmp"
    if vim.api.nvim_get_mode().mode == "c" then
        local text = vim.fn.getcmdline()
        local expanded = vim.fn.expandcmd(text)
        if expanded ~= text then
            vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-U>", true, true, true) .. expanded, "n", false)
            -- triggering right away won't work, need to wait a cycle
            delay(cmp.complete, 0)
        elseif cmp.visible() then
            cmp.select_next_item()
        else
            cmp.complete()
        end
    end -- in the real mapping there are other elseif clauses
end

What this does is it expands the commandline on tab and then triggers the completion. It will reset your cursor position to the end of the line, but if that annoys you, you can play with vim.fn.setcmdpos() and expansion rules.

This is just another workaround though.

alfaix avatar Jun 30 '22 16:06 alfaix

couldn't get the workarounds above working somehow. any hints on resolving this?

kting28 avatar Feb 18 '23 17:02 kting28

I am corrently experiencing this, too. And I have a currently not well functionality, that avoids completion in case of a path-expansion-thingy

I do not know why, but currently it is not showing the provided completion options, maybe I am missing something, you can ha ve a look here. https://github.com/m42e/cmp-cmdline

Maybe @hrsh7th give me a hint.

m42e avatar Feb 24 '23 09:02 m42e

couldn't get the workarounds above working somehow. any hints on resolving this?

I tried this, it works!

local function delay(fn, time)
    local timer = vim.loop.new_timer()
    timer:start(time, 0, vim.schedule_wrap(function()
        fn()
        timer:stop()
    end))
end

cmd_mapping = cmp.mapping.preset.cmdline()
cmd_mapping_override = {
    ['<Tab>'] = {
        c = function()
            if vim.api.nvim_get_mode().mode == "c" and cmp.get_selected_entry() == nil then
                local text = vim.fn.getcmdline()
                local expanded = vim.fn.expandcmd(text)
                if expanded ~= text then
                    vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-U>", true, true, true) .. expanded, "n", false)
                    -- triggering right away won't work, need to wait a cycle
                    delay(cmp.complete, 0)
                elseif cmp.visible() then
                    cmp.select_next_item()
                else
                    cmp.complete()
                end
            else
                if cmp.visible() then
                    cmp.select_next_item()
                else
                    cmp.complete()
                end
            end -- in the real mapping there are other elseif clauses
        end,
    },
    ['<S-Tab>'] = {
        c = function()
            if vim.api.nvim_get_mode().mode == "c" and cmp.get_selected_entry() == nil then
                local text = vim.fn.getcmdline()
                local expanded = vim.fn.expandcmd(text)
                if expanded ~= text then
                    vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-U>", true, true, true) .. expanded, "n", false)
                    -- triggering right away won't work, need to wait a cycle
                    delay(cmp.complete, 0)
                elseif cmp.visible() then
                    cmp.select_prev_item()
                else
                    cmp.complete()
                end
            else
                if cmp.visible() then
                    cmp.select_prev_item()
                else
                    cmp.complete()
                end
            end
        end
    }
}
for k, v in pairs(cmd_mapping_override) do
    cmd_mapping[k] = v
end

-- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
cmp.setup.cmdline(':', {
    mapping = cmd_mapping,
    sources = cmp.config.sources({
        { name = 'cmdline' },
        { name = 'path' },
        { name = 'cmdline_history' }
    })
})

chengzeyi avatar Jun 16 '23 03:06 chengzeyi

My workaround: life is very simple if you like to use <C-n> and <C-p> to select completion options. You can just delete the tab binding and the nvim builtin behavior continues to work. 🙂

local cmdline_mapping = cmp.mapping.preset.cmdline{ ... }
cmdline_mapping['<Tab>'] = nil

cmp.setup.cmdline({':'}, {
    mapping = cmdline_mapping,
    sources = cmp.config.sources({
        { name = 'cmdline' },
    }
})

atspaeth avatar Jul 27 '23 17:07 atspaeth

I have a feeling this issue is related to this other one as it only seems to manifest itself after doing a search using /.

fuadsaud avatar Sep 01 '23 22:09 fuadsaud

Thanks @chengzeyi for this! I've simplified it a bit with a helper function:

local function delay(cb, time)
  local timer = vim.loop.new_timer()
  timer:start(
    time,
    0,
    vim.schedule_wrap(function()
      cb()
      timer:stop()
    end)
  )
end

local function handle_tab_complete(direction)
  return function()
    if vim.api.nvim_get_mode().mode == 'c' and cmp.get_selected_entry() == nil then
      local text = vim.fn.getcmdline()
      ---@diagnostic disable-next-line: param-type-mismatch
      local expanded = vim.fn.expandcmd(text)
      if expanded ~= text then
        vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-U>', true, true, true) .. expanded, 'n', false)
        -- triggering right away won't work, need to wait a cycle
        delay(cmp.complete, 0)
      elseif cmp.visible() then
        direction()
      else
        cmp.complete()
      end
    else
      if cmp.visible() then
        direction()
      else
        cmp.complete()
      end
    end
  end
end

cmp.setup.cmdline(':', {
  mapping = cmp.mapping.preset.cmdline {
    ['<Tab>'] = { c = handle_tab_complete(cmp.select_next_item) },
    ['<S-Tab>'] = { c = handle_tab_complete(cmp.select_prev_item) },
    -- ... rest of your mappings ...
  },
  -- ... rest of your config ...
}

Also, the delay doesn't seem necessary to me, this seems to work as well:

local function handle_tab_complete(direction)
  return function()
    if vim.api.nvim_get_mode().mode == 'c' and cmp.get_selected_entry() == nil then
      local text = vim.fn.getcmdline()
      ---@diagnostic disable-next-line: param-type-mismatch
      local expanded = vim.fn.expandcmd(text)
      if expanded ~= text then
        vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-U>', true, true, true) .. expanded, 'n', false)
        cmp.complete()
      elseif cmp.visible() then
        direction()
      else
        cmp.complete()
      end
    else
      if cmp.visible() then
        direction()
      else
        cmp.complete()
      end
    end
  end
end

b0o avatar Nov 06 '23 00:11 b0o