hrsh7th / cmp-cmdline

nvim-cmp source for vim's cmdline
MIT License
505 stars 41 forks source link

Expansion of % in cmdline has an extraneous % prepended #33

Open dlee opened 2 years ago

dlee commented 2 years ago

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.

fuadsaud commented 2 years ago

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.

dkarter commented 2 years ago

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?

alfaix commented 2 years ago

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.

kting28 commented 1 year ago

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

m42e commented 1 year ago

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.

chengzeyi commented 1 year ago

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' }
    })
})
atspaeth commented 11 months ago

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' },
    }
})
fuadsaud commented 10 months ago

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

b0o commented 8 months ago

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