فهرست منبع

Allow users to change the options by restarting the server

micbou 6 سال پیش
والد
کامیت
d16d3cebc4

+ 2 - 1
README.md

@@ -2021,7 +2021,8 @@ let g:ycm_min_num_of_chars_for_completion = 1
 ```
 
 Note that after changing an option in your [vimrc script][vimrc] you have to
-restart Vim for the changes to take effect.
+restart [ycmd][] with the `:YcmRestartServer` command for the changes to take
+effect.
 
 ### The `g:ycm_min_num_of_chars_for_completion` option
 

+ 22 - 14
autoload/youcompleteme.vim

@@ -98,15 +98,7 @@ function! s:ReceiveMessages( timer_id )
 endfunction
 
 
-function! youcompleteme#Enable()
-  call s:SetUpBackwardsCompatibility()
-
-  " This can be 0 if YCM libs are old or -1 if an exception occured while
-  " executing the function.
-  if s:SetUpPython() != 1
-    return
-  endif
-
+function! s:SetUpOptions()
   call s:SetUpCommands()
   call s:SetUpCpoptions()
   call s:SetUpCompleteopt()
@@ -118,6 +110,17 @@ function! youcompleteme#Enable()
 
   call s:SetUpSigns()
   call s:SetUpSyntaxHighlighting()
+endfunction
+
+
+function! youcompleteme#Enable()
+  call s:SetUpBackwardsCompatibility()
+
+  if !s:SetUpPython()
+    return
+  endif
+
+  call s:SetUpOptions()
 
   call youcompleteme#EnableCursorMovedAutocommands()
   augroup youcompleteme
@@ -191,17 +194,20 @@ import vim
 # Add python sources folder to the system path.
 script_folder = vim.eval( 's:script_folder_path' )
 sys.path.insert( 0, os.path.join( script_folder, '..', 'python' ) )
-
-from ycm.setup import SetUpSystemPaths, SetUpYCM
+sys.path.insert( 0, os.path.join( script_folder, '..', 'third_party', 'ycmd' ) )
 
 # We enclose this code in a try/except block to avoid backtraces in Vim.
 try:
-  SetUpSystemPaths()
+  from ycmd import server_utils as su
+  su.AddNearestThirdPartyFoldersToSysPath( script_folder )
+  # We need to import ycmd's third_party folders as well since we import and
+  # use ycmd code in the client.
+  su.AddNearestThirdPartyFoldersToSysPath( su.__file__ )
 
   # Import the modules used in this file.
-  from ycm import base, vimsupport
+  from ycm import base, vimsupport, youcompleteme
 
-  ycm_state = SetUpYCM()
+  ycm_state = youcompleteme.YouCompleteMe()
 except Exception as error:
   # We don't use PostVimMessage or EchoText from the vimsupport module because
   # importing this module may fail.
@@ -849,6 +855,8 @@ endfunction
 
 
 function! s:RestartServer()
+  call s:SetUpOptions()
+
   exec s:python_command "ycm_state.RestartServer()"
 
   call timer_stop( s:pollers.receive_messages.id )

+ 11 - 8
doc/youcompleteme.txt

@@ -718,10 +718,9 @@ Make sure you have Vim 7.4.1578 with Python 2 or Python 3 support.
 OpenBSD 5.5 and later have a Vim that's recent enough. You can see the version
 of Vim installed by running 'vim --version'.
 
-FreeBSD 10.x comes with clang compiler but not the libraries needed to install.
+For FreeBSD 11.x, the requirement is cmake:
 >
-  pkg install llvm38 boost-all boost-python-libs clang38
-  export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/llvm38/lib/
+  pkg install cmake
 <
 Install YouCompleteMe with Vundle [26].
 
@@ -729,17 +728,20 @@ Install YouCompleteMe with Vundle [26].
 using Vundle and the ycm_core library APIs have changed (happens rarely), YCM
 will notify you to recompile it. You should then rerun the install process.
 
-Install dependencies and CMake: 'sudo pkg_add llvm boost cmake'
-
 Compiling YCM **with** semantic support for C-family languages:
 >
   cd ~/.vim/bundle/YouCompleteMe
-  ./install.py --clang-completer --system-libclang --system-boost
+  ./install.py --clang-completer
 <
 Compiling YCM **without** semantic support for C-family languages:
 >
   cd ~/.vim/bundle/YouCompleteMe
-  ./install.py --system-boost
+  ./install.py
+<
+If the 'python' executable is not present, or the default 'python' is not the
+one that should be compiled against, specify the python interpreter explicitly:
+>
+  python3 install.py --clang-completer
 <
 The following additional language support options are available:
 
@@ -2309,7 +2311,8 @@ vimrc script [38] by including a line like this:
   let g:ycm_min_num_of_chars_for_completion = 1
 <
 Note that after changing an option in your vimrc script [38] you have to
-restart Vim for the changes to take effect.
+restart ycmd [49] with the |:YcmRestartServer| command for the changes to take
+effect.
 
 -------------------------------------------------------------------------------
 The *g:ycm_min_num_of_chars_for_completion* option

+ 2 - 7
python/ycm/diagnostic_interface.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2013  Google Inc.
+# Copyright (C) 2013-2018 YouCompleteMe contributors
 #
 # This file is part of YouCompleteMe.
 #
@@ -36,7 +36,6 @@ class DiagnosticInterface( object ):
     self._diag_filter = DiagnosticFilter.CreateFromOptions( user_options )
     # Line and column numbers are 1-based
     self._line_to_diags = defaultdict( list )
-    self._next_sign_id = vimsupport.SIGN_BUFFER_ID_INITIAL_VALUE
     self._previous_diag_line_number = -1
     self._diag_message_needs_clearing = False
 
@@ -183,16 +182,12 @@ class DiagnosticInterface( object ):
       # are sorted by errors in priority and Vim can only display one sign by
       # line.
       name = 'YcmError' if _DiagnosticIsError( diags[ 0 ] ) else 'YcmWarning'
-      sign = vimsupport.DiagnosticSign( self._next_sign_id,
-                                        line,
-                                        name,
-                                        self._bufnr )
+      sign = vimsupport.CreateSign( line, name, self._bufnr )
 
       try:
         signs_to_unplace.remove( sign )
       except ValueError:
         vimsupport.PlaceSign( sign )
-        self._next_sign_id += 1
 
     for sign in signs_to_unplace:
       vimsupport.UnplaceSign( sign )

+ 0 - 53
python/ycm/setup.py

@@ -1,53 +0,0 @@
-# Copyright (C) 2016 YouCompleteMe contributors
-#
-# This file is part of YouCompleteMe.
-#
-# YouCompleteMe is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# YouCompleteMe is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
-
-from __future__ import unicode_literals
-from __future__ import print_function
-from __future__ import division
-from __future__ import absolute_import
-# No imports from `future` because when this is loaded, sys.path hasn't been set
-# up yet!
-
-import sys
-import os
-
-# Can't import these from paths.py because that uses `future` imports
-DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) )
-DIR_OF_YCMD = os.path.join( DIR_OF_CURRENT_SCRIPT, '..', '..', 'third_party',
-                            'ycmd' )
-
-
-def SetUpSystemPaths():
-  sys.path.insert( 0, os.path.join( DIR_OF_YCMD ) )
-
-  from ycmd import server_utils as su
-  su.AddNearestThirdPartyFoldersToSysPath( DIR_OF_CURRENT_SCRIPT )
-  # We need to import ycmd's third_party folders as well since we import and
-  # use ycmd code in the client.
-  su.AddNearestThirdPartyFoldersToSysPath( su.__file__ )
-
-
-def SetUpYCM():
-  from ycm import base
-  from ycmd import user_options_store
-  from ycm.youcompleteme import YouCompleteMe
-
-  base.LoadJsonDefaultsIntoVim()
-
-  user_options_store.SetAll( base.BuildServerConf() )
-
-  return YouCompleteMe( user_options_store.GetAll() )

+ 29 - 22
python/ycm/tests/__init__.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2016 YouCompleteMe contributors
+# Copyright (C) 2016-2018 YouCompleteMe contributors
 #
 # This file is part of YouCompleteMe.
 #
@@ -25,6 +25,7 @@ from builtins import *  # noqa
 from ycm.tests.test_utils import MockVimModule
 MockVimModule()
 
+import contextlib
 import functools
 import os
 import requests
@@ -32,22 +33,23 @@ import time
 import warnings
 
 from ycm.client.base_request import BaseRequest
+from ycm.tests import test_utils
 from ycm.youcompleteme import YouCompleteMe
-from ycmd import user_options_store
 from ycmd.utils import CloseStandardStreams, WaitUntilProcessIsTerminated
 
 # The default options which are only relevant to the client, not the server and
 # thus are not part of default_options.json, but are required for a working
 # YouCompleteMe object.
 DEFAULT_CLIENT_OPTIONS = {
-  'log_level': 'info',
-  'keep_logfiles': 0,
-  'extra_conf_vim_data': [],
-  'show_diagnostics_ui': 1,
-  'echo_current_diagnostic': 1,
-  'enable_diagnostic_signs': 1,
-  'enable_diagnostic_highlighting': 0,
-  'always_populate_location_list': 0,
+  'g:ycm_server_python_interpreter': '',
+  'g:ycm_log_level': 'info',
+  'g:ycm_keep_logfiles': 0,
+  'g:ycm_extra_conf_vim_data': [],
+  'g:ycm_show_diagnostics_ui': 1,
+  'g:ycm_echo_current_diagnostic': 1,
+  'g:ycm_enable_diagnostic_signs': 1,
+  'g:ycm_enable_diagnostic_highlighting': 0,
+  'g:ycm_always_populate_location_list': 0,
 }
 
 
@@ -56,11 +58,15 @@ def PathToTestFile( *args ):
   return os.path.join( dir_of_current_script, 'testdata', *args )
 
 
-def MakeUserOptions( custom_options = {} ):
-  options = dict( user_options_store.DefaultOptions() )
-  options.update( DEFAULT_CLIENT_OPTIONS )
-  options.update( custom_options )
-  return options
+@contextlib.contextmanager
+def UserOptions( options ):
+  old_vim_options = test_utils.VIM_OPTIONS.copy()
+  test_utils.VIM_OPTIONS.update( DEFAULT_CLIENT_OPTIONS )
+  test_utils.VIM_OPTIONS.update( options )
+  try:
+    yield
+  finally:
+    test_utils.VIM_OPTIONS = old_vim_options
 
 
 def _IsReady():
@@ -123,12 +129,13 @@ def YouCompleteMeInstance( custom_options = {} ):
   def Decorator( test ):
     @functools.wraps( test )
     def Wrapper( *args, **kwargs ):
-      ycm = YouCompleteMe( MakeUserOptions( custom_options ) )
-      WaitUntilReady()
-      ycm.CheckIfServerIsReady()
-      try:
-        test( ycm, *args, **kwargs )
-      finally:
-        StopServer( ycm )
+      with UserOptions( custom_options ):
+        ycm = YouCompleteMe()
+        WaitUntilReady()
+        ycm.CheckIfServerIsReady()
+        try:
+          test( ycm, *args, **kwargs )
+        finally:
+          StopServer( ycm )
     return Wrapper
   return Decorator

+ 2 - 2
python/ycm/tests/command_test.py

@@ -31,7 +31,7 @@ from mock import patch
 from ycm.tests import YouCompleteMeInstance
 
 
-@YouCompleteMeInstance( { 'extra_conf_vim_data': [ 'tempname()' ] } )
+@YouCompleteMeInstance( { 'g:ycm_extra_conf_vim_data': [ 'tempname()' ] } )
 def SendCommandRequest_ExtraConfVimData_Works_test( ycm ):
   current_buffer = VimBuffer( 'buffer' )
   with MockVimBuffers( [ current_buffer ], current_buffer ):
@@ -56,7 +56,7 @@ def SendCommandRequest_ExtraConfVimData_Works_test( ycm ):
       )
 
 
-@YouCompleteMeInstance( { 'extra_conf_vim_data': [ 'undefined_value' ] } )
+@YouCompleteMeInstance( { 'g:ycm_extra_conf_vim_data': [ 'undefined_value' ] } )
 def SendCommandRequest_ExtraConfData_UndefinedValue_test( ycm ):
   current_buffer = VimBuffer( 'buffer' )
   with MockVimBuffers( [ current_buffer ], current_buffer ):

+ 5 - 5
python/ycm/tests/event_notification_test.py

@@ -351,7 +351,7 @@ def _Check_FileReadyToParse_Diagnostic_Warning( ycm ):
       assert_that(
         test_utils.VIM_SIGNS,
         contains(
-          VimSign( SIGN_BUFFER_ID_INITIAL_VALUE + 1, 2, 'YcmWarning', 1 )
+          VimSign( SIGN_BUFFER_ID_INITIAL_VALUE + 2, 2, 'YcmWarning', 1 )
         )
       )
       eq_( ycm.GetErrorCount(), 0 )
@@ -363,7 +363,7 @@ def _Check_FileReadyToParse_Diagnostic_Warning( ycm ):
       assert_that(
         test_utils.VIM_SIGNS,
         contains(
-          VimSign( SIGN_BUFFER_ID_INITIAL_VALUE + 1, 2, 'YcmWarning', 1 )
+          VimSign( SIGN_BUFFER_ID_INITIAL_VALUE + 2, 2, 'YcmWarning', 1 )
         )
       )
       eq_( ycm.GetErrorCount(), 0 )
@@ -390,7 +390,7 @@ def _Check_FileReadyToParse_Diagnostic_Clean( ycm ):
 
 
 @patch( 'ycm.youcompleteme.YouCompleteMe._AddUltiSnipsDataIfNeeded' )
-@YouCompleteMeInstance( { 'collect_identifiers_from_tags_files': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_collect_identifiers_from_tags_files': 1 } )
 def EventNotification_FileReadyToParse_TagFiles_UnicodeWorkingDirectory_test(
     ycm, *args ):
   unicode_dir = PathToTestFile( 'uni¢𐍈d€' )
@@ -534,7 +534,7 @@ def EventNotification_BufferUnload_BuildRequestForDeletedAndUnsavedBuffers_test(
 @patch( 'ycm.vimsupport.CaptureVimCommand', return_value = """
 fooGroup xxx foo bar
              links to Statement""" )
-@YouCompleteMeInstance( { 'seed_identifiers_with_syntax': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_seed_identifiers_with_syntax': 1 } )
 def EventNotification_FileReadyToParse_SyntaxKeywords_SeedWithCache_test(
     ycm, *args ):
 
@@ -569,7 +569,7 @@ def EventNotification_FileReadyToParse_SyntaxKeywords_SeedWithCache_test(
 @patch( 'ycm.vimsupport.CaptureVimCommand', return_value = """
 fooGroup xxx foo bar
              links to Statement""" )
-@YouCompleteMeInstance( { 'seed_identifiers_with_syntax': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_seed_identifiers_with_syntax': 1 } )
 def EventNotification_FileReadyToParse_SyntaxKeywords_ClearCacheIfRestart_test(
     ycm, *args ):
 

+ 53 - 33
python/ycm/tests/omni_completer_test.py

@@ -1,6 +1,6 @@
 # encoding: utf-8
 #
-# Copyright (C) 2016 YouCompleteMe contributors
+# Copyright (C) 2016-2018 YouCompleteMe contributors
 #
 # This file is part of YouCompleteMe.
 #
@@ -39,7 +39,8 @@ TRIGGERS = {
 }
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_List_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -62,7 +63,8 @@ def OmniCompleter_GetCompletions_Cache_List_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_ListFilter_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -85,7 +87,8 @@ def OmniCompleter_GetCompletions_Cache_ListFilter_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_List_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -108,7 +111,8 @@ def OmniCompleter_GetCompletions_NoCache_List_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_ListFilter_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -133,7 +137,8 @@ def OmniCompleter_GetCompletions_NoCache_ListFilter_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_UseFindStart_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -158,7 +163,8 @@ def OmniCompleter_GetCompletions_NoCache_UseFindStart_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_UseFindStart_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -183,7 +189,8 @@ def OmniCompleter_GetCompletions_Cache_UseFindStart_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_Object_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -206,7 +213,8 @@ def OmniCompleter_GetCompletions_Cache_Object_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_ObjectList_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -250,7 +258,8 @@ def OmniCompleter_GetCompletions_Cache_ObjectList_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_ObjectList_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -302,7 +311,8 @@ def OmniCompleter_GetCompletions_NoCache_ObjectList_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_ObjectListObject_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -346,7 +356,8 @@ def OmniCompleter_GetCompletions_Cache_ObjectListObject_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_ObjectListObject_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -398,7 +409,8 @@ def OmniCompleter_GetCompletions_NoCache_ObjectListObject_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_List_Unicode_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -423,7 +435,8 @@ def OmniCompleter_GetCompletions_Cache_List_Unicode_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_List_Unicode_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -448,7 +461,8 @@ def OmniCompleter_GetCompletions_NoCache_List_Unicode_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_List_Filter_Unicode_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -471,7 +485,8 @@ def OmniCompleter_GetCompletions_Cache_List_Filter_Unicode_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_List_Filter_Unicode_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -494,7 +509,8 @@ def OmniCompleter_GetCompletions_NoCache_List_Filter_Unicode_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_ObjectList_Unicode_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -538,7 +554,8 @@ def OmniCompleter_GetCompletions_Cache_ObjectList_Unicode_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_Cache_ObjectListObject_Unicode_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -597,7 +614,8 @@ def OmniCompleter_GetCompletions_Cache_ObjectListObject_Unicode_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 1, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 1,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_RestoreCursorPositionAfterOmnifuncCall_test(
   ycm ):
 
@@ -631,7 +649,8 @@ def OmniCompleter_GetCompletions_RestoreCursorPositionAfterOmnifuncCall_test(
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_NoSemanticTrigger_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -654,7 +673,8 @@ def OmniCompleter_GetCompletions_NoCache_NoSemanticTrigger_test( ycm ):
     )
 
 
-@YouCompleteMeInstance( { 'cache_omnifunc': 0, 'semantic_triggers': TRIGGERS } )
+@YouCompleteMeInstance( { 'g:ycm_cache_omnifunc': 0,
+                          'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_NoCache_ForceSemantic_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -678,9 +698,9 @@ def OmniCompleter_GetCompletions_NoCache_ForceSemantic_test( ycm ):
 
 
 @YouCompleteMeInstance( {
-  'cache_omnifunc': 0,
-  'filetype_specific_completion_to_disable': { FILETYPE: 1 },
-  'semantic_triggers': TRIGGERS } )
+  'g:ycm_cache_omnifunc': 0,
+  'g:ycm_filetype_specific_completion_to_disable': { FILETYPE: 1 },
+  'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_FiletypeDisabled_SemanticTrigger_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -704,9 +724,9 @@ def OmniCompleter_GetCompletions_FiletypeDisabled_SemanticTrigger_test( ycm ):
 
 
 @YouCompleteMeInstance( {
-  'cache_omnifunc': 0,
-  'filetype_specific_completion_to_disable': { '*': 1 },
-  'semantic_triggers': TRIGGERS } )
+  'g:ycm_cache_omnifunc': 0,
+  'g:ycm_filetype_specific_completion_to_disable': { '*': 1 },
+  'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_AllFiletypesDisabled_SemanticTrigger_test(
   ycm ):
 
@@ -732,9 +752,9 @@ def OmniCompleter_GetCompletions_AllFiletypesDisabled_SemanticTrigger_test(
 
 
 @YouCompleteMeInstance( {
-  'cache_omnifunc': 0,
-  'filetype_specific_completion_to_disable': { FILETYPE: 1 },
-  'semantic_triggers': TRIGGERS } )
+  'g:ycm_cache_omnifunc': 0,
+  'g:ycm_filetype_specific_completion_to_disable': { FILETYPE: 1 },
+  'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_FiletypeDisabled_ForceSemantic_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:
@@ -758,9 +778,9 @@ def OmniCompleter_GetCompletions_FiletypeDisabled_ForceSemantic_test( ycm ):
 
 
 @YouCompleteMeInstance( {
-  'cache_omnifunc': 0,
-  'filetype_specific_completion_to_disable': { '*': 1 },
-  'semantic_triggers': TRIGGERS } )
+  'g:ycm_cache_omnifunc': 0,
+  'g:ycm_filetype_specific_completion_to_disable': { '*': 1 },
+  'g:ycm_semantic_triggers': TRIGGERS } )
 def OmniCompleter_GetCompletions_AllFiletypesDisabled_ForceSemantic_test( ycm ):
   def Omnifunc( findstart, base ):
     if findstart:

+ 35 - 29
python/ycm/tests/test_utils.py

@@ -22,11 +22,12 @@ from __future__ import absolute_import
 # Not installing aliases from python-future; it's unreliable and slow.
 from builtins import *  # noqa
 
-from future.utils import PY2
+from future.utils import iteritems, PY2
 from mock import DEFAULT, MagicMock, patch
 from hamcrest import assert_that, equal_to
 import contextlib
 import functools
+import json
 import nose
 import os
 import re
@@ -56,6 +57,8 @@ SIGN_UNPLACE_REGEX = re.compile(
   '^sign unplace (?P<id>\d+) buffer=(?P<bufnr>\d+)$' )
 REDIR_START_REGEX = re.compile( '^redir => (?P<variable>[\w:]+)$' )
 REDIR_END_REGEX = re.compile( '^redir END$' )
+EXISTS_REGEX = re.compile( '^exists\( \'(?P<option>[\w:]+)\' \)$' )
+LET_REGEX = re.compile( '^let (?P<option>[\w:]+) = (?P<value>.*)$' )
 
 # One-and only instance of mocked Vim object. The first 'import vim' that is
 # executed binds the vim module to the instance of MagicMock that is created,
@@ -70,6 +73,15 @@ VIM_MOCK = MagicMock()
 VIM_MATCHES = []
 VIM_SIGNS = []
 
+VIM_OPTIONS = {
+  '&previewheight': 12,
+  '&columns': 80,
+  '&ruler': 0,
+  '&showcmd': 1,
+  '&hidden': 0,
+  '&expandtab': 1
+}
+
 REDIR = {
   'status': False,
   'variable': '',
@@ -153,23 +165,21 @@ def _MockVimBufferEval( value ):
 
 
 def _MockVimOptionsEval( value ):
-  if value == '&previewheight':
-    return 12
-
-  if value == '&columns':
-    return 80
-
-  if value == '&ruler':
-    return 0
-
-  if value == '&showcmd':
-    return 1
+  result = VIM_OPTIONS.get( value )
+  if result is not None:
+    return result
 
-  if value == '&hidden':
-    return 0
+  if value == 'keys( g: )':
+    global_options = {}
+    for key, value in iteritems( VIM_OPTIONS ):
+      if key.startswith( 'g:' ):
+        global_options[ key[ 2: ] ] = value
+    return global_options
 
-  if value == '&expandtab':
-    return 1
+  match = EXISTS_REGEX.search( value )
+  if match:
+    option = match.group( 'option' )
+    return option in VIM_OPTIONS
 
   return None
 
@@ -213,23 +223,12 @@ def _MockVimMatchEval( value ):
   return None
 
 
-# This variable exists to easily mock the 'g:ycm_server_python_interpreter'
-# option in tests.
-server_python_interpreter = ''
-
-
 def _MockVimEval( value ):
-  if value == 'g:ycm_min_num_of_chars_for_completion':
-    return 0
-
-  if value == 'g:ycm_server_python_interpreter':
-    return server_python_interpreter
-
-  result = _MockVimFunctionsEval( value )
+  result = _MockVimOptionsEval( value )
   if result is not None:
     return result
 
-  result = _MockVimOptionsEval( value )
+  result = _MockVimFunctionsEval( value )
   if result is not None:
     return result
 
@@ -317,6 +316,13 @@ def _MockVimCommand( command ):
   if result:
     return
 
+  match = LET_REGEX.search( command )
+  if match:
+    option = match.group( 'option' )
+    value = json.loads( match.group( 'value' ) )
+    VIM_OPTIONS[ option ] = value
+    return
+
   return DEFAULT
 
 

+ 43 - 41
python/ycm/tests/youcompleteme_test.py

@@ -33,11 +33,11 @@ from hamcrest import ( assert_that, contains, empty, equal_to, is_in, is_not,
 from mock import call, MagicMock, patch
 
 from ycm.paths import _PathToPythonUsedDuringBuild
-from ycm.vimsupport import SIGN_BUFFER_ID_INITIAL_VALUE
-from ycm.youcompleteme import YouCompleteMe
-from ycm.tests import ( MakeUserOptions, StopServer, test_utils,
-                        WaitUntilReady, YouCompleteMeInstance )
+from ycm.vimsupport import SetVariableValue, SIGN_BUFFER_ID_INITIAL_VALUE
+from ycm.tests import ( StopServer, test_utils, UserOptions, WaitUntilReady,
+                        YouCompleteMeInstance )
 from ycm.client.base_request import _LoadExtraConfFile
+from ycm.youcompleteme import YouCompleteMe
 from ycmd.responses import ServerError
 from ycm.tests.mock_utils import ( MockAsyncServerResponseDone,
                                    MockAsyncServerResponseInProgress,
@@ -54,10 +54,11 @@ def YouCompleteMe_YcmCoreNotImported_test( ycm ):
 
 @patch( 'ycm.vimsupport.PostVimMessage' )
 def YouCompleteMe_InvalidPythonInterpreterPath_test( post_vim_message ):
-  try:
-    with patch( 'ycm.tests.test_utils.server_python_interpreter',
-                '/invalid/path/to/python' ):
-      ycm = YouCompleteMe( MakeUserOptions() )
+  with UserOptions( {
+    'g:ycm_server_python_interpreter': '/invalid/python/path' } ):
+    try:
+      ycm = YouCompleteMe()
+
       assert_that( ycm.IsServerAlive(), equal_to( False ) )
       post_vim_message.assert_called_once_with(
         "Unable to start the ycmd server. "
@@ -65,27 +66,28 @@ def YouCompleteMe_InvalidPythonInterpreterPath_test( post_vim_message ):
         "to a valid Python 2.7 or 3.4+. "
         "Correct the error then restart the server with ':YcmRestartServer'." )
 
-    post_vim_message.reset_mock()
+      post_vim_message.reset_mock()
 
-    with patch( 'ycm.tests.test_utils.server_python_interpreter',
-                _PathToPythonUsedDuringBuild() ):
+      SetVariableValue( 'g:ycm_server_python_interpreter',
+                        _PathToPythonUsedDuringBuild() )
       ycm.RestartServer()
 
-    assert_that( ycm.IsServerAlive(), equal_to( True ) )
-    post_vim_message.assert_called_once_with( 'Restarting ycmd server...' )
-  finally:
-    WaitUntilReady()
-    StopServer( ycm )
+      assert_that( ycm.IsServerAlive(), equal_to( True ) )
+      post_vim_message.assert_called_once_with( 'Restarting ycmd server...' )
+    finally:
+      WaitUntilReady()
+      StopServer( ycm )
 
 
 @patch( 'ycmd.utils.PathToFirstExistingExecutable', return_value = None )
 @patch( 'ycm.paths._EndsWithPython', return_value = False )
 @patch( 'ycm.vimsupport.PostVimMessage' )
 def YouCompleteMe_NoPythonInterpreterFound_test( post_vim_message, *args ):
-  try:
-    with patch( 'ycmd.utils.ReadFile', side_effect = IOError ):
+  with UserOptions( {} ):
+    try:
+      with patch( 'ycmd.utils.ReadFile', side_effect = IOError ):
+        ycm = YouCompleteMe()
 
-      ycm = YouCompleteMe( MakeUserOptions() )
       assert_that( ycm.IsServerAlive(), equal_to( False ) )
       post_vim_message.assert_called_once_with(
         "Unable to start the ycmd server. Cannot find Python 2.7 or 3.4+. "
@@ -93,17 +95,17 @@ def YouCompleteMe_NoPythonInterpreterFound_test( post_vim_message, *args ):
         "interpreter path. "
         "Correct the error then restart the server with ':YcmRestartServer'." )
 
-    post_vim_message.reset_mock()
+      post_vim_message.reset_mock()
 
-    with patch( 'ycm.tests.test_utils.server_python_interpreter',
-                _PathToPythonUsedDuringBuild() ):
+      SetVariableValue( 'g:ycm_server_python_interpreter',
+                        _PathToPythonUsedDuringBuild() )
       ycm.RestartServer()
 
-    assert_that( ycm.IsServerAlive(), equal_to( True ) )
-    post_vim_message.assert_called_once_with( 'Restarting ycmd server...' )
-  finally:
-    WaitUntilReady()
-    StopServer( ycm )
+      assert_that( ycm.IsServerAlive(), equal_to( True ) )
+      post_vim_message.assert_called_once_with( 'Restarting ycmd server...' )
+    finally:
+      WaitUntilReady()
+      StopServer( ycm )
 
 
 @YouCompleteMeInstance()
@@ -185,7 +187,7 @@ def YouCompleteMe_NotifyUserIfServerCrashed_UnexpectedExitCode_test():
   } )
 
 
-@YouCompleteMeInstance( { 'extra_conf_vim_data': [ 'tempname()' ] } )
+@YouCompleteMeInstance( { 'g:ycm_extra_conf_vim_data': [ 'tempname()' ] } )
 def YouCompleteMe_DebugInfo_ServerRunning_test( ycm ):
   dir_of_script = os.path.dirname( os.path.abspath( __file__ ) )
   buf_name = os.path.join( dir_of_script, 'testdata', 'test.cpp' )
@@ -246,7 +248,7 @@ def YouCompleteMe_OnVimLeave_RemoveClientLogfileByDefault_test( ycm ):
                  'Logfile {0} was not removed.'.format( client_logfile ) )
 
 
-@YouCompleteMeInstance( { 'keep_logfiles': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_keep_logfiles': 1 } )
 def YouCompleteMe_OnVimLeave_KeepClientLogfile_test( ycm ):
     client_logfile = ycm._client_logfile
     assert_that( os.path.isfile( client_logfile ),
@@ -421,9 +423,9 @@ def YouCompleteMe_ShowDiagnostics_NoDiagnosticsDetected_test(
   set_location_list_for_window.assert_called_once_with( 0, [] )
 
 
-@YouCompleteMeInstance( { 'log_level': 'debug',
-                          'keep_logfiles': 1,
-                          'open_loclist_on_ycm_diags': 0 } )
+@YouCompleteMeInstance( { 'g:ycm_log_level': 'debug',
+                          'g:ycm_keep_logfiles': 1,
+                          'g:ycm_open_loclist_on_ycm_diags': 0 } )
 @patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
         return_value = True )
 @patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@@ -465,7 +467,7 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
   } ] )
 
 
-@YouCompleteMeInstance( { 'open_loclist_on_ycm_diags': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_open_loclist_on_ycm_diags': 1 } )
 @patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
         return_value = True )
 @patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@@ -513,9 +515,9 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_OpenLocationList_test(
   open_location_list.assert_called_once_with( focus = True )
 
 
-@YouCompleteMeInstance( { 'echo_current_diagnostic': 1,
-                          'enable_diagnostic_signs': 1,
-                          'enable_diagnostic_highlighting': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_echo_current_diagnostic': 1,
+                          'g:ycm_enable_diagnostic_signs': 1,
+                          'g:ycm_enable_diagnostic_highlighting': 1 } )
 @patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
         return_value = True )
 @patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@@ -665,7 +667,7 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
     )
 
 
-@YouCompleteMeInstance( { 'enable_diagnostic_highlighting': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_enable_diagnostic_highlighting': 1 } )
 def YouCompleteMe_UpdateMatches_ClearDiagnosticMatchesInNewBuffer_test( ycm ):
   current_buffer = VimBuffer( 'buffer',
                               filetype = 'c',
@@ -684,8 +686,8 @@ def YouCompleteMe_UpdateMatches_ClearDiagnosticMatchesInNewBuffer_test( ycm ):
     assert_that( test_utils.VIM_MATCHES, empty() )
 
 
-@YouCompleteMeInstance( { 'echo_current_diagnostic': 1,
-                          'always_populate_location_list': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_echo_current_diagnostic': 1,
+                          'g:ycm_always_populate_location_list': 1 } )
 @patch.object( ycm_buffer_module,
                'DIAGNOSTIC_UI_ASYNC_FILETYPES',
                [ 'ycmtest' ] )
@@ -799,8 +801,8 @@ def YouCompleteMe_AsyncDiagnosticUpdate_SingleFile_test( ycm,
     ] )
 
 
-@YouCompleteMeInstance( { 'echo_current_diagnostic': 1,
-                          'always_populate_location_list': 1 } )
+@YouCompleteMeInstance( { 'g:ycm_echo_current_diagnostic': 1,
+                          'g:ycm_always_populate_location_list': 1 } )
 @patch.object( ycm_buffer_module,
                'DIAGNOSTIC_UI_ASYNC_FILETYPES',
                [ 'ycmtest' ] )

+ 8 - 0
python/ycm/vimsupport.py

@@ -49,6 +49,8 @@ NO_SELECTION_MADE_MSG = "No valid selection was made; aborting."
 # value is then incremented for each new sign. This should prevent conflicts
 # with other plugins using signs.
 SIGN_BUFFER_ID_INITIAL_VALUE = 100000000
+# This holds the next sign's id to assign for each buffer.
+SIGN_ID_FOR_BUFFER = defaultdict( lambda: SIGN_BUFFER_ID_INITIAL_VALUE )
 
 SIGN_PLACE_REGEX = re.compile(
   r"^.*=(?P<line>\d+).*=(?P<id>\d+).*=(?P<name>Ycm\w+)$" )
@@ -204,6 +206,12 @@ def GetSignsInBuffer( buffer_number ):
   return signs
 
 
+def CreateSign( line, name, buffer_number ):
+  sign_id = SIGN_ID_FOR_BUFFER[ buffer_number ]
+  SIGN_ID_FOR_BUFFER[ buffer_number ] += 1
+  return DiagnosticSign( sign_id, line, name, buffer_number )
+
+
 def UnplaceSign( sign ):
   vim.command( 'sign unplace {0} buffer={1}'.format( sign.id,
                                                      sign.buffer_number ) )

+ 21 - 12
python/ycm/youcompleteme.py

@@ -35,8 +35,7 @@ from ycm import base, paths, vimsupport
 from ycm.buffer import ( BufferDict,
                          DIAGNOSTIC_UI_FILETYPES,
                          DIAGNOSTIC_UI_ASYNC_FILETYPES )
-from ycmd import utils
-from ycmd import server_utils
+from ycmd import server_utils, user_options_store, utils
 from ycmd.request_wrap import RequestWrap
 from ycm.omni_completer import OmniCompleter
 from ycm import syntax_parse
@@ -107,12 +106,12 @@ HANDLE_FLAG_INHERIT = 0x00000001
 
 
 class YouCompleteMe( object ):
-  def __init__( self, user_options ):
+  def __init__( self ):
     self._available_completers = {}
-    self._user_options = user_options
+    self._user_options = None
     self._user_notified_about_crash = False
-    self._omnicomp = OmniCompleter( user_options )
-    self._buffers = BufferDict( user_options )
+    self._omnicomp = None
+    self._buffers = None
     self._latest_completion_request = None
     self._logger = logging.getLogger( 'ycm' )
     self._client_logfile = None
@@ -134,6 +133,14 @@ class YouCompleteMe( object ):
     self._server_is_ready_with_cache = False
     self._message_poll_request = None
 
+    base.LoadJsonDefaultsIntoVim()
+    user_options_store.SetAll( base.BuildServerConf() )
+    self._user_options = user_options_store.GetAll()
+    self._omnicomp = OmniCompleter( self._user_options )
+    self._buffers = BufferDict( self._user_options )
+
+    self._SetLogLevel()
+
     hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
     options_dict = dict( self._user_options )
     options_dict[ 'hmac_secret' ] = utils.ToUnicode(
@@ -196,12 +203,6 @@ class YouCompleteMe( object ):
 
     self._client_logfile = utils.CreateLogfile( CLIENT_LOGFILE_FORMAT )
 
-    log_level = self._user_options[ 'log_level' ]
-    numeric_level = getattr( logging, log_level.upper(), None )
-    if not isinstance( numeric_level, int ):
-      raise ValueError( 'Invalid log level: {0}'.format( log_level ) )
-    self._logger.setLevel( numeric_level )
-
     handler = logging.FileHandler( self._client_logfile )
 
     # On Windows and Python prior to 3.4, file handles are inherited by child
@@ -221,6 +222,14 @@ class YouCompleteMe( object ):
     self._logger.addHandler( handler )
 
 
+  def _SetLogLevel( self ):
+    log_level = self._user_options[ 'log_level' ]
+    numeric_level = getattr( logging, log_level.upper(), None )
+    if not isinstance( numeric_level, int ):
+      raise ValueError( 'Invalid log level: {0}'.format( log_level ) )
+    self._logger.setLevel( numeric_level )
+
+
   def IsServerAlive( self ):
     # When the process hasn't finished yet, poll() returns None.
     return bool( self._server_popen ) and self._server_popen.poll() is None