Ver código fonte

Merge https://github.com/Valloric/YouCompleteMe into patch-1

Chiel ten Brinke 9 anos atrás
pai
commit
40db3c0865

+ 50 - 0
CODE_OF_CONDUCT.md

@@ -0,0 +1,50 @@
+# Contributor Code of Conduct
+
+As contributors and maintainers of this project, and in the interest of
+fostering an open and welcoming community, we pledge to respect all people who
+contribute through reporting issues, posting feature requests, updating
+documentation, submitting pull requests or patches, and other activities.
+
+We are committed to making participation in this project a harassment-free
+experience for everyone, regardless of level of experience, gender, gender
+identity and expression, sexual orientation, disability, personal appearance,
+body size, race, ethnicity, age, religion, or nationality.
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery
+* Personal attacks
+* Trolling or insulting/derogatory comments
+* Public or private harassment
+* Publishing other's private information, such as physical or electronic
+  addresses, without explicit permission
+* Other unethical or unprofessional conduct
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+By adopting this Code of Conduct, project maintainers commit themselves to
+fairly and consistently applying these principles to every aspect of managing
+this project. Project maintainers who do not follow or enforce the Code of
+Conduct may be permanently removed from the project team.
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting a project maintainer at val@markovic.io. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. Maintainers are
+obligated to maintain confidentiality with regard to the reporter of an
+incident.
+
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 1.3.0, available at
+[http://contributor-covenant.org/version/1/3/0/][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/3/0/

+ 11 - 1
README.md

@@ -30,6 +30,7 @@ YouCompleteMe: a code-completion engine for Vim
     - [YcmCompleter subcommands](#ycmcompleter-subcommands)
 - [Options](#options)
 - [FAQ](#faq)
+- [Contributor Code of Conduct](#contributor-code-of-conduct)
 - [Contact](#contact)
 - [License](#license)
 
@@ -2609,6 +2610,14 @@ os.environ['PATH'] = ';'.join(path)
 EOF
 ```
 
+Contributor Code of Conduct
+---------------------------
+
+Please note that this project is released with a [Contributor Code of
+Conduct][ccoc]. By participating in this project you agree to abide by its
+terms.
+
+
 Contact
 -------
 
@@ -2623,6 +2632,7 @@ The latest version of the plugin is available at
 
 The author's homepage is <http://val.markovic.io>.
 
+
 License
 -------
 
@@ -2684,4 +2694,4 @@ This software is licensed under the [GPL v3 license][gpl].
 [rust-src]: https://www.rust-lang.org/downloads.html
 [add-msbuild-to-path]: http://stackoverflow.com/questions/6319274/how-do-i-run-msbuild-from-the-command-line-using-windows-sdk-7-1
 [identify-R6034-cause]: http://stackoverflow.com/questions/14552348/runtime-error-r6034-in-embedded-python-application/34696022
-
+[ccoc]: https://github.com/Valloric/YouCompleteMe/blob/master/CODE_OF_CONDUCT.md

+ 2 - 2
appveyor.yml

@@ -6,9 +6,9 @@ environment:
 install:
   - git submodule update --init --recursive
   - ps: $env:python = if ($env:arch -eq 32) { 'C:\Python27' } else { 'C:\Python27-x64' }
-  - ps: (new-object net.webclient).DownloadFile('https://raw.github.com/pypa/pip/master/contrib/get-pip.py', 'C:\get-pip.py')
+  - appveyor DownloadFile https://bootstrap.pypa.io/get-pip.py
   - set PATH=%python%;%python%\Scripts;%PATH%
-  - python C:\get-pip.py
+  - python get-pip.py
   - pip install -r python\test_requirements.txt
 build_script:
   - python run_tests.py

+ 28 - 0
python/ycm/client/omni_completion_request.py

@@ -17,6 +17,7 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from ycmd.utils import ToUtf8IfNeeded
 from ycm.client.completion_request import CompletionRequest
 
 
@@ -34,5 +35,32 @@ class OmniCompletionRequest( CompletionRequest ):
     return True
 
 
+  def RawResponse( self ):
+    return _ConvertVimDatasToCompletionDatas( self._results )
+
+
   def Response( self ):
     return self._results
+
+
+def ConvertVimDataToCompletionData( vim_data ):
+  # see :h complete-items for a description of the dictionary fields
+  completion_data = {}
+
+  if 'word' in vim_data:
+    completion_data[ 'insertion_text' ] = ToUtf8IfNeeded( vim_data[ 'word' ] )
+  if 'abbr' in vim_data:
+    completion_data[ 'menu_text' ] = ToUtf8IfNeeded( vim_data[ 'abbr' ] )
+  if 'menu' in vim_data:
+    completion_data[ 'extra_menu_info' ] = ToUtf8IfNeeded( vim_data[ 'menu' ] )
+  if 'kind' in vim_data:
+    completion_data[ 'kind' ] = [ ToUtf8IfNeeded( vim_data[ 'kind' ] ) ]
+  if 'info' in vim_data:
+    completion_data[ 'detailed_info' ] = ToUtf8IfNeeded( vim_data[ 'info' ] )
+
+  return completion_data
+
+
+def _ConvertVimDatasToCompletionDatas( response_data ):
+  return [ ConvertVimDataToCompletionData( x )
+           for x in response_data ]

+ 49 - 20
python/ycm/paths.py

@@ -18,14 +18,16 @@
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
+import sys
 import vim
 import functools
+import re
 from ycmd import utils
 
 DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) )
 
-WIN_PYTHON27_PATH = 'C:\python27\python.exe'
-WIN_PYTHON26_PATH = 'C:\python26\python.exe'
+WIN_PYTHON_PATH = os.path.join( sys.exec_prefix, 'python.exe' )
+PYTHON_BINARY_REGEX = re.compile( r'python(2(\.[67])?)?(.exe)?$' )
 
 
 def Memoize( obj ):
@@ -42,29 +44,56 @@ def Memoize( obj ):
 
 @Memoize
 def PathToPythonInterpreter():
-  user_path_to_python = vim.eval( 'g:ycm_path_to_python_interpreter' )
+  python_interpreter = vim.eval( 'g:ycm_path_to_python_interpreter' )
 
-  if user_path_to_python:
-    return user_path_to_python
+  if python_interpreter:
+    if IsPythonVersionCorrect( python_interpreter ):
+      return python_interpreter
 
-  # We check for 'python2' before 'python' because some OS's (I'm looking at
-  # you Arch Linux) have made the... interesting decision to point
-  # /usr/bin/python to python3.
-  python_names = [ 'python2', 'python' ]
+    raise RuntimeError( "Path in 'g:ycm_path_to_python_interpreter' option "
+                        "does not point to a valid Python 2.6 or 2.7." )
 
-  path_to_python = utils.PathToFirstExistingExecutable( python_names )
-  if path_to_python:
-    return path_to_python
+  # On UNIX platforms, we use sys.executable as the Python interpreter path.
+  # We cannot use sys.executable on Windows because for unknown reasons, it
+  # returns the Vim executable. Instead, we use sys.exec_prefix to deduce the
+  # interpreter path.
+  python_interpreter = ( WIN_PYTHON_PATH if utils.OnWindows() else
+                         sys.executable )
 
-  # On Windows, Python may not be on the PATH at all, so we check some common
-  # install locations.
-  if utils.OnWindows():
-    if os.path.exists( WIN_PYTHON27_PATH ):
-      return WIN_PYTHON27_PATH
-    elif os.path.exists( WIN_PYTHON26_PATH ):
-      return WIN_PYTHON26_PATH
+  if IsPythonVersionCorrect( python_interpreter ):
+    return python_interpreter
 
-  raise RuntimeError( 'Python 2.7/2.6 not installed!' )
+  # As a last resort, we search python in the PATH. We check 'python2' before
+  # 'python' because on some distributions (Arch Linux for example), python
+  # refers to python3.
+  python_interpreter = utils.PathToFirstExistingExecutable( [ 'python2',
+                                                              'python' ] )
+
+  if IsPythonVersionCorrect( python_interpreter ):
+    return python_interpreter
+
+  raise RuntimeError( "Cannot find Python 2.6 or 2.7. You can set its path "
+                      "using the 'g:ycm_path_to_python_interpreter' "
+                      "option." )
+
+
+def EndsWithPython( path ):
+  """Check if given path ends with a python 2.6 or 2.7 name."""
+  return PYTHON_BINARY_REGEX.search( path ) is not None
+
+
+def IsPythonVersionCorrect( path ):
+  """Check if given path is the Python interpreter version 2.6 or 2.7."""
+  if not EndsWithPython( path ):
+    return False
+
+  command = [ path,
+              '-c',
+              "import sys;"
+              "major, minor = sys.version_info[ :2 ];"
+              "sys.exit( major != 2 or minor < 6)" ]
+
+  return utils.SafePopen( command ).wait() == 0
 
 
 def PathToServerScript():

+ 8 - 0
python/ycm/test_utils.py

@@ -18,6 +18,7 @@
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
 from mock import MagicMock
+from hamcrest import assert_that, equal_to
 import re
 import sys
 
@@ -116,3 +117,10 @@ def MockVimModule():
   sys.modules[ 'vim' ] = VIM_MOCK
 
   return VIM_MOCK
+
+
+class ExtendedMock( MagicMock ):
+
+  def assert_has_exact_calls( self, calls, any_order = False ):
+    self.assert_has_calls( calls, any_order )
+    assert_that( self.call_count, equal_to( len( calls ) ) )

+ 30 - 24
python/ycm/tests/event_notification_test.py

@@ -16,10 +16,11 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
-from ycm.test_utils import MockVimModule
+from ycm.test_utils import MockVimModule, ExtendedMock
 MockVimModule()
 
-import contextlib, os
+import contextlib
+import os
 
 from ycm.youcompleteme import YouCompleteMe
 from ycmd import user_options_store
@@ -36,6 +37,7 @@ DEFAULT_CLIENT_OPTIONS = {
   'extra_conf_vim_data': [],
 }
 
+
 def PostVimMessage_Call( message ):
   """Return a mock.call object for a call to vimsupport.PostVimMesasge with the
   supplied message"""
@@ -74,7 +76,7 @@ def MockArbitraryBuffer( filetype, native_available = True ):
       raise ValueError( 'Unexpected evaluation' )
 
     # Arbitrary, but valid, cursor position
-    vim_current.window.cursor = (1,2)
+    vim_current.window.cursor = ( 1, 2 )
 
     # Arbitrary, but valid, single buffer open
     current_buffer = MagicMock()
@@ -129,7 +131,7 @@ class EventNotification_test( object ):
 
   def setUp( self ):
     options = dict( user_options_store.DefaultOptions() )
-    options.update ( DEFAULT_CLIENT_OPTIONS )
+    options.update( DEFAULT_CLIENT_OPTIONS )
     user_options_store.SetAll( options )
 
     self.server_state = YouCompleteMe( user_options_store.GetAll() )
@@ -141,7 +143,7 @@ class EventNotification_test( object ):
       self.server_state.OnVimLeave()
 
 
-  @patch( 'vim.command' )
+  @patch( 'vim.command', new_callable = ExtendedMock )
   def FileReadyToParse_NonDiagnostic_Error_test( self, vim_command ):
     # This test validates the behaviour of YouCompleteMe.ValidateParseRequest in
     # combination with YouCompleteMe.OnFileReadyToParse when the completer
@@ -158,13 +160,13 @@ class EventNotification_test( object ):
         self.server_state.ValidateParseRequest()
 
         # The first call raises a warning
-        vim_command.assert_has_calls( [
+        vim_command.assert_has_exact_calls( [
           PostVimMessage_Call( ERROR_TEXT ),
         ] )
 
         # Subsequent calls don't re-raise the warning
         self.server_state.ValidateParseRequest()
-        vim_command.assert_has_calls( [
+        vim_command.assert_has_exact_calls( [
           PostVimMessage_Call( ERROR_TEXT ),
         ] )
 
@@ -172,7 +174,7 @@ class EventNotification_test( object ):
         self.server_state.OnFileReadyToParse()
         assert self.server_state.DiagnosticsForCurrentFileReady()
         self.server_state.ValidateParseRequest()
-        vim_command.assert_has_calls( [
+        vim_command.assert_has_exact_calls( [
           PostVimMessage_Call( ERROR_TEXT ),
           PostVimMessage_Call( ERROR_TEXT ),
         ] )
@@ -187,8 +189,10 @@ class EventNotification_test( object ):
         vim_command.assert_not_called()
 
 
-  @patch( 'ycm.client.event_notification._LoadExtraConfFile' )
-  @patch( 'ycm.client.event_notification._IgnoreExtraConfFile' )
+  @patch( 'ycm.client.event_notification._LoadExtraConfFile',
+          new_callable = ExtendedMock )
+  @patch( 'ycm.client.event_notification._IgnoreExtraConfFile',
+          new_callable = ExtendedMock )
   def FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
       self,
       ignore_extra_conf,
@@ -211,25 +215,26 @@ class EventNotification_test( object ):
 
         # When the user accepts the extra conf, we load it
         with patch( 'ycm.vimsupport.PresentDialog',
-                    return_value = 0 ) as present_dialog:
+                    return_value = 0,
+                    new_callable = ExtendedMock ) as present_dialog:
           self.server_state.OnFileReadyToParse()
           assert self.server_state.DiagnosticsForCurrentFileReady()
           self.server_state.ValidateParseRequest()
 
-          present_dialog.assert_has_calls( [
+          present_dialog.assert_has_exact_calls( [
             PresentDialog_Confirm_Call( MESSAGE ),
           ] )
-          load_extra_conf.assert_has_calls( [
+          load_extra_conf.assert_has_exact_calls( [
             call( FILE_NAME ),
           ] )
 
           # Subsequent calls don't re-raise the warning
           self.server_state.ValidateParseRequest()
 
-          present_dialog.assert_has_calls( [
+          present_dialog.assert_has_exact_calls( [
             PresentDialog_Confirm_Call( MESSAGE )
           ] )
-          load_extra_conf.assert_has_calls( [
+          load_extra_conf.assert_has_exact_calls( [
             call( FILE_NAME ),
           ] )
 
@@ -238,36 +243,37 @@ class EventNotification_test( object ):
           assert self.server_state.DiagnosticsForCurrentFileReady()
           self.server_state.ValidateParseRequest()
 
-          present_dialog.assert_has_calls( [
+          present_dialog.assert_has_exact_calls( [
             PresentDialog_Confirm_Call( MESSAGE ),
             PresentDialog_Confirm_Call( MESSAGE ),
           ] )
-          load_extra_conf.assert_has_calls( [
+          load_extra_conf.assert_has_exact_calls( [
             call( FILE_NAME ),
             call( FILE_NAME ),
           ] )
 
         # When the user rejects the extra conf, we reject it
         with patch( 'ycm.vimsupport.PresentDialog',
-                    return_value = 1 ) as present_dialog:
+                    return_value = 1,
+                    new_callable = ExtendedMock ) as present_dialog:
           self.server_state.OnFileReadyToParse()
           assert self.server_state.DiagnosticsForCurrentFileReady()
           self.server_state.ValidateParseRequest()
 
-          present_dialog.assert_has_calls( [
+          present_dialog.assert_has_exact_calls( [
             PresentDialog_Confirm_Call( MESSAGE ),
           ] )
-          ignore_extra_conf.assert_has_calls( [
+          ignore_extra_conf.assert_has_exact_calls( [
             call( FILE_NAME ),
           ] )
 
           # Subsequent calls don't re-raise the warning
           self.server_state.ValidateParseRequest()
 
-          present_dialog.assert_has_calls( [
+          present_dialog.assert_has_exact_calls( [
             PresentDialog_Confirm_Call( MESSAGE )
           ] )
-          ignore_extra_conf.assert_has_calls( [
+          ignore_extra_conf.assert_has_exact_calls( [
             call( FILE_NAME ),
           ] )
 
@@ -276,11 +282,11 @@ class EventNotification_test( object ):
           assert self.server_state.DiagnosticsForCurrentFileReady()
           self.server_state.ValidateParseRequest()
 
-          present_dialog.assert_has_calls( [
+          present_dialog.assert_has_exact_calls( [
             PresentDialog_Confirm_Call( MESSAGE ),
             PresentDialog_Confirm_Call( MESSAGE ),
           ] )
-          ignore_extra_conf.assert_has_calls( [
+          ignore_extra_conf.assert_has_exact_calls( [
             call( FILE_NAME ),
             call( FILE_NAME ),
           ] )

+ 76 - 0
python/ycm/tests/omni_completion_request_tests.py

@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+#
+# 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 mock import MagicMock
+from nose.tools import eq_
+from hamcrest import assert_that, has_entries
+
+from ycm.client.omni_completion_request import OmniCompletionRequest
+
+
+def BuildOmnicompletionRequest( results ):
+  omni_completer = MagicMock()
+  omni_completer.ComputeCandidates = MagicMock( return_value = results )
+
+  request = OmniCompletionRequest( omni_completer, None )
+  request.Start()
+
+  return request;
+
+
+def Done_AlwaysTrue_test():
+  request = BuildOmnicompletionRequest( [] )
+
+  eq_( request.Done(), True )
+
+
+def Response_FromOmniCompleter_test():
+  results = [ { "word": "test" } ]
+  request = BuildOmnicompletionRequest( results )
+
+  eq_( request.Response(), results )
+
+
+def RawResponse_ConvertedFromOmniCompleter_test():
+  vim_results = [
+    { "word": "WORD", "abbr": "ABBR", "menu": "MENU",
+      "kind": "KIND", "info": "INFO" },
+    { "word": "WORD2", "abbr": "ABBR2", "menu": "MENU2",
+      "kind": "KIND2", "info": "INFO" },
+    { "word": "WORD", "abbr": "ABBR",  },
+    {  },
+  ]
+  expected_results = [
+    has_entries( { "insertion_text": "WORD", "menu_text": "ABBR",
+                   "extra_menu_info": "MENU", "kind": [ "KIND" ],
+                   "detailed_info": "INFO" } ),
+    has_entries( { "insertion_text": "WORD2", "menu_text": "ABBR2",
+                   "extra_menu_info": "MENU2", "kind": [ "KIND2" ],
+                   "detailed_info": "INFO" } ),
+    has_entries( { "insertion_text": "WORD", "menu_text": "ABBR",  } ),
+    has_entries( {  } ),
+  ]
+  request = BuildOmnicompletionRequest( vim_results )
+
+  results = request.RawResponse()
+
+  eq_( len( results ), len( expected_results ) )
+  for result, expected_result in zip( results, expected_results ):
+    assert_that( result, expected_result )

+ 47 - 0
python/ycm/tests/paths_test.py

@@ -0,0 +1,47 @@
+#
+# 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 ycm.test_utils import MockVimModule
+MockVimModule()
+
+from nose.tools import ok_
+from ycm.paths import EndsWithPython
+
+
+def EndsWithPython_Python2Paths_test():
+  python_paths = [
+    'python',
+    '/usr/bin/python2.6',
+    '/home/user/.pyenv/shims/python2.7',
+    r'C:\Python26\python.exe'
+  ]
+
+  for path in python_paths:
+    ok_( EndsWithPython( path ) )
+
+
+def EndsWithPython_NotPython2Paths_test():
+  not_python_paths = [
+    '/opt/local/bin/vim',
+    r'C:\Program Files\Vim\vim74\gvim.exe',
+    '/usr/bin/python3',
+    '/home/user/.pyenv/shims/python3',
+  ]
+
+  for path in not_python_paths:
+    ok_( not EndsWithPython( path ) )