1
0
Эх сурвалжийг харах

Update matches for all windows

micbou 6 жил өмнө
parent
commit
115f360092

+ 51 - 43
python/ycm/diagnostic_interface.py

@@ -59,7 +59,7 @@ class DiagnosticInterface( object ):
   def PopulateLocationList( self ):
     # Do nothing if loc list is already populated by diag_interface
     if not self._user_options[ 'always_populate_location_list' ]:
-      self._UpdateLocationList()
+      self._UpdateLocationLists()
     return bool( self._diagnostics )
 
 
@@ -77,7 +77,7 @@ class DiagnosticInterface( object ):
     self.UpdateMatches()
 
     if self._user_options[ 'always_populate_location_list' ]:
-      self._UpdateLocationList()
+      self._UpdateLocationLists()
 
 
   def _ApplyDiagnosticFilter( self, diags ):
@@ -119,8 +119,8 @@ class DiagnosticInterface( object ):
     return count
 
 
-  def _UpdateLocationList( self ):
-    vimsupport.SetLocationListForBuffer(
+  def _UpdateLocationLists( self ):
+    vimsupport.SetLocationListsForBuffer(
       self._bufnr,
       vimsupport.ConvertDiagnosticsToQfList( self._diagnostics ) )
 
@@ -129,46 +129,28 @@ class DiagnosticInterface( object ):
     if not self._user_options[ 'enable_diagnostic_highlighting' ]:
       return
 
-    matches_to_remove = vimsupport.GetDiagnosticMatchesInCurrentWindow()
+    with vimsupport.CurrentWindow():
+      for window in vimsupport.GetWindowsForBufferNumber( self._bufnr ):
+        vimsupport.SwitchWindow( window )
 
-    for diags in itervalues( self._line_to_diags ):
-      # Insert squiggles in reverse order so that errors overlap warnings.
-      for diag in reversed( diags ):
-        patterns = []
-
-        group = ( 'YcmErrorSection' if _DiagnosticIsError( diag ) else
-                  'YcmWarningSection' )
-
-        location_extent = diag[ 'location_extent' ]
-        if location_extent[ 'start' ][ 'line_num' ] <= 0:
-          location = diag[ 'location' ]
-          patterns.append( vimsupport.GetDiagnosticMatchPattern(
-            location[ 'line_num' ],
-            location[ 'column_num' ] ) )
-        else:
-          patterns.append( vimsupport.GetDiagnosticMatchPattern(
-            location_extent[ 'start' ][ 'line_num' ],
-            location_extent[ 'start' ][ 'column_num' ],
-            location_extent[ 'end' ][ 'line_num' ],
-            location_extent[ 'end' ][ 'column_num' ] ) )
-
-        for diag_range in diag[ 'ranges' ]:
-          patterns.append( vimsupport.GetDiagnosticMatchPattern(
-            diag_range[ 'start' ][ 'line_num' ],
-            diag_range[ 'start' ][ 'column_num' ],
-            diag_range[ 'end' ][ 'line_num' ],
-            diag_range[ 'end' ][ 'column_num' ] ) )
-
-        for pattern in patterns:
-          # The id doesn't matter for matches that we may add.
-          match = vimsupport.DiagnosticMatch( 0, group, pattern )
-          try:
-            matches_to_remove.remove( match )
-          except ValueError:
-            vimsupport.AddDiagnosticMatch( match )
-
-    for match in matches_to_remove:
-      vimsupport.RemoveDiagnosticMatch( match )
+        matches_to_remove = vimsupport.GetDiagnosticMatchesInCurrentWindow()
+
+        for diags in itervalues( self._line_to_diags ):
+          # Insert squiggles in reverse order so that errors overlap warnings.
+          for diag in reversed( diags ):
+            group = ( 'YcmErrorSection' if _DiagnosticIsError( diag ) else
+                      'YcmWarningSection' )
+
+            for pattern in _ConvertDiagnosticToMatchPatterns( diag ):
+              # The id doesn't matter for matches that we may add.
+              match = vimsupport.DiagnosticMatch( 0, group, pattern )
+              try:
+                matches_to_remove.remove( match )
+              except ValueError:
+                vimsupport.AddDiagnosticMatch( match )
+
+        for match in matches_to_remove:
+          vimsupport.RemoveDiagnosticMatch( match )
 
 
   def _UpdateSigns( self ):
@@ -221,3 +203,29 @@ def _NormalizeDiagnostic( diag ):
   location[ 'column_num' ] = ClampToOne( location[ 'column_num' ] )
   location[ 'line_num' ] = ClampToOne( location[ 'line_num' ] )
   return diag
+
+
+def _ConvertDiagnosticToMatchPatterns( diagnostic ):
+  patterns = []
+
+  location_extent = diagnostic[ 'location_extent' ]
+  if location_extent[ 'start' ][ 'line_num' ] <= 0:
+    location = diagnostic[ 'location' ]
+    patterns.append( vimsupport.GetDiagnosticMatchPattern(
+      location[ 'line_num' ],
+      location[ 'column_num' ] ) )
+  else:
+    patterns.append( vimsupport.GetDiagnosticMatchPattern(
+      location_extent[ 'start' ][ 'line_num' ],
+      location_extent[ 'start' ][ 'column_num' ],
+      location_extent[ 'end' ][ 'line_num' ],
+      location_extent[ 'end' ][ 'column_num' ] ) )
+
+  for diagnostic_range in diagnostic[ 'ranges' ]:
+    patterns.append( vimsupport.GetDiagnosticMatchPattern(
+      diagnostic_range[ 'start' ][ 'line_num' ],
+      diagnostic_range[ 'start' ][ 'column_num' ],
+      diagnostic_range[ 'end' ][ 'line_num' ],
+      diagnostic_range[ 'end' ][ 'column_num' ] ) )
+
+  return patterns

+ 2 - 2
python/ycm/tests/client/base_request_test.py

@@ -34,7 +34,7 @@ from ycm.client.base_request import BuildRequestData
         return_value = '/some/dir' )
 def BuildRequestData_AddWorkingDir_test( *args ):
   current_buffer = VimBuffer( 'foo' )
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     assert_that( BuildRequestData(), has_entry( 'working_dir', '/some/dir' ) )
 
 
@@ -42,6 +42,6 @@ def BuildRequestData_AddWorkingDir_test( *args ):
         return_value = '/some/dir' )
 def BuildRequestData_AddWorkingDirWithFileName_test( *args ):
   current_buffer = VimBuffer( 'foo' )
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     assert_that( BuildRequestData( current_buffer.number ),
                  has_entry( 'working_dir', '/some/dir' ) )

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

@@ -34,7 +34,7 @@ from ycm.tests import YouCompleteMeInstance
 @YouCompleteMeInstance( { 'g:ycm_extra_conf_vim_data': [ 'tempname()' ] } )
 def SendCommandRequest_ExtraConfVimData_Works_test( ycm ):
   current_buffer = VimBuffer( 'buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.youcompleteme.SendCommandRequest' ) as send_request:
       ycm.SendCommandRequest( [ 'GoTo' ], 'python', False, 1, 1 )
       assert_that(
@@ -59,7 +59,7 @@ def SendCommandRequest_ExtraConfVimData_Works_test( ycm ):
 @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 ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.youcompleteme.SendCommandRequest' ) as send_request:
       ycm.SendCommandRequest( [ 'GoTo' ], 'python', False, 1, 1 )
       assert_that(
@@ -82,7 +82,7 @@ def SendCommandRequest_ExtraConfData_UndefinedValue_test( ycm ):
 def SendCommandRequest_BuildRange_NoVisualMarks_test( ycm, *args ):
   current_buffer = VimBuffer( 'buffer', contents = [ 'first line',
                                                      'second line' ] )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.youcompleteme.SendCommandRequest' ) as send_request:
       ycm.SendCommandRequest( [ 'GoTo' ], 'python', True, 1, 2 )
       send_request.assert_called_once_with(
@@ -114,7 +114,7 @@ def SendCommandRequest_BuildRange_VisualMarks_test( ycm, *args ):
                                            'second line' ],
                               visual_start = [ 1, 4 ],
                               visual_end = [ 2, 8 ] )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.youcompleteme.SendCommandRequest' ) as send_request:
       ycm.SendCommandRequest( [ 'GoTo' ], 'python', True, 1, 2 )
       send_request.assert_called_once_with(

+ 3 - 3
python/ycm/tests/completion_test.py

@@ -66,7 +66,7 @@ def SendCompletionRequest_UnicodeWorkingDirectory_test( ycm ):
     return { 'completions': [], 'completion_start_column': 1 }
 
   with CurrentWorkingDirectory( unicode_dir ):
-    with MockVimBuffers( [ current_buffer ], current_buffer ):
+    with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
       with MockCompletionRequest( ServerResponse ):
         ycm.SendCompletionRequest()
         ok_( ycm.CompletionRequestReady() )
@@ -106,7 +106,7 @@ def SendCompletionRequest_ResponseContainingError_test( ycm, post_vim_message ):
       } ]
     }
 
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with MockCompletionRequest( ServerResponse ):
       ycm.SendCompletionRequest()
       ok_( ycm.CompletionRequestReady() )
@@ -138,7 +138,7 @@ def SendCompletionRequest_ErrorFromServer_test( ycm,
                                                 post_vim_message,
                                                 logger ):
   current_buffer = VimBuffer( 'buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with MockCompletionRequest( ServerError( 'Server error' ) ):
       ycm.SendCompletionRequest()
       ok_( ycm.CompletionRequestReady() )

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

@@ -57,10 +57,9 @@ def MockArbitraryBuffer( filetype ):
 
   # Arbitrary, but valid, single buffer open.
   current_buffer = VimBuffer( os.path.realpath( 'TEST_BUFFER' ),
-                              window = 1,
                               filetype = filetype )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     yield
 
 
@@ -402,7 +401,7 @@ def EventNotification_FileReadyToParse_TagFiles_UnicodeWorkingDirectory_test(
   with patch( 'ycm.client.event_notification.EventNotification.'
               'PostDataToHandlerAsync' ) as post_data_to_handler_async:
     with CurrentWorkingDirectory( unicode_dir ):
-      with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 5 ) ):
+      with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 5 ) ):
         ycm.OnFileReadyToParse()
 
     assert_that(
@@ -455,7 +454,7 @@ def EventNotification_BufferVisit_BuildRequestForCurrentAndUnsavedBuffers_test(
   with patch( 'ycm.client.event_notification.EventNotification.'
               'PostDataToHandlerAsync' ) as post_data_to_handler_async:
     with MockVimBuffers( [ current_buffer, modified_buffer, unmodified_buffer ],
-                         current_buffer,
+                         [ current_buffer ],
                          ( 1, 5 ) ):
       ycm.OnBufferVisit()
 
@@ -503,7 +502,8 @@ def EventNotification_BufferUnload_BuildRequestForDeletedAndUnsavedBuffers_test(
 
   with patch( 'ycm.client.event_notification.EventNotification.'
               'PostDataToHandlerAsync' ) as post_data_to_handler_async:
-    with MockVimBuffers( [ current_buffer, deleted_buffer ], current_buffer ):
+    with MockVimBuffers( [ current_buffer, deleted_buffer ],
+                         [ current_buffer ] ):
       ycm.OnBufferUnload( deleted_buffer.number )
 
   assert_that(
@@ -543,7 +543,7 @@ def EventNotification_FileReadyToParse_SyntaxKeywords_SeedWithCache_test(
 
   with patch( 'ycm.client.event_notification.EventNotification.'
               'PostDataToHandlerAsync' ) as post_data_to_handler_async:
-    with MockVimBuffers( [ current_buffer ], current_buffer ):
+    with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
       ycm.OnFileReadyToParse()
       assert_that(
         # Positional arguments passed to PostDataToHandlerAsync.
@@ -578,7 +578,7 @@ def EventNotification_FileReadyToParse_SyntaxKeywords_ClearCacheIfRestart_test(
 
   with patch( 'ycm.client.event_notification.EventNotification.'
               'PostDataToHandlerAsync' ) as post_data_to_handler_async:
-    with MockVimBuffers( [ current_buffer ], current_buffer ):
+    with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
       ycm.OnFileReadyToParse()
       assert_that(
         # Positional arguments passed to PostDataToHandlerAsync.

+ 24 - 24
python/ycm/tests/omni_completer_test.py

@@ -52,7 +52,7 @@ def OmniCompleter_GetCompletions_Cache_List_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 5 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 5 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -76,7 +76,7 @@ def OmniCompleter_GetCompletions_Cache_ListFilter_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -100,7 +100,7 @@ def OmniCompleter_GetCompletions_NoCache_List_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 5 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 5 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -124,7 +124,7 @@ def OmniCompleter_GetCompletions_NoCache_ListFilter_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest()
     # Actual result is that the results are not filtered, as we expect the
     # omnifunc or vim itself to do this filtering.
@@ -150,7 +150,7 @@ def OmniCompleter_GetCompletions_NoCache_UseFindStart_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest()
     # Actual result is that the results are not filtered, as we expect the
     # omnifunc or vim itself to do this filtering.
@@ -176,7 +176,7 @@ def OmniCompleter_GetCompletions_Cache_UseFindStart_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest()
     # There are no results because the query 'test.t' doesn't match any
     # candidate (and cache_omnifunc=1, so we FilterAndSortCandidates).
@@ -202,7 +202,7 @@ def OmniCompleter_GetCompletions_Cache_Object_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -241,7 +241,7 @@ def OmniCompleter_GetCompletions_Cache_ObjectList_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 7 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 7 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -286,7 +286,7 @@ def OmniCompleter_GetCompletions_NoCache_ObjectList_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 7 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 7 ) ):
     ycm.SendCompletionRequest()
     # We don't filter the result - we expect the omnifunc to do that
     # based on the query we supplied (Note: that means no fuzzy matching!).
@@ -339,7 +339,7 @@ def OmniCompleter_GetCompletions_Cache_ObjectListObject_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 7 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 7 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -384,7 +384,7 @@ def OmniCompleter_GetCompletions_NoCache_ObjectListObject_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 7 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 7 ) ):
     ycm.SendCompletionRequest()
     # No FilterAndSortCandidates for cache_omnifunc=0 (we expect the omnifunc
     # to do the filtering?)
@@ -422,7 +422,7 @@ def OmniCompleter_GetCompletions_Cache_List_Unicode_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 12 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 12 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -448,7 +448,7 @@ def OmniCompleter_GetCompletions_NoCache_List_Unicode_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 12 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 12 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -474,7 +474,7 @@ def OmniCompleter_GetCompletions_Cache_List_Filter_Unicode_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 17 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 17 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -498,7 +498,7 @@ def OmniCompleter_GetCompletions_NoCache_List_Filter_Unicode_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 17 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 17 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -537,7 +537,7 @@ def OmniCompleter_GetCompletions_Cache_ObjectList_Unicode_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 17 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 17 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -591,7 +591,7 @@ def OmniCompleter_GetCompletions_Cache_ObjectListObject_Unicode_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 13 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 13 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -634,7 +634,7 @@ def OmniCompleter_GetCompletions_RestoreCursorPositionAfterOmnifuncCall_test(
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 5 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 3, 5 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       vimsupport.CurrentLineAndColumn(),
@@ -662,7 +662,7 @@ def OmniCompleter_GetCompletions_NoCache_NoSemanticTrigger_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 3 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 3 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -686,7 +686,7 @@ def OmniCompleter_GetCompletions_NoCache_ForceSemantic_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 3 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 3 ) ):
     ycm.SendCompletionRequest( force_semantic = True )
     assert_that(
       ycm.GetCompletionResponse(),
@@ -712,7 +712,7 @@ def OmniCompleter_GetCompletions_FiletypeDisabled_SemanticTrigger_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -740,7 +740,7 @@ def OmniCompleter_GetCompletions_AllFiletypesDisabled_SemanticTrigger_test(
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest()
     assert_that(
       ycm.GetCompletionResponse(),
@@ -766,7 +766,7 @@ def OmniCompleter_GetCompletions_FiletypeDisabled_ForceSemantic_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest( force_semantic = True )
     assert_that(
       ycm.GetCompletionResponse(),
@@ -792,7 +792,7 @@ def OmniCompleter_GetCompletions_AllFiletypesDisabled_ForceSemantic_test( ycm ):
                               filetype = FILETYPE,
                               omnifunc = Omnifunc )
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 6 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 6 ) ):
     ycm.SendCompletionRequest( force_semantic = True )
     assert_that(
       ycm.GetCompletionResponse(),

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

@@ -22,6 +22,7 @@ from __future__ import absolute_import
 # Not installing aliases from python-future; it's unreliable and slow.
 from builtins import *  # noqa
 
+from collections import defaultdict
 from future.utils import iteritems, PY2
 from mock import DEFAULT, MagicMock, patch
 from hamcrest import assert_that, equal_to
@@ -70,7 +71,7 @@ LET_REGEX = re.compile( '^let (?P<option>[\w:]+) = (?P<value>.*)$' )
 # https://github.com/Valloric/YouCompleteMe/pull/1694
 VIM_MOCK = MagicMock()
 
-VIM_MATCHES = []
+VIM_MATCHES_FOR_WINDOW = defaultdict( list )
 VIM_SIGNS = []
 
 VIM_OPTIONS = {
@@ -107,9 +108,9 @@ def _MockGetBufferNumber( buffer_filename ):
 
 
 def _MockGetBufferWindowNumber( buffer_number ):
-  for vim_buffer in VIM_MOCK.buffers:
-    if vim_buffer.number == buffer_number and vim_buffer.window:
-      return vim_buffer.window
+  for window in VIM_MOCK.windows:
+    if window.buffer.number == buffer_number:
+      return window.number
   return -1
 
 
@@ -198,25 +199,26 @@ def _MockVimFunctionsEval( value ):
 
 
 def _MockVimMatchEval( value ):
+  current_window = VIM_MOCK.current.window.number
+
   if value == 'getmatches()':
-    # Returning a copy, because ClearYcmSyntaxMatches() gets the result of
-    # getmatches(), iterates over it and removes elements from VIM_MATCHES.
-    return list( VIM_MATCHES )
+    return VIM_MATCHES_FOR_WINDOW[ current_window ]
 
   match = MATCHADD_REGEX.search( value )
   if match:
     group = match.group( 'group' )
     option = match.group( 'pattern' )
     vim_match = VimMatch( group, option )
-    VIM_MATCHES.append( vim_match )
+    VIM_MATCHES_FOR_WINDOW[ current_window ].append( vim_match )
     return vim_match.id
 
   match = MATCHDELETE_REGEX.search( value )
   if match:
     match_id = int( match.group( 'id' ) )
-    for index, vim_match in enumerate( VIM_MATCHES ):
+    vim_matches = VIM_MATCHES_FOR_WINDOW[ current_window ]
+    for index, vim_match in enumerate( vim_matches ):
       if vim_match.id == match_id:
-        VIM_MATCHES.pop( index )
+        vim_matches.pop( index )
         return -1
     return 0
 
@@ -334,7 +336,6 @@ class VimBuffer( object ):
    - |filetype| : buffer filetype. Empty string if no filetype is set;
    - |modified| : True if the buffer has unsaved changes, False otherwise;
    - |bufhidden|: value of the 'bufhidden' option (see :h bufhidden);
-   - |window|   : number of the buffer window. None if the buffer is hidden;
    - |omnifunc| : omni completion function used by the buffer. Must be a Python
                   function that takes the same arguments and returns the same
                   values as a Vim completion function (:h complete-functions).
@@ -351,7 +352,6 @@ class VimBuffer( object ):
                       filetype = '',
                       modified = False,
                       bufhidden = '',
-                      window = None,
                       omnifunc = None,
                       visual_start = None,
                       visual_end = None ):
@@ -361,7 +361,6 @@ class VimBuffer( object ):
     self.filetype = filetype
     self.modified = modified
     self.bufhidden = bufhidden
-    self.window = window
     self.omnifunc = omnifunc
     self.omnifunc_name = omnifunc.__name__ if omnifunc else ''
     self.changedtick = 1
@@ -402,9 +401,9 @@ class VimBuffer( object ):
 class VimBuffers( object ):
   """An object that looks like a vim.buffers object."""
 
-  def __init__( self, *buffers ):
-    """Arguments are VimBuffer objects."""
-    self._buffers = list( buffers )
+  def __init__( self, buffers ):
+    """|buffers| is a list of VimBuffer objects."""
+    self._buffers = buffers
 
 
   def __getitem__( self, number ):
@@ -424,10 +423,61 @@ class VimBuffers( object ):
     return self._buffers.pop( index )
 
 
+class VimWindow( object ):
+  """An object that looks like a vim.window object:
+    - |number|: number of the window;
+    - |buffer_object|: a VimBuffer object representing the buffer inside the
+      window;
+    - |cursor|: a tuple corresponding to the cursor position."""
+
+  def __init__( self, number, buffer_object, cursor = None ):
+    self.number = number
+    self.buffer = buffer_object
+    self.cursor = cursor
+
+
+class VimWindows( object ):
+  """An object that looks like a vim.windows object."""
+
+  def __init__( self, buffers, cursor ):
+    """|buffers| is a list of VimBuffer objects corresponding to the window
+    layout. The first element of that list is assumed to be the current window.
+    |cursor| is the cursor position of that window."""
+    windows = []
+    windows.append( VimWindow( 1, buffers[ 0 ], cursor ) )
+    for window_number in range( 1, len( buffers ) ):
+      windows.append( VimWindow( window_number + 1, buffers[ window_number ] ) )
+    self._windows = windows
+
+
+  def __getitem__( self, number ):
+    """Emulates vim.windows[ number ]"""
+    for window in self._windows:
+      if number == window.number:
+        return window
+    raise KeyError( number )
+
+
+  def __iter__( self ):
+    """Emulates for loop on vim.windows"""
+    return iter( self._windows )
+
+
+class VimCurrent( object ):
+  """An object that looks like a vim.current object. |current_window| must be a
+  VimWindow object."""
+
+  def __init__( self, current_window ):
+    self.buffer = current_window.buffer
+    self.window = current_window
+    self.line = self.buffer.contents[ current_window.cursor[ 0 ] - 1 ]
+
+
 class VimMatch( object ):
 
   def __init__( self, group, pattern ):
-    self.id = len( VIM_MATCHES ) + 1
+    current_window = VIM_MOCK.current.window.number
+    self.id = len( VIM_MATCHES_FOR_WINDOW[ current_window ] ) + 1
     self.group = group
     self.pattern = pattern
 
@@ -480,20 +530,26 @@ class VimSign( object ):
 
 
 @contextlib.contextmanager
-def MockVimBuffers( buffers, current_buffer, cursor_position = ( 1, 1 ) ):
+def MockVimBuffers( buffers, window_buffers, cursor_position = ( 1, 1 ) ):
   """Simulates the Vim buffers list |buffers| where |current_buffer| is the
   buffer displayed in the current window and |cursor_position| is the current
   cursor position. All buffers are represented by a VimBuffer object."""
-  if current_buffer not in buffers:
-    raise RuntimeError( 'Current buffer must be part of the buffers list.' )
-
-  line = current_buffer.contents[ cursor_position[ 0 ] - 1 ]
-
-  with patch( 'vim.buffers', VimBuffers( *buffers ) ):
-    with patch( 'vim.current.buffer', current_buffer ):
-      with patch( 'vim.current.window.cursor', cursor_position ):
-        with patch( 'vim.current.line', line ):
-          yield VIM_MOCK
+  if ( not isinstance( buffers, list ) or
+       not all( isinstance( buf, VimBuffer ) for buf in buffers ) ):
+    raise RuntimeError( 'First parameter must be a list of VimBuffer objects.' )
+  if ( not isinstance( window_buffers, list ) or
+       not all( isinstance( buf, VimBuffer ) for buf in window_buffers ) ):
+    raise RuntimeError( 'Second parameter must be a list of VimBuffer objects '
+                        'representing the window layout.' )
+  if len( window_buffers ) < 1:
+    raise RuntimeError( 'Second parameter must contain at least one element '
+                        'which corresponds to the current window.' )
+
+  with patch( 'vim.buffers', VimBuffers( buffers ) ):
+    with patch( 'vim.windows', VimWindows( window_buffers,
+                                           cursor_position ) ) as windows:
+      with patch( 'vim.current', VimCurrent( windows[ 1 ] ) ):
+        yield VIM_MOCK
 
 
 def MockVimModule():
@@ -518,7 +574,6 @@ def MockVimModule():
   Failure to use this approach may lead to unexpected failures in other
   tests."""
 
-  VIM_MOCK.buffers = {}
   VIM_MOCK.command = MagicMock( side_effect = _MockVimCommand )
   VIM_MOCK.eval = MagicMock( side_effect = _MockVimEval )
   VIM_MOCK.error = VimError

+ 59 - 54
python/ycm/tests/vimsupport_test.py

@@ -41,7 +41,7 @@ import json
 
 
 @patch( 'vim.eval', new_callable = ExtendedMock )
-def SetLocationListForBuffer_Current_test( vim_eval ):
+def SetLocationListsForBuffer_Current_test( vim_eval ):
   diagnostics = [ {
     'bufnr': 3,
     'filename': 'some_filename',
@@ -50,18 +50,17 @@ def SetLocationListForBuffer_Current_test( vim_eval ):
     'type': 'E',
     'valid': 1
   } ]
-  current_buffer = VimBuffer( '/test', number = 3, window = 7 )
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ):
-    vimsupport.SetLocationListForBuffer( 3, diagnostics )
+  current_buffer = VimBuffer( '/test', number = 3 )
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
+    vimsupport.SetLocationListsForBuffer( 3, diagnostics )
 
-  # We asked for the buffer which is current, so we use winnr 0
   vim_eval.assert_has_exact_calls( [
-    call( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) )
+    call( 'setloclist( 1, {0} )'.format( json.dumps( diagnostics ) ) )
   ] )
 
 
-@patch( 'vim.eval', new_callable = ExtendedMock, side_effect = [ 8, 1 ] )
-def SetLocationListForBuffer_NotCurrent_test( vim_eval ):
+@patch( 'vim.eval', new_callable = ExtendedMock )
+def SetLocationListsForBuffer_NotCurrent_test( vim_eval ):
   diagnostics = [ {
     'bufnr': 3,
     'filename': 'some_filename',
@@ -70,22 +69,34 @@ def SetLocationListForBuffer_NotCurrent_test( vim_eval ):
     'type': 'E',
     'valid': 1
   } ]
-  current_buffer = VimBuffer( '/test', number = 3, window = 7 )
-  other_buffer = VimBuffer( '/notcurrent', number = 1, window = 8 )
-  with MockVimBuffers( [ current_buffer, other_buffer ],
-                       current_buffer,
-                       ( 1, 1 ) ):
-    vimsupport.SetLocationListForBuffer( 1, diagnostics )
+  current_buffer = VimBuffer( '/test', number = 3 )
+  other_buffer = VimBuffer( '/notcurrent', number = 1 )
+  with MockVimBuffers( [ current_buffer, other_buffer ], [ current_buffer ] ):
+    vimsupport.SetLocationListsForBuffer( 1, diagnostics )
 
-  # We asked for a buffer which is not current, so we find the window
-  vim_eval.assert_has_exact_calls( [
-    call( 'bufwinnr(1)' ), # returns 8 due to side_effect
-    call( 'setloclist( 8, {0} )'.format( json.dumps( diagnostics ) ) )
-  ] )
+  vim_eval.assert_not_called()
+
+
+@patch( 'vim.eval', new_callable = ExtendedMock, side_effect = [ -1, 1 ] )
+def SetLocationListsForBuffer_NotVisible_test( vim_eval ):
+  diagnostics = [ {
+    'bufnr': 3,
+    'filename': 'some_filename',
+    'lnum': 5,
+    'col': 22,
+    'type': 'E',
+    'valid': 1
+  } ]
+  current_buffer = VimBuffer( '/test', number = 3 )
+  other_buffer = VimBuffer( '/notcurrent', number = 1 )
+  with MockVimBuffers( [ current_buffer, other_buffer ], [ current_buffer ] ):
+    vimsupport.SetLocationListsForBuffer( 1, diagnostics )
+
+  vim_eval.assert_not_called()
 
 
 @patch( 'vim.eval', new_callable = ExtendedMock, side_effect = [ -1, 1 ] )
-def SetLocationListForBuffer_NotVisible_test( vim_eval ):
+def SetLocationListsForBuffer_MultipleWindows_test( vim_eval ):
   diagnostics = [ {
     'bufnr': 3,
     'filename': 'some_filename',
@@ -94,17 +105,14 @@ def SetLocationListForBuffer_NotVisible_test( vim_eval ):
     'type': 'E',
     'valid': 1
   } ]
-  current_buffer = VimBuffer( '/test', number = 3, window = 7 )
-  other_buffer = VimBuffer( '/notcurrent', number = 1, window = 8 )
+  current_buffer = VimBuffer( '/test', number = 3 )
+  other_buffer = VimBuffer( '/notcurrent', number = 1 )
   with MockVimBuffers( [ current_buffer, other_buffer ],
-                       current_buffer,
-                       ( 1, 1 ) ):
-    vimsupport.SetLocationListForBuffer( 1, diagnostics )
+                       [ current_buffer, other_buffer ] ):
+    vimsupport.SetLocationListsForBuffer( 1, diagnostics )
 
-  # We asked for a buffer which is not current, so we find the window
   vim_eval.assert_has_exact_calls( [
-    call( 'bufwinnr(1)' ), # returns -1 due to side_effect
-    call( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) )
+    call( 'setloclist( 2, {0} )'.format( json.dumps( diagnostics ) ) )
   ] )
 
 
@@ -118,8 +126,8 @@ def SetLocationList_test( vim_eval ):
     'type': 'E',
     'valid': 1
   } ]
-  current_buffer = VimBuffer( '/test', number = 3, window = 7 )
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ):
+  current_buffer = VimBuffer( '/test', number = 3 )
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 1 ) ):
     vimsupport.SetLocationList( diagnostics )
 
   vim_eval.assert_has_calls( [
@@ -137,10 +145,10 @@ def SetLocationList_NotCurrent_test( vim_eval ):
     'type': 'E',
     'valid': 1
   } ]
-  current_buffer = VimBuffer( '/test', number = 3, window = 7 )
-  other_buffer = VimBuffer( '/notcurrent', number = 1, window = 8 )
+  current_buffer = VimBuffer( '/test', number = 3 )
+  other_buffer = VimBuffer( '/notcurrent', number = 1 )
   with MockVimBuffers( [ current_buffer, other_buffer ],
-                       current_buffer,
+                       [ current_buffer, other_buffer ],
                        ( 1, 1 ) ):
     vimsupport.SetLocationList( diagnostics )
 
@@ -171,7 +179,7 @@ def SetFittingHeightForCurrentWindow_test( vim_command, *args ):
   # Create a buffer with one line that is longer than the window width.
   current_buffer = VimBuffer( 'buffer',
                               contents = [ 'a' * 140 ] )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     vimsupport.SetFittingHeightForCurrentWindow()
   vim_command.assert_called_once_with( '2wincmd _' )
 
@@ -1373,24 +1381,21 @@ def WriteToPreviewWindow_JumpFail_MultiLine_test( vim_current, vim_command ):
 
 
 def BufferIsVisibleForFilename_test():
-  vim_buffers = [
-    VimBuffer( 'visible_filename', number = 1, window = 1 ),
-    VimBuffer( 'hidden_filename', number = 2, window = None )
-  ]
+  visible_buffer = VimBuffer( 'visible_filename', number = 1 )
+  hidden_buffer = VimBuffer( 'hidden_filename', number = 2 )
 
-  with patch( 'vim.buffers', vim_buffers ):
+  with MockVimBuffers( [ visible_buffer, hidden_buffer ], [ visible_buffer ] ):
     eq_( vimsupport.BufferIsVisibleForFilename( 'visible_filename' ), True )
     eq_( vimsupport.BufferIsVisibleForFilename( 'hidden_filename' ), False )
     eq_( vimsupport.BufferIsVisibleForFilename( 'another_filename' ), False )
 
 
 def CloseBuffersForFilename_test():
-  vim_buffers = [
-    VimBuffer( 'some_filename', number = 2 ),
-    VimBuffer( 'some_filename', number = 5 )
-  ]
+  current_buffer = VimBuffer( 'some_filename', number = 2 )
+  other_buffer = VimBuffer( 'some_filename', number = 5 )
 
-  with MockVimBuffers( vim_buffers, vim_buffers[ 0 ] ) as vim:
+  with MockVimBuffers( [ current_buffer, other_buffer ],
+                       [ current_buffer ] ) as vim:
     vimsupport.CloseBuffersForFilename( 'some_filename' )
 
   assert_that( vim.buffers, empty() )
@@ -1555,7 +1560,7 @@ def SelectFromList_Negative_test( vim_eval ):
 
 def Filetypes_IntegerFiletype_test():
   current_buffer = VimBuffer( 'buffer', number = 1, filetype = '42' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     assert_that( vimsupport.CurrentFiletypes(), contains( '42' ) )
     assert_that( vimsupport.GetBufferFiletypes( 1 ), contains( '42' ) )
     assert_that( vimsupport.FiletypesForBuffer( current_buffer ),
@@ -1613,7 +1618,7 @@ def JumpToLocation_SameFile_SameBuffer_NoSwapFile_test( vim_command ):
   # No 'u' prefix for the current buffer name string to simulate Vim returning
   # bytes on Python 2 but unicode on Python 3.
   current_buffer = VimBuffer( 'uni¢𐍈d€' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ) as vim:
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
     vimsupport.JumpToLocation( os.path.realpath( u'uni¢𐍈d€' ), 2, 5 )
 
     assert_that( vim.current.window.cursor, equal_to( ( 2, 4 ) ) )
@@ -1628,7 +1633,7 @@ def JumpToLocation_SameFile_SameBuffer_NoSwapFile_test( vim_command ):
 @patch( 'vim.command', new_callable = ExtendedMock )
 def JumpToLocation_DifferentFile_SameBuffer_Unmodified_test( vim_command ):
   current_buffer = VimBuffer( 'uni¢𐍈d€' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ) as vim:
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
     target_name = os.path.realpath( u'different_uni¢𐍈d€' )
 
     vimsupport.JumpToLocation( target_name, 2, 5 )
@@ -1648,7 +1653,7 @@ def JumpToLocation_DifferentFile_SameBuffer_Modified_CannotHide_test(
     vim_command ):
 
   current_buffer = VimBuffer( 'uni¢𐍈d€', modified = True )
-  with MockVimBuffers( [ current_buffer ], current_buffer ) as vim:
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
     target_name = os.path.realpath( u'different_uni¢𐍈d€' )
 
     vimsupport.JumpToLocation( target_name, 2, 5 )
@@ -1668,7 +1673,7 @@ def JumpToLocation_DifferentFile_SameBuffer_Modified_CanHide_test(
     vim_command ):
 
   current_buffer = VimBuffer( 'uni¢𐍈d€', modified = True, bufhidden = "hide" )
-  with MockVimBuffers( [ current_buffer ], current_buffer ) as vim:
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
     target_name = os.path.realpath( u'different_uni¢𐍈d€' )
 
     vimsupport.JumpToLocation( target_name, 2, 5 )
@@ -1689,7 +1694,7 @@ def JumpToLocation_DifferentFile_SameBuffer_SwapFile_Unexpected_test(
     vim_command ):
 
   current_buffer = VimBuffer( 'uni¢𐍈d€' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     assert_that(
       calling( vimsupport.JumpToLocation ).with_args(
           os.path.realpath( u'different_uni¢𐍈d€' ), 2, 5 ),
@@ -1704,7 +1709,7 @@ def JumpToLocation_DifferentFile_SameBuffer_SwapFile_Unexpected_test(
         side_effect = [ None, VimError( 'E325' ), None ] )
 def JumpToLocation_DifferentFile_SameBuffer_SwapFile_Quit_test( vim_command ):
   current_buffer = VimBuffer( 'uni¢𐍈d€' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     target_name = os.path.realpath( u'different_uni¢𐍈d€' )
 
     vimsupport.JumpToLocation( target_name, 2, 5 )
@@ -1722,7 +1727,7 @@ def JumpToLocation_DifferentFile_SameBuffer_SwapFile_Quit_test( vim_command ):
         side_effect = [ None, KeyboardInterrupt, None ] )
 def JumpToLocation_DifferentFile_SameBuffer_SwapFile_Abort_test( vim_command ):
   current_buffer = VimBuffer( 'uni¢𐍈d€' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     target_name = os.path.realpath( u'different_uni¢𐍈d€' )
 
     vimsupport.JumpToLocation( target_name, 2, 5 )
@@ -1740,7 +1745,7 @@ def JumpToLocation_DifferentFile_NewOrExistingTab_NotAlreadyOpened_test(
     vim_command ):
 
   current_buffer = VimBuffer( 'uni¢𐍈d€' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     target_name = os.path.realpath( u'different_uni¢𐍈d€' )
 
     vimsupport.JumpToLocation( target_name, 2, 5 )
@@ -1765,7 +1770,7 @@ def JumpToLocation_DifferentFile_NewOrExistingTab_AlreadyOpened_test(
   current_tab = MagicMock( windows = [ current_window, different_window ] )
   with patch( 'vim.tabpages', [ current_tab ] ):
     with MockVimBuffers( [ current_buffer, different_buffer ],
-                         current_buffer ) as vim:
+                         [ current_buffer ] ) as vim:
       vimsupport.JumpToLocation( os.path.realpath( u'different_uni¢𐍈d€' ),
                                  2, 5 )
 

+ 312 - 178
python/ycm/tests/youcompleteme_test.py

@@ -28,8 +28,8 @@ MockVimModule()
 
 import os
 import sys
-from hamcrest import ( assert_that, contains, empty, equal_to, is_in, is_not,
-                       matches_regexp )
+from hamcrest import ( assert_that, contains, empty, equal_to, has_entries,
+                       is_in, is_not, matches_regexp )
 from mock import call, MagicMock, patch
 
 from ycm.paths import _PathToPythonUsedDuringBuild
@@ -194,8 +194,8 @@ def YouCompleteMe_DebugInfo_ServerRunning_test( ycm ):
   extra_conf = os.path.join( dir_of_script, 'testdata', '.ycm_extra_conf.py' )
   _LoadExtraConfFile( extra_conf )
 
-  current_buffer = VimBuffer( buf_name, filetype='cpp' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  current_buffer = VimBuffer( buf_name, filetype = 'cpp' )
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     assert_that(
       ycm.DebugInfo(),
       matches_regexp(
@@ -224,7 +224,7 @@ def YouCompleteMe_DebugInfo_ServerNotRunning_test( ycm ):
   StopServer( ycm )
 
   current_buffer = VimBuffer( 'current_buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     assert_that(
       ycm.DebugInfo(),
       matches_regexp(
@@ -264,8 +264,8 @@ def YouCompleteMe_OnVimLeave_KeepClientLogfile_test( ycm ):
 def YouCompleteMe_ToggleLogs_WithParameters_test( ycm,
                                                   open_filename,
                                                   close_buffers_for_filename ):
-  logfile_buffer = VimBuffer( ycm._client_logfile, window = 1 )
-  with MockVimBuffers( [ logfile_buffer ], logfile_buffer ):
+  logfile_buffer = VimBuffer( ycm._client_logfile )
+  with MockVimBuffers( [ logfile_buffer ], [ logfile_buffer ] ):
     ycm.ToggleLogs( os.path.basename( ycm._client_logfile ),
                     'nonexisting_logfile',
                     os.path.basename( ycm._server_stdout ) )
@@ -290,7 +290,7 @@ def YouCompleteMe_ToggleLogs_WithoutParameters_SelectLogfileNotAlreadyOpen_test(
   ycm, open_filename, *args ):
 
   current_buffer = VimBuffer( 'current_buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     ycm.ToggleLogs()
 
   open_filename.assert_has_exact_calls( [
@@ -309,8 +309,8 @@ def YouCompleteMe_ToggleLogs_WithoutParameters_SelectLogfileNotAlreadyOpen_test(
 def YouCompleteMe_ToggleLogs_WithoutParameters_SelectLogfileAlreadyOpen_test(
   ycm, close_buffers_for_filename, *args ):
 
-  logfile_buffer = VimBuffer( ycm._server_stdout, window = 1 )
-  with MockVimBuffers( [ logfile_buffer ], logfile_buffer ):
+  logfile_buffer = VimBuffer( ycm._server_stdout )
+  with MockVimBuffers( [ logfile_buffer ], [ logfile_buffer ] ):
     ycm.ToggleLogs()
 
   close_buffers_for_filename.assert_has_exact_calls( [
@@ -326,7 +326,7 @@ def YouCompleteMe_ToggleLogs_WithoutParameters_NoSelection_test(
   ycm, post_vim_message, *args ):
 
   current_buffer = VimBuffer( 'current_buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     ycm.ToggleLogs()
 
   assert_that(
@@ -339,7 +339,7 @@ def YouCompleteMe_ToggleLogs_WithoutParameters_NoSelection_test(
 @YouCompleteMeInstance()
 def YouCompleteMe_GetDefinedSubcommands_ListFromServer_test( ycm ):
   current_buffer = VimBuffer( 'buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.client.base_request._JsonFromFuture',
                 return_value = [ 'SomeCommand', 'AnotherCommand' ] ):
       assert_that(
@@ -358,7 +358,7 @@ def YouCompleteMe_GetDefinedSubcommands_ErrorFromServer_test( ycm,
                                                               post_vim_message,
                                                               logger ):
   current_buffer = VimBuffer( 'buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.client.base_request._JsonFromFuture',
                 side_effect = ServerError( 'Server error' ) ):
       result = ycm.GetDefinedSubcommands()
@@ -376,7 +376,7 @@ def YouCompleteMe_ShowDetailedDiagnostic_MessageFromServer_test(
   ycm, post_vim_message ):
 
   current_buffer = VimBuffer( 'buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.client.base_request._JsonFromFuture',
                 return_value = { 'message': 'some_detailed_diagnostic' } ):
       ycm.ShowDetailedDiagnostic(),
@@ -392,7 +392,7 @@ def YouCompleteMe_ShowDetailedDiagnostic_Exception_test(
   ycm, post_vim_message ):
 
   current_buffer = VimBuffer( 'buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.client.base_request._JsonFromFuture',
                 side_effect = RuntimeError( 'Some exception' ) ):
       ycm.ShowDetailedDiagnostic(),
@@ -408,7 +408,7 @@ def YouCompleteMe_ShowDiagnostics_FiletypeNotSupported_test( ycm,
                                                              post_vim_message ):
 
   current_buffer = VimBuffer( 'buffer', filetype = 'not_supported' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     ycm.ShowDiagnostics()
 
   post_vim_message.assert_called_once_with(
@@ -424,8 +424,8 @@ def YouCompleteMe_ShowDiagnostics_FiletypeNotSupported_test( ycm,
 def YouCompleteMe_ShowDiagnostics_NoDiagnosticsDetected_test(
   ycm, set_location_list_for_window, post_vim_message, *args ):
 
-  current_buffer = VimBuffer( 'buffer', filetype = 'cpp', window = 99 )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  current_buffer = VimBuffer( 'buffer', filetype = 'cpp' )
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.client.event_notification.EventNotification.Response',
                 return_value = {} ):
       ycm.ShowDiagnostics()
@@ -436,7 +436,7 @@ def YouCompleteMe_ShowDiagnostics_NoDiagnosticsDetected_test(
     call( 'Diagnostics refreshed', warning = False ),
     call( 'No warnings or errors detected.', warning = False )
   ] )
-  set_location_list_for_window.assert_called_once_with( 0, [] )
+  set_location_list_for_window.assert_called_once_with( 1, [] )
 
 
 @YouCompleteMeInstance( { 'g:ycm_log_level': 'debug',
@@ -461,9 +461,8 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
 
   current_buffer = VimBuffer( 'buffer',
                               filetype = 'cpp',
-                              number = 3,
-                              window = 99 )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+                              number = 3 )
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.client.event_notification.EventNotification.Response',
                 return_value = [ diagnostic ] ):
       ycm.ShowDiagnostics()
@@ -473,7 +472,7 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
           warning = False ),
     call( 'Diagnostics refreshed', warning = False )
   ] )
-  set_location_list_for_window.assert_called_once_with( 0, [ {
+  set_location_list_for_window.assert_called_once_with( 1, [ {
       'bufnr': 3,
       'lnum': 19,
       'col': 2,
@@ -508,9 +507,8 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_OpenLocationList_test(
 
   current_buffer = VimBuffer( 'buffer',
                               filetype = 'cpp',
-                              number = 3,
-                              window = 99 )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+                              number = 3 )
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     with patch( 'ycm.client.event_notification.EventNotification.Response',
                 return_value = [ diagnostic ] ):
       ycm.ShowDiagnostics()
@@ -520,7 +518,7 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_OpenLocationList_test(
           warning = False ),
     call( 'Diagnostics refreshed', warning = False )
   ] )
-  set_location_list_for_window.assert_called_once_with( 0, [ {
+  set_location_list_for_window.assert_called_once_with( 1, [ {
       'bufnr': 3,
       'lnum': 19,
       'col': 2,
@@ -607,13 +605,12 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
   current_buffer = VimBuffer( 'buffer',
                               filetype = 'c',
                               contents = contents.splitlines(),
-                              number = 5,
-                              window = 2 )
+                              number = 5 )
 
-  test_utils.VIM_MATCHES = []
+  test_utils.VIM_MATCHES_FOR_WINDOW.clear()
   test_utils.VIM_SIGNS = []
 
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 1 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 3, 1 ) ):
     with patch( 'ycm.client.event_notification.EventNotification.Response',
                 return_value = diagnostics ):
       ycm.OnFileReadyToParse()
@@ -626,12 +623,14 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
 
     # Error match is added after warning matches.
     assert_that(
-      test_utils.VIM_MATCHES,
-      contains(
-        VimMatch( 'YcmWarningSection', '\%3l\%5c\_.\{-}\%3l\%7c' ),
-        VimMatch( 'YcmWarningSection', '\%3l\%3c\_.\{-}\%3l\%9c' ),
-        VimMatch( 'YcmErrorSection', '\%3l\%8c' )
-      )
+      test_utils.VIM_MATCHES_FOR_WINDOW,
+      has_entries( {
+        1: contains(
+          VimMatch( 'YcmWarningSection', '\%3l\%5c\_.\{-}\%3l\%7c' ),
+          VimMatch( 'YcmWarningSection', '\%3l\%3c\_.\{-}\%3l\%9c' ),
+          VimMatch( 'YcmErrorSection', '\%3l\%8c' )
+        )
+      } )
     )
 
     # Only the error sign is placed.
@@ -643,19 +642,19 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
     )
 
   # The error is not echoed again when moving the cursor along the line.
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 2 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 3, 2 ) ):
     post_vim_message.reset_mock()
     ycm.OnCursorMoved()
     post_vim_message.assert_not_called()
 
   # The error is cleared when moving the cursor to another line.
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 2, 2 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 2, 2 ) ):
     post_vim_message.reset_mock()
     ycm.OnCursorMoved()
     post_vim_message.assert_called_once_with( "", warning = False )
 
   # The error is echoed when moving the cursor back.
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 2 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 3, 2 ) ):
     post_vim_message.reset_mock()
     ycm.OnCursorMoved()
     post_vim_message.assert_called_once_with(
@@ -668,11 +667,13 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
       ycm.HandleFileParseRequest( block = True )
 
     assert_that(
-      test_utils.VIM_MATCHES,
-      contains(
-        VimMatch( 'YcmWarningSection', '\%3l\%5c\_.\{-}\%3l\%7c' ),
-        VimMatch( 'YcmWarningSection', '\%3l\%3c\_.\{-}\%3l\%9c' )
-      )
+      test_utils.VIM_MATCHES_FOR_WINDOW,
+      has_entries( {
+        1: contains(
+          VimMatch( 'YcmWarningSection', '\%3l\%5c\_.\{-}\%3l\%7c' ),
+          VimMatch( 'YcmWarningSection', '\%3l\%3c\_.\{-}\%3l\%9c' )
+        )
+      } )
     )
 
     assert_that(
@@ -687,23 +688,25 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
 def YouCompleteMe_UpdateMatches_ClearDiagnosticMatchesInNewBuffer_test( ycm ):
   current_buffer = VimBuffer( 'buffer',
                               filetype = 'c',
-                              number = 5,
-                              window = 2 )
+                              number = 5 )
 
-  test_utils.VIM_MATCHES = [
+  test_utils.VIM_MATCHES_FOR_WINDOW.clear()
+  test_utils.VIM_MATCHES_FOR_WINDOW[ 1 ] = [
     VimMatch( 'YcmWarningSection', '\%3l\%5c\_.\{-}\%3l\%7c' ),
     VimMatch( 'YcmWarningSection', '\%3l\%3c\_.\{-}\%3l\%9c' ),
     VimMatch( 'YcmErrorSection', '\%3l\%8c' )
   ]
 
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     ycm.UpdateMatches()
 
-    assert_that( test_utils.VIM_MATCHES, empty() )
+  assert_that( test_utils.VIM_MATCHES_FOR_WINDOW,
+               has_entries( { 1: empty() } ) )
 
 
 @YouCompleteMeInstance( { 'g:ycm_echo_current_diagnostic': 1,
-                          'g:ycm_always_populate_location_list': 1 } )
+                          'g:ycm_always_populate_location_list': 1,
+                          'g:ycm_enable_diagnostic_highlighting': 1 } )
 @patch.object( ycm_buffer_module,
                'DIAGNOSTIC_UI_ASYNC_FILETYPES',
                [ 'ycmtest' ] )
@@ -731,6 +734,19 @@ def YouCompleteMe_AsyncDiagnosticUpdate_SingleFile_test( ycm,
         'line_num': 1,
         'column_num': 1
       },
+      'location_extent': {
+        'start': {
+          'filepath': '/current',
+          'line_num': 1,
+          'column_num': 1,
+        },
+        'end': {
+          'filepath': '/current',
+          'line_num': 1,
+          'column_num': 1,
+        }
+      },
+      'ranges': []
     },
     {
       'kind': 'ERROR',
@@ -740,6 +756,19 @@ def YouCompleteMe_AsyncDiagnosticUpdate_SingleFile_test( ycm,
         'line_num': 4,
         'column_num': 2
       },
+      'location_extent': {
+        'start': {
+          'filepath': '/has_diags',
+          'line_num': 4,
+          'column_num': 2,
+        },
+        'end': {
+          'filepath': '/has_diags',
+          'line_num': 4,
+          'column_num': 2,
+        }
+      },
+      'ranges': []
     },
     {
       'kind': 'ERROR',
@@ -749,76 +778,99 @@ def YouCompleteMe_AsyncDiagnosticUpdate_SingleFile_test( ycm,
         'line_num': 8,
         'column_num': 4
       },
-    },
+      'location_extent': {
+        'start': {
+          'filepath': '/not_open',
+          'line_num': 8,
+          'column_num': 4,
+        },
+        'end': {
+          'filepath': '/not_open',
+          'line_num': 8,
+          'column_num': 4,
+        }
+      },
+      'ranges': []
+    }
   ]
 
   current_buffer = VimBuffer( '/current',
                               filetype = 'ycmtest',
-                              number = 1,
-                              window = 10 )
-  buffers = [
-    current_buffer,
-    VimBuffer( '/no_diags',
-               filetype = 'ycmtest',
-               number = 2,
-               window = 9 ),
-    VimBuffer( '/has_diags',
-               filetype = 'ycmtest',
-               number = 3,
-               window = 8 ),
-  ]
+                              contents = [ 'current' ] * 10,
+                              number = 1 )
+  no_diags_buffer = VimBuffer( '/no_diags',
+                               filetype = 'ycmtest',
+                               contents = [ 'nodiags' ] * 10,
+                               number = 2 )
+  hidden_buffer = VimBuffer( '/has_diags',
+                             filetype = 'ycmtest',
+                             contents = [ 'hasdiags' ] * 10,
+                             number = 3 )
+
+  buffers = [ current_buffer, no_diags_buffer, hidden_buffer ]
+  windows = [ current_buffer, no_diags_buffer ]
 
   # Register each buffer internally with YCM
   for current in buffers:
-    with MockVimBuffers( buffers, current, ( 1, 1 ) ):
+    with MockVimBuffers( buffers, [ current ] ):
       ycm.OnFileReadyToParse()
 
   with patch( 'ycm.vimsupport.SetLocationListForWindow',
               new_callable = ExtendedMock ) as set_location_list_for_window:
-    with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+    with MockVimBuffers( buffers, windows ):
       ycm.UpdateWithNewDiagnosticsForFile( '/current', diagnostics )
 
-    # We update the diagnostic on the current cursor position
-    post_vim_message.assert_has_exact_calls( [
-      call( "error text in current buffer", truncate = True, warning = False ),
-    ] )
+  # We update the diagnostic on the current cursor position
+  post_vim_message.assert_has_exact_calls( [
+    call( "error text in current buffer", truncate = True, warning = False ),
+  ] )
 
-    # Ensure we included all the diags though
-    set_location_list_for_window.assert_has_exact_calls( [
-      call( 0, [
-        {
-          'lnum': 1,
-          'col': 1,
-          'bufnr': 1,
-          'valid': 1,
-          'type': 'E',
-          'text': 'error text in current buffer',
-        },
-        {
-          'lnum': 4,
-          'col': 2,
-          'bufnr': 3,
-          'valid': 1,
-          'type': 'E',
-          'text': 'error text in hidden buffer',
-        },
-        {
-          'lnum': 8,
-          'col': 4,
-          'bufnr': -1, # sic: Our mocked bufnr function actually returns -1,
-                       # even though YCM is passing "create if needed".
-                       # FIXME? we shouldn't do that, and we should pass
-                       # filename instead
-          'valid': 1,
-          'type': 'E',
-          'text': 'error text in buffer not open in Vim'
-        }
-      ] )
+  # Ensure we included all the diags though
+  set_location_list_for_window.assert_has_exact_calls( [
+    call( 1, [
+      {
+        'lnum': 1,
+        'col': 1,
+        'bufnr': 1,
+        'valid': 1,
+        'type': 'E',
+        'text': 'error text in current buffer',
+      },
+      {
+        'lnum': 4,
+        'col': 2,
+        'bufnr': 3,
+        'valid': 1,
+        'type': 'E',
+        'text': 'error text in hidden buffer',
+      },
+      {
+        'lnum': 8,
+        'col': 4,
+        'bufnr': -1, # sic: Our mocked bufnr function actually returns -1,
+                     # even though YCM is passing "create if needed".
+                     # FIXME? we shouldn't do that, and we should pass
+                     # filename instead
+        'valid': 1,
+        'type': 'E',
+        'text': 'error text in buffer not open in Vim'
+      }
     ] )
+  ] )
+
+  assert_that(
+    test_utils.VIM_MATCHES_FOR_WINDOW,
+    has_entries( {
+      1: contains(
+        VimMatch( 'YcmErrorSection', '\%1l\%1c\_.\{-}\%1l\%1c' )
+      )
+    } )
+  )
 
 
 @YouCompleteMeInstance( { 'g:ycm_echo_current_diagnostic': 1,
-                          'g:ycm_always_populate_location_list': 1 } )
+                          'g:ycm_always_populate_location_list': 1,
+                          'g:ycm_enable_diagnostic_highlighting': 1 } )
 @patch.object( ycm_buffer_module,
                'DIAGNOSTIC_UI_ASYNC_FILETYPES',
                [ 'ycmtest' ] )
@@ -835,87 +887,175 @@ def YouCompleteMe_AsyncDiagnosticUpdate_PerFile_test( ycm,
   # Ordered to ensure that the calls to update are deterministic
   diagnostics_per_file = [
     ( '/current', [ {
-      'kind': 'ERROR',
-      'text': 'error text in current buffer',
-      'location': {
-        'filepath': '/current',
-        'line_num': 1,
-        'column_num': 1
-      }, }, ] ),
-    ( '/has_diags', [ {
-      'kind': 'ERROR',
-      'text': 'error text in hidden buffer',
-      'location': {
-        'filepath': '/has_diags',
-        'line_num': 4,
-        'column_num': 2
-      }, }, ] ),
+        'kind': 'ERROR',
+        'text': 'error text in current buffer',
+        'location': {
+          'filepath': '/current',
+          'line_num': 1,
+          'column_num': 1
+        },
+        'location_extent': {
+          'start': {
+            'filepath': '/current',
+            'line_num': 1,
+            'column_num': 1,
+          },
+          'end': {
+            'filepath': '/current',
+            'line_num': 1,
+            'column_num': 1,
+          }
+        },
+        'ranges': [],
+      } ] ),
+    ( '/separate_window', [ {
+        'kind': 'ERROR',
+        'text': 'error text in a buffer open in a separate window',
+        'location': {
+          'filepath': '/separate_window',
+          'line_num': 3,
+          'column_num': 3
+        },
+        'location_extent': {
+          'start': {
+            'filepath': '/separate_window',
+            'line_num': 3,
+            'column_num': 3,
+          },
+          'end': {
+            'filepath': '/separate_window',
+            'line_num': 3,
+            'column_num': 3,
+          }
+        },
+        'ranges': []
+      } ] ),
+    ( '/hidden', [ {
+        'kind': 'ERROR',
+        'text': 'error text in hidden buffer',
+        'location': {
+          'filepath': '/hidden',
+          'line_num': 4,
+          'column_num': 2
+        },
+        'location_extent': {
+          'start': {
+            'filepath': '/hidden',
+            'line_num': 4,
+            'column_num': 2,
+          },
+          'end': {
+            'filepath': '/hidden',
+            'line_num': 4,
+            'column_num': 2,
+          }
+        },
+        'ranges': []
+      } ] ),
     ( '/not_open', [ {
-      'kind': 'ERROR',
-      'text': 'error text in buffer not open in Vim',
-      'location': {
-        'filepath': '/not_open',
-        'line_num': 8,
-        'column_num': 4
-      }, }, ] )
+        'kind': 'ERROR',
+        'text': 'error text in buffer not open in Vim',
+        'location': {
+          'filepath': '/not_open',
+          'line_num': 8,
+          'column_num': 4
+        },
+        'location_extent': {
+          'start': {
+            'filepath': '/not_open',
+            'line_num': 8,
+            'column_num': 4,
+          },
+          'end': {
+            'filepath': '/not_open',
+            'line_num': 8,
+            'column_num': 4,
+          }
+        },
+        'ranges': []
+      } ] )
   ]
 
   current_buffer = VimBuffer( '/current',
                               filetype = 'ycmtest',
-                              number = 1,
-                              window = 10 )
+                              contents = [ 'current' ] * 10,
+                              number = 1 )
+  no_diags_buffer = VimBuffer( '/no_diags',
+                               filetype = 'ycmtest',
+                               contents = [ 'no_diags' ] * 10,
+                               number = 2 )
+  separate_window = VimBuffer( '/separate_window',
+                               filetype = 'ycmtest',
+                               contents = [ 'separate_window' ] * 10,
+                               number = 3 )
+  hidden_buffer = VimBuffer( '/hidden',
+                             filetype = 'ycmtest',
+                             contents = [ 'hidden' ] * 10,
+                             number = 4 )
   buffers = [
     current_buffer,
-    VimBuffer( '/no_diags',
-               filetype = 'ycmtest',
-               number = 2,
-               window = 9 ),
-    VimBuffer( '/has_diags',
-               filetype = 'ycmtest',
-               number = 3,
-               window = 8 ),
+    no_diags_buffer,
+    separate_window,
+    hidden_buffer
+  ]
+  windows = [
+    current_buffer,
+    no_diags_buffer,
+    separate_window
   ]
 
   # Register each buffer internally with YCM
   for current in buffers:
-    with MockVimBuffers( buffers, current, ( 1, 1 ) ):
+    with MockVimBuffers( buffers, [ current ] ):
       ycm.OnFileReadyToParse()
 
   with patch( 'ycm.vimsupport.SetLocationListForWindow',
               new_callable = ExtendedMock ) as set_location_list_for_window:
-    with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+    with MockVimBuffers( buffers, windows ):
       for filename, diagnostics in diagnostics_per_file:
         ycm.UpdateWithNewDiagnosticsForFile( filename, diagnostics )
 
-    # We update the diagnostic on the current cursor position
-    post_vim_message.assert_has_exact_calls( [
-      call( "error text in current buffer", truncate = True, warning = False ),
-    ] )
+  # We update the diagnostic on the current cursor position
+  post_vim_message.assert_has_exact_calls( [
+    call( "error text in current buffer", truncate = True, warning = False ),
+  ] )
 
-    # Ensure we included all the diags though
-    set_location_list_for_window.assert_has_exact_calls( [
-      call( 0, [
-        {
-          'lnum': 1,
-          'col': 1,
-          'bufnr': 1,
-          'valid': 1,
-          'type': 'E',
-          'text': 'error text in current buffer',
-        },
-      ] ),
-
-      call( 8, [
-        {
-          'lnum': 4,
-          'col': 2,
-          'bufnr': 3,
-          'valid': 1,
-          'type': 'E',
-          'text': 'error text in hidden buffer',
-        },
-      ] )
+  # Ensure we included all the diags though
+  set_location_list_for_window.assert_has_exact_calls( [
+    call( 1, [
+      {
+        'lnum': 1,
+        'col': 1,
+        'bufnr': 1,
+        'valid': 1,
+        'type': 'E',
+        'text': 'error text in current buffer',
+      },
+    ] ),
+
+    call( 3, [
+      {
+        'lnum': 3,
+        'col': 3,
+        'bufnr': 3,
+        'valid': 1,
+        'type': 'E',
+        'text': 'error text in a buffer open in a separate window',
+      },
     ] )
+  ] )
+
+  assert_that(
+    test_utils.VIM_MATCHES_FOR_WINDOW,
+    has_entries( {
+      1: contains(
+        VimMatch( 'YcmErrorSection', '\%1l\%1c\_.\{-}\%1l\%1c' )
+      ),
+      3: contains(
+        VimMatch( 'YcmErrorSection', '\%3l\%3c\_.\{-}\%3l\%3c' )
+      )
+    } )
+  )
 
 
 @YouCompleteMeInstance()
@@ -945,12 +1085,10 @@ def YouCompleteMe_OnPeriodicTick_DontRetry_test( ycm,
 
   current_buffer = VimBuffer( '/current',
                               filetype = 'ycmtest',
-                              number = 1,
-                              window = 10 )
-  buffers = [ current_buffer ]
+                              number = 1 )
 
   # Create the request and make the first poll; we expect no response
-  with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 1 ) ):
     assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
     post_data_to_handler_async.assert_called()
 
@@ -996,12 +1134,10 @@ def YouCompleteMe_OnPeriodicTick_Exception_test( ycm,
 
   current_buffer = VimBuffer( '/current',
                               filetype = 'ycmtest',
-                              number = 1,
-                              window = 10 )
-  buffers = [ current_buffer ]
+                              number = 1 )
 
   # Create the request and make the first poll; we expect no response
-  with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 1 ) ):
     assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
     post_data_to_handler_async.assert_called()
 
@@ -1036,12 +1172,10 @@ def YouCompleteMe_OnPeriodicTick_ValidResponse_test( ycm,
 
   current_buffer = VimBuffer( '/current',
                               filetype = 'ycmtest',
-                              number = 1,
-                              window = 10 )
-  buffers = [ current_buffer ]
+                              number = 1 )
 
   # Create the request and make the first poll; we expect no response
-  with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 1 ) ):
     assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
     post_data_to_handler_async.assert_called()
 
@@ -1065,7 +1199,7 @@ def YouCompleteMe_OnPeriodicTick_ValidResponse_test( ycm,
 def YouCompleteMe_OnCompleteDone_CompletionRequest_test( ycm,
                                                          on_complete_done ):
   current_buffer = VimBuffer( 'current_buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 1 ) ):
     ycm.SendCompletionRequest()
   ycm.OnCompleteDone()
   on_complete_done.assert_called()
@@ -1082,5 +1216,5 @@ def YouCompleteMe_OnCompleteDone_NoCompletionRequest_test( ycm,
 @YouCompleteMeInstance()
 def YouCompleteMe_ShouldResendFileParseRequest_NoParseRequest_test( ycm ):
   current_buffer = VimBuffer( 'current_buffer' )
-  with MockVimBuffers( [ current_buffer ], current_buffer ):
+  with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
     assert_that( ycm.ShouldResendFileParseRequest(), equal_to( False ) )

+ 47 - 28
python/ycm/vimsupport.py

@@ -23,6 +23,7 @@ from __future__ import absolute_import
 from builtins import *  # noqa
 
 from future.utils import iterkeys
+import contextlib
 import vim
 import os
 import json
@@ -289,36 +290,18 @@ def SetLocationList( diagnostics ):
   SetLocationListForWindow( 0, diagnostics )
 
 
-def GetWindowNumberForBufferDiagnostics( buffer_number ):
-  """Return an appropriate window number to use for displaying diagnostics
-  associated with the buffer number supplied. Always returns a valid window
-  number or 0 meaning the current window."""
+def GetWindowsForBufferNumber( buffer_number ):
+  """Return the list of windows containing the buffer with number
+  |buffer_number| for the current tab page."""
+  return [ window for window in vim.windows
+           if window.buffer.number == buffer_number ]
 
-  # Location lists are associated with _windows_ not _buffers_. This makes a lot
-  # of sense, but YCM associates diagnostics with _buffers_, because it is the
-  # buffer that actually gets parsed.
-  #
-  # The heuristic we use is to determine any open window for a specified buffer,
-  # and set that. If there is no such window on the current tab page, we use the
-  # current window (by passing 0 as the window number)
-
-  if buffer_number == vim.current.buffer.number:
-    return 0
-
-  window_number = GetIntValue( "bufwinnr({0})".format( buffer_number ) )
 
-  if window_number < 0:
-    return 0
-
-  return window_number
-
-
-def SetLocationListForBuffer( buffer_number, diagnostics ):
-  """Populate the location list of an apppropriate window for the supplied
-  buffer number. See SetLocationListForWindow for format of diagnostics."""
-  return SetLocationListForWindow(
-    GetWindowNumberForBufferDiagnostics( buffer_number ),
-    diagnostics )
+def SetLocationListsForBuffer( buffer_number, diagnostics ):
+  """Populate location lists for all windows containing the buffer with number
+  |buffer_number|. See SetLocationListForWindow for format of diagnostics."""
+  for window in GetWindowsForBufferNumber( buffer_number ):
+    SetLocationListForWindow( window.number, diagnostics )
 
 
 def SetLocationListForWindow( window_number, diagnostics ):
@@ -1181,3 +1164,39 @@ def BuildRange( start_line, end_line ):
       }
     }
   }
+
+
+@contextlib.contextmanager
+def AutocommandEventsIgnored( events = [ 'all' ] ):
+  """Context manager to perform operations without triggering autocommand
+  events. |events| is a list of events to ignore. By default, all events are
+  ignored."""
+  old_eventignore = vim.options[ 'eventignore' ]
+  ignored_events = {
+    event for event in ToUnicode( old_eventignore ).split( ',' ) if event }
+  ignored_events.update( events )
+  vim.options[ 'eventignore' ] = ','.join( ignored_events )
+  try:
+    yield
+  finally:
+    vim.options[ 'eventignore' ] = old_eventignore
+
+
+@contextlib.contextmanager
+def CurrentWindow():
+  """Context manager to perform operations on other windows than the current one
+  without triggering autocommands related to window movement. Use the
+  SwitchWindow function to move to other windows while under the context."""
+  current_window = vim.current.window
+  with AutocommandEventsIgnored( [ 'WinEnter', 'Winleave' ] ):
+    try:
+      yield
+    finally:
+      vim.current.window = current_window
+
+
+def SwitchWindow( window ):
+  """Move to the window object |window|. This function should be called under
+  the CurrentWindow context if you are going to switch back to the original
+  window."""
+  vim.current.window = window

+ 1 - 1
python/ycm/youcompleteme.py

@@ -385,7 +385,7 @@ class YouCompleteMe( object ):
     bufnr = vimsupport.GetBufferNumberForFilename( filepath )
     if bufnr in self._buffers and vimsupport.BufferIsVisible( bufnr ):
       # Note: We only update location lists, etc. for visible buffers, because
-      # otherwise we defualt to using the curren location list and the results
+      # otherwise we default to using the current location list and the results
       # are that non-visible buffer errors clobber visible ones.
       self._buffers[ bufnr ].UpdateWithNewDiagnostics( diagnostics )
     else: