From d2fd4d2060f4e9ca0f6534ed0a9ec7a8f3b2afeb Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Wed, 13 May 2020 11:26:39 -0400 Subject: [PATCH 01/11] Truncate the extra_menu_info if using preview popup Fixes ycm-core#3547 Based entirely on: https://github.com/puremourning/YouCompleteMe/commit/0dd1b7d141acc02877b56b5c75f9f67320b499c2 --- autoload/youcompleteme.vim | 4 +++- python/ycm/client/completion_request.py | 18 ++++++++++++++++-- python/ycm/vimsupport.py | 12 ++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 21011bc074..673fa12ef5 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -505,7 +505,9 @@ function! s:SetUpCompleteopt() " Also, having this option set breaks the plugin. set completeopt-=longest - if g:ycm_add_preview_to_completeopt + if g:ycm_add_preview_to_completeopt ==# 'popup' && exists( '*popup_open' ) + set completeopt+=popup + elseif g:ycm_add_preview_to_completeopt set completeopt+=preview endif endfunction diff --git a/python/ycm/client/completion_request.py b/python/ycm/client/completion_request.py index 50e53ec443..16293ccd7e 100644 --- a/python/ycm/client/completion_request.py +++ b/python/ycm/client/completion_request.py @@ -181,11 +181,25 @@ def _GetCompletionInfoField( completion_data ): def _ConvertCompletionDataToVimData( completion_data ): # See :h complete-items for a description of the dictionary fields. + extra_menu_info = completion_data.get( 'extra_menu_info', '' ) + preview_info = _GetCompletionInfoField( completion_data ) + + # When we are using a popup for the preview_info, it needs to fit on the + # screen alongside the extra_menu_info. Let's use some heuristics. If the + # length of the extra_menu_info is more than, say 50% of screen, truncate it + # and stick it in the preview_info. + if vimsupport.UsingPreviewPopup(): + max_width = max( int( vimsupport.DisplayWidth() / 3 ), 3 ) + extra_menu_info_width = vimsupport.DisplayWidthOfString( extra_menu_info ) + if extra_menu_info_width > max_width: + preview_info = extra_menu_info + '\n\n' + preview_info + extra_menu_info = extra_menu_info[ : ( max_width - 3 ) ] + '...' + return { 'word' : completion_data[ 'insertion_text' ], 'abbr' : completion_data.get( 'menu_text', '' ), - 'menu' : completion_data.get( 'extra_menu_info', '' ), - 'info' : _GetCompletionInfoField( completion_data ), + 'menu' : extra_menu_info, + 'info' : preview_info, 'kind' : ToUnicode( completion_data.get( 'kind', '' ) )[ :1 ].lower(), # Disable Vim filtering. 'equal' : 1, diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index f25ef025b6..d9e91db003 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -1308,3 +1308,15 @@ def ScreenPositionForLineColumnInWindow( window, line, column ): WinIDForWindow( window ), line, column ) ) + + +def UsingPreviewPopup(): + return 'popup' in ToUnicode( vim.options[ 'completeopt' ] ).split( ',' ) + + +def DisplayWidth(): + return GetIntValue( '&columns' ) + + +def DisplayWidthOfString( s ): + return GetIntValue( "strdisplaywidth( '{}' )".format( EscapeForVim( s ) ) ) From 4772b66627be720329616d52e8bb7f933555715f Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Wed, 13 May 2020 14:38:27 -0400 Subject: [PATCH 02/11] Add a couple basic popup truncation tests --- python/ycm/client/completion_request.py | 5 +- .../tests/client/completion_request_test.py | 59 ++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/python/ycm/client/completion_request.py b/python/ycm/client/completion_request.py index 16293ccd7e..1c70a549a4 100644 --- a/python/ycm/client/completion_request.py +++ b/python/ycm/client/completion_request.py @@ -186,13 +186,14 @@ def _ConvertCompletionDataToVimData( completion_data ): # When we are using a popup for the preview_info, it needs to fit on the # screen alongside the extra_menu_info. Let's use some heuristics. If the - # length of the extra_menu_info is more than, say 50% of screen, truncate it + # length of the extra_menu_info is more than, say, 1/3 of screen, truncate it # and stick it in the preview_info. if vimsupport.UsingPreviewPopup(): max_width = max( int( vimsupport.DisplayWidth() / 3 ), 3 ) extra_menu_info_width = vimsupport.DisplayWidthOfString( extra_menu_info ) if extra_menu_info_width > max_width: - preview_info = extra_menu_info + '\n\n' + preview_info + if not preview_info.startswith( extra_menu_info ): + preview_info = extra_menu_info + '\n\n' + preview_info extra_menu_info = extra_menu_info[ : ( max_width - 3 ) ] + '...' return { diff --git a/python/ycm/tests/client/completion_request_test.py b/python/ycm/tests/client/completion_request_test.py index 07f794dd09..f32449cfbb 100644 --- a/python/ycm/tests/client/completion_request_test.py +++ b/python/ycm/tests/client/completion_request_test.py @@ -17,6 +17,7 @@ import json from hamcrest import assert_that, equal_to +from unittest.mock import patch from ycm.tests.test_utils import MockVimModule vim_mock = MockVimModule() @@ -32,7 +33,7 @@ def _Check( self, completion_data, expected_vim_data ): completion_data ) try: - assert_that( expected_vim_data, equal_to( vim_data ) ) + assert_that( vim_data, equal_to( expected_vim_data ) ) except Exception: print( "Expected:\n'{}'\nwhen parsing:\n'{}'\nBut found:\n'{}'".format( expected_vim_data, @@ -213,3 +214,59 @@ def EmptyInsertionText_test( self ): 'empty' : 1, 'user_data': json.dumps( extra_data ), } ) + + + @patch( "ycm.vimsupport.UsingPreviewPopup", return_value = True ) + @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) + @patch( "ycm.vimsupport.DisplayWidthOfString", len ) + def TruncateForPopup_test( self, *args ): + extra_data = { + 'doc_string': 'DOC STRING', + } + self._Check( { + 'insertion_text': '', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'ESPECIALLY LONG EXTRA MENU INFO LOREM IPSUM DOLOR', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': extra_data, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'ESPECIALLY LONG E...', + 'kind' : 'k', + 'info' : 'ESPECIALLY LONG EXTRA MENU INFO LOREM IPSUM DOLOR\n\n' + + 'DETAILED INFO\nDOC STRING', + 'equal' : 1, + 'dup' : 1, + 'empty' : 1, + 'user_data': json.dumps( extra_data ), + } ) + + + @patch( "ycm.vimsupport.UsingPreviewPopup", return_value = True ) + @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) + @patch( "ycm.vimsupport.DisplayWidthOfString", len ) + def TruncateForPopupWithoutDuplication_test( self, *args ): + extra_data = { + 'doc_string': 'DOC STRING', + } + self._Check( { + 'insertion_text': '', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM', + 'kind': 'K', + 'detailed_info': 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM', + 'extra_data': extra_data, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'ESPECIALLY LONG M...', + 'kind' : 'k', + 'info' : 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM\n' + + 'DOC STRING', + 'equal' : 1, + 'dup' : 1, + 'empty' : 1, + 'user_data': json.dumps( extra_data ), + } ) From 3f177652407747f5964cb6f44775544e69303fbf Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Wed, 13 May 2020 14:40:30 -0400 Subject: [PATCH 03/11] Add a negative test of only truncating in popup mode if needed --- .../tests/client/completion_request_test.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/python/ycm/tests/client/completion_request_test.py b/python/ycm/tests/client/completion_request_test.py index f32449cfbb..c739056d3c 100644 --- a/python/ycm/tests/client/completion_request_test.py +++ b/python/ycm/tests/client/completion_request_test.py @@ -244,6 +244,33 @@ def TruncateForPopup_test( self, *args ): } ) + @patch( "ycm.vimsupport.UsingPreviewPopup", return_value = True ) + @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) + @patch( "ycm.vimsupport.DisplayWidthOfString", len ) + def OnlyTruncateForPopupIfNecessary_test( self, *args ): + extra_data = { + 'doc_string': 'DOC STRING', + } + self._Check( { + 'insertion_text': '', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': extra_data, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'info' : 'DETAILED INFO\nDOC STRING', + 'equal' : 1, + 'dup' : 1, + 'empty' : 1, + 'user_data': json.dumps( extra_data ), + } ) + + @patch( "ycm.vimsupport.UsingPreviewPopup", return_value = True ) @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) From 7374f969232b9ae5e7108a79b1ec8d1aeb8a7b8f Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Wed, 13 May 2020 14:56:57 -0400 Subject: [PATCH 04/11] Document ycm_add_preview_to_completeopt = 'preview' --- doc/youcompleteme.txt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index 85927994b6..f5cd9271f5 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -2936,9 +2936,11 @@ The *g:ycm_add_preview_to_completeopt* option When this option is set to '1', YCM will add the 'preview' string to Vim's 'completeopt' option (see ':h completeopt'). If your 'completeopt' option -already has 'preview' set, there will be no effect. You can see the current -state of your 'completeopt' setting with ':set completeopt?' (yes, the question -mark is important). +already has 'preview' set, there will be no effect. Alternatively, when set to +'popup' and your version of Vim supports popup windows (see ':help popup'), +the 'popup' string will be used instead. You can see the current state of your +'completeopt' setting with ':set completeopt?' (yes, the question mark is +important). When 'preview' is present in 'completeopt', YCM will use the 'preview' window at the top of the file to store detailed information about the current @@ -2946,6 +2948,12 @@ completion candidate (but only if the candidate came from the semantic engine). For instance, it would show the full function prototype and all the function overloads in the window if the current completion is a function name. +When 'popup' is present in 'completeopt', YCM will instead use a 'popup' +window to the side of the completion popup for storing detailed information +about the current completion candidate. In addition, YCM may truncate the +detailed completion information in order to give the popup sufficient room +to display that detailed information. + Default: '0' > let g:ycm_add_preview_to_completeopt = 0 From 858d4f3759aca9bf0f94290feda402a6fb928f9d Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Thu, 14 May 2020 09:10:46 -0400 Subject: [PATCH 05/11] Simplify UsingPreviewPopup; add coverage of it by patching vim.options --- python/ycm/tests/client/completion_request_test.py | 6 +++--- python/ycm/vimsupport.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/ycm/tests/client/completion_request_test.py b/python/ycm/tests/client/completion_request_test.py index c739056d3c..f96ebd17ba 100644 --- a/python/ycm/tests/client/completion_request_test.py +++ b/python/ycm/tests/client/completion_request_test.py @@ -216,7 +216,7 @@ def EmptyInsertionText_test( self ): } ) - @patch( "ycm.vimsupport.UsingPreviewPopup", return_value = True ) + @patch( "vim.options", { 'completeopt': b'popup,menuone' } ) @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def TruncateForPopup_test( self, *args ): @@ -244,7 +244,7 @@ def TruncateForPopup_test( self, *args ): } ) - @patch( "ycm.vimsupport.UsingPreviewPopup", return_value = True ) + @patch( "vim.options", { 'completeopt': b'popup,menuone' } ) @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def OnlyTruncateForPopupIfNecessary_test( self, *args ): @@ -271,7 +271,7 @@ def OnlyTruncateForPopupIfNecessary_test( self, *args ): } ) - @patch( "ycm.vimsupport.UsingPreviewPopup", return_value = True ) + @patch( "vim.options", { 'completeopt': b'popup,menuone' } ) @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def TruncateForPopupWithoutDuplication_test( self, *args ): diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index d9e91db003..5fe757f792 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -1311,7 +1311,7 @@ def ScreenPositionForLineColumnInWindow( window, line, column ): def UsingPreviewPopup(): - return 'popup' in ToUnicode( vim.options[ 'completeopt' ] ).split( ',' ) + return b'popup' in vim.options[ 'completeopt' ] def DisplayWidth(): From decb78f5c604cbbe1e12797f1893a7a98e49a89f Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Thu, 14 May 2020 09:20:30 -0400 Subject: [PATCH 06/11] Update README.md --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 87b17d7080..aaaee66b4c 100644 --- a/README.md +++ b/README.md @@ -2703,9 +2703,11 @@ let g:ycm_csharp_insert_namespace_expr = '' When this option is set to `1`, YCM will add the `preview` string to Vim's `completeopt` option (see `:h completeopt`). If your `completeopt` option -already has `preview` set, there will be no effect. You can see the current -state of your `completeopt` setting with `:set completeopt?` (yes, the question -mark is important). +already has `preview` set, there will be no effect. Alternatively, when set to +`popup` and your version of Vim supports popup windows (see `:help popup`), the +`popup` string will be used instead. You can see the current state of your +`completeopt` setting with `:set completeopt?` (yes, the question mark is +important). When `preview` is present in `completeopt`, YCM will use the `preview` window at the top of the file to store detailed information about the current completion @@ -2713,6 +2715,12 @@ candidate (but only if the candidate came from the semantic engine). For instance, it would show the full function prototype and all the function overloads in the window if the current completion is a function name. +When `popup` is present in `completeopt`, YCM will instead use a `popup` +window to the side of the completion popup for storing detailed information +about the current completion candidate. In addition, YCM may truncate the +detailed completion information in order to give the popup sufficient room +to display that detailed information. + Default: `0` ```viml From 32af52fa688f0a8b82ff47a8c925a4217242683e Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Thu, 14 May 2020 09:39:03 -0400 Subject: [PATCH 07/11] Support pulling mocked vim options from @patch('vim.options') Allows us to get coverage of DisplayWidth() --- python/ycm/tests/client/completion_request_test.py | 12 ++++++------ python/ycm/tests/test_utils.py | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/python/ycm/tests/client/completion_request_test.py b/python/ycm/tests/client/completion_request_test.py index f96ebd17ba..fa31ad8f0b 100644 --- a/python/ycm/tests/client/completion_request_test.py +++ b/python/ycm/tests/client/completion_request_test.py @@ -216,8 +216,8 @@ def EmptyInsertionText_test( self ): } ) - @patch( "vim.options", { 'completeopt': b'popup,menuone' } ) - @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) + @patch( "vim.options", { 'completeopt': b'popup,menuone', + '&columns': 60 } ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def TruncateForPopup_test( self, *args ): extra_data = { @@ -244,8 +244,8 @@ def TruncateForPopup_test( self, *args ): } ) - @patch( "vim.options", { 'completeopt': b'popup,menuone' } ) - @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) + @patch( "vim.options", { 'completeopt': b'popup,menuone', + '&columns': 60 } ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def OnlyTruncateForPopupIfNecessary_test( self, *args ): extra_data = { @@ -271,8 +271,8 @@ def OnlyTruncateForPopupIfNecessary_test( self, *args ): } ) - @patch( "vim.options", { 'completeopt': b'popup,menuone' } ) - @patch( "ycm.vimsupport.DisplayWidth", return_value = 60 ) + @patch( "vim.options", { 'completeopt': b'popup,menuone', + '&columns': 60 } ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def TruncateForPopupWithoutDuplication_test( self, *args ): extra_data = { diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py index 0f3fb3380d..46c89fca2d 100644 --- a/python/ycm/tests/test_utils.py +++ b/python/ycm/tests/test_utils.py @@ -183,6 +183,10 @@ def _MockVimWindowEval( value ): def _MockVimOptionsEval( value ): + result = VIM_MOCK.options.get( value ) + if result is not None: + return result + result = VIM_OPTIONS.get( value ) if result is not None: return result From f6093794fe7f931e86b0f6f23a92ae8692c2702d Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Thu, 14 May 2020 10:03:12 -0400 Subject: [PATCH 08/11] Regenerate doc/youcompletme.txt via vim-tools --- doc/youcompleteme.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index f5cd9271f5..29a918dad4 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -2937,8 +2937,8 @@ The *g:ycm_add_preview_to_completeopt* option When this option is set to '1', YCM will add the 'preview' string to Vim's 'completeopt' option (see ':h completeopt'). If your 'completeopt' option already has 'preview' set, there will be no effect. Alternatively, when set to -'popup' and your version of Vim supports popup windows (see ':help popup'), -the 'popup' string will be used instead. You can see the current state of your +'popup' and your version of Vim supports popup windows (see ':help popup'), the +'popup' string will be used instead. You can see the current state of your 'completeopt' setting with ':set completeopt?' (yes, the question mark is important). @@ -2948,11 +2948,11 @@ completion candidate (but only if the candidate came from the semantic engine). For instance, it would show the full function prototype and all the function overloads in the window if the current completion is a function name. -When 'popup' is present in 'completeopt', YCM will instead use a 'popup' -window to the side of the completion popup for storing detailed information -about the current completion candidate. In addition, YCM may truncate the -detailed completion information in order to give the popup sufficient room -to display that detailed information. +When 'popup' is present in 'completeopt', YCM will instead use a 'popup' window +to the side of the completion popup for storing detailed information about the +current completion candidate. In addition, YCM may truncate the detailed +completion information in order to give the popup sufficient room to display +that detailed information. Default: '0' > From 982715fcd0e36778dfaa07478d8ab6450ae00192 Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Thu, 14 May 2020 10:54:26 -0400 Subject: [PATCH 09/11] Switch to `with UserOptions`; read from there for vim.options --- .../tests/client/completion_request_test.py | 166 ++++++++++-------- python/ycm/tests/test_utils.py | 15 +- 2 files changed, 106 insertions(+), 75 deletions(-) diff --git a/python/ycm/tests/client/completion_request_test.py b/python/ycm/tests/client/completion_request_test.py index fa31ad8f0b..522c7e8041 100644 --- a/python/ycm/tests/client/completion_request_test.py +++ b/python/ycm/tests/client/completion_request_test.py @@ -18,6 +18,7 @@ import json from hamcrest import assert_that, equal_to from unittest.mock import patch +from ycm.tests.conftest import UserOptions from ycm.tests.test_utils import MockVimModule vim_mock = MockVimModule() @@ -216,84 +217,107 @@ def EmptyInsertionText_test( self ): } ) - @patch( "vim.options", { 'completeopt': b'popup,menuone', - '&columns': 60 } ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def TruncateForPopup_test( self, *args ): - extra_data = { - 'doc_string': 'DOC STRING', - } - self._Check( { - 'insertion_text': '', - 'menu_text': 'MENU TEXT', - 'extra_menu_info': 'ESPECIALLY LONG EXTRA MENU INFO LOREM IPSUM DOLOR', - 'kind': 'K', - 'detailed_info': 'DETAILED INFO', - 'extra_data': extra_data, - }, { - 'word' : '', - 'abbr' : 'MENU TEXT', - 'menu' : 'ESPECIALLY LONG E...', - 'kind' : 'k', - 'info' : 'ESPECIALLY LONG EXTRA MENU INFO LOREM IPSUM DOLOR\n\n' + - 'DETAILED INFO\nDOC STRING', - 'equal' : 1, - 'dup' : 1, - 'empty' : 1, - 'user_data': json.dumps( extra_data ), - } ) + with UserOptions( { '&columns': 60, '&completeopt': b'popup,menuone' } ): + extra_data = { + 'doc_string': 'DOC STRING', + } + self._Check( { + 'insertion_text': '', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'ESPECIALLY LONG EXTRA MENU INFO LOREM IPSUM DOLOR', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': extra_data, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'ESPECIALLY LONG E...', + 'kind' : 'k', + 'info' : 'ESPECIALLY LONG EXTRA MENU INFO LOREM IPSUM DOLOR\n\n' + + 'DETAILED INFO\nDOC STRING', + 'equal' : 1, + 'dup' : 1, + 'empty' : 1, + 'user_data': json.dumps( extra_data ), + } ) - @patch( "vim.options", { 'completeopt': b'popup,menuone', - '&columns': 60 } ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def OnlyTruncateForPopupIfNecessary_test( self, *args ): - extra_data = { - 'doc_string': 'DOC STRING', - } - self._Check( { - 'insertion_text': '', - 'menu_text': 'MENU TEXT', - 'extra_menu_info': 'EXTRA MENU INFO', - 'kind': 'K', - 'detailed_info': 'DETAILED INFO', - 'extra_data': extra_data, - }, { - 'word' : '', - 'abbr' : 'MENU TEXT', - 'menu' : 'EXTRA MENU INFO', - 'kind' : 'k', - 'info' : 'DETAILED INFO\nDOC STRING', - 'equal' : 1, - 'dup' : 1, - 'empty' : 1, - 'user_data': json.dumps( extra_data ), - } ) + with UserOptions( { '&columns': 60, '&completeopt': b'popup,menuone' } ): + extra_data = { + 'doc_string': 'DOC STRING', + } + self._Check( { + 'insertion_text': '', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': extra_data, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'info' : 'DETAILED INFO\nDOC STRING', + 'equal' : 1, + 'dup' : 1, + 'empty' : 1, + 'user_data': json.dumps( extra_data ), + } ) + + + @patch( "ycm.vimsupport.DisplayWidthOfString", len ) + def DontTruncateIfNotPopup_test( self, *args ): + with UserOptions( { '&columns': 60, '&completeopt': b'preview,menuone' } ): + extra_data = { + 'doc_string': 'DOC STRING', + } + self._Check( { + 'insertion_text': '', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'ESPECIALLY LONG EXTRA MENU INFO LOREM IPSUM DOLOR', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': extra_data, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'ESPECIALLY LONG EXTRA MENU INFO LOREM IPSUM DOLOR', + 'kind' : 'k', + 'info' : 'DETAILED INFO\nDOC STRING', + 'equal' : 1, + 'dup' : 1, + 'empty' : 1, + 'user_data': json.dumps( extra_data ), + } ) - @patch( "vim.options", { 'completeopt': b'popup,menuone', - '&columns': 60 } ) @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def TruncateForPopupWithoutDuplication_test( self, *args ): - extra_data = { - 'doc_string': 'DOC STRING', - } - self._Check( { - 'insertion_text': '', - 'menu_text': 'MENU TEXT', - 'extra_menu_info': 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM', - 'kind': 'K', - 'detailed_info': 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM', - 'extra_data': extra_data, - }, { - 'word' : '', - 'abbr' : 'MENU TEXT', - 'menu' : 'ESPECIALLY LONG M...', - 'kind' : 'k', - 'info' : 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM\n' + - 'DOC STRING', - 'equal' : 1, - 'dup' : 1, - 'empty' : 1, - 'user_data': json.dumps( extra_data ), - } ) + with UserOptions( { '&columns': 60, '&completeopt': b'popup,menuone' } ): + extra_data = { + 'doc_string': 'DOC STRING', + } + self._Check( { + 'insertion_text': '', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM', + 'kind': 'K', + 'detailed_info': 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM', + 'extra_data': extra_data, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'ESPECIALLY LONG M...', + 'kind' : 'k', + 'info' : 'ESPECIALLY LONG METHOD SIGNATURE LOREM IPSUM\n' + + 'DOC STRING', + 'equal' : 1, + 'dup' : 1, + 'empty' : 1, + 'user_data': json.dumps( extra_data ), + } ) diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py index 46c89fca2d..85ad9f00cf 100644 --- a/python/ycm/tests/test_utils.py +++ b/python/ycm/tests/test_utils.py @@ -70,6 +70,7 @@ VIM_SIGNS = [] VIM_OPTIONS = { + '&completeopt': b'', '&previewheight': 12, '&columns': 80, '&ruler': 0, @@ -183,10 +184,6 @@ def _MockVimWindowEval( value ): def _MockVimOptionsEval( value ): - result = VIM_MOCK.options.get( value ) - if result is not None: - return result - result = VIM_OPTIONS.get( value ) if result is not None: return result @@ -374,6 +371,14 @@ def _MockVimCommand( command ): return DEFAULT +def _MockVimOptions( option ): + result = VIM_OPTIONS.get( '&' + option ) + if result is not None: + return result + + return None + + class VimBuffer: """An object that looks like a vim.buffer object: - |name| : full path of the buffer with symbolic links resolved; @@ -636,6 +641,8 @@ def test( vim_command, vim_eval ): VIM_MOCK.command = MagicMock( side_effect = _MockVimCommand ) VIM_MOCK.eval = MagicMock( side_effect = _MockVimEval ) VIM_MOCK.error = VimError + VIM_MOCK.options = MagicMock() + VIM_MOCK.options.__getitem__.side_effect = _MockVimOptions sys.modules[ 'vim' ] = VIM_MOCK return VIM_MOCK From 5e470ec4a885bcd3c006422dcbb691c21347ec30 Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Fri, 15 May 2020 09:07:57 -0400 Subject: [PATCH 10/11] Revert change to UsingPreviewPopup --- python/ycm/vimsupport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 5fe757f792..d9e91db003 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -1311,7 +1311,7 @@ def ScreenPositionForLineColumnInWindow( window, line, column ): def UsingPreviewPopup(): - return b'popup' in vim.options[ 'completeopt' ] + return 'popup' in ToUnicode( vim.options[ 'completeopt' ] ).split( ',' ) def DisplayWidth(): From 8f0646317c80e284b91c4e3152d18306f538716f Mon Sep 17 00:00:00 2001 From: Daniel Leong Date: Thu, 28 May 2020 11:23:23 -0400 Subject: [PATCH 11/11] Apply patch requested from @bstaletic --- python/ycm/tests/client/completion_request_test.py | 5 ----- python/ycm/tests/test_utils.py | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/python/ycm/tests/client/completion_request_test.py b/python/ycm/tests/client/completion_request_test.py index 522c7e8041..049b18ca4e 100644 --- a/python/ycm/tests/client/completion_request_test.py +++ b/python/ycm/tests/client/completion_request_test.py @@ -17,7 +17,6 @@ import json from hamcrest import assert_that, equal_to -from unittest.mock import patch from ycm.tests.conftest import UserOptions from ycm.tests.test_utils import MockVimModule vim_mock = MockVimModule() @@ -217,7 +216,6 @@ def EmptyInsertionText_test( self ): } ) - @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def TruncateForPopup_test( self, *args ): with UserOptions( { '&columns': 60, '&completeopt': b'popup,menuone' } ): extra_data = { @@ -244,7 +242,6 @@ def TruncateForPopup_test( self, *args ): } ) - @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def OnlyTruncateForPopupIfNecessary_test( self, *args ): with UserOptions( { '&columns': 60, '&completeopt': b'popup,menuone' } ): extra_data = { @@ -270,7 +267,6 @@ def OnlyTruncateForPopupIfNecessary_test( self, *args ): } ) - @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def DontTruncateIfNotPopup_test( self, *args ): with UserOptions( { '&columns': 60, '&completeopt': b'preview,menuone' } ): extra_data = { @@ -296,7 +292,6 @@ def DontTruncateIfNotPopup_test( self, *args ): } ) - @patch( "ycm.vimsupport.DisplayWidthOfString", len ) def TruncateForPopupWithoutDuplication_test( self, *args ): with UserOptions( { '&columns': 60, '&completeopt': b'popup,menuone' } ): extra_data = { diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py index 85ad9f00cf..2ba126785b 100644 --- a/python/ycm/tests/test_utils.py +++ b/python/ycm/tests/test_utils.py @@ -43,6 +43,8 @@ OMNIFUNC_REGEX_FORMAT = ( '^{omnifunc_name}\\((?P[01]),[\'"](?P.*)[\'"]\\)$' ) FNAMEESCAPE_REGEX = re.compile( '^fnameescape\\(\'(?P.+)\'\\)$' ) +STRDISPLAYWIDTH_REGEX = re.compile( + '^strdisplaywidth\\( ?\'(?P.+)\' ?\\)$' ) SIGN_LIST_REGEX = re.compile( '^silent! sign place buffer=(?P\\d+)$' ) SIGN_PLACE_REGEX = re.compile( @@ -290,6 +292,10 @@ def _MockVimEval( value ): if value == REDIR[ 'variable' ]: return REDIR[ 'output' ] + match = STRDISPLAYWIDTH_REGEX.search( value ) + if match: + return len( match.group( 'text' ) ) + raise VimError( 'Unexpected evaluation: {}'.format( value ) )