From b923431d7d330d9fd7218f2007b280e3b7083647 Mon Sep 17 00:00:00 2001 From: micbou Date: Sat, 30 Apr 2016 18:14:48 +0200 Subject: [PATCH] Avoid evaluating Vim globals in Python On Python 3, evaluating a Vim expression will raise a unicode exception if it contains an invalid sequence of bytes for the current encoding. We can't really do anything about it because this is the way Vim and Python 3 interact. However, we can prevent this situation to occur by not evaluating Vim data that we have no control over: in particular, the Vim globals. This is done by: - adding one by one the YCM default options instead of extending the Vim globals with them; - only evaluating the Vim global variable names (and not their values) when building the YCM options for the ycmd server. --- python/ycm/base.py | 20 +++++++++----------- python/ycm/vimsupport.py | 29 +++++------------------------ 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/python/ycm/base.py b/python/ycm/base.py index 238d23ca..4de4c0bf 100644 --- a/python/ycm/base.py +++ b/python/ycm/base.py @@ -35,17 +35,16 @@ YCM_VAR_PREFIX = 'ycm_' def BuildServerConf(): """Builds a dictionary mapping YCM Vim user options to values. Option names don't have the 'ycm_' prefix.""" - - vim_globals = vimsupport.GetReadOnlyVimGlobals( force_python_objects = True ) + # We only evaluate the keys of the vim globals and not the whole dictionary + # to avoid unicode issues. + # See https://github.com/Valloric/YouCompleteMe/pull/2151 for details. + keys = vimsupport.GetVimGlobalsKeys() server_conf = {} - for key, value in iteritems( vim_globals ): + for key in keys: if not key.startswith( YCM_VAR_PREFIX ): continue - try: - new_value = int( value ) - except: - new_value = value new_key = key[ len( YCM_VAR_PREFIX ): ] + new_value = vimsupport.VimExpressionToPythonType( 'g:' + key ) server_conf[ new_key ] = new_value return server_conf @@ -53,11 +52,10 @@ def BuildServerConf(): def LoadJsonDefaultsIntoVim(): defaults = user_options_store.DefaultOptions() - vim_defaults = {} for key, value in iteritems( defaults ): - vim_defaults[ 'ycm_' + key ] = value - - vimsupport.LoadDictIntoVimGlobals( vim_defaults, overwrite = False ) + new_key = 'g:ycm_' + key + if not vimsupport.VariableExists( new_key ): + vimsupport.SetVariableValue( new_key, value ) def CompletionStartColumn(): diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 115c0e9d..5c2302bc 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -294,27 +294,8 @@ def ConvertDiagnosticsToQfList( diagnostics ): return [ ConvertDiagnosticToQfFormat( x ) for x in diagnostics ] -# Given a dict like {'a': 1}, loads it into Vim as if you ran 'let g:a = 1' -# When |overwrite| is True, overwrites the existing value in Vim. -def LoadDictIntoVimGlobals( new_globals, overwrite = True ): - extend_option = '"force"' if overwrite else '"keep"' - - # We need to use json.dumps because that won't use the 'u' prefix on strings - # which Vim would bork on. - vim.eval( 'extend( g:, {0}, {1})'.format( json.dumps( new_globals ), - extend_option ) ) - - -# Changing the returned dict will NOT change the value in Vim. -def GetReadOnlyVimGlobals( force_python_objects = False ): - if force_python_objects: - return vim.eval( 'g:' ) - - try: - # vim.vars is fairly new so it might not exist - return vim.vars - except: - return vim.eval( 'g:' ) +def GetVimGlobalsKeys(): + return vim.eval( 'keys( g: )' ) def VimExpressionToPythonType( vim_expression ): @@ -491,8 +472,8 @@ def EchoTextVimWidth( text ): EchoText( truncated_text, False ) - vim.command( 'let &ruler = {0}'.format( old_ruler ) ) - vim.command( 'let &showcmd = {0}'.format( old_showcmd ) ) + SetVariableValue( '&ruler', old_ruler ) + SetVariableValue( '&showcmd', old_showcmd ) def EscapeForVim( text ): @@ -514,7 +495,7 @@ def VariableExists( variable ): def SetVariableValue( variable, value ): - vim.command( "let {0} = '{1}'".format( variable, EscapeForVim( value ) ) ) + vim.command( "let {0} = {1}".format( variable, json.dumps( value ) ) ) def GetVariableValue( variable ):