Repository: ycm-core/YouCompleteMe (language: python)

Issue:
[Feature request]: API/mapping to manually trigger completions
# Issue Prelude

**Please complete these steps and check these boxes (by putting an `x` inside
the brackets) _before_ filing your issue:**

- [x] I have read and understood YCM's [CONTRIBUTING][cont] document.
- [x] I have read and understood YCM's [CODE_OF_CONDUCT][code] document.
- [x] I have read and understood YCM's [README][readme], **especially the
  [Frequently Asked Questions][faq] section.**
- [x] I have searched YCM's issue tracker to find issues similar to the one I'm
  about to report and couldn't find an answer to my problem. ([Example Google
  search.][search])
- [x] If filing a bug report, I have included the output of `vim --version`.
- [x] If filing a bug report, I have included the output of `:YcmDebugInfo`.
- [x] If filing a bug report, I have attached the contents of the logfiles using
  the `:YcmToggleLogs` command.
- [x] If filing a bug report, I have included which OS (including specific OS
  version) I am using.
- [x] If filing a bug report, I have included a minimal test case that reproduces
  my issue, using `vim -Nu /path/to/YCM/vimrc_ycm_minimal`, including what I
  expected to happen and what actually happened.
- [x] If filing a installation failure report, I have included the entire output
  of `install.py` (or `cmake`/`make`/`ninja`) including its invocation
- [x] **I understand this is an open-source project staffed by volunteers and
  that any help I receive is a selfless, heartfelt _gift_ of their free time. I
  know I am not entitled to anything and will be polite and courteous.**
- [x] **I understand my issue may be closed if it becomes obvious I didn't
  actually perform all of these steps.**

Thank you for adhering to this process! It ensures your issue is resolved
quickly and that neither your nor our time is needlessly wasted.

# Issue Details
Is there a way YCM shows suggestions only if I press tab and not as I type. I have searched and tried a bunch of things but can't seem to find it.

## Output of `vim --version`

```
VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Mar  3 2021 07:02:32)
Included patches: 1-2562
Compiled by nikhil.mundra@nikhil-mundra
Huge version without GUI.  Features included (+) or not (-):
+acl               -farsi             +mouse_sgr         +tag_binary
+arabic            +file_in_path      -mouse_sysmouse    -tag_old_static
+autocmd           +find_in_path      +mouse_urxvt       -tag_any_white
+autochdir         +float             +mouse_xterm       -tcl
-autoservername    +folding           +multi_byte        +termguicolors
-balloon_eval      -footer            +multi_lang        +terminal
+balloon_eval_term +fork()            -mzscheme          +terminfo
-browse            +gettext           +netbeans_intg     +termresponse
++builtin_terms    -hangul_input      +num64             +textobjects
+byte_offset       +iconv             +packages          +textprop
+channel           +insert_expand     +path_extra        +timers
+cindent           +ipv6              +perl              +title
-clientserver      +job               +persistent_undo   -toolbar
-clipboard         +jumplist          +popupwin          +user_commands
+cmdline_compl     +keymap            +postscript        +vartabs
+cmdline_hist      +lambda            +printer           +vertsplit
+cmdline_info      +langmap           +profile           +virtualedit
+comments          +libcall           +python            +visual
+conceal           +linebreak         -python3           +visualextra
+cryptv            +lispindent        +quickfix          +viminfo
+cscope            +listcmds          +reltime           +vreplace
+cursorbind        +localmap          +rightleft         +wildignore
+cursorshape       +lua               +ruby              +wildmenu
+dialog_con        +menu              +scrollbind        +windows
+diff              +mksession         +signs             +writebackup
+digraphs          +modify_fname      +smartindent       -X11
-dnd               +mouse             -sound             -xfontset
-ebcdic            -mouseshape        +spell             -xim
+emacs_tags        +mouse_dec         +startuptime       -xpm
+eval              -mouse_gpm         +statusline        -xsmp
+ex_extra          -mouse_jsbterm     -sun_workshop      -xterm_clipboard
+extra_search      +mouse_netterm     +syntax            -xterm_save
   system vimrc file: "$VIM/vimrc"
     user vimrc file: "$HOME/.vimrc"
 2nd user vimrc file: "~/.vim/vimrc"
      user exrc file: "$HOME/.exrc"
       defaults file: "$VIMRUNTIME/defaults.vim"
  fall-back for $VIM: "/usr/local/share/vim"
Compilation: gcc -std=gnu99 -c -I. -Iproto -DHAVE_CONFIG_H -g -O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
Linking: gcc -std=gnu99 -L. -Wl,-z,relro -fstack-protector -rdynamic -Wl,-export-dynamic -Wl,--enable-new-dtags -Wl,-rpath,/usr/lib64/perl5/CORE -L/usr/local/lib -Wl,--as-needed -o vim -lm -ltinfo -ldl -L/usr/lib -llua -Wl,--enable-new-dtags -Wl,-rpath,/usr/lib64/perl5/CORE -fstack-protector -L/usr/lib64/perl5/CORE -lperl -lresolv -lnsl -ldl -lm -lcrypt -lutil -lpthread -lc -L/usr/lib64/python2.7/config -lpython2.7 -lpthread -ldl -lutil -lm -Xlinker -export-dynamic -lruby -lpthread -lrt -ldl -lcrypt -lm -L/usr/lib64
```

[cont]: https://github.com/ycm-core/YouCompleteMe/blob/master/CONTRIBUTING.md
[code]: https://github.com/ycm-core/YouCompleteMe/blob/master/CODE_OF_CONDUCT.md
[readme]: https://github.com/ycm-core/YouCompleteMe/blob/master/README.md
[faq]: https://github.com/ycm-core/YouCompleteMe/wiki/FAQ
[search]: https://www.google.com/search?q=site%3Ahttps%3A%2F%2Fgithub.com%2Fycm-core%2FYouCompleteMe%2Fissues%20python%20mac
[gist]: https://gist.github.com/



Discussion / hints:
Not currently, but you can try the following diff:

```diff
diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim
index cf3f3f41..dfab8c0e 100644
--- a/autoload/youcompleteme.vim
+++ b/autoload/youcompleteme.vim
@@ -855,7 +855,7 @@ function! s:OnTextChangedInsertMode( popup_is_visible )
         \ ( g:ycm_auto_trigger || s:force_semantic ) &&
         \ !s:InsideCommentOrStringAndShouldStop() &&
         \ !s:OnBlankLine()
-    " The call to s:Complete here is necessary, to minimize flicker when we
+    " The call to youcompleteme#Complete here is necessary, to minimize flicker when we
     " close the pum on every keypress. In that case, we try to quickly show it
     " again with whatver the latest completion result is. When using complete(),
     " we don't need to do this, as we only close the pum when there are no
@@ -864,7 +864,7 @@ function! s:OnTextChangedInsertMode( popup_is_visible )
     " keys to filter (that are not a prefix of the completion), then deleting a
     " character. Normally Vim would re-filter based on the new "query", but we
     " don't want that.
-    call s:Complete()
+    call youcompleteme#Complete()
     call s:RequestCompletion()

     call s:UpdateSignatureHelp()
@@ -1030,7 +1030,7 @@ function! s:PollCompletion( ... )
   endif

   let s:completion = py3eval( 'ycm_state.GetCompletionResponse()' )
-  call s:Complete()
+  call youcompleteme#Complete()
 endfunction


@@ -1106,7 +1106,7 @@ function! s:PollSignatureHelp( ... )
   call s:UpdateSignatureHelp()
 endfunction

-function! s:Complete()
+function! youcompleteme#Complete()
   " It's possible for us to be called (by our timer) when we're not _strictly_
   " in insert mode. This can happen when mode is temporarily switched, e.g.
   " due to Ctrl-r or Ctrl-o or a timer or something. If we're not in insert
```

Then:

1. Set `g:ycm_min_num_of_chars_for_completion` to 99999999999999999
2. Map `<Tab>` to `call youcompleteme#Complete()` in insert mode.
I’d be happy to add this api. When we stopped using completefunc we sort of regressed this capability. 
Yeah, it would be good to have this functionality for those who don't want suggestions all the time.
@puremourning `s/s:Complete/youcompleteme#Complete/` is simple enough. Do we also want some `<Plug>` map?
Yeah plug map would be good. 
Like I said, my approach didn't work, because it doesn't actually send the completion request. It just checks if a response arrived.
Thumbing up this issue. I don't get why the fact that we don't have to press a shortcut to get completions is listed on main page as an advantage. It's advantage in the sense that it's not mandatory, but we still have to have an ability to set up keybinding for completion activation. 

Fix patch:
diff --git a/README.md b/README.md
index 0e1da8cf8e..1f8418e4cb 100644
--- a/README.md
+++ b/README.md
@@ -2672,6 +2672,21 @@ If you want to just turn off the identifier completer but keep the semantic
 triggers, you should set `g:ycm_min_num_of_chars_for_completion` to a high
 number like `99`.
 
+When `g:ycm_auto_trigger` is `0`, YCM sets the `completefunc`, so that you can
+manually trigger normal completion using `C-x C-u`.
+
+If you want to map something else to trigger completion, such as `C-d``,
+then you can map it to `<plug>(YCMComplete)`. For example:
+
+```viml
+let g:ycm_auto_trigger = 0
+imap <c-d> <plug>(YCMComplete)
+```
+
+NOTE: It's not possible to map one of the keys in
+`g:ycm_key_list_select_completion` (or similar) to `<plug>(YCMComplete)`. In
+practice that means that you can't use `<Tab>` for this.
+
 Default: `1`
 
 ```viml
@@ -3386,9 +3401,10 @@ let g:ycm_key_list_stop_completion = ['<C-y>']
 
 This option controls the key mapping used to invoke the completion menu for
 semantic completion. By default, semantic completion is triggered automatically
-after typing `.`, `->` and `::` in insert mode (if semantic completion support
-has been compiled in). This key mapping can be used to trigger semantic
-completion anywhere. Useful for searching for top-level functions and classes.
+after typing characters appropriate for the language, such as `.`, `->`, `::`,
+etc. in insert mode (if semantic completion support has been compiled in). This
+key mapping can be used to trigger semantic completion anywhere. Useful for
+searching for top-level functions and classes.
 
 Console Vim (not Gvim or MacVim) passes `<Nul>` to Vim when the user types
 `<C-Space>` so YCM will make sure that `<Nul>` is used in the map command when
diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim
index 7edea51d97..bd4a94d92c 100644
--- a/autoload/youcompleteme.vim
+++ b/autoload/youcompleteme.vim
@@ -30,6 +30,7 @@ let g:ycm_neovim_ns_id = s:is_neovim ? nvim_create_namespace( 'ycm_id' ) : -1
 " This needs to be called outside of a function
 let s:script_folder_path = escape( expand( '<sfile>:p:h' ), '\' )
 let s:force_semantic = 0
+let s:force_manual = 0
 let s:completion_stopped = 0
 " These two variables are initialized in youcompleteme#Enable.
 let s:default_completion = {}
@@ -224,9 +225,9 @@ function! youcompleteme#Enable()
           \ } )
   endif
 
-  nnoremap <silent> <Plug>(YCMFindSymbolInWorkspace)
+  nnoremap <silent> <plug>(YCMFindSymbolInWorkspace)
         \ :call youcompleteme#finder#FindSymbol( 'workspace' )<CR>
-  nnoremap <silent> <Plug>(YCMFindSymbolInDocument)
+  nnoremap <silent> <plug>(YCMFindSymbolInDocument)
         \ :call youcompleteme#finder#FindSymbol( 'document' )<CR>
 endfunction
 
@@ -639,6 +640,9 @@ endfunction
 
 
 function! s:EnableCompletingInCurrentBuffer()
+  if !g:ycm_auto_trigger
+    call s:SetCompleteFunc()
+  endif
   let b:ycm_completing = 1
 endfunction
 
@@ -1010,10 +1014,11 @@ function! s:OnTextChangedInsertMode( popup_is_visible )
   " CurrentIdentifierFinished check.
   if s:force_semantic && !py3eval( 'base.LastEnteredCharIsIdentifierChar()' )
     let s:force_semantic = 0
+    let s:force_manual = 0
   endif
 
   if get( b:, 'ycm_completing' ) &&
-        \ ( g:ycm_auto_trigger || s:force_semantic ) &&
+        \ ( g:ycm_auto_trigger || s:force_semantic || s:force_manual ) &&
         \ !s:InsideCommentOrStringAndShouldStop() &&
         \ !s:OnBlankLine()
     call s:RequestCompletion()
@@ -1045,6 +1050,7 @@ function! s:OnInsertLeave()
 
   call s:StopPoller( s:pollers.completion )
   let s:force_semantic = 0
+  let s:force_manual = 0
   let s:completion = s:default_completion
 
   call s:OnFileReadyToParse()
@@ -1085,6 +1091,7 @@ function! s:IdentifierFinishedOperations()
   endif
   py3 ycm_state.OnCurrentIdentifierFinished()
   let s:force_semantic = 0
+  let s:force_manual = 0
   let s:completion = s:default_completion
 endfunction
 
@@ -1149,8 +1156,37 @@ function! s:RequestCompletion()
   endif
 endfunction
 
+function! s:ManuallyRequestCompletion() abort
+  " Since this function is called in a mapping through the expression register
+  " <C-R>=, its return value is inserted (see :h c_CTRL-R_=). We don't want to
+  " insert anything so we return an empty string.
+
+  if !s:AllowedToCompleteInCurrentBuffer()
+    return ''
+  endif
+
+  if get( b:, 'ycm_completing' )
+    let s:force_manual = 0
+    call s:RequestCompletion()
+    call s:RequestSignatureHelp()
+  endif
+
+  return ''
+endfunction
+
+function! s:SetCompleteFunc()
+   let &completefunc = 'youcompleteme#CompleteFunc'
+endfunction
+
+function! youcompleteme#CompleteFunc( findstart, base ) abort
+  call s:ManuallyRequestCompletion()
+  " Cancel, but silently stay in completion mode.
+  return -2
+endfunction
+
+inoremap <silent> <plug>(YCMComplete) <C-r>=<SID>ManuallyRequestCompletion()<CR>
 
-function! s:RequestSemanticCompletion()
+function! s:RequestSemanticCompletion() abort
   if !s:AllowedToCompleteInCurrentBuffer()
     return ''
   endif
@@ -1682,7 +1718,7 @@ function! s:ToggleInlayHints()
   endif
 endfunction
 
-silent! nnoremap <silent> <Plug>(YCMToggleInlayHints)
+silent! nnoremap <silent> <plug>(YCMToggleInlayHints)
       \ <cmd>call <SID>ToggleInlayHints()<CR>
 
 " This is basic vim plugin boilerplate
diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py
index 4017f879de..56de1aa8d2 100644
--- a/python/ycm/vimsupport.py
+++ b/python/ycm/vimsupport.py
@@ -182,7 +182,7 @@ def GetCurrentBufferNumber():
 
 
 def GetBufferChangedTick( bufnr ):
-  return GetIntValue( f'getbufvar({ bufnr }, "changedtick") ?? 0' )
+  return GetIntValue( f'getbufvar({ bufnr }, "changedtick")' ) or 0
 
 
 # Returns a range covering the earliest and latest lines visible in the current
diff --git a/vimrc_ycm_minimal b/vimrc_ycm_minimal
index c908d985d3..14934285f9 100644
--- a/vimrc_ycm_minimal
+++ b/vimrc_ycm_minimal
@@ -13,9 +13,6 @@ set encoding=utf-8
 let g:ycm_keep_logfiles = 1
 let g:ycm_log_level = 'debug'
 
-" If you're on an arm mac, uncomment the following line
-" let g:ycm_clangd_binary_path=trim(system('brew --prefix llvm')).'/bin/clangd'
-
 " If the base settings don't repro, paste your existing config for YCM only,
 " here:
 " let g:ycm_....
@@ -23,4 +20,4 @@ let g:ycm_log_level = 'debug'
 " Load YCM (only)
 let &rtp .= ',' . expand( '<sfile>:p:h' )
 filetype plugin indent on
-
+syn on


Test patch:
diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py
index b5dda4f69f..f89ae25b2c 100644
--- a/python/ycm/tests/test_utils.py
+++ b/python/ycm/tests/test_utils.py
@@ -40,7 +40,7 @@
 BWIPEOUT_REGEX = re.compile(
   '^(?:silent! )bwipeout!? (?P<buffer_number>[0-9]+)$' )
 GETBUFVAR_REGEX = re.compile(
-  '^getbufvar\\((?P<buffer_number>[0-9]+), "(?P<option>.+)"\\)$' )
+  '^getbufvar\\((?P<buffer_number>[0-9]+), "(?P<option>.+)"\\)( \\?\\? 0)?$' )
 PROP_ADD_REGEX = re.compile(
         '^prop_add\\( '            # A literal at the start
         '(?P<start_line>\\d+), '   # First argument - number
diff --git a/test/completion.common.vim b/test/completion.common.vim
index f8d0224995..9ae8ffcabd 100644
--- a/test/completion.common.vim
+++ b/test/completion.common.vim
@@ -439,3 +439,98 @@ function! Test_Completion_WorksWithoutMovingCursor()
   call setpos( '.', [ 0, 3, 1 ] )
   call FeedAndCheckMain( "i\<C-Space>", funcref( 'Check' ) )
 endfunction
+
+function! SetUp_Test_Manual_Trigger()
+  call youcompleteme#test#setup#PushGlobal( 'ycm_auto_trigger', 0 )
+endfunction
+
+function! Test_Manual_Trigger()
+  call youcompleteme#test#setup#OpenFile(
+        \ '/third_party/ycmd/ycmd/tests/clangd/testdata/basic.cpp', {} )
+
+  call setpos( '.', [ 0, 11, 6 ] )
+
+  imap <C-d> <plug>(YCMComplete)
+
+  " Required to trigger TextChangedI
+  " https://github.com/vim/vim/issues/4665#event-2480928194
+  call test_override( 'char_avail', 1 )
+
+  function! Check( id )
+    call WaitForCompletion()
+
+    call CheckCurrentLine( '  tfthne' )
+    call CheckCompletionItemsContainsExactly( [
+          \ 'test_function_that_has_no_errors' ], 'word' )
+
+    call FeedAndCheckAgain( "\<BS>", funcref( 'Check2' ) )
+  endfunction
+
+  function! Check2( id )
+    call WaitForCompletion()
+
+    call CheckCurrentLine( '  tfthn' )
+    call CheckCompletionItemsContainsExactly( [
+          \ 'test_function_that_has_no_errors' ], 'word' )
+
+    call feedkeys( "\<Esc>" )
+  endfunction
+
+  call FeedAndCheckMain( "Otfthne\<C-d>", funcref( 'Check' ) )
+  " Checks run in insert mode, then exit insert mode.
+  call assert_false( pumvisible(), 'pumvisible()' )
+
+  call test_override( 'ALL', 0 )
+  iunmap <C-d>
+endfunction
+
+function! TearDown_Test_Manual_Trigger()
+  call youcompleteme#test#setup#PopGlobal( 'ycm_auto_trigger' )
+endfunction
+
+function! SetUp_Test_Manual_Trigger_CompleteFunc()
+  call youcompleteme#test#setup#PushGlobal( 'ycm_auto_trigger', 0 )
+endfunction
+
+function! Test_Manual_Trigger_CompleteFunc()
+  call youcompleteme#test#setup#OpenFile(
+        \ '/third_party/ycmd/ycmd/tests/clangd/testdata/basic.cpp', {} )
+
+  call setpos( '.', [ 0, 11, 6 ] )
+  set completefunc=youcompleteme#CompleteFunc
+
+  " Required to trigger TextChangedI
+  " https://github.com/vim/vim/issues/4665#event-2480928194
+  call test_override( 'char_avail', 1 )
+
+  function! Check( id )
+    call WaitForCompletion()
+
+    call CheckCurrentLine( '  tfthne' )
+    call CheckCompletionItemsContainsExactly( [
+          \ 'test_function_that_has_no_errors' ], 'word' )
+
+    call FeedAndCheckAgain( "\<BS>", funcref( 'Check2' ) )
+  endfunction
+
+  function! Check2( id )
+    call WaitForCompletion()
+
+    call CheckCurrentLine( '  tfthn' )
+    call CheckCompletionItemsContainsExactly( [
+          \ 'test_function_that_has_no_errors' ], 'word' )
+
+    call feedkeys( "\<Esc>" )
+  endfunction
+
+  call FeedAndCheckMain( "Otfthne\<C-x>\<C-u>", funcref( 'Check' ) )
+  " Checks run in insert mode, then exit insert mode.
+  call assert_false( pumvisible(), 'pumvisible()' )
+
+  call test_override( 'ALL', 0 )
+  set completefunc=
+endfunction
+
+function! TearDown_Test_Manual_Trigger_CompleteFunc()
+  call youcompleteme#test#setup#PopGlobal( 'ycm_auto_trigger' )
+endfunction