hrsh7th / cmp-cmdline

nvim-cmp source for vim's cmdline
MIT License
547 stars 42 forks source link

Can't cycle through history with Ctrl-N and Ctrl-P in command-line mode #108

Open yangmillstheory opened 8 months ago

yangmillstheory commented 8 months ago

Hello,

I can't use Ctrl-P to cycle through my ex-command history, I think due to some poor interaction with this plugin. For example, when type : and then repeatedly hit Ctrl-P, this happens:

https://github.com/hrsh7th/cmp-cmdline/assets/2729079/7b7b205e-33fe-4368-ad67-f3fc0b5cf534

When I comment out the cmdline source (see the top right pane), instead I can do:

https://github.com/hrsh7th/cmp-cmdline/assets/2729079/3ac3de56-52e1-4156-b97b-a333cb7a19a9

Here's some information about my setup:

:set
--- Options ---
  cmdheight=0         cursorcolumn        filetype=lua        lines=63            relativenumber    noshowmode            suffixesadd=.lua    termguicolors
  columns=166         cursorline          helplang=en       noloadplugins         scroll=10           showtabline=2     noswapfile            window=62
  comments=:--        expandtab           inccommand=split    number              shiftwidth=2        softtabstop=2       tabstop=2         nowrap
  commentstring=-- %s
  completeopt=menu,menuone,noselect
  define=\<function\|\<local\%(\s\+function\)\=
  fileencoding=utf-8
  formatexpr=v:lua.vim.lsp.formatexpr
  formatoptions=jcroql
  includeexpr=tr(v:fname,'.','/')
  indentexpr=GetLuaIndent()
  indentkeys=0{,0},0),0],:,0#,!^F,o,O,e,0=end,0=until
  omnifunc=v:lua.vim.lsp.omnifunc
  operatorfunc=<SNR>14_go
  packpath=~/code/nvim-macos/share/nvim/runtime
  runtimepath=~/.config/nvim,~/.local/share/nvim/lazy/lazy.nvim,~/.local/share/nvim/lazy/indent-blankline.nvim,~/.local/share/nvim/lazy/fzf,~/.local/share/nvim/lazy/v
im-obsession,~/.local/share/nvim/lazy/cmp-nvim-lsp,~/.local/share/nvim/lazy/lspkind.nvim,~/.local/share/nvim/lazy/trouble.nvim,~/.local/share/nvim/lazy/nvim-cmp,~/.lo
cal/share/nvim/lazy/vim-vsnip,~/.local/share/nvim/lazy/nvim-treesitter,~/.local/share/nvim/lazy/vim-tmux-navigator,~/.local/share/nvim/lazy/vim-commentary,~/.local/sh
are/nvim/lazy/cmp-path,~/.local/share/nvim/lazy/nvim-lspconfig,~/.local/share/nvim/lazy/bufferline.nvim,~/.local/share/nvim/lazy/cmp-buffer,~/.local/share/nvim/lazy/c
mp-vsnip,~/.local/share/nvim/lazy/nvim-surround,~/.local/share/nvim/lazy/vim-move,~/.local/share/nvim/lazy/cmp-cmdline,~/.local/share/nvim/lazy/kanagawa.nvim,~/.local
/share/nvim/lazy/cmp-emoji,~/.local/share/nvim/lazy/nvim-autopairs,~/.local/share/nvim/lazy/cmp-nvim-lua,~/.local/share/nvim/lazy/nvim-web-devicons,~/.local/share/nvi
m/lazy/lualine.nvim,~/.local/share/nvim/lazy/vim-snipe,~/.local/share/nvim/lazy/fzf.vim,~/.local/share/nvim/lazy/vim-tmux,~/code/nvim-macos/share/nvim/runtime,~/code/
nvim-macos/share/nvim/runtime/pack/dist/opt/matchit,~/code/nvim-macos/lib/nvim,~/.local/share/nvim/lazy/indent-blankline.nvim/after,~/.local/share/nvim/lazy/cmp-nvim-
lsp/after,~/.local/share/nvim/lazy/cmp-path/after,~/.local/share/nvim/lazy/cmp-buffer/after,~/.local/share/nvim/lazy/cmp-vsnip/after,~/.local/share/nvim/lazy/cmp-cmdl
ine/after,~/.local/share/nvim/lazy/cmp-emoji/after,~/.local/share/nvim/lazy/cmp-nvim-lua/after,~/.config/nvim/after,~/.local/state/nvim/lazy/readme
  shortmess=linFTotfOcx
  spelloptions=noplainbuffer
  statusline=%#lualine_a_command# COMMAND %#lualine_transitional_lualine_a_command_to_lualine_b_command#%#lualine_b_command# [$] %#lualine_transitional_lualine_b_com
mand_to_lualine_c_normal#%<%#lualine_c_normal# nvim/.config/nvim/lua/settings.lua %#lualine_c_normal#%=%#lualine_c_normal# utf-8 %#lualine_c_normal#  %#lualine_x_
filetype_DevIconLua_command# %#lualine_c_normal# lua %#lualine_transitional_lualine_b_command_to_lualine_c_normal#%#lualine_b_command# 64%% %#lualine_transitional_l
ualine_a_command_to_lualine_b_command#%#lualine_a_command#  18:1
  tagfunc=v:lua.vim.lsp.tagfunc
  tabline=%!v:lua.nvim_bufferline()
  wildignore=*.o,*.a,*.obj,__pycache__
Press ENTER or type command to continue

Also here's the result of :checkhealth:


==============================================================================
lazy: require("lazy.health").check()

lazy.nvim ~
- OK Git installed
- OK no existing packages found by other package managers
- OK packer_compiled.lua not found

==============================================================================
nvim: require("nvim.health").check()

Configuration ~
- OK no issues found

Runtime ~
- OK $VIMRUNTIME: /Users/yangmillstheory/code/nvim-macos/share/nvim/runtime

Performance ~
- OK Build type: Release

Remote Plugins ~
- OK Up to date

terminal ~
- key_backspace (kbs) terminfo entry: `key_backspace=\177`
- key_dc (kdch1) terminfo entry: `key_dc=\E[3~`
- $TERM_PROGRAM="tmux"
- $COLORTERM="truecolor"

tmux ~
- OK escape-time: 1
- OK focus-events: on
- $TERM: alacritty
- ERROR $TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.
  - ADVICE:
    - Set default-terminal in ~/.tmux.conf:
      set-option -g default-terminal "screen-256color"
    - https://github.com/neovim/neovim/wiki/Building-Neovim#optimized-builds

==============================================================================
nvim-treesitter: require("nvim-treesitter.health").check()

Installation ~
- WARNING `tree-sitter` executable not found (parser generator, only needed for :TSInstallFromGrammar, not required for :TSInstall)
- WARNING `node` executable not found (only needed for :TSInstallFromGrammar, not required for :TSInstall)
- OK `git` executable found.
- OK `cc` executable found. Selected from { vim.NIL, "cc", "gcc", "clang", "cl", "zig" }
  Version: Apple clang version 15.0.0 (clang-1500.1.0.2.5)
- OK Neovim was compiled with tree-sitter runtime ABI version 14 (required >=13). Parsers must be compatible with runtime ABI.

OS Info:
{
  machine = "x86_64",
  release = "22.6.0",
  sysname = "Darwin",
  version = "Darwin Kernel Version 22.6.0: Sun Dec 17 22:18:09 PST 2023; root:xnu-8796.141.3.703.2~2/RELEASE_X86_64"
} ~

Parser/Features         H L F I J
  - c                   ✓ ✓ ✓ ✓ ✓
  - cpp                 ✓ ✓ ✓ ✓ ✓
  - go                  ✓ ✓ ✓ ✓ ✓
  - lua                 ✓ ✓ ✓ ✓ ✓
  - python              ✓ ✓ ✓ ✓ ✓
  - query               ✓ ✓ ✓ ✓ ✓
  - vim                 ✓ ✓ ✓ . ✓
  - vimdoc              x . . . ✓

  Legend: H[ighlight], L[ocals], F[olds], I[ndents], In[j]ections
         +) multiple parsers found, only one will be used
         x) errors found in the query, try to run :TSUpdate {lang} ~

The following errors have been detected: ~
- ERROR vimdoc(highlights): ...im-macos/share/nvim/runtime/lua/vim/treesitter/query.lua:259: query: invalid node type at position 746 for language vimdoc
  vimdoc(highlights) is concatenated from the following files:
  | [ERROR]:"/Users/yangmillstheory/.local/share/nvim/lazy/nvim-treesitter/queries/vimdoc/highlights.scm", failed to load: ...im-macos/share/nvim/runtime/lua/vim/treesitter/query.lua:259: query: invalid node type at position 746 for language vimdoc

==============================================================================
provider: health#provider#check

- ERROR Failed to run healthcheck for "provider" plugin. Exception:
  function health#check[25]..health#provider#check[4]..<SNR>88_check_ruby, line 15
  Vim(let):E117: Unknown function: provider#ruby#Detect

==============================================================================
vim.lsp: require("vim.lsp.health").check()

- LSP log level : WARN
- Log path: /Users/yangmillstheory/.local/state/nvim/lsp.log
- Log size: 0 KB

vim.lsp: Active Clients ~
- lua_ls (id=1, root_dir=/Users/yangmillstheory/code/dotfiles/nvim/.config/nvim/lua/)

==============================================================================
vim.treesitter: require("vim.treesitter.health").check()

- Nvim runtime ABI version: 14
- OK Parser: cpp        ABI: 14, path: /Users/yangmillstheory/.local/share/nvim/lazy/nvim-treesitter/parser/cpp.so
- OK Parser: go         ABI: 14, path: /Users/yangmillstheory/.local/share/nvim/lazy/nvim-treesitter/parser/go.so
- OK Parser: lua        ABI: 14, path: /Users/yangmillstheory/.local/share/nvim/lazy/nvim-treesitter/parser/lua.so
- OK Parser: python     ABI: 14, path: /Users/yangmillstheory/.local/share/nvim/lazy/nvim-treesitter/parser/python.so
- OK Parser: vim        ABI: 14, path: /Users/yangmillstheory/.local/share/nvim/lazy/nvim-treesitter/parser/vim.so
- OK Parser: c          ABI: 14, path: /Users/yangmillstheory/code/nvim-macos/lib/nvim/parser/c.so
- OK Parser: lua        ABI: 14, path: /Users/yangmillstheory/code/nvim-macos/lib/nvim/parser/lua.so
- OK Parser: query      ABI: 14, path: /Users/yangmillstheory/code/nvim-macos/lib/nvim/parser/query.so
- OK Parser: vim        ABI: 14, path: /Users/yangmillstheory/code/nvim-macos/lib/nvim/parser/vim.so
- OK Parser: vimdoc     ABI: 14, path: /Users/yangmillstheory/code/nvim-macos/lib/nvim/parser/vimdoc.so

When I run :cmap, it looks like the binding comes from nvim-cmp:

image

However, commenting out the cmdline source fixes the issue, so I'm filing the bug here. Please let me know how I can help.

adinhodovic commented 7 months ago

I just did disabled the built in mappings for the cmdline preset.

            cmp.setup.cmdline(":", {
                mapping = cmp.mapping.preset.cmdline({
                    -- Use default nvim history scrolling
                    ["<C-n>"] = {
                        c = false,
                    },
                    ["<C-p>"] = {
                        c = false,
                    },
                }),
                sources = cmp.config.sources({
                    { name = "path" },
                }, {
                    { name = "cmdline" },
                }),
            })

Might solve ur usecase. Nvim Config

yangmillstheory commented 7 months ago

Thanks @adinhodovic, this looks like it works with no observable downsides. I wonder what the intent of the C-n / C-p mappings that we're overriding were? Because I guess the downside of the override, is that we're losing whatever benefit would be otherwise provided. So far I can't find any.

adinhodovic commented 7 months ago

Thanks @adinhodovic, this looks like it works with no observable downsides. I wonder what the intent of the C-n / C-p mappings that we're overriding were? Because I guess the downside of the override, is that we're losing whatever benefit would be otherwise provided. So far I can't find any.

I think they're used for scrolling up and down in the completion list, why I'm not sure.

yangmillstheory commented 7 months ago

I see. In that case I'm still able to use and to cycle forwards and backwards through the completion list, so no harm done.

I do think this is still a bug that should be resolved.

Thanks!

pogopaule commented 7 months ago

@yangmillstheory, how do you cycle backwards and forwards now? With the mapping proposed by @adinhodovic, I can't do that anymore.

yangmillstheory commented 7 months ago

I use <Tab> and <S-Tab>. I thought I mentioned that above, but I guess I didn't.

maciejzj commented 6 months ago

I've faced a similar problem, but in a slightly different context. I'm used to navigating history with C-N/C-P and also using them for selecting completions in vim popup windows. So, I developed a solution that lets me do both simultaneously.

Here's the trick: With the code snippet below, C-N/C-P will navigate through the command line history (like in shell), unless you start typing a character. When you start typing, C-N/C-P switches to selecting completions instead of navigating history. This way, you get the best of both worlds. The may not be the setup OP is looking for, however, someone may find it useful one day, so I decided to share it here :)

-- For command line
-- This custom mappig setup causes CTRL-P, CTRL-N to fallback to history
-- browsing, unless user has explicitly typed something in the cmdline, then
-- these two activate to browse completion options.
local cmdline_cmp_state = "has_not_typed"
vim.api.nvim_create_autocmd({ "CmdlineEnter" }, {
  command = "lua cmdline_cmp_state = 'has_not_typed'",
})
vim.api.nvim_create_autocmd({ "CmdlineChanged" }, {
  callback = function()
    if cmdline_cmp_state == "has_not_typed" then
      cmdline_cmp_state = "has_typed"
    elseif cmdline_cmp_state == "has_browsed_history" then
      cmdline_cmp_state = "has_not_typed"
    end
  end,
})
local function select_or_fallback(select_action)
  return cmp.mapping(function(fallback)
    if cmdline_cmp_state == "has_typed" and cmp.visible() then
      select_action()
    else
      cmdline_cmp_state = "has_browsed_history"
      cmp.close()
      fallback()
    end
  end, { "i", "c" })
end
cmp.setup.cmdline(":", {
  mapping = cmp.mapping.preset.cmdline({
    ["<C-n>"] = select_or_fallback(cmp.select_next_item),
    ["<C-p>"] = select_or_fallback(cmp.select_prev_item),
  }),
  sources = cmp.config.sources({
    { name = "path" },
  }, {
    { name = "cmdline" },
  }),
})
plt3 commented 5 months ago

I've faced a similar problem, but in a slightly different context. I'm used to navigating history with C-N/C-P and also using them for selecting completions in vim popup windows. So, I developed a solution that lets me do both simultaneously.

@maciejzj, I was facing this exact same issue and saw your solution, which works great! However, after some digging around I found a simpler configuration that achieves the same thing (for me, at least). Posting it here if it can be useful:

-- Use cmdline & path source for ':'
cmp.setup.cmdline(":", {
    -- C-n/C-p cycle through completions if a character has been typed and through
    -- command history if not (from https://www.reddit.com/r/neovim/comments/v5pfmy/comment/ibb61w3/)
    mapping = cmp.mapping.preset.cmdline({
        ["<C-n>"] = { c = cmp.mapping.select_next_item() },
        ["<C-p>"] = { c = cmp.mapping.select_prev_item() },
    }),
    sources = cmp.config.sources({
        { name = "path" },
    }, {
        { name = "cmdline" },
    }),
})
maciejzj commented 5 months ago

@plt3 Thanks a lot, this will slim down my init.lua a bit :))

It would be great to see this setup in some more visible place like the README. I guess many users would want this behaviour and it seems rather unobvious how to achieve it (TBH I would have never figured out that specifying that next/prev should explicitly work in "command mode" would result in this behaviour overall and still don't fully get it 🙃).

aimestereo commented 4 months ago

@plt3 @maciejzj guys, you're the best,

Also, your solution have these attrs:

I will also share my snippet that I based on your work: I don't use preset, only set maps that I use.

-- Declare only keys that I actually use
-- Based on https://github.com/hrsh7th/cmp-cmdline/issues/108#issuecomment-2052449375
-- C-n/C-p cycle through completions if a character has been typed and through
-- command history if not (from https://www.reddit.com/r/neovim/comments/v5pfmy/comment/ibb61w3/)
local cmd_mapping = {
  ["<C-Space>"] = { c = cmp.mapping.complete({}) },
  ["<C-n>"] = { c = cmp.mapping.select_next_item() },
  ["<C-p>"] = { c = cmp.mapping.select_prev_item() },
  ["<C-e>"] = { c = cmp.mapping.abort() },
  ["<C-y>"] = {
    c = cmp.mapping.confirm({
      behavior = cmp.ConfirmBehavior.Insert,
      select = true,
    }),
  },
}

-- Use buffer source for `/` and `?`
cmp.setup.cmdline({ "/", "?" }, {
  mapping = cmd_mapping,
  sources = {
    { name = "buffer" },
  },
})

-- Use cmdline & path source for ':'
cmp.setup.cmdline(":", {
  mapping = cmd_mapping,
  sources = cmp.config.sources({
    { name = "path" },
  }, {
    { name = "cmdline" },
  }),
})
GitMurf commented 1 week ago

Wow @plt3 I cannot tell you how much you saved me here! I have been trying to figure this out off and on for weeks! For me it was not being able to use the Up / Down arrows to cycle through "/" search history. Your suggestion allowed me to figure it out with this below!

I still don't quite understand why this works! I thought select_next_item() would do exactly what I was trying to prevent... opening the cmp menu and selecting the next completion suggestion! But if it works, it works! Thanks!

    mapping = cmp.mapping.preset.cmdline({
        ["<Up>"] = { c = cmp.mapping.select_next_item() },
        ["<Down>"] = { c = cmp.mapping.select_prev_item() },
    }),