Jelajahi Sumber

Merge remote-tracking branch 'origin/master' into CsAddImport

Spencer G. Jones 9 tahun lalu
induk
melakukan
2a41101955

+ 1 - 2
README.md

@@ -1,7 +1,7 @@
 YouCompleteMe: a code-completion engine for Vim
 ===============================================
 
-[![Build Status](https://travis-ci.org/Valloric/YouCompleteMe.png?branch=master)](https://travis-ci.org/Valloric/YouCompleteMe)
+[![Build Status](https://travis-ci.org/Valloric/YouCompleteMe.svg?branch=master)](https://travis-ci.org/Valloric/YouCompleteMe)
 
 - [Intro](#intro)
 - [Installation](#installation)
@@ -26,7 +26,6 @@ YouCompleteMe: a code-completion engine for Vim
 - [Options](#options)
 - [FAQ](#faq)
 - [Contact](#contact)
-- [Project Management](#project-management)
 - [License](#license)
 
 Intro

+ 1 - 2
doc/youcompleteme.txt

@@ -159,7 +159,6 @@ Introduction ~
 - Options
 - FAQ
 - Contact
-- Project Management
 - License
 
 ===============================================================================
@@ -2322,7 +2321,7 @@ contributors
 References ~
 
 [1] https://travis-ci.org/Valloric/YouCompleteMe
-[2] https://travis-ci.org/Valloric/YouCompleteMe.png?branch=master
+[2] https://travis-ci.org/Valloric/YouCompleteMe.svg?branch=master
 [3] http://clang.llvm.org/
 [4] https://github.com/davidhalter/jedi
 [5] https://github.com/nosami/OmniSharpServer

+ 16 - 42
python/ycm/client/command_request.py

@@ -22,6 +22,7 @@ from ycm.client.base_request import BaseRequest, BuildRequestData, ServerError
 from ycm import vimsupport
 from ycmd.utils import ToUtf8IfNeeded
 
+
 def _EnsureBackwardsCompatibility( arguments ):
   if arguments and arguments[ 0 ] == 'GoToDefinitionElseDeclaration':
     arguments[ 0 ] = 'GoTo'
@@ -49,7 +50,7 @@ class CommandRequest( BaseRequest ):
     } )
     try:
       self._response = self.PostDataToHandler( request_data,
-                                              'run_completer_command' )
+                                               'run_completer_command' )
     except ServerError as e:
       vimsupport.PostMultiLineNotice( e )
 
@@ -57,6 +58,7 @@ class CommandRequest( BaseRequest ):
   def Response( self ):
     return self._response
 
+
   def RunPostCommandActionsIfNeeded( self ):
     if not self.Done() or not self._response:
       return
@@ -68,6 +70,7 @@ class CommandRequest( BaseRequest ):
     elif 'message' in self._response:
       self._HandleMessageResponse()
 
+
   def _HandleGotoResponse( self ):
     if isinstance( self._response, list ):
       defs = [ _BuildQfListItem( x ) for x in self._response ]
@@ -75,56 +78,27 @@ class CommandRequest( BaseRequest ):
       vim.eval( 'youcompleteme#OpenGoToList()' )
     else:
       vimsupport.JumpToLocation( self._response[ 'filepath' ],
-                                  self._response[ 'line_num' ],
-                                  self._response[ 'column_num' ] )
+                                 self._response[ 'line_num' ],
+                                 self._response[ 'column_num' ] )
+
 
   def _HandleFixitResponse( self ):
     if not len( self._response[ 'fixits' ] ):
       vimsupport.EchoText( "No fixits found for current line" )
     else:
-      fixit = self._response[ 'fixits' ][ 0 ]
-
-      # We need to track the difference in length, but ensuring we apply fixes
-      # in ascending order of insertion point.
-      fixit[ 'chunks' ].sort( key = lambda chunk:  (
-        str(chunk[ 'range' ][ 'start' ][ 'line_num' ])
-        + ','
-        + str(chunk[ 'range' ][ 'start' ][ 'column_num' ])
-      ))
-
-      # Remember the line number we're processing. Negative line number means we
-      # haven't processed any lines yet (by nature of being not equal to any
-      # real line number).
-      last_line = -1
-
-      # Counter of changes applied, so the user has a mental picture of the
-      # undo history this change is creating.
-      num_fixed = 0
-      line_delta = 0
-      for chunk in fixit[ 'chunks' ]:
-        if chunk[ 'range' ][ 'start' ][ 'line_num' ] != last_line:
-          # If this chunk is on a different line than the previous chunk,
-          # then ignore previous deltas (as offsets won't have changed).
-          last_line = chunk[ 'range' ][ 'end' ][ 'line_num' ]
-          char_delta = 0
-
-        (new_line_delta, new_char_delta) = vimsupport.ReplaceChunk(
-                                          chunk[ 'range' ][ 'start' ],
-                                          chunk[ 'range' ][ 'end' ],
-                                          chunk[ 'replacement_text' ],
-                                          line_delta, char_delta )
-        line_delta += new_line_delta
-        char_delta += new_char_delta
-
-        num_fixed = num_fixed + 1
-
-      vimsupport.EchoTextVimWidth("FixIt applied " 
-                                  + str(num_fixed) 
-                                  + " changes")
+      chunks = self._response[ 'fixits' ][ 0 ][ 'chunks' ]
+
+      vimsupport.ReplaceChunksList( chunks )
+
+      vimsupport.EchoTextVimWidth( "FixIt applied "
+                                   + str( len( chunks ) )
+                                   + " changes" )
+
 
   def _HandleMessageResponse( self ):
     vimsupport.EchoText( self._response[ 'message' ] )
 
+
 def SendCommandRequest( arguments, completer ):
   request = CommandRequest( arguments, completer )
   # This is a blocking call.

+ 12 - 0
python/ycm/client/completion_request.py

@@ -21,6 +21,7 @@ from ycmd.utils import ToUtf8IfNeeded
 from ycm.client.base_request import ( BaseRequest, JsonFromFuture,
                                       HandleServerException,
                                       MakeServerException )
+import os
 
 TIMEOUT_SECONDS = 0.5
 
@@ -67,6 +68,13 @@ def ConvertCompletionDataToVimData( completion_data ):
     'dup'  : 1,
   }
 
+  if ( 'extra_data' in completion_data and
+       'doc_string' in completion_data[ 'extra_data' ] ):
+    doc_string = ToUtf8IfNeeded(
+                              completion_data[ 'extra_data' ][ 'doc_string' ] )
+  else:
+    doc_string = ""
+
   if 'menu_text' in completion_data:
     vim_data[ 'abbr' ] = ToUtf8IfNeeded( completion_data[ 'menu_text' ] )
   if 'extra_menu_info' in completion_data:
@@ -76,6 +84,10 @@ def ConvertCompletionDataToVimData( completion_data ):
         completion_data[ 'kind' ] )[ 0 ].lower()
   if 'detailed_info' in completion_data:
     vim_data[ 'info' ] = ToUtf8IfNeeded( completion_data[ 'detailed_info' ] )
+    if doc_string:
+      vim_data[ 'info' ] += os.linesep + doc_string
+  elif doc_string:
+    vim_data[ 'info' ] = doc_string
 
   return vim_data
 

+ 0 - 0
python/ycm/client/tests/__init__.py


+ 134 - 0
python/ycm/client/tests/completion_request_test.py

@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2015 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 nose.tools import eq_
+from ycm.test_utils import MockVimModule
+vim_mock = MockVimModule()
+import os
+
+from .. import completion_request
+
+class ConvertCompletionResponseToVimDatas_test:
+  """ This class tests the
+      completion_request._ConvertCompletionResponseToVimDatas method """
+
+  def _Check( self, completion_data, expected_vim_data ):
+    vim_data = completion_request._ConvertCompletionDataToVimData(
+        completion_data )
+
+    try:
+      eq_( expected_vim_data, vim_data )
+    except:
+      print "Expected:\n'{0}'\nwhen parsing:\n'{1}'\nBut found:\n'{2}'".format(
+          expected_vim_data,
+          completion_data,
+          vim_data )
+      raise
+
+
+  def All_Fields_test( self ):
+    self._Check( {
+      'insertion_text':  'INSERTION TEXT',
+      'menu_text':       'MENU TEXT',
+      'extra_menu_info': 'EXTRA MENU INFO',
+      'kind':            'K',
+      'detailed_info':   'DETAILED INFO',
+      'extra_data': {
+        'doc_string':    'DOC STRING',
+      },
+    }, {
+      'word': 'INSERTION TEXT',
+      'abbr': 'MENU TEXT',
+      'menu': 'EXTRA MENU INFO',
+      'kind': 'k',
+      'info': 'DETAILED INFO' + os.linesep + 'DOC STRING',
+      'dup' : 1,
+    } )
+
+
+  def Just_Detailed_Info_test( self ):
+    self._Check( {
+      'insertion_text':  'INSERTION TEXT',
+      'menu_text':       'MENU TEXT',
+      'extra_menu_info': 'EXTRA MENU INFO',
+      'kind':            'K',
+      'detailed_info':   'DETAILED INFO',
+    }, {
+      'word': 'INSERTION TEXT',
+      'abbr': 'MENU TEXT',
+      'menu': 'EXTRA MENU INFO',
+      'kind': 'k',
+      'info': 'DETAILED INFO',
+      'dup' : 1,
+    } )
+
+
+  def Just_Doc_String_test( self ):
+    self._Check( {
+      'insertion_text':  'INSERTION TEXT',
+      'menu_text':       'MENU TEXT',
+      'extra_menu_info': 'EXTRA MENU INFO',
+      'kind':            'K',
+      'extra_data': {
+        'doc_string':    'DOC STRING',
+      },
+    }, {
+      'word': 'INSERTION TEXT',
+      'abbr': 'MENU TEXT',
+      'menu': 'EXTRA MENU INFO',
+      'kind': 'k',
+      'info': 'DOC STRING',
+      'dup' : 1,
+    } )
+
+
+  def Extra_Info_No_Doc_String_test( self ):
+    self._Check( {
+      'insertion_text':  'INSERTION TEXT',
+      'menu_text':       'MENU TEXT',
+      'extra_menu_info': 'EXTRA MENU INFO',
+      'kind':            'K',
+      'extra_data': {
+      },
+    }, {
+      'word': 'INSERTION TEXT',
+      'abbr': 'MENU TEXT',
+      'menu': 'EXTRA MENU INFO',
+      'kind': 'k',
+      'dup' : 1,
+    } )
+
+
+  def Extra_Info_No_Doc_String_With_Detailed_Info_test( self ):
+    self._Check( {
+      'insertion_text':  'INSERTION TEXT',
+      'menu_text':       'MENU TEXT',
+      'extra_menu_info': 'EXTRA MENU INFO',
+      'kind':            'K',
+      'detailed_info':   'DETAILED INFO',
+      'extra_data': {
+      },
+    }, {
+      'word': 'INSERTION TEXT',
+      'abbr': 'MENU TEXT',
+      'menu': 'EXTRA MENU INFO',
+      'kind': 'k',
+      'info': 'DETAILED INFO',
+      'dup' : 1,
+    } )

+ 97 - 42
python/ycm/tests/vimsupport_test.py

@@ -20,6 +20,7 @@
 from ycm import vimsupport
 from nose.tools import eq_
 
+
 def ReplaceChunk_SingleLine_Repl_1_test():
   # Replace with longer range
   #                  12345678901234567
@@ -38,7 +39,7 @@ def ReplaceChunk_SingleLine_Repl_1_test():
 
   # and replace again, using delta
   start, end = _BuildLocations( 1, 10, 1, 11 )
-  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( 
+  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk(
                                                           start,
                                                           end,
                                                           ' piece of ',
@@ -58,7 +59,7 @@ def ReplaceChunk_SingleLine_Repl_1_test():
   # and once more, for luck
   start, end = _BuildLocations( 1, 11, 1, 17 )
 
-  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( 
+  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk(
                                                           start,
                                                           end,
                                                           'pie',
@@ -75,6 +76,7 @@ def ReplaceChunk_SingleLine_Repl_1_test():
   eq_( line_offset, 0 )
   eq_( char_offset, 10 )
 
+
 def ReplaceChunk_SingleLine_Repl_2_test():
   # Replace with shorter range
   #                  12345678901234567
@@ -91,6 +93,7 @@ def ReplaceChunk_SingleLine_Repl_2_test():
   eq_( line_offset, 0 )
   eq_( char_offset, -2 )
 
+
 def ReplaceChunk_SingleLine_Repl_3_test():
   # Replace with equal range
   #                  12345678901234567
@@ -107,6 +110,7 @@ def ReplaceChunk_SingleLine_Repl_3_test():
   eq_( line_offset, 0 )
   eq_( char_offset, 0 )
 
+
 def ReplaceChunk_SingleLine_Add_1_test():
   # Insert at start
   result_buffer = [ "is a string" ]
@@ -122,6 +126,7 @@ def ReplaceChunk_SingleLine_Add_1_test():
   eq_( line_offset, 0 )
   eq_( char_offset, 5 )
 
+
 def ReplaceChunk_SingleLine_Add_2_test():
   # Insert at end
   result_buffer = [ "This is a " ]
@@ -137,6 +142,7 @@ def ReplaceChunk_SingleLine_Add_2_test():
   eq_( line_offset, 0 )
   eq_( char_offset, 6 )
 
+
 def ReplaceChunk_SingleLine_Add_3_test():
   # Insert in the middle
   result_buffer = [ "This is a string" ]
@@ -152,6 +158,7 @@ def ReplaceChunk_SingleLine_Add_3_test():
   eq_( line_offset, 0 )
   eq_( char_offset, 4 )
 
+
 def ReplaceChunk_SingleLine_Del_1_test():
   # Delete from start
   result_buffer = [ "This is a string" ]
@@ -167,6 +174,7 @@ def ReplaceChunk_SingleLine_Del_1_test():
   eq_( line_offset, 0 )
   eq_( char_offset, -5 )
 
+
 def ReplaceChunk_SingleLine_Del_2_test():
   # Delete from end
   result_buffer = [ "This is a string" ]
@@ -182,6 +190,7 @@ def ReplaceChunk_SingleLine_Del_2_test():
   eq_( line_offset, 0 )
   eq_( char_offset, -8 )
 
+
 def ReplaceChunk_SingleLine_Del_3_test():
   # Delete from middle
   result_buffer = [ "This is not a string" ]
@@ -197,6 +206,7 @@ def ReplaceChunk_SingleLine_Del_3_test():
   eq_( line_offset, 0 )
   eq_( char_offset, -4 )
 
+
 def ReplaceChunk_RemoveSingleLine_test():
   result_buffer = [ "aAa", "aBa", "aCa" ]
   start, end = _BuildLocations( 2, 1, 3, 1 )
@@ -209,13 +219,13 @@ def ReplaceChunk_RemoveSingleLine_test():
 
 
 def ReplaceChunk_SingleToMultipleLines_test():
-  result_buffer = [ "aAa", 
-                    "aBa", 
+  result_buffer = [ "aAa",
+                    "aBa",
                     "aCa" ]
   start, end = _BuildLocations( 2, 2, 2, 2 )
   ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Eb\nbF',
                                                           0, 0, result_buffer )
-  expected_buffer = [ "aAa", 
+  expected_buffer = [ "aAa",
                       "aEb",
                       "bFBa",
                       "aCa" ]
@@ -245,12 +255,12 @@ def ReplaceChunk_SingleToMultipleLines2_test():
   result_buffer = [ "aAa", "aBa", "aCa" ]
   start, end = _BuildLocations( 2, 2, 2, 2 )
   ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
-                                                          end, 
+                                                          end,
                                                           'Eb\nbFb\nG',
-                                                          0, 
-                                                          0, 
+                                                          0,
+                                                          0,
                                                           result_buffer )
-  expected_buffer = [ "aAa", "aEb" ,"bFb", "GBa", "aCa" ]
+  expected_buffer = [ "aAa", "aEb", "bFb", "GBa", "aCa" ]
   eq_( expected_buffer, result_buffer )
   eq_( line_offset, 2 )
   eq_( char_offset, 0 )
@@ -259,45 +269,47 @@ def ReplaceChunk_SingleToMultipleLines2_test():
 def ReplaceChunk_SingleToMultipleLines3_test():
   result_buffer = [ "aAa", "aBa", "aCa" ]
   start, end = _BuildLocations( 2, 2, 2, 2 )
-  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, 
-                                                          end, 
+  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
+                                                          end,
                                                           'Eb\nbFb\nbGb',
-                                                          0, 
-                                                          0, 
+                                                          0,
+                                                          0,
                                                           result_buffer )
-  expected_buffer = [ "aAa", "aEb" ,"bFb", "bGbBa", "aCa" ]
+  expected_buffer = [ "aAa", "aEb", "bFb", "bGbBa", "aCa" ]
   eq_( expected_buffer, result_buffer )
   eq_( line_offset, 2 )
   eq_( char_offset, 2 )
 
+
 def ReplaceChunk_SingleToMultipleLinesReplace_test():
   result_buffer = [ "aAa", "aBa", "aCa" ]
   start, end = _BuildLocations( 1, 2, 1, 4 )
-  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, 
-                                                          end, 
+  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
+                                                          end,
                                                           'Eb\nbFb\nbGb',
-                                                          0, 
-                                                          0, 
+                                                          0,
+                                                          0,
                                                           result_buffer )
   expected_buffer = [ "aEb", "bFb", "bGb", "aBa", "aCa" ]
   eq_( expected_buffer, result_buffer )
   eq_( line_offset, 2 )
   eq_( char_offset, 0 )
 
+
 def ReplaceChunk_SingleToMultipleLinesReplace_2_test():
-  result_buffer = [ "aAa", 
-                    "aBa", 
+  result_buffer = [ "aAa",
+                    "aBa",
                     "aCa" ]
   start, end = _BuildLocations( 1, 2, 1, 4 )
-  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, 
-                                                          end, 
+  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
+                                                          end,
                                                           'Eb\nbFb\nbGb',
-                                                          0, 
-                                                          0, 
+                                                          0,
+                                                          0,
                                                           result_buffer )
-  expected_buffer = [ "aEb", 
-                      "bFb", 
-                      "bGb", 
+  expected_buffer = [ "aEb",
+                      "bFb",
+                      "bGb",
                       "aBa",
                       "aCa" ]
   eq_( expected_buffer, result_buffer )
@@ -306,7 +318,7 @@ def ReplaceChunk_SingleToMultipleLinesReplace_2_test():
 
   # now do a subsequent change (insert at end of line "1")
   start, end = _BuildLocations( 1, 4, 1, 4 )
-  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( 
+  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk(
                                                           start,
                                                           end,
                                                           'cccc',
@@ -340,7 +352,7 @@ def ReplaceChunk_MultipleLinesToSingleLine_test():
 
   # make another modification applying offsets
   start, end = _BuildLocations( 3, 3, 3, 4 )
-  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( 
+  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk(
                                                           start,
                                                           end,
                                                           'cccc',
@@ -357,7 +369,7 @@ def ReplaceChunk_MultipleLinesToSingleLine_test():
 
   # and another, for luck
   start, end = _BuildLocations( 3, 4, 3, 5 )
-  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( 
+  ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk(
                                                           start,
                                                           end,
                                                           'dd\ndd',
@@ -367,7 +379,7 @@ def ReplaceChunk_MultipleLinesToSingleLine_test():
 
   line_offset += new_line_offset
   char_offset += new_char_offset
-  
+
   eq_( [ "aAa", "aECccccdd", "ddaa" ], result_buffer )
   eq_( line_offset, 0 )
   eq_( char_offset, -2 )
@@ -387,11 +399,11 @@ def ReplaceChunk_MultipleLinesToSameMultipleLines_test():
 def ReplaceChunk_MultipleLinesToMoreMultipleLines_test():
   result_buffer = [ "aAa", "aBa", "aCa", "aDe" ]
   start, end = _BuildLocations( 2, 2, 3, 2 )
-  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, 
-                                                          end, 
+  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
+                                                          end,
                                                           'Eb\nbFb\nbG',
-                                                          0, 
-                                                          0, 
+                                                          0,
+                                                          0,
                                                           result_buffer )
   expected_buffer = [ "aAa", "aEb", "bFb", "bGCa", "aDe" ]
   eq_( expected_buffer, result_buffer )
@@ -501,11 +513,11 @@ def ReplaceChunk_MultipleLinesToSingleLineOffsetWorks_test():
 def ReplaceChunk_MultipleLineOffsetWorks_test():
   result_buffer = [ "aAa", "aBa", "aCa" ]
   start, end = _BuildLocations( 3, 1, 4, 3 )
-  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, 
-                                                          end, 
+  ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
+                                                          end,
                                                           'bDb\nbEb\nbFb',
-                                                          -1, 
-                                                          1, 
+                                                          -1,
+                                                          1,
                                                           result_buffer )
   expected_buffer = [ "aAa", "abDb", "bEb", "bFba" ]
   eq_( expected_buffer, result_buffer )
@@ -514,10 +526,53 @@ def ReplaceChunk_MultipleLineOffsetWorks_test():
 
 
 def _BuildLocations( start_line, start_column, end_line, end_column ):
-  return { 
-    'line_num'  : start_line, 
+  return {
+    'line_num'  : start_line,
     'column_num': start_column,
   }, {
-    'line_num'  : end_line, 
+    'line_num'  : end_line,
     'column_num': end_column,
   }
+
+
+def ReplaceChunksList_SortedChunks_test():
+  chunks = [
+    _BuildChunk( 1, 4, 1, 4, '('),
+    _BuildChunk( 1, 11, 1, 11, ')' )
+  ]
+
+  result_buffer = [ "CT<10 >> 2> ct" ]
+  vimsupport.ReplaceChunksList( chunks, result_buffer )
+
+  expected_buffer = [ "CT<(10 >> 2)> ct" ]
+  eq_( expected_buffer, result_buffer )
+
+
+def ReplaceChunksList_UnsortedChunks_test():
+  chunks = [
+    _BuildChunk( 1, 11, 1, 11, ')'),
+    _BuildChunk( 1, 4, 1, 4, '(' )
+  ]
+
+  result_buffer = [ "CT<10 >> 2> ct" ]
+  vimsupport.ReplaceChunksList( chunks, result_buffer )
+
+  expected_buffer = [ "CT<(10 >> 2)> ct" ]
+  eq_( expected_buffer, result_buffer )
+
+
+def _BuildChunk( start_line, start_column, end_line, end_column,
+                 replacement_text ):
+  return {
+    'range': {
+      'start': {
+        'line_num': start_line,
+        'column_num': start_column,
+      },
+      'end': {
+        'line_num': end_line,
+        'column_num': end_column,
+      },
+    },
+    'replacement_text': replacement_text
+  }

+ 35 - 4
python/ycm/vimsupport.py

@@ -474,6 +474,40 @@ def GetIntValue( variable ):
   return int( vim.eval( variable ) )
 
 
+def ReplaceChunksList( chunks, vim_buffer = None ):
+  if vim_buffer is None:
+    vim_buffer = vim.current.buffer
+
+  # We need to track the difference in length, but ensuring we apply fixes
+  # in ascending order of insertion point.
+  chunks.sort( key = lambda chunk: (
+    chunk[ 'range' ][ 'start' ][ 'line_num' ],
+    chunk[ 'range' ][ 'start' ][ 'column_num' ]
+  ) )
+
+  # Remember the line number we're processing. Negative line number means we
+  # haven't processed any lines yet (by nature of being not equal to any
+  # real line number).
+  last_line = -1
+
+  line_delta = 0
+  for chunk in chunks:
+    if chunk[ 'range' ][ 'start' ][ 'line_num' ] != last_line:
+      # If this chunk is on a different line than the previous chunk,
+      # then ignore previous deltas (as offsets won't have changed).
+      last_line = chunk[ 'range' ][ 'end' ][ 'line_num' ]
+      char_delta = 0
+
+    ( new_line_delta, new_char_delta ) = ReplaceChunk(
+      chunk[ 'range' ][ 'start' ],
+      chunk[ 'range' ][ 'end' ],
+      chunk[ 'replacement_text' ],
+      line_delta, char_delta,
+      vim_buffer )
+    line_delta += new_line_delta
+    char_delta += new_char_delta
+
+
 # Replace the chunk of text specified by a contiguous range with the supplied
 # text.
 # * start and end are objects with line_num and column_num properties
@@ -484,10 +518,7 @@ def GetIntValue( variable ):
 # returns the delta (in lines and characters) that any position after the end
 # needs to be adjusted by.
 def ReplaceChunk( start, end, replacement_text, line_delta, char_delta,
-                  vim_buffer = None ):
-  if vim_buffer is None:
-    vim_buffer = vim.current.buffer
-
+                  vim_buffer ):
   # ycmd's results are all 1-based, but vim's/python's are all 0-based
   # (so we do -1 on all of the values)
   start_line = start[ 'line_num' ] - 1 + line_delta

+ 2 - 0
python/ycm/youcompleteme.py

@@ -193,6 +193,8 @@ class YouCompleteMe( object ):
             self._omnicomp, wrapped_request_data )
         return self._latest_completion_request
 
+    request_data[ 'working_dir' ] = os.getcwd()
+
     self._AddExtraConfDataIfNeeded( request_data )
     if force_semantic:
       request_data[ 'force_semantic' ] = True

+ 1 - 1
third_party/ycmd

@@ -1 +1 @@
-Subproject commit 01d5f2df8da4a4d960ddc8ab0349d62813147f59
+Subproject commit b46b8f09e33ccb6c70dfd02bba879c0b77fff4d5