瀏覽代碼

Decoupling completers from Vim; still WIP & broken

Note to self: squash this commit before merging into master.
Strahinja Val Markovic 11 年之前
父節點
當前提交
29bb90a6b4

+ 17 - 18
autoload/youcompleteme.vim

@@ -41,6 +41,7 @@ function! youcompleteme#Enable()
   py import vim
   exe 'python sys.path.insert( 0, "' . s:script_folder_path . '/../python" )'
   py from ycm import base
+  py from ycm import vimsupport
   py from ycm import user_options_store
   py user_options_store.SetAll( base.BuildServerConf() )
   py from ycm import extra_conf_store
@@ -260,7 +261,7 @@ function! s:OnCursorHold()
   call s:SetUpCompleteopt()
   " Order is important here; we need to extract any done diagnostics before
   " reparsing the file again
-  call s:UpdateDiagnosticNotifications()
+  " call s:UpdateDiagnosticNotifications()
   call s:OnFileReadyToParse()
 endfunction
 
@@ -327,7 +328,7 @@ function! s:OnCursorMovedNormalMode()
     return
   endif
 
-  call s:UpdateDiagnosticNotifications()
+  " call s:UpdateDiagnosticNotifications()
   call s:OnFileReadyToParse()
 endfunction
 
@@ -338,7 +339,7 @@ function! s:OnInsertLeave()
   endif
 
   let s:omnifunc_mode = 0
-  call s:UpdateDiagnosticNotifications()
+  " call s:UpdateDiagnosticNotifications()
   call s:OnFileReadyToParse()
   py ycm_state.OnInsertLeave()
   if g:ycm_autoclose_preview_window_after_completion ||
@@ -572,7 +573,9 @@ command! YcmShowDetailedDiagnostic call s:ShowDetailedDiagnostic()
 " required (currently that's on buffer save) OR when the SyntasticCheck command
 " is invoked
 function! youcompleteme#CurrentFileDiagnostics()
-  return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' )
+  " TODO: Make this work again.
+  " return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' )
+  return []
 endfunction
 
 
@@ -595,28 +598,24 @@ function! s:CompleterCommand(...)
   " to select the omni completer or "ft=ycm:ident" to select the identifier
   " completer.  The remaining arguments will passed to the completer.
   let arguments = copy(a:000)
+  let completer = ''
 
   if a:0 > 0 && strpart(a:1, 0, 3) == 'ft='
     if a:1 == 'ft=ycm:omni'
-      py completer = ycm_state.GetOmniCompleter()
+      let completer = 'omni'
     elseif a:1 == 'ft=ycm:ident'
-      py completer = ycm_state.GetGeneralCompleter()
-    else
-      py completer = ycm_state.GetFiletypeCompleterForFiletype(
-                   \ vim.eval('a:1').lstrip('ft=') )
+      let completer = 'identifier'
     endif
     let arguments = arguments[1:]
-  elseif pyeval( 'ycm_state.NativeFiletypeCompletionAvailable()' )
-    py completer = ycm_state.GetFiletypeCompleter()
-  else
-    echohl WarningMsg |
-      \ echomsg "No native completer found for current buffer." |
-      \ echomsg  "Use ft=... as the first argument to specify a completer." |
-      \ echohl None
-    return
   endif
 
-  py completer.OnUserCommand( vim.eval( 'l:arguments' ) )
+py << EOF
+response = ycm_state.SendCommandRequest( vim.eval( 'l:arguments' ),
+                                         vim.eval( 'l:completer' ) )
+if not response.Valid():
+  vimsupport.PostVimMessage( 'No native completer found for current buffer. ' +
+     'Use ft=... as the first argument to specify a completer.')
+EOF
 endfunction
 
 

+ 23 - 19
cpp/ycm/ClangCompleter/ClangCompleter.cpp

@@ -103,7 +103,7 @@ void ClangCompleter::EnableThreading() {
 
 
 std::vector< Diagnostic > ClangCompleter::DiagnosticsForFile(
-  const std::string &filename ) {
+  std::string filename ) {
   shared_ptr< TranslationUnit > unit = translation_unit_store_.Get( filename );
 
   if ( !unit )
@@ -127,9 +127,9 @@ bool ClangCompleter::UpdatingTranslationUnit( const std::string &filename ) {
 
 
 void ClangCompleter::UpdateTranslationUnit(
-  const std::string &filename,
-  const std::vector< UnsavedFile > &unsaved_files,
-  const std::vector< std::string > &flags ) {
+  std::string filename,
+  std::vector< UnsavedFile > unsaved_files,
+  std::vector< std::string > flags ) {
   bool translation_unit_created;
   shared_ptr< TranslationUnit > unit = translation_unit_store_.GetOrCreate(
       filename,
@@ -182,11 +182,11 @@ Future< void > ClangCompleter::UpdateTranslationUnitAsync(
 
 std::vector< CompletionData >
 ClangCompleter::CandidatesForLocationInFile(
-  const std::string &filename,
+  std::string filename,
   int line,
   int column,
-  const std::vector< UnsavedFile > &unsaved_files,
-  const std::vector< std::string > &flags ) {
+  std::vector< UnsavedFile > unsaved_files,
+  std::vector< std::string > flags ) {
   shared_ptr< TranslationUnit > unit =
       translation_unit_store_.GetOrCreate( filename, unsaved_files, flags );
 
@@ -201,12 +201,12 @@ ClangCompleter::CandidatesForLocationInFile(
 
 Future< AsyncCompletions >
 ClangCompleter::CandidatesForQueryAndLocationInFileAsync(
-  const std::string &query,
-  const std::string &filename,
+  std::string query,
+  std::string filename,
   int line,
   int column,
-  const std::vector< UnsavedFile > &unsaved_files,
-  const std::vector< std::string > &flags ) {
+  std::vector< UnsavedFile > unsaved_files,
+  std::vector< std::string > flags ) {
   // TODO: throw exception when threading is not enabled and this is called
   if ( !threading_enabled_ )
     return Future< AsyncCompletions >();
@@ -238,7 +238,11 @@ ClangCompleter::CandidatesForQueryAndLocationInFileAsync(
   CreateSortingTask( query, future );
 
   if ( skip_clang_result_cache ) {
-    CreateClangTask( filename, line, column, unsaved_files, flags );
+    CreateClangTask( boost::move( filename ),
+                     line,
+                     column,
+                     boost::move( unsaved_files ),
+                     boost::move( flags ) );
   }
 
   return Future< AsyncCompletions >( boost::move( future ) );
@@ -246,11 +250,11 @@ ClangCompleter::CandidatesForQueryAndLocationInFileAsync(
 
 
 Location ClangCompleter::GetDeclarationLocation(
-  const std::string &filename,
+  std::string filename,
   int line,
   int column,
-  const std::vector< UnsavedFile > &unsaved_files,
-  const std::vector< std::string > &flags ) {
+  std::vector< UnsavedFile > unsaved_files,
+  std::vector< std::string > flags ) {
   shared_ptr< TranslationUnit > unit =
       translation_unit_store_.GetOrCreate( filename, unsaved_files, flags );
 
@@ -263,11 +267,11 @@ Location ClangCompleter::GetDeclarationLocation(
 
 
 Location ClangCompleter::GetDefinitionLocation(
-  const std::string &filename,
+  std::string filename,
   int line,
   int column,
-  const std::vector< UnsavedFile > &unsaved_files,
-  const std::vector< std::string > &flags ) {
+  std::vector< UnsavedFile > unsaved_files,
+  std::vector< std::string > flags ) {
   shared_ptr< TranslationUnit > unit =
       translation_unit_store_.GetOrCreate( filename, unsaved_files, flags );
 
@@ -279,7 +283,7 @@ Location ClangCompleter::GetDefinitionLocation(
 }
 
 
-void ClangCompleter::DeleteCachesForFileAsync( const std::string &filename ) {
+void ClangCompleter::DeleteCachesForFileAsync( std::string filename ) {
   file_cache_delete_stack_.Push( filename );
 }
 

+ 25 - 20
cpp/ycm/ClangCompleter/ClangCompleter.h

@@ -59,18 +59,23 @@ public:
 
   void EnableThreading();
 
-  std::vector< Diagnostic > DiagnosticsForFile( const std::string &filename );
+  std::vector< Diagnostic > DiagnosticsForFile( std::string filename );
 
   bool UpdatingTranslationUnit( const std::string &filename );
 
+  // NOTE: params are taken by value on purpose! With a C++11 compiler we can
+  // avoid internal copies if params are taken by value (move ctors FTW), and we
+  // need to ensure we own the memory.
+  // TODO: Change some of these params back to const ref where possible after we
+  // get the server up.
+  // TODO: Remove the async methods and the threads when the server is ready.
+
   // Public because of unit tests (gtest is not very thread-friendly)
   void UpdateTranslationUnit(
-    const std::string &filename,
-    const std::vector< UnsavedFile > &unsaved_files,
-    const std::vector< std::string > &flags );
+    std::string filename,
+    std::vector< UnsavedFile > unsaved_files,
+    std::vector< std::string > flags );
 
-  // NOTE: params are taken by value on purpose! With a C++11 compiler we can
-  // avoid internal copies if params are taken by value (move ctors FTW)
   Future< void > UpdateTranslationUnitAsync(
     std::string filename,
     std::vector< UnsavedFile > unsaved_files,
@@ -78,35 +83,35 @@ public:
 
   // Public because of unit tests (gtest is not very thread-friendly)
   std::vector< CompletionData > CandidatesForLocationInFile(
-    const std::string &filename,
+    std::string filename,
     int line,
     int column,
-    const std::vector< UnsavedFile > &unsaved_files,
-    const std::vector< std::string > &flags );
+    std::vector< UnsavedFile > unsaved_files,
+    std::vector< std::string > flags );
 
   Future< AsyncCompletions > CandidatesForQueryAndLocationInFileAsync(
-    const std::string &query,
-    const std::string &filename,
+    std::string query,
+    std::string filename,
     int line,
     int column,
-    const std::vector< UnsavedFile > &unsaved_files,
-    const std::vector< std::string > &flags );
+    std::vector< UnsavedFile > unsaved_files,
+    std::vector< std::string > flags );
 
   Location GetDeclarationLocation(
-    const std::string &filename,
+    std::string filename,
     int line,
     int column,
-    const std::vector< UnsavedFile > &unsaved_files,
-    const std::vector< std::string > &flags );
+    std::vector< UnsavedFile > unsaved_files,
+    std::vector< std::string > flags );
 
   Location GetDefinitionLocation(
-    const std::string &filename,
+    std::string filename,
     int line,
     int column,
-    const std::vector< UnsavedFile > &unsaved_files,
-    const std::vector< std::string > &flags );
+    std::vector< UnsavedFile > unsaved_files,
+    std::vector< std::string > flags );
 
-  void DeleteCachesForFileAsync( const std::string &filename );
+  void DeleteCachesForFileAsync( std::string filename );
 
 private:
 

+ 2 - 2
plugin/youcompleteme.vim

@@ -143,8 +143,8 @@ let g:ycm_extra_conf_globlist =
 let g:ycm_filepath_completion_use_working_dir =
       \ get( g:, 'ycm_filepath_completion_use_working_dir', 0 )
 
-" Default semantic triggers are in python/ycm/completers/completer.py, these
-" just append new triggers to the default dict.
+" Default semantic triggers are in python/ycm/completers/completer_utils.py
+" these just append new triggers to the default dict.
 let g:ycm_semantic_triggers =
       \ get( g:, 'ycm_semantic_triggers', {} )
 

+ 18 - 22
python/ycm/completers/all/identifier_completer.py

@@ -25,6 +25,7 @@ from ycm.completers.general_completer import GeneralCompleter
 from ycm.completers.general import syntax_parse
 from ycm import vimsupport
 from ycm import utils
+from ycm import server_responses
 
 MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10
 SYNTAX_FILENAME = 'YCM_PLACEHOLDER_FOR_SYNTAX'
@@ -39,15 +40,14 @@ class IdentifierCompleter( GeneralCompleter ):
     self.filetypes_with_keywords_loaded = set()
 
 
-  def ShouldUseNow( self, start_column, unused_current_line ):
-    return self.QueryLengthAboveMinThreshold( start_column )
+  def ShouldUseNow( self, request_data ):
+    return self.QueryLengthAboveMinThreshold( request_data )
 
 
-  def CandidatesForQueryAsync( self, query, unused_start_column ):
-    filetype = vim.eval( "&filetype" )
+  def CandidatesForQueryAsync( self, request_data ):
     self.completions_future = self.completer.CandidatesForQueryAndTypeAsync(
-      utils.SanitizeQuery( query ),
-      filetype )
+      utils.SanitizeQuery( request_data[ 'query' ] ),
+      request_data[ 'filetypes' ][ 0 ] )
 
 
   def AddIdentifier( self, identifier ):
@@ -83,17 +83,16 @@ class IdentifierCompleter( GeneralCompleter ):
     self.AddIdentifier( stripped_cursor_identifier )
 
 
-  def AddBufferIdentifiers( self ):
-    # TODO: use vimsupport.GetFiletypes; also elsewhere in file
-    filetype = vim.eval( "&filetype" )
-    filepath = vim.eval( "expand('%:p')" )
+  def AddBufferIdentifiers( self, request_data ):
+    filetype = request_data[ 'filetypes' ][ 0 ]
+    filepath = request_data[ 'filepath' ]
     collect_from_comments_and_strings = bool( self.user_options[
       'collect_identifiers_from_comments_and_strings' ] )
 
     if not filetype or not filepath:
       return
 
-    text = "\n".join( vim.current.buffer )
+    text = request_data[ 'file_data' ][ filepath ][ 'contents' ]
     self.completer.AddIdentifiersToDatabaseFromBufferAsync(
       text,
       filetype,
@@ -147,14 +146,15 @@ class IdentifierCompleter( GeneralCompleter ):
                                              filepath )
 
 
-  def OnFileReadyToParse( self ):
-    self.AddBufferIdentifiers()
+  def OnFileReadyToParse( self, request_data ):
+    self.AddBufferIdentifiers( request_data )
 
-    if self.user_options[ 'collect_identifiers_from_tags_files' ]:
-      self.AddIdentifiersFromTagFiles()
+    # TODO: make these work again
+    # if self.user_options[ 'collect_identifiers_from_tags_files' ]:
+    #   self.AddIdentifiersFromTagFiles()
 
-    if self.user_options[ 'seed_identifiers_with_syntax' ]:
-      self.AddIdentifiersFromSyntax()
+    # if self.user_options[ 'seed_identifiers_with_syntax' ]:
+    #   self.AddIdentifiersFromSyntax()
 
 
   def OnInsertLeave( self ):
@@ -174,11 +174,7 @@ class IdentifierCompleter( GeneralCompleter ):
     completions = _RemoveSmallCandidates(
       completions, self.user_options[ 'min_num_identifier_candidate_chars' ] )
 
-    # We will never have duplicates in completions so with 'dup':1 we tell Vim
-    # to add this candidate even if it's a duplicate of an existing one (which
-    # will never happen). This saves us some expensive string matching
-    # operations in Vim.
-    return [ { 'word': x, 'dup': 1 } for x in completions ]
+    return [ server_responses.BuildCompletionData( x ) for x in completions ]
 
 
 def _PreviousIdentifier( min_num_completion_start_chars ):

+ 11 - 13
python/ycm/completers/all/omni_completer.py

@@ -40,29 +40,27 @@ class OmniCompleter( Completer ):
     return bool( self.user_options[ 'cache_omnifunc' ] )
 
 
-  def ShouldUseNow( self, start_column, current_line ):
+  def ShouldUseNow( self, request_data ):
     if self.ShouldUseCache():
-      return super( OmniCompleter, self ).ShouldUseNow( start_column,
-                                                        current_line )
-    return self.ShouldUseNowInner( start_column, current_line )
+      return super( OmniCompleter, self ).ShouldUseNow( request_data )
+    return self.ShouldUseNowInner( request_data )
 
 
-  def ShouldUseNowInner( self, start_column, current_line ):
+  def ShouldUseNowInner( self, request_data ):
     if not self.omnifunc:
       return False
-    return super( OmniCompleter, self ).ShouldUseNowInner( start_column,
-                                                           current_line )
+    return super( OmniCompleter, self ).ShouldUseNowInner( request_data )
 
 
-  def CandidatesForQueryAsync( self, query, unused_start_column ):
+  def CandidatesForQueryAsync( self, request_data ):
     if self.ShouldUseCache():
       return super( OmniCompleter, self ).CandidatesForQueryAsync(
-          query, unused_start_column )
+        request_data )
     else:
-      return self.CandidatesForQueryAsyncInner( query, unused_start_column )
+      return self.CandidatesForQueryAsyncInner( request_data )
 
 
-  def CandidatesForQueryAsyncInner( self, query, unused_start_column ):
+  def CandidatesForQueryAsyncInner( self, request_data ):
     if not self.omnifunc:
       self.stored_candidates = None
       return
@@ -75,7 +73,7 @@ class OmniCompleter( Completer ):
 
       omnifunc_call = [ self.omnifunc,
                         "(0,'",
-                        vimsupport.EscapeForVim( query ),
+                        vimsupport.EscapeForVim( request_data[ 'query' ] ),
                         "')" ]
 
       items = vim.eval( ''.join( omnifunc_call ) )
@@ -98,7 +96,7 @@ class OmniCompleter( Completer ):
     return True
 
 
-  def OnFileReadyToParse( self ):
+  def OnFileReadyToParse( self, request_data ):
     self.omnifunc = vim.eval( '&omnifunc' )
 
 

+ 45 - 37
python/ycm/completers/completer.py

@@ -19,7 +19,6 @@
 
 import abc
 import ycm_core
-from ycm import vimsupport
 from ycm.completers.completer_utils import TriggersForFiletype
 
 NO_USER_COMMANDS = 'This completer does not define any commands.'
@@ -116,32 +115,35 @@ class Completer( object ):
   def __init__( self, user_options ):
     self.user_options = user_options
     self.min_num_chars = user_options[ 'min_num_of_chars_for_completion' ]
-    self.triggers_for_filetype = TriggersForFiletype()
+    self.triggers_for_filetype = TriggersForFiletype(
+      user_options[ 'semantic_triggers' ] )
     self.completions_future = None
     self.completions_cache = None
-    self.completion_start_column = None
 
 
   # It's highly likely you DON'T want to override this function but the *Inner
   # version of it.
-  def ShouldUseNow( self, start_column, current_line ):
-    inner_says_yes = self.ShouldUseNowInner( start_column, current_line )
+  def ShouldUseNow( self, request_data ):
+    inner_says_yes = self.ShouldUseNowInner( request_data )
     if not inner_says_yes:
       self.completions_cache = None
 
     previous_results_were_empty = ( self.completions_cache and
                                     self.completions_cache.CacheValid(
-                                      start_column ) and
+                                      request_data[ 'line_num' ],
+                                      request_data[ 'start_column' ] ) and
                                     not self.completions_cache.raw_completions )
     return inner_says_yes and not previous_results_were_empty
 
 
-  def ShouldUseNowInner( self, start_column, current_line ):
+  def ShouldUseNowInner( self, request_data ):
+    current_line = request_data[ 'line_value' ]
+    start_column = request_data[ 'start_column' ]
     line_length = len( current_line )
     if not line_length or start_column - 1 >= line_length:
       return False
 
-    filetype = self._CurrentFiletype()
+    filetype = self._CurrentFiletype( request_data[ 'filetypes' ] )
     triggers = self.triggers_for_filetype[ filetype ]
 
     for trigger in triggers:
@@ -158,52 +160,61 @@ class Completer( object ):
     return False
 
 
-  def QueryLengthAboveMinThreshold( self, start_column ):
-    query_length = vimsupport.CurrentColumn() - start_column
+  def QueryLengthAboveMinThreshold( self, request_data ):
+    query_length = request_data[ 'column_num' ] - request_data[ 'start_column' ]
     return query_length >= self.min_num_chars
 
 
   # It's highly likely you DON'T want to override this function but the *Inner
   # version of it.
-  def CandidatesForQueryAsync( self, query, start_column ):
-    self.completion_start_column = start_column
+  def CandidatesForQueryAsync( self, request_data ):
+    self.request_data = request_data
 
-    if query and self.completions_cache and self.completions_cache.CacheValid(
-      start_column ):
+    if ( request_data[ 'query' ] and
+         self.completions_cache and
+         self.completions_cache.CacheValid( request_data[ 'line_num' ],
+                                            request_data[ 'start_column' ] ) ):
       self.completions_cache.filtered_completions = (
         self.FilterAndSortCandidates(
           self.completions_cache.raw_completions,
-          query ) )
+          request_data[ 'query' ] ) )
     else:
       self.completions_cache = None
-      self.CandidatesForQueryAsyncInner( query, start_column )
+      self.CandidatesForQueryAsyncInner( request_data )
 
 
   def DefinedSubcommands( self ):
     return []
 
 
-  def EchoUserCommandsHelpMessage( self ):
+  def UserCommandsHelpMessage( self ):
     subcommands = self.DefinedSubcommands()
     if subcommands:
-      vimsupport.EchoText( 'Supported commands are:\n' +
-                           '\n'.join( subcommands ) +
-                           '\nSee the docs for information on what they do.' )
+      return ( 'Supported commands are:\n' +
+               '\n'.join( subcommands ) +
+               '\nSee the docs for information on what they do.' )
     else:
-      vimsupport.EchoText( 'No supported subcommands' )
+      return 'No supported subcommands'
 
 
   def FilterAndSortCandidates( self, candidates, query ):
     if not candidates:
       return []
 
-    if hasattr( candidates, 'words' ):
-      candidates = candidates.words
-    items_are_objects = 'word' in candidates[ 0 ]
+    # We need to handle both an omni_completer style completer and a server
+    # style completer
+    if 'words' in candidates:
+      candidates = candidates[ 'words' ]
+
+    sort_property = ''
+    if 'word' in candidates[ 0 ]:
+      sort_property = 'word'
+    elif 'insertion_text' in candidates[ 0 ]:
+      sort_property = 'insertion_text'
 
     matches = ycm_core.FilterAndSortCandidates(
       candidates,
-      'word' if items_are_objects else '',
+      sort_property,
       query )
 
     return matches
@@ -238,8 +249,8 @@ class Completer( object ):
     else:
       self.completions_cache = CompletionsCache()
       self.completions_cache.raw_completions = self.CandidatesFromStoredRequestInner()
-      self.completions_cache.line, _ = vimsupport.CurrentLineAndColumn()
-      self.completions_cache.column = self.completion_start_column
+      self.completions_cache.line = self.request_data[ 'line_num' ]
+      self.completions_cache.column = self.request_data[ 'start_column' ]
       return self.completions_cache.raw_completions
 
 
@@ -249,7 +260,7 @@ class Completer( object ):
     return self.completions_future.GetResults()
 
 
-  def OnFileReadyToParse( self ):
+  def OnFileReadyToParse( self, request_data ):
     pass
 
 
@@ -269,8 +280,8 @@ class Completer( object ):
     pass
 
 
-  def OnUserCommand( self, arguments ):
-    vimsupport.PostVimMessage( NO_USER_COMMANDS )
+  def OnUserCommand( self, arguments, request_data ):
+    raise NotImplementedError( NO_USER_COMMANDS )
 
 
   def OnCurrentIdentifierFinished( self ):
@@ -285,7 +296,7 @@ class Completer( object ):
     return []
 
 
-  def ShowDetailedDiagnostic( self ):
+  def GetDetailedDiagnostic( self ):
     pass
 
 
@@ -293,8 +304,7 @@ class Completer( object ):
     return False
 
 
-  def _CurrentFiletype( self ):
-    filetypes = vimsupport.CurrentFiletypes()
+  def _CurrentFiletype( self, filetypes ):
     supported = self.SupportedFiletypes()
 
     for filetype in filetypes:
@@ -321,9 +331,7 @@ class CompletionsCache( object ):
     self.filtered_completions = []
 
 
-  def CacheValid( self, start_column ):
-    completion_line, _ = vimsupport.CurrentLineAndColumn()
-    completion_column = start_column
-    return completion_line == self.line and completion_column == self.column
+  def CacheValid( self, current_line, start_column ):
+    return current_line == self.line and start_column == self.column
 
 

+ 2 - 6
python/ycm/completers/completer_utils.py

@@ -19,7 +19,6 @@
 
 from collections import defaultdict
 from copy import deepcopy
-import vim
 
 DEFAULT_FILETYPE_TRIGGERS = {
   'c' : ['->', '.'],
@@ -58,12 +57,9 @@ def _FiletypeDictUnion( dict_one, dict_two ):
   return final_dict
 
 
-def TriggersForFiletype():
-  user_triggers = _FiletypeTriggerDictFromSpec(
-    vim.eval( 'g:ycm_semantic_triggers' ) )
-
+def TriggersForFiletype( user_triggers ):
   default_triggers = _FiletypeTriggerDictFromSpec(
     DEFAULT_FILETYPE_TRIGGERS )
 
-  return _FiletypeDictUnion( default_triggers, user_triggers )
+  return _FiletypeDictUnion( default_triggers, dict( user_triggers ) )
 

+ 142 - 134
python/ycm/completers/cpp/clang_completer.py

@@ -18,14 +18,23 @@
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
 from collections import defaultdict
-import vim
 import ycm_core
-from ycm import vimsupport
+import logging
+from ycm import server_responses
 from ycm import extra_conf_store
 from ycm.completers.completer import Completer
 from ycm.completers.cpp.flags import Flags
 
 CLANG_FILETYPES = set( [ 'c', 'cpp', 'objc', 'objcpp' ] )
+MIN_LINES_IN_FILE_TO_PARSE = 5
+PARSING_FILE_MESSAGE = 'Still parsing file, no completions yet.'
+NO_COMPILE_FLAGS_MESSAGE = 'Still no compile flags, no completions yet.'
+NO_COMPLETIONS_MESSAGE = 'No completions found; errors in the file?'
+INVALID_FILE_MESSAGE = 'File is invalid.'
+FILE_TOO_SHORT_MESSAGE = (
+  'File is less than {} lines long; not compiling.'.format(
+    MIN_LINES_IN_FILE_TO_PARSE ) )
+NO_DIAGNOSTIC_MESSAGE = 'No diagnostic for current line!'
 
 
 class ClangCompleter( Completer ):
@@ -35,12 +44,11 @@ class ClangCompleter( Completer ):
       'max_diagnostics_to_display' ]
     self.completer = ycm_core.ClangCompleter()
     self.completer.EnableThreading()
-    self.contents_holder = []
-    self.filename_holder = []
     self.last_prepared_diagnostics = []
     self.parse_future = None
     self.flags = Flags()
     self.diagnostic_store = None
+    self._logger = logging.getLogger( __name__ )
 
     # We set this flag when a compilation request comes in while one is already
     # in progress. We use this to trigger the pending request after the previous
@@ -53,63 +61,52 @@ class ClangCompleter( Completer ):
     return CLANG_FILETYPES
 
 
-  def GetUnsavedFilesVector( self ):
-    # CAREFUL HERE! For UnsavedFile filename and contents we are referring
-    # directly to Python-allocated and -managed memory since we are accepting
-    # pointers to data members of python objects. We need to ensure that those
-    # objects outlive our UnsavedFile objects. This is why we need the
-    # contents_holder and filename_holder lists, to make sure the string objects
-    # are still around when we call CandidatesForQueryAndLocationInFile.  We do
-    # this to avoid an extra copy of the entire file contents.
-
+  def GetUnsavedFilesVector( self, request_data ):
     files = ycm_core.UnsavedFileVec()
-    self.contents_holder = []
-    self.filename_holder = []
-    for buffer in vimsupport.GetUnsavedBuffers():
-      if not ClangAvailableForBuffer( buffer ):
+    for filename, file_data in request_data[ 'file_data' ].iteritems():
+      if not ClangAvailableForFiletypes( file_data[ 'filetypes' ] ):
         continue
-      contents = '\n'.join( buffer )
-      name = buffer.name
-      if not contents or not name:
+      contents = file_data[ 'contents' ]
+      if not contents or not filename:
         continue
-      self.contents_holder.append( contents )
-      self.filename_holder.append( name )
 
       unsaved_file = ycm_core.UnsavedFile()
-      unsaved_file.contents_ = self.contents_holder[ -1 ]
-      unsaved_file.length_ = len( self.contents_holder[ -1 ] )
-      unsaved_file.filename_ = self.filename_holder[ -1 ]
+      unsaved_file.contents_ = contents
+      unsaved_file.length_ = len( contents )
+      unsaved_file.filename_ = filename
 
       files.append( unsaved_file )
-
     return files
 
 
-  def CandidatesForQueryAsync( self, query, start_column ):
-    filename = vim.current.buffer.name
+  def CandidatesForQueryAsync( self, request_data ):
+    filename = request_data[ 'filepath' ]
 
     if not filename:
       return
 
     if self.completer.UpdatingTranslationUnit( filename ):
-      vimsupport.PostVimMessage( 'Still parsing file, no completions yet.' )
       self.completions_future = None
-      return
+      self._logger.info( PARSING_FILE_MESSAGE )
+      return server_responses.BuildDisplayMessageResponse(
+        PARSING_FILE_MESSAGE )
 
     flags = self.flags.FlagsForFile( filename )
     if not flags:
-      vimsupport.PostVimMessage( 'Still no compile flags, no completions yet.' )
       self.completions_future = None
-      return
+      self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
+      return server_responses.BuildDisplayMessageResponse(
+        NO_COMPILE_FLAGS_MESSAGE )
 
     # TODO: sanitize query, probably in C++ code
 
     files = ycm_core.UnsavedFileVec()
+    query = request_data[ 'query' ]
     if not query:
-      files = self.GetUnsavedFilesVector()
+      files = self.GetUnsavedFilesVector( request_data )
 
-    line, _ = vim.current.window.cursor
-    column = start_column + 1
+    line = request_data[ 'line_num' ] + 1
+    column = request_data[ 'start_column' ] + 1
     self.completions_future = (
       self.completer.CandidatesForQueryAndLocationInFileAsync(
         query,
@@ -123,10 +120,11 @@ class ClangCompleter( Completer ):
   def CandidatesFromStoredRequest( self ):
     if not self.completions_future:
       return []
-    results = [ CompletionDataToDict( x ) for x in
+    results = [ ConvertCompletionData( x ) for x in
                 self.completions_future.GetResults() ]
     if not results:
-      vimsupport.PostVimMessage( 'No completions found; errors in the file?' )
+      self._logger.warning( NO_COMPLETIONS_MESSAGE )
+      raise RuntimeError( NO_COMPLETIONS_MESSAGE )
     return results
 
 
@@ -137,37 +135,37 @@ class ClangCompleter( Completer ):
              'ClearCompilationFlagCache']
 
 
-  def OnUserCommand( self, arguments ):
+  def OnUserCommand( self, arguments, request_data ):
     if not arguments:
-      self.EchoUserCommandsHelpMessage()
-      return
+      raise ValueError( self.UserCommandsHelpMessage() )
 
     command = arguments[ 0 ]
     if command == 'GoToDefinition':
-      self._GoToDefinition()
+      self._GoToDefinition( request_data )
     elif command == 'GoToDeclaration':
-      self._GoToDeclaration()
+      self._GoToDeclaration( request_data )
     elif command == 'GoToDefinitionElseDeclaration':
-      self._GoToDefinitionElseDeclaration()
+      self._GoToDefinitionElseDeclaration( request_data )
     elif command == 'ClearCompilationFlagCache':
-      self._ClearCompilationFlagCache()
+      self._ClearCompilationFlagCache( request_data )
 
 
-  def _LocationForGoTo( self, goto_function ):
-    filename = vim.current.buffer.name
+  def _LocationForGoTo( self, goto_function, request_data ):
+    filename = request_data[ 'filepath' ]
     if not filename:
-      return None
+      self._logger.warning( INVALID_FILE_MESSAGE )
+      return server_responses.BuildDisplayMessageResponse(
+        INVALID_FILE_MESSAGE )
 
     flags = self.flags.FlagsForFile( filename )
     if not flags:
-      vimsupport.PostVimMessage( 'Still no compile flags, can\'t compile.' )
-      return None
+      self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
+      return server_responses.BuildDisplayMessageResponse(
+        NO_COMPILE_FLAGS_MESSAGE )
 
     files = self.GetUnsavedFilesVector()
-    line, column = vimsupport.CurrentLineAndColumn()
-    # Making the line & column 1-based instead of 0-based
-    line += 1
-    column += 1
+    line = request_data[ 'line_num' ] + 1
+    column = request_data[ 'start_column' ] + 1
     return getattr( self.completer, goto_function )(
         filename,
         line,
@@ -176,39 +174,37 @@ class ClangCompleter( Completer ):
         flags )
 
 
-  def _GoToDefinition( self ):
+  def _GoToDefinition( self, request_data ):
     location = self._LocationForGoTo( 'GetDefinitionLocation' )
     if not location or not location.IsValid():
-      vimsupport.PostVimMessage( 'Can\'t jump to definition.' )
-      return
+      raise RuntimeError( 'Can\'t jump to definition.' )
 
-    vimsupport.JumpToLocation( location.filename_,
-                               location.line_number_,
-                               location.column_number_ )
+    return server_responses.BuildGoToResponse( location.filename_,
+                                               location.line_number_,
+                                               location.column_number_ )
 
 
-  def _GoToDeclaration( self ):
+  def _GoToDeclaration( self, request_data ):
     location = self._LocationForGoTo( 'GetDeclarationLocation' )
     if not location or not location.IsValid():
-      vimsupport.PostVimMessage( 'Can\'t jump to declaration.' )
-      return
+      raise RuntimeError( 'Can\'t jump to declaration.' )
 
-    vimsupport.JumpToLocation( location.filename_,
-                               location.line_number_,
-                               location.column_number_ )
+    return server_responses.BuildGoToResponse( location.filename_,
+                                               location.line_number_,
+                                               location.column_number_ )
 
 
-  def _GoToDefinitionElseDeclaration( self ):
+  def _GoToDefinitionElseDeclaration( self, request_data ):
     location = self._LocationForGoTo( 'GetDefinitionLocation' )
     if not location or not location.IsValid():
       location = self._LocationForGoTo( 'GetDeclarationLocation' )
     if not location or not location.IsValid():
-      vimsupport.PostVimMessage( 'Can\'t jump to definition or declaration.' )
-      return
+      raise RuntimeError( 'Can\'t jump to definition or declaration.' )
+
+    return server_responses.BuildGoToResponse( location.filename_,
+                                               location.line_number_,
+                                               location.column_number_ )
 
-    vimsupport.JumpToLocation( location.filename_,
-                               location.line_number_,
-                               location.column_number_ )
 
 
   def _ClearCompilationFlagCache( self ):
@@ -216,14 +212,18 @@ class ClangCompleter( Completer ):
 
 
 
-  def OnFileReadyToParse( self ):
-    if vimsupport.NumLinesInBuffer( vim.current.buffer ) < 5:
+  def OnFileReadyToParse( self, request_data ):
+    filename = request_data[ 'filepath' ]
+    contents = request_data[ 'file_data' ][ filename ][ 'contents' ]
+    if contents.count( '\n' ) < MIN_LINES_IN_FILE_TO_PARSE:
       self.parse_future = None
-      return
+      self._logger.warning( FILE_TOO_SHORT_MESSAGE )
+      raise ValueError( FILE_TOO_SHORT_MESSAGE )
 
-    filename = vim.current.buffer.name
     if not filename:
-      return
+      self._logger.warning( INVALID_FILE_MESSAGE )
+      return server_responses.BuildDisplayMessageResponse(
+        INVALID_FILE_MESSAGE )
 
     if self.completer.UpdatingTranslationUnit( filename ):
       self.extra_parse_desired = True
@@ -232,11 +232,13 @@ class ClangCompleter( Completer ):
     flags = self.flags.FlagsForFile( filename )
     if not flags:
       self.parse_future = None
-      return
+      self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
+      return server_responses.BuildDisplayMessageResponse(
+        NO_COMPILE_FLAGS_MESSAGE )
 
     self.parse_future = self.completer.UpdateTranslationUnitAsync(
       filename,
-      self.GetUnsavedFilesVector(),
+      self.GetUnsavedFilesVector( request_data ),
       flags )
 
     self.extra_parse_desired = False
@@ -253,16 +255,18 @@ class ClangCompleter( Completer ):
     return self.parse_future.ResultsReady()
 
 
-  def GettingCompletions( self ):
-    return self.completer.UpdatingTranslationUnit( vim.current.buffer.name )
+  def GettingCompletions( self, request_data ):
+    return self.completer.UpdatingTranslationUnit( request_data[ 'filepath' ] )
 
 
-  def GetDiagnosticsForCurrentFile( self ):
+  def GetDiagnosticsForCurrentFile( self, request_data ):
+    filename = request_data[ 'filepath' ]
     if self.DiagnosticsForCurrentFileReady():
-      diagnostics = self.completer.DiagnosticsForFile( vim.current.buffer.name )
+      diagnostics = self.completer.DiagnosticsForFile( filename )
       self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
-      self.last_prepared_diagnostics = [ DiagnosticToDict( x ) for x in
-          diagnostics[ : self.max_diagnostics_to_display ] ]
+      self.last_prepared_diagnostics = [
+        server_responses.BuildDiagnosticData( x ) for x in
+        diagnostics[ : self.max_diagnostics_to_display ] ]
       self.parse_future = None
 
       if self.extra_parse_desired:
@@ -271,23 +275,19 @@ class ClangCompleter( Completer ):
     return self.last_prepared_diagnostics
 
 
-  def ShowDetailedDiagnostic( self ):
-    current_line, current_column = vimsupport.CurrentLineAndColumn()
-
-    # CurrentLineAndColumn() numbers are 0-based, clang numbers are 1-based
-    current_line += 1
-    current_column += 1
-
-    current_file = vim.current.buffer.name
+  def GetDetailedDiagnostic( self, request_data ):
+    current_line = request_data[ 'line_num' ] + 1
+    current_column = request_data[ 'column_num' ] + 1
+    current_file = request_data[ 'filepath' ]
 
     if not self.diagnostic_store:
-      vimsupport.PostVimMessage( "No diagnostic for current line!" )
-      return
+      return server_responses.BuildDisplayMessageResponse(
+        NO_DIAGNOSTIC_MESSAGE )
 
     diagnostics = self.diagnostic_store[ current_file ][ current_line ]
     if not diagnostics:
-      vimsupport.PostVimMessage( "No diagnostic for current line!" )
-      return
+      return server_responses.BuildDisplayMessageResponse(
+        NO_DIAGNOSTIC_MESSAGE )
 
     closest_diagnostic = None
     distance_to_closest_diagnostic = 999
@@ -298,50 +298,61 @@ class ClangCompleter( Completer ):
         distance_to_closest_diagnostic = distance
         closest_diagnostic = diagnostic
 
-    vimsupport.EchoText( closest_diagnostic.long_formatted_text_ )
+    return server_responses.BuildDisplayMessageResponse(
+      closest_diagnostic.long_formatted_text_ )
 
 
-  def ShouldUseNow( self, start_column, current_line ):
+  def ShouldUseNow( self, request_data ):
     # We don't want to use the Completer API cache, we use one in the C++ code.
-    return self.ShouldUseNowInner( start_column, current_line )
+    return self.ShouldUseNowInner( request_data )
 
 
-  def DebugInfo( self ):
-    filename = vim.current.buffer.name
+  def DebugInfo( self, request_data ):
+    filename = request_data[ 'filepath' ]
     if not filename:
       return ''
     flags = self.flags.FlagsForFile( filename ) or []
     source = extra_conf_store.ModuleFileForSourceFile( filename )
-    return 'Flags for {0} loaded from {1}:\n{2}'.format( filename,
-                                                         source,
-                                                         list( flags ) )
+    return server_responses.BuildDisplayMessageResponse(
+      'Flags for {0} loaded from {1}:\n{2}'.format( filename,
+                                                    source,
+                                                    list( flags ) ) )
 
 
 # TODO: make these functions module-local
-def CompletionDataToDict( completion_data ):
-  # see :h complete-items for a description of the dictionary fields
-  return {
-    'word' : completion_data.TextToInsertInBuffer(),
-    'abbr' : completion_data.MainCompletionText(),
-    'menu' : completion_data.ExtraMenuInfo(),
-    'kind' : completion_data.kind_,
-    'info' : completion_data.DetailedInfoForPreviewWindow(),
-    'dup'  : 1,
-  }
-
-
-def DiagnosticToDict( diagnostic ):
-  # see :h getqflist for a description of the dictionary fields
-  return {
-    # TODO: wrap the bufnr generation into a function
-    'bufnr' : int( vim.eval( "bufnr('{0}', 1)".format(
-      diagnostic.filename_ ) ) ),
-    'lnum'  : diagnostic.line_number_,
-    'col'   : diagnostic.column_number_,
-    'text'  : diagnostic.text_,
-    'type'  : diagnostic.kind_,
-    'valid' : 1
-  }
+# def CompletionDataToDict( completion_data ):
+#   # see :h complete-items for a description of the dictionary fields
+#   return {
+#     'word' : completion_data.TextToInsertInBuffer(),
+#     'abbr' : completion_data.MainCompletionText(),
+#     'menu' : completion_data.ExtraMenuInfo(),
+#     'kind' : completion_data.kind_,
+#     'info' : completion_data.DetailedInfoForPreviewWindow(),
+#     'dup'  : 1,
+#   }
+
+
+# def DiagnosticToDict( diagnostic ):
+#   # see :h getqflist for a description of the dictionary fields
+#   return {
+#     # TODO: wrap the bufnr generation into a function
+#     'bufnr' : int( vim.eval( "bufnr('{0}', 1)".format(
+#       diagnostic.filename_ ) ) ),
+#     'lnum'  : diagnostic.line_number_,
+#     'col'   : diagnostic.column_number_,
+#     'text'  : diagnostic.text_,
+#     'type'  : diagnostic.kind_,
+#     'valid' : 1
+#   }
+
+
+def ConvertCompletionData( completion_data ):
+  return server_responses.BuildCompletionData(
+    insertion_text = completion_data.TextToInsertInBuffer(),
+    menu_text = completion_data.MainCompletionText(),
+    extra_menu_info = completion_data.ExtraMenuInfo(),
+    kind = completion_data.kind_,
+    detailed_info = completion_data.DetailedInfoForPreviewWindow() )
 
 
 def DiagnosticsToDiagStructure( diagnostics ):
@@ -352,12 +363,9 @@ def DiagnosticsToDiagStructure( diagnostics ):
   return structure
 
 
-def ClangAvailableForBuffer( buffer_object ):
-  filetypes = vimsupport.FiletypesForBuffer( buffer_object )
+def ClangAvailableForFiletypes( filetypes ):
   return any( [ filetype in CLANG_FILETYPES for filetype in filetypes ] )
 
 
-def InCFamilyFile():
-  return any( [ filetype in CLANG_FILETYPES for filetype in
-                vimsupport.CurrentFiletypes() ] )
-
+def InCFamilyFile( filetypes ):
+  return ClangAvailableForFiletypes( filetypes )

+ 3 - 8
python/ycm/completers/cpp/flags.py

@@ -19,12 +19,11 @@
 
 import ycm_core
 import os
-from ycm import vimsupport
 from ycm import extra_conf_store
 
-NO_EXTRA_CONF_FILENAME_MESSAGE = ('No {0} file detected, so no compile flags '
+NO_EXTRA_CONF_FILENAME_MESSAGE = ( 'No {0} file detected, so no compile flags '
   'are available. Thus no semantic support for C/C++/ObjC/ObjC++. Go READ THE '
-  'DOCS *NOW*, DON\'T file a bug report.').format(
+  'DOCS *NOW*, DON\'T file a bug report.' ).format(
     extra_conf_store.YCM_EXTRA_CONF_FILENAME )
 
 
@@ -37,7 +36,6 @@ class Flags( object ):
     # It's caches all the way down...
     self.flags_for_file = {}
     self.special_clang_flags = _SpecialClangIncludes()
-    self.no_extra_conf_file_warning_posted = False
 
 
   def FlagsForFile( self, filename, add_special_clang_flags = True ):
@@ -46,10 +44,7 @@ class Flags( object ):
     except KeyError:
       module = extra_conf_store.ModuleForSourceFile( filename )
       if not module:
-        if not self.no_extra_conf_file_warning_posted:
-          vimsupport.PostVimMessage( NO_EXTRA_CONF_FILENAME_MESSAGE )
-          self.no_extra_conf_file_warning_posted = True
-        return None
+        raise RuntimeError( NO_EXTRA_CONF_FILENAME_MESSAGE )
 
       results = module.FlagsForFile( filename )
 

+ 48 - 55
python/ycm/completers/cs/cs_completer.py

@@ -18,18 +18,18 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
-import vim
 import os
 from sys import platform
 import glob
 from ycm.completers.threaded_completer import ThreadedCompleter
-from ycm import vimsupport
+from ycm import server_responses
 import urllib2
 import urllib
 import urlparse
 import json
 import subprocess
 import tempfile
+import logging
 
 
 SERVER_NOT_FOUND_MSG = ( 'OmniSharp server binary not found at {0}. ' +
@@ -44,9 +44,10 @@ class CsharpCompleter( ThreadedCompleter ):
   def __init__( self, user_options ):
     super( CsharpCompleter, self ).__init__( user_options )
     self._omnisharp_port = None
+    self._logger = logging.getLogger(__name__)
 
-    if self.user_options[ 'auto_start_csharp_server' ]:
-      self._StartServer()
+    # if self.user_options[ 'auto_start_csharp_server' ]:
+    #   self._StartServer()
 
 
   def OnVimLeave( self ):
@@ -60,11 +61,12 @@ class CsharpCompleter( ThreadedCompleter ):
     return [ 'cs' ]
 
 
-  def ComputeCandidates( self, unused_query, unused_start_column ):
-    return [ { 'word': str( completion[ 'CompletionText' ] ),
-               'menu': str( completion[ 'DisplayText' ] ),
-               'info': str( completion[ 'Description' ] ) }
-             for completion in self._GetCompletions() ]
+  def ComputeCandidates( self, request_data ):
+    return [ server_responses.BuildCompletionData(
+                completion[ 'CompletionText' ],
+                completion[ 'DisplayText' ],
+                completion[ 'Description' ] )
+             for completion in self._GetCompletions( request_data ) ]
 
 
   def DefinedSubcommands( self ):
@@ -76,24 +78,23 @@ class CsharpCompleter( ThreadedCompleter ):
              'GoToDefinitionElseDeclaration' ]
 
 
-  def OnUserCommand( self, arguments ):
+  def OnUserCommand( self, arguments, request_data ):
     if not arguments:
-      self.EchoUserCommandsHelpMessage()
-      return
+      raise ValueError( self.UserCommandsHelpMessage() )
 
     command = arguments[ 0 ]
     if command == 'StartServer':
-      self._StartServer()
+      self._StartServer( request_data )
     elif command == 'StopServer':
       self._StopServer()
     elif command == 'RestartServer':
       if self._ServerIsRunning():
         self._StopServer()
-      self._StartServer()
+      self._StartServer( request_data )
     elif command in [ 'GoToDefinition',
                       'GoToDeclaration',
                       'GoToDefinitionElseDeclaration' ]:
-      self._GoToDefinition()
+      return self._GoToDefinition( request_data )
 
 
   def DebugInfo( self ):
@@ -104,35 +105,27 @@ class CsharpCompleter( ThreadedCompleter ):
       return 'Server is not running'
 
 
-  def _StartServer( self ):
+  def _StartServer( self, request_data ):
     """ Start the OmniSharp server """
     self._omnisharp_port = self._FindFreePort()
-    solutionfiles, folder = _FindSolutionFiles()
+    solutionfiles, folder = _FindSolutionFiles( request_data[ 'filepath' ] )
 
     if len( solutionfiles ) == 0:
-      vimsupport.PostVimMessage(
-              'Error starting OmniSharp server: no solutionfile found' )
-      return
+      raise RuntimeError(
+        'Error starting OmniSharp server: no solutionfile found' )
     elif len( solutionfiles ) == 1:
       solutionfile = solutionfiles[ 0 ]
     else:
-      choice = vimsupport.PresentDialog(
-              'Which solutionfile should be loaded?',
-              [ str( i ) + " " + solution for i, solution in
-                enumerate( solutionfiles ) ] )
-      if choice == -1:
-        vimsupport.PostVimMessage( 'OmniSharp not started' )
-        return
-      else:
-        solutionfile = solutionfiles[ choice ]
+      raise RuntimeError(
+        'Found multiple solution files instead of one!\n{}'.format(
+          solutionfiles ) )
 
     omnisharp = os.path.join(
       os.path.abspath( os.path.dirname( __file__ ) ),
       'OmniSharpServer/OmniSharp/bin/Debug/OmniSharp.exe' )
 
     if not os.path.isfile( omnisharp ):
-      vimsupport.PostVimMessage( SERVER_NOT_FOUND_MSG.format( omnisharp ) )
-      return
+      raise RuntimeError( SERVER_NOT_FOUND_MSG.format( omnisharp ) )
 
     if not platform.startswith( 'win' ):
       omnisharp = 'mono ' + omnisharp
@@ -154,40 +147,44 @@ class CsharpCompleter( ThreadedCompleter ):
       with open( self._filename_stdout, 'w' ) as fstdout:
         subprocess.Popen( command, stdout=fstdout, stderr=fstderr, shell=True )
 
-    vimsupport.PostVimMessage( 'Starting OmniSharp server' )
+    self._logger.info( 'Starting OmniSharp server' )
 
 
   def _StopServer( self ):
     """ Stop the OmniSharp server """
     self._GetResponse( '/stopserver' )
     self._omnisharp_port = None
-    vimsupport.PostVimMessage( 'Stopping OmniSharp server' )
+    self._logger.info( 'Stopping OmniSharp server' )
 
 
-  def _GetCompletions( self ):
+  def _GetCompletions( self, request_data ):
     """ Ask server for completions """
-    completions = self._GetResponse( '/autocomplete', self._DefaultParameters() )
+    completions = self._GetResponse( '/autocomplete',
+                                     self._DefaultParameters( request_data ) )
     return completions if completions != None else []
 
 
-  def _GoToDefinition( self ):
+  def _GoToDefinition( self, request_data ):
     """ Jump to definition of identifier under cursor """
-    definition = self._GetResponse( '/gotodefinition', self._DefaultParameters() )
+    definition = self._GetResponse( '/gotodefinition',
+                                    self._DefaultParameters( request_data ) )
     if definition[ 'FileName' ] != None:
-      vimsupport.JumpToLocation( definition[ 'FileName' ],
-                                 definition[ 'Line' ],
-                                 definition[ 'Column' ] )
+      return server_responses.BuildGoToResponse( definition[ 'FileName' ],
+                                                 definition[ 'Line' ],
+                                                 definition[ 'Column' ] )
     else:
-      vimsupport.PostVimMessage( 'Can\'t jump to definition' )
+      raise RuntimeError( 'Can\'t jump to definition' )
 
 
-  def _DefaultParameters( self ):
+  def _DefaultParameters( self, request_data ):
     """ Some very common request parameters """
-    line, column = vimsupport.CurrentLineAndColumn()
     parameters = {}
-    parameters[ 'line' ], parameters[ 'column' ] = line + 1, column + 1
-    parameters[ 'buffer' ] = '\n'.join( vim.current.buffer )
-    parameters[ 'filename' ] = vim.current.buffer.name
+    parameters[ 'line' ] = request_data[ 'line_num' ] + 1
+    parameters[ 'column' ] = request_data[ 'column_num' ] + 1
+    filepath = request_data[ 'filepath' ]
+    parameters[ 'buffer' ] = request_data[ 'file_data' ][ filepath ][
+      'contents' ]
+    parameters[ 'filename' ] = filepath
     return parameters
 
 
@@ -215,20 +212,16 @@ class CsharpCompleter( ThreadedCompleter ):
 
   def _GetResponse( self, endPoint, parameters={}, silent=False, port=None ):
     """ Handle communication with server """
+    # TODO: Replace usage of urllib with Requests
     target = urlparse.urljoin( self._PortToHost( port ), endPoint )
     parameters = urllib.urlencode( parameters )
-    try:
-      response = urllib2.urlopen( target, parameters )
-      return json.loads( response.read() )
-    except Exception:
-      # TODO: Add logging for this case. We can't post a Vim message because Vim
-      # crashes when that's done from a no-GUI thread.
-      return None
+    response = urllib2.urlopen( target, parameters )
+    return json.loads( response.read() )
 
 
-def _FindSolutionFiles():
+def _FindSolutionFiles( filepath ):
   """ Find solution files by searching upwards in the file tree """
-  folder = os.path.dirname( vim.current.buffer.name )
+  folder = os.path.dirname( filepath )
   solutionfiles = glob.glob1( folder, '*.sln' )
   while not solutionfiles:
     lastfolder = folder

+ 32 - 19
python/ycm/completers/general/filename_completer.py

@@ -16,13 +16,13 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
-import vim
 import os
 import re
 
 from ycm.completers.threaded_completer import ThreadedCompleter
 from ycm.completers.cpp.clang_completer import InCFamilyFile
 from ycm.completers.cpp.flags import Flags
+from ycm import server_responses
 
 class FilenameCompleter( ThreadedCompleter ):
   """
@@ -52,23 +52,32 @@ class FilenameCompleter( ThreadedCompleter ):
     self._include_regex = re.compile( include_regex_common )
 
 
-  def AtIncludeStatementStart( self, start_column ):
-    return ( InCFamilyFile() and
+  def AtIncludeStatementStart( self, request_data ):
+    start_column = request_data[ 'start_column' ]
+    current_line = request_data[ 'line_value' ]
+    filepath = request_data[ 'filepath' ]
+    filetypes = request_data[ 'file_data' ][ filepath ][ 'filetypes' ]
+    return ( InCFamilyFile( filetypes ) and
              self._include_start_regex.match(
-               vim.current.line[ :start_column ] ) )
+               current_line[ :start_column ] ) )
 
 
-  def ShouldUseNowInner( self, start_column, current_line ):
+  def ShouldUseNowInner( self, request_data ):
+    start_column = request_data[ 'start_column' ]
+    current_line = request_data[ 'line_value' ]
     return ( start_column and ( current_line[ start_column - 1 ] == '/' or
-             self.AtIncludeStatementStart( start_column ) ) )
+             self.AtIncludeStatementStart( request_data ) ) )
 
 
   def SupportedFiletypes( self ):
     return []
 
 
-  def ComputeCandidates( self, unused_query, start_column ):
-    line = vim.current.line[ :start_column ]
+  def ComputeCandidates( self, request_data ):
+    current_line = request_data[ 'line_value' ]
+    start_column = request_data[ 'start_column' ]
+    filepath = request_data[ 'filepath' ]
+    line = current_line[ :start_column ]
 
     if InCFamilyFile():
       include_match = self._include_regex.search( line )
@@ -78,22 +87,26 @@ class FilenameCompleter( ThreadedCompleter ):
         # http://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html
         include_current_file_dir = '<' not in include_match.group()
         return _GenerateCandidatesForPaths(
-          self.GetPathsIncludeCase( path_dir, include_current_file_dir ) )
+          self.GetPathsIncludeCase( path_dir,
+                                    include_current_file_dir,
+                                    filepath ) )
 
     path_match = self._path_regex.search( line )
     path_dir = os.path.expanduser( path_match.group() ) if path_match else ''
 
     return _GenerateCandidatesForPaths(
-      _GetPathsStandardCase( path_dir, self.user_options[
-        'filepath_completion_use_working_dir' ] ) )
+      _GetPathsStandardCase(
+        path_dir,
+        self.user_options[ 'filepath_completion_use_working_dir' ],
+        filepath ) )
 
 
-  def GetPathsIncludeCase( self, path_dir, include_current_file_dir ):
+  def GetPathsIncludeCase( self, path_dir, include_current_file_dir, filepath ):
     paths = []
-    include_paths = self._flags.UserIncludePaths( vim.current.buffer.name )
+    include_paths = self._flags.UserIncludePaths( filepath )
 
     if include_current_file_dir:
-      include_paths.append( os.path.dirname( vim.current.buffer.name ) )
+      include_paths.append( os.path.dirname( filepath ) )
 
     for include_path in include_paths:
       try:
@@ -107,9 +120,9 @@ class FilenameCompleter( ThreadedCompleter ):
     return sorted( set( paths ) )
 
 
-def _GetPathsStandardCase( path_dir, use_working_dir ):
+def _GetPathsStandardCase( path_dir, use_working_dir, filepath ):
   if not use_working_dir and not path_dir.startswith( '/' ):
-    path_dir = os.path.join( os.path.dirname( vim.current.buffer.name ),
+    path_dir = os.path.join( os.path.dirname( filepath ),
                              path_dir )
 
   try:
@@ -132,8 +145,8 @@ def _GenerateCandidatesForPaths( absolute_paths ):
     seen_basenames.add( basename )
 
     is_dir = os.path.isdir( absolute_path )
-    completion_dicts.append( { 'word': basename,
-                               'dup': 1,
-                               'menu': '[Dir]' if is_dir else '[File]' } )
+    completion_dicts.append(
+      server_responses.BuildCompletionData( basename,
+                                            '[Dir]' if is_dir else '[File]' ) )
 
   return completion_dicts

+ 7 - 8
python/ycm/completers/general/general_completer_store.py

@@ -58,18 +58,17 @@ class GeneralCompleterStore( Completer ):
     return set()
 
 
-  def ShouldUseNow( self, start_column, current_line ):
+  def ShouldUseNow( self, request_data ):
     self._current_query_completers = []
 
-    if self._filename_completer.ShouldUseNow( start_column, current_line ):
+    if self._filename_completer.ShouldUseNow( request_data ):
       self._current_query_completers = [ self._filename_completer ]
       return True
 
     should_use_now = False
 
     for completer in self._non_filename_completers:
-      should_use_this_completer = completer.ShouldUseNow( start_column,
-                                                          current_line )
+      should_use_this_completer = completer.ShouldUseNow( request_data )
       should_use_now = should_use_now or should_use_this_completer
 
       if should_use_this_completer:
@@ -78,9 +77,9 @@ class GeneralCompleterStore( Completer ):
     return should_use_now
 
 
-  def CandidatesForQueryAsync( self, query, start_column ):
+  def CandidatesForQueryAsync( self, request_data ):
     for completer in self._current_query_completers:
-      completer.CandidatesForQueryAsync( query, start_column )
+      completer.CandidatesForQueryAsync( request_data )
 
 
   def AsyncCandidateRequestReady( self ):
@@ -96,9 +95,9 @@ class GeneralCompleterStore( Completer ):
     return candidates
 
 
-  def OnFileReadyToParse( self ):
+  def OnFileReadyToParse( self, request_data ):
     for completer in self._all_completers:
-      completer.OnFileReadyToParse()
+      completer.OnFileReadyToParse( request_data )
 
 
   def OnBufferVisit( self ):

+ 10 - 8
python/ycm/completers/general/ultisnips_completer.py

@@ -20,6 +20,7 @@
 
 from ycm.completers.general_completer import GeneralCompleter
 from UltiSnips import UltiSnips_Manager
+from ycm import server_responses
 
 
 class UltiSnipsCompleter( GeneralCompleter ):
@@ -33,13 +34,13 @@ class UltiSnipsCompleter( GeneralCompleter ):
     self._filtered_candidates = None
 
 
-  def ShouldUseNowInner( self, start_column, unused_current_line ):
-    return self.QueryLengthAboveMinThreshold( start_column )
+  def ShouldUseNowInner( self, request_data ):
+    return self.QueryLengthAboveMinThreshold( request_data )
 
 
-  def CandidatesForQueryAsync( self, query, unused_start_column ):
-    self._filtered_candidates = self.FilterAndSortCandidates( self._candidates,
-                                                              query )
+  def CandidatesForQueryAsync( self, request_data ):
+    self._filtered_candidates = self.FilterAndSortCandidates(
+      self._candidates, request_data[ 'query' ] )
 
 
   def AsyncCandidateRequestReady( self ):
@@ -61,8 +62,9 @@ def _GetCandidates():
     # UltiSnips_Manager._snips() returns a class instance where:
     # class.trigger - name of snippet trigger word ( e.g. defn or testcase )
     # class.description - description of the snippet
-    return  [ { 'word': str( snip.trigger ),
-                'menu': str( '<snip> ' + snip.description.encode('utf-8') ) }
-              for snip in rawsnips ]
+    return [ server_responses.BuildCompletionData(
+              str( snip.trigger ),
+              str( '<snip> ' + snip.description.encode( 'utf-8' ) ) )
+            for snip in rawsnips ]
   except:
     return []

+ 49 - 55
python/ycm/completers/python/jedi_completer.py

@@ -19,9 +19,8 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
-import vim
 from ycm.completers.threaded_completer import ThreadedCompleter
-from ycm import vimsupport
+from ycm import server_responses
 
 import sys
 from os.path import join, abspath, dirname
@@ -33,7 +32,7 @@ sys.path.insert( 0, join( abspath( dirname( __file__ ) ), 'jedi' ) )
 try:
   import jedi
 except ImportError:
-  vimsupport.PostVimMessage(
+  raise ImportError(
     'Error importing jedi. Make sure the jedi submodule has been checked out. '
     'In the YouCompleteMe folder, run "git submodule update --init --recursive"')
 sys.path.pop( 0 )
@@ -54,113 +53,108 @@ class JediCompleter( ThreadedCompleter ):
     return [ 'python' ]
 
 
-  def _GetJediScript( self ):
-      contents = '\n'.join( vim.current.buffer )
-      line, column = vimsupport.CurrentLineAndColumn()
+  def _GetJediScript( self, request_data ):
+      filename = request_data[ 'filepath' ]
+      contents = request_data[ 'file_data' ][ filename ][ 'contents' ]
       # Jedi expects lines to start at 1, not 0
-      line += 1
-      filename = vim.current.buffer.name
+      line = request_data[ 'line_num' ] + 1
+      column = request_data[ 'column_num' ]
+      print contents
 
       return jedi.Script( contents, line, column, filename )
 
 
-  def ComputeCandidates( self, unused_query, unused_start_column ):
-    script = self._GetJediScript()
-
-    return [ { 'word': str( completion.name ),
-               'menu': str( completion.description ),
-               'info': str( completion.doc ) }
+  def ComputeCandidates( self, request_data ):
+    script = self._GetJediScript( request_data )
+    return [ server_responses.BuildCompletionData( completion.name,
+                                                   completion.description,
+                                                   completion.doc )
              for completion in script.completions() ]
 
-
   def DefinedSubcommands( self ):
     return [ "GoToDefinition",
              "GoToDeclaration",
              "GoToDefinitionElseDeclaration" ]
 
 
-  def OnUserCommand( self, arguments ):
+  def OnUserCommand( self, arguments, request_data ):
     if not arguments:
-      self.EchoUserCommandsHelpMessage()
-      return
+      raise ValueError( self.UserCommandsHelpMessage() )
 
     command = arguments[ 0 ]
     if command == 'GoToDefinition':
-      self._GoToDefinition()
+      return self._GoToDefinition( request_data )
     elif command == 'GoToDeclaration':
-      self._GoToDeclaration()
+      return self._GoToDeclaration( request_data )
     elif command == 'GoToDefinitionElseDeclaration':
-      self._GoToDefinitionElseDeclaration()
+      return self._GoToDefinitionElseDeclaration( request_data )
 
 
-  def _GoToDefinition( self ):
-    definitions = self._GetDefinitionsList()
+  def _GoToDefinition( self, request_data ):
+    definitions = self._GetDefinitionsList( request_data )
     if definitions:
-      self._JumpToLocation( definitions )
+      return self._BuildGoToResponse( definitions )
     else:
-      vimsupport.PostVimMessage( 'Can\'t jump to definition.' )
+      raise RuntimeError( 'Can\'t jump to definition.' )
 
 
-  def _GoToDeclaration( self ):
-    definitions = self._GetDefinitionsList( declaration = True )
+  def _GoToDeclaration( self, request_data ):
+    definitions = self._GetDefinitionsList( request_data, declaration = True )
     if definitions:
-      self._JumpToLocation( definitions )
+      return self._BuildGoToResponse( definitions )
     else:
-      vimsupport.PostVimMessage( 'Can\'t jump to declaration.' )
+      raise RuntimeError( 'Can\'t jump to declaration.' )
 
 
-  def _GoToDefinitionElseDeclaration( self ):
+  def _GoToDefinitionElseDeclaration( self, request_data ):
     definitions = self._GetDefinitionsList() or \
-        self._GetDefinitionsList( declaration = True )
+        self._GetDefinitionsList( request_data, declaration = True )
     if definitions:
-      self._JumpToLocation( definitions )
+      return self._BuildGoToResponse( definitions )
     else:
-      vimsupport.PostVimMessage( 'Can\'t jump to definition or declaration.' )
+      raise RuntimeError( 'Can\'t jump to definition or declaration.' )
 
 
-  def _GetDefinitionsList( self, declaration = False ):
+  def _GetDefinitionsList( self, request_data, declaration = False ):
     definitions = []
-    script = self._GetJediScript()
+    script = self._GetJediScript( request_data )
     try:
       if declaration:
         definitions = script.goto_definitions()
       else:
         definitions = script.goto_assignments()
     except jedi.NotFoundError:
-      vimsupport.PostVimMessage(
-                  "Cannot follow nothing. Put your cursor on a valid name." )
-    except Exception as e:
-      vimsupport.PostVimMessage(
-                  "Caught exception, aborting. Full error: " + str( e ) )
+      raise RuntimeError(
+                  'Cannot follow nothing. Put your cursor on a valid name.' )
 
     return definitions
 
 
-  def _JumpToLocation( self, definition_list ):
+  def _BuildGoToResponse( self, definition_list ):
     if len( definition_list ) == 1:
       definition = definition_list[ 0 ]
       if definition.in_builtin_module():
         if definition.is_keyword:
-          vimsupport.PostVimMessage(
-                  "Cannot get the definition of Python keywords." )
+          raise RuntimeError(
+                  'Cannot get the definition of Python keywords.' )
         else:
-          vimsupport.PostVimMessage( "Builtin modules cannot be displayed." )
+          raise RuntimeError( 'Builtin modules cannot be displayed.' )
       else:
-        vimsupport.JumpToLocation( definition.module_path,
-                                   definition.line,
-                                   definition.column + 1 )
+        return server_responses.BuildGoToResponse( definition.module_path,
+                                                   definition.line -1,
+                                                   definition.column )
     else:
       # multiple definitions
       defs = []
       for definition in definition_list:
         if definition.in_builtin_module():
-          defs.append( {'text': 'Builtin ' + \
-                       definition.description.encode( 'utf-8' ) } )
+          defs.append( server_responses.BuildDescriptionOnlyGoToResponse(
+                       'Builting ' + definition.description ) )
         else:
-          defs.append( {'filename': definition.module_path.encode( 'utf-8' ),
-                        'lnum': definition.line,
-                        'col': definition.column + 1,
-                        'text': definition.description.encode( 'utf-8' ) } )
+          defs.append(
+            server_responses.BuildGoToResponse( definition.module_path,
+                                                definition.line -1,
+                                                definition.column,
+                                                definition.description ) )
+      return defs
 
-      vim.eval( 'setqflist( %s )' % repr( defs ) )
-      vim.eval( 'youcompleteme#OpenGoToList()' )

+ 4 - 6
python/ycm/completers/threaded_completer.py

@@ -52,11 +52,10 @@ class ThreadedCompleter( Completer ):
     self._completion_thread.start()
 
 
-  def CandidatesForQueryAsyncInner( self, query, start_column ):
+  def CandidatesForQueryAsyncInner( self, request_data ):
     self._candidates = None
     self._candidates_ready.clear()
-    self._query = query
-    self._start_column = start_column
+    self._request_data = request_data
     self._query_ready.set()
 
 
@@ -69,7 +68,7 @@ class ThreadedCompleter( Completer ):
 
 
   @abc.abstractmethod
-  def ComputeCandidates( self, query, start_column ):
+  def ComputeCandidates( self, request_data ):
     """This function should compute the candidates to show to the user.
     The return value should be of the same type as that for
     CandidatesFromStoredRequest()."""
@@ -80,8 +79,7 @@ class ThreadedCompleter( Completer ):
     while True:
       try:
         WaitAndClearIfSet( self._query_ready )
-        self._candidates = self.ComputeCandidates( self._query,
-                                                   self._start_column )
+        self._candidates = self.ComputeCandidates( self._request_data )
       except:
         self._query_ready.clear()
         self._candidates = []

+ 1 - 0
python/ycm/extra_conf_store.py

@@ -68,6 +68,7 @@ def CallExtraConfVimCloseIfExists():
 def _CallExtraConfMethod( function_name ):
   vim_current_working_directory = vim.eval( 'getcwd()' )
   path_to_dummy = os.path.join( vim_current_working_directory, 'DUMMY_FILE' )
+  # The dummy file in the Vim CWD ensures we find the correct extra conf file
   module = ModuleForSourceFile( path_to_dummy )
   if not module or not hasattr( module, function_name ):
     return

+ 76 - 0
python/ycm/server_responses.py

@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2013  Strahinja Val Markovic <val@markovic.io>
+#
+# 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/>.
+
+
+def BuildGoToResponse( filepath, line_num, column_num, description = None ):
+  response = {
+    'filepath': filepath,
+    'line_num': line_num,
+    'column_num': column_num
+  }
+
+  if description:
+    response[ 'description' ] = description
+  return response
+
+
+def BuildDescriptionOnlyGoToResponse( text ):
+  return {
+    'description': text,
+  }
+
+
+def BuildDisplayMessageResponse( text ):
+  return {
+    'message': text
+  }
+
+
+def BuildCompletionData( insertion_text,
+                         extra_menu_info = None,
+                         detailed_info = None,
+                         menu_text = None,
+                         kind = None ):
+  completion_data = {
+    'insertion_text': insertion_text
+  }
+
+  if extra_menu_info:
+    completion_data[ 'extra_menu_info' ] = extra_menu_info
+  if menu_text:
+    completion_data[ 'menu_text' ] = menu_text
+  if detailed_info:
+    completion_data[ 'detailed_info' ] = detailed_info
+  if kind:
+    completion_data[ 'kind' ] = kind
+  return completion_data
+
+
+def BuildDiagnosticData( filepath,
+                         line_num,
+                         column_num,
+                         text,
+                         kind ):
+  return {
+    'filepath': filepath,
+    'line_num': line_num,
+    'column_num': column_num,
+    'text': text,
+    'kind': kind
+  }

+ 30 - 11
python/ycm/vimsupport.py

@@ -46,12 +46,33 @@ def TextAfterCursor():
   return vim.current.line[ CurrentColumn(): ]
 
 
-def GetUnsavedBuffers():
-  def BufferModified( buffer_number ):
-    to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number )
-    return GetBoolValue( to_eval )
+# Note the difference between buffer OPTIONS and VARIABLES; the two are not
+# the same.
+def GetBufferOption( buffer_object, option ):
+  # The 'options' property is only available in recent (7.4+) Vim builds
+  if hasattr( buffer_object, 'options' ):
+    return buffer_object.options[ option ]
 
-  return ( x for x in vim.buffers if BufferModified( x.number ) )
+  to_eval = 'getbufvar({0}, "&{1}")'.format( buffer.number, option )
+  return GetVariableValue( to_eval )
+
+
+def GetUnsavedAndCurrentBufferData():
+  def BufferModified( buffer_object ):
+    return bool( int( GetBufferOption( buffer_object, 'mod' ) ) )
+
+  buffers_data = {}
+  for buffer_object in vim.buffers:
+    if not ( BufferModified( buffer_object ) or
+             buffer_object == vim.current.buffer ):
+      continue
+
+    buffers_data[ buffer_object.name ] = {
+      'contents': '\n'.join( buffer_object ),
+      'filetypes': FiletypesForBuffer( buffer_object )
+    }
+
+  return buffers_data
 
 
 # Both |line| and |column| need to be 1-based
@@ -73,9 +94,9 @@ def JumpToLocation( filename, line, column ):
   vim.command( 'normal! zz' )
 
 
-def NumLinesInBuffer( buffer ):
+def NumLinesInBuffer( buffer_object ):
   # This is actually less than obvious, that's why it's wrapped in a function
-  return len( buffer )
+  return len( buffer_object )
 
 
 def PostVimMessage( message ):
@@ -128,15 +149,13 @@ def EscapeForVim( text ):
 
 
 def CurrentFiletypes():
-  ft_string = vim.eval( "&filetype" )
-  return ft_string.split( '.' )
+  return vim.eval( "&filetype" ).split( '.' )
 
 
 def FiletypesForBuffer( buffer_object ):
   # NOTE: Getting &ft for other buffers only works when the buffer has been
   # visited by the user at least once, which is true for modified buffers
-  ft_string = vim.eval( 'getbufvar({0}, "&ft")'.format( buffer_object.number ) )
-  return ft_string.split( '.' )
+  return GetBufferOption( buffer_object, 'ft' ).split( '.' )
 
 
 def GetVariableValue( variable ):

+ 122 - 15
python/ycm/youcompleteme.py

@@ -19,20 +19,25 @@
 
 import imp
 import os
+import time
 import vim
 import ycm_core
+import logging
+import tempfile
 from ycm import vimsupport
 from ycm import base
 from ycm.completers.all.omni_completer import OmniCompleter
 from ycm.completers.general.general_completer_store import GeneralCompleterStore
 
 
+# TODO: Put the Request classes in separate files
 class CompletionRequest( object ):
   def __init__( self, ycm_state ):
     self._completion_start_column = base.CompletionStartColumn()
     self._ycm_state = ycm_state
+    self._request_data = _BuildRequestData( self._completion_start_column )
     self._do_filetype_completion = self._ycm_state.ShouldUseFiletypeCompleter(
-      self._completion_start_column )
+      self._request_data )
     self._completer = ( self._ycm_state.GetFiletypeCompleter() if
                         self._do_filetype_completion else
                         self._ycm_state.GetGeneralCompleter() )
@@ -40,8 +45,7 @@ class CompletionRequest( object ):
 
   def ShouldComplete( self ):
     return ( self._do_filetype_completion or
-             self._ycm_state.ShouldUseGeneralCompleter(
-               self._completion_start_column ) )
+             self._ycm_state.ShouldUseGeneralCompleter( self._request_data ) )
 
 
   def CompletionStartColumn( self ):
@@ -49,20 +53,80 @@ class CompletionRequest( object ):
 
 
   def Start( self, query ):
-    self._completer.CandidatesForQueryAsync( query,
-                                             self._completion_start_column )
+    self._request_data[ 'query' ] = query
+    self._completer.CandidatesForQueryAsync( self._request_data )
 
   def Done( self ):
     return self._completer.AsyncCandidateRequestReady()
 
 
   def Results( self ):
-    return self._completer.CandidatesFromStoredRequest()
+    try:
+      return [ _ConvertCompletionDataToVimData( x )
+              for x in self._completer.CandidatesFromStoredRequest() ]
+    except Exception as e:
+      vimsupport.PostVimMessage( str( e ) )
+      return []
+
+
+
+class CommandRequest( object ):
+  class ServerResponse( object ):
+    def __init__( self ):
+      pass
+
+    def Valid( self ):
+      return True
+
+  def __init__( self, ycm_state, arguments, completer_target = None ):
+    if not completer_target:
+      completer_target = 'filetpe_default'
+
+    if completer_target == 'omni':
+      self._completer = ycm_state.GetOmniCompleter()
+    elif completer_target == 'identifier':
+      self._completer = ycm_state.GetGeneralCompleter()
+    else:
+      self._completer = ycm_state.GetFiletypeCompleter()
+    self._arguments = arguments
+
+
+  def Start( self ):
+    self._completer.OnUserCommand( self._arguments,
+                                   _BuildRequestData() )
+
+  def Done( self ):
+    return True
+
+
+  def Response( self ):
+    # TODO: Call vimsupport.JumpToLocation if the user called a GoTo command...
+    # we may want to have specific subclasses of CommandRequest so that a
+    # GoToRequest knows it needs to jump after the data comes back.
+    #
+    # Also need to run the following on GoTo data:
+    # CAREFUL about line/column number 0-based/1-based confusion!
+    #
+    # defs = []
+    # defs.append( {'filename': definition.module_path.encode( 'utf-8' ),
+    #               'lnum': definition.line,
+    #               'col': definition.column + 1,
+    #               'text': definition.description.encode( 'utf-8' ) } )
+    # vim.eval( 'setqflist( %s )' % repr( defs ) )
+    # vim.eval( 'youcompleteme#OpenGoToList()' )
+    return self.ServerResponse()
 
 
 
 class YouCompleteMe( object ):
   def __init__( self, user_options ):
+    # TODO: This should go into the server
+    # TODO: Use more logging like we do in cs_completer
+    self._logfile = tempfile.NamedTemporaryFile()
+    logging.basicConfig( format='%(asctime)s - %(levelname)s - %(message)s',
+                         filename=self._logfile.name,
+                         level=logging.DEBUG )
+
     self._user_options = user_options
     self._gencomp = GeneralCompleterStore( user_options )
     self._omnicomp = OmniCompleter( user_options )
@@ -78,6 +142,16 @@ class YouCompleteMe( object ):
     return self._current_completion_request
 
 
+  def SendCommandRequest( self, arguments, completer ):
+    # TODO: This should be inside a method in a command_request module
+    request = CommandRequest( self, arguments, completer )
+    request.Start()
+    while not request.Done():
+      time.sleep( 0.1 )
+
+    return request.Response()
+
+
   def GetCurrentCompletionRequest( self ):
     return self._current_completion_request
 
@@ -131,14 +205,13 @@ class YouCompleteMe( object ):
     return completer
 
 
-  def ShouldUseGeneralCompleter( self, start_column ):
-    return self._gencomp.ShouldUseNow( start_column, vim.current.line )
+  def ShouldUseGeneralCompleter( self, request_data ):
+    return self._gencomp.ShouldUseNow( request_data )
 
 
-  def ShouldUseFiletypeCompleter( self, start_column ):
+  def ShouldUseFiletypeCompleter( self, request_data ):
     if self.FiletypeCompletionUsable():
-      return self.GetFiletypeCompleter().ShouldUseNow(
-        start_column, vim.current.line )
+      return self.GetFiletypeCompleter().ShouldUseNow( request_data )
     return False
 
 
@@ -162,10 +235,10 @@ class YouCompleteMe( object ):
 
 
   def OnFileReadyToParse( self ):
-    self._gencomp.OnFileReadyToParse()
+    self._gencomp.OnFileReadyToParse( _BuildRequestData() )
 
     if self.FiletypeCompletionUsable():
-      self.GetFiletypeCompleter().OnFileReadyToParse()
+      self.GetFiletypeCompleter().OnFileReadyToParse( _BuildRequestData() )
 
 
   def OnBufferUnload( self, deleted_buffer_file ):
@@ -208,9 +281,9 @@ class YouCompleteMe( object ):
     return []
 
 
-  def ShowDetailedDiagnostic( self ):
+  def GetDetailedDiagnostic( self ):
     if self.FiletypeCompletionUsable():
-      return self.GetFiletypeCompleter().ShowDetailedDiagnostic()
+      return self.GetFiletypeCompleter().GetDetailedDiagnostic()
 
 
   def GettingCompletions( self ):
@@ -263,3 +336,37 @@ def _PathToFiletypeCompleterPluginLoader( filetype ):
   return os.path.join( _PathToCompletersFolder(), filetype, 'hook.py' )
 
 
+def _BuildRequestData( start_column = None, query = None ):
+  line, column = vimsupport.CurrentLineAndColumn()
+  request_data = {
+    'filetypes': vimsupport.CurrentFiletypes(),
+    'line_num': line,
+    'column_num': column,
+    'start_column': start_column,
+    'line_value': vim.current.line,
+    'filepath': vim.current.buffer.name,
+    'file_data': vimsupport.GetUnsavedAndCurrentBufferData()
+  }
+
+  if query:
+    request_data[ 'query' ] = query
+
+  return request_data
+
+def _ConvertCompletionDataToVimData( completion_data ):
+  # see :h complete-items for a description of the dictionary fields
+  vim_data = {
+    'word' : completion_data[ 'insertion_text' ],
+    'dup'  : 1,
+  }
+
+  if 'menu_text' in completion_data:
+    vim_data[ 'abbr' ] = completion_data[ 'menu_text' ]
+  if 'extra_menu_info' in completion_data:
+    vim_data[ 'menu' ] = completion_data[ 'extra_menu_info' ]
+  if 'kind' in completion_data:
+    vim_data[ 'kind' ] = completion_data[ 'kind' ]
+  if 'detailed_info' in completion_data:
+    vim_data[ 'info' ] = completion_data[ 'detailed_info' ]
+
+  return vim_data