vimsupport_test.py 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162
  1. # Copyright (C) 2015-2018 YouCompleteMe contributors
  2. #
  3. # This file is part of YouCompleteMe.
  4. #
  5. # YouCompleteMe is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # YouCompleteMe is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  17. from ycm.tests import PathToTestFile
  18. from ycm.tests.test_utils import ( CurrentWorkingDirectory, ExtendedMock,
  19. MockVimBuffers, MockVimModule, Version,
  20. VimBuffer, VimError, WindowsAndMacOnly )
  21. MockVimModule()
  22. from ycm import vimsupport
  23. from hamcrest import ( assert_that, calling, contains_exactly, empty, equal_to,
  24. has_entry, raises )
  25. from unittest import TestCase
  26. from unittest.mock import MagicMock, call, patch
  27. from ycmd.utils import ToBytes
  28. import os
  29. import json
  30. def AssertBuffersAreEqualAsBytes( result_buffer, expected_buffer ):
  31. assert_that( len( result_buffer ), equal_to( len( expected_buffer ) ) )
  32. for result_line, expected_line in zip( result_buffer, expected_buffer ):
  33. assert_that( ToBytes( result_line ), equal_to( ToBytes( expected_line ) ) )
  34. def _BuildChunk( start_line,
  35. start_column,
  36. end_line,
  37. end_column,
  38. replacement_text, filepath='test_file_name' ):
  39. return {
  40. 'range': {
  41. 'start': {
  42. 'filepath': filepath,
  43. 'line_num': start_line,
  44. 'column_num': start_column,
  45. },
  46. 'end': {
  47. 'filepath': filepath,
  48. 'line_num': end_line,
  49. 'column_num': end_column,
  50. },
  51. },
  52. 'replacement_text': replacement_text
  53. }
  54. def _BuildLocations( start_line, start_column, end_line, end_column ):
  55. return {
  56. 'line_num' : start_line,
  57. 'column_num': start_column,
  58. }, {
  59. 'line_num' : end_line,
  60. 'column_num': end_column,
  61. }
  62. class VimsupportTest( TestCase ):
  63. @patch( 'ycm.vimsupport.WinIDForWindow', side_effect = range( 1001, 1010 ) )
  64. @patch( 'vim.eval', new_callable = ExtendedMock )
  65. def test_SetLocationListsForBuffer_Current( self, vim_eval, *args ):
  66. diagnostics = [ {
  67. 'bufnr': 3,
  68. 'filename': 'some_filename',
  69. 'lnum': 5,
  70. 'col': 22,
  71. 'type': 'E',
  72. 'valid': 1
  73. } ]
  74. current_buffer = VimBuffer( '/test', number = 3 )
  75. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
  76. vimsupport.SetLocationListsForBuffer( 3, diagnostics )
  77. vim_eval.assert_has_exact_calls( [
  78. call( 'setloclist( 1001, [], " ", { "title": "ycm_loc", '
  79. '"items": [{"bufnr": 3, "filename": "some_filename", "lnum": 5, '
  80. '"col": 22, "type": "E", "valid": 1}] } )' ),
  81. call( 'getloclist( 1001, { "nr": "$", "id": 0 } ).id' ),
  82. ] )
  83. @patch( 'vim.eval', new_callable = ExtendedMock )
  84. def test_SetLocationListsForBuffer_NotCurrent( self, vim_eval ):
  85. diagnostics = [ {
  86. 'bufnr': 3,
  87. 'filename': 'some_filename',
  88. 'lnum': 5,
  89. 'col': 22,
  90. 'type': 'E',
  91. 'valid': 1
  92. } ]
  93. current_buffer = VimBuffer( '/test', number = 3 )
  94. other_buffer = VimBuffer( '/notcurrent', number = 1 )
  95. with MockVimBuffers( [ current_buffer, other_buffer ], [ current_buffer ] ):
  96. vimsupport.SetLocationListsForBuffer( 1, diagnostics )
  97. vim_eval.assert_not_called()
  98. @patch( 'vim.eval', new_callable = ExtendedMock, side_effect = [ -1, 1 ] )
  99. def test_SetLocationListsForBuffer_NotVisible( self, vim_eval ):
  100. diagnostics = [ {
  101. 'bufnr': 3,
  102. 'filename': 'some_filename',
  103. 'lnum': 5,
  104. 'col': 22,
  105. 'type': 'E',
  106. 'valid': 1
  107. } ]
  108. current_buffer = VimBuffer( '/test', number = 3 )
  109. other_buffer = VimBuffer( '/notcurrent', number = 1 )
  110. with MockVimBuffers( [ current_buffer, other_buffer ], [ current_buffer ] ):
  111. vimsupport.SetLocationListsForBuffer( 1, diagnostics )
  112. vim_eval.assert_not_called()
  113. @patch( 'ycm.vimsupport.WinIDForWindow', side_effect = range( 1001, 1010 ) )
  114. @patch( 'vim.eval', new_callable = ExtendedMock, side_effect = [ -1, 1 ] )
  115. def test_SetLocationListsForBuffer_MultipleWindows( self, vim_eval, *args ):
  116. diagnostics = [ {
  117. 'bufnr': 3,
  118. 'filename': 'some_filename',
  119. 'lnum': 5,
  120. 'col': 22,
  121. 'type': 'E',
  122. 'valid': 1
  123. } ]
  124. current_buffer = VimBuffer( '/test', number = 3 )
  125. other_buffer = VimBuffer( '/notcurrent', number = 1 )
  126. with MockVimBuffers( [ current_buffer, other_buffer ],
  127. [ current_buffer, other_buffer ] ):
  128. vimsupport.SetLocationListsForBuffer( 1, diagnostics )
  129. vim_eval.assert_has_exact_calls( [
  130. call( 'setloclist( 1001, [], " ", { "title": "ycm_loc", "items": '
  131. f'{ json.dumps( diagnostics ) } }} )' ),
  132. call( 'getloclist( 1001, { "nr": "$", "id": 0 } ).id' ),
  133. ] )
  134. @patch( 'ycm.vimsupport.WinIDForWindow', side_effect = range( 1001, 1010 ) )
  135. @patch( 'vim.eval', new_callable = ExtendedMock )
  136. def test_SetLocationList( self, vim_eval, *args ):
  137. diagnostics = [ {
  138. 'bufnr': 3,
  139. 'filename': 'some_filename',
  140. 'lnum': 5,
  141. 'col': 22,
  142. 'type': 'E',
  143. 'valid': 1
  144. } ]
  145. current_buffer = VimBuffer( '/test', number = 3 )
  146. with MockVimBuffers( [ current_buffer ], [ current_buffer ], ( 1, 1 ) ):
  147. vimsupport.SetLocationList( diagnostics )
  148. vim_eval.assert_has_exact_calls( [
  149. call( 'setloclist( 1001, [], " ", { "title": "ycm_loc", "items": '
  150. '[{"bufnr": 3, "filename": "some_filename", "lnum": '
  151. '5, "col": 22, "type": "E", "valid": 1}] } )' ),
  152. call( 'getloclist( 1001, { "nr": "$", "id": 0 } ).id' ),
  153. ] )
  154. @patch( 'ycm.vimsupport.WinIDForWindow', side_effect = range( 1001, 1010 ) )
  155. @patch( 'vim.eval', new_callable = ExtendedMock )
  156. def test_SetLocationList_NotCurrent( self, vim_eval, *args ):
  157. diagnostics = [ {
  158. 'bufnr': 3,
  159. 'filename': 'some_filename',
  160. 'lnum': 5,
  161. 'col': 22,
  162. 'type': 'E',
  163. 'valid': 1
  164. } ]
  165. current_buffer = VimBuffer( '/test', number = 3 )
  166. other_buffer = VimBuffer( '/notcurrent', number = 1 )
  167. with MockVimBuffers( [ current_buffer, other_buffer ],
  168. [ current_buffer, other_buffer ],
  169. ( 1, 1 ) ):
  170. vimsupport.SetLocationList( diagnostics )
  171. # This version does not check the current
  172. # buffer and just sets the current win
  173. vim_eval.assert_has_exact_calls( [
  174. call( 'setloclist( 1001, [], " ", { "title": "ycm_loc", "items": '
  175. '[{"bufnr": 3, "filename": "some_filename", "lnum": 5, "col": 22, '
  176. '"type": "E", "valid": 1}] } )' ),
  177. call( 'getloclist( 1001, { "nr": "$", "id": 0 } ).id' ),
  178. ] )
  179. @patch( 'ycm.vimsupport.VariableExists', return_value = True )
  180. @patch( 'ycm.vimsupport.SetFittingHeightForCurrentWindow' )
  181. @patch( 'vim.command', new_callable = ExtendedMock )
  182. def test_OpenLocationList(
  183. self, vim_command, fitting_height, variable_exists ):
  184. vimsupport.OpenLocationList( focus = False, autoclose = True )
  185. vim_command.assert_has_exact_calls( [
  186. call( 'lopen' ),
  187. call( 'augroup ycmlocation' ),
  188. call( 'autocmd! * <buffer>' ),
  189. call( 'autocmd WinLeave <buffer> '
  190. 'if bufnr( "%" ) == expand( "<abuf>" ) | q | endif '
  191. '| autocmd! ycmlocation' ),
  192. call( 'augroup END' ),
  193. call( 'doautocmd User YcmLocationOpened' ),
  194. call( 'silent! wincmd p' )
  195. ] )
  196. fitting_height.assert_called_once_with()
  197. variable_exists.assert_called_once_with( '#User#YcmLocationOpened' )
  198. @patch( 'vim.command' )
  199. def test_SetFittingHeightForCurrentWindow_LineWrapOn(
  200. self, vim_command, *args ):
  201. # Create a two lines buffer whose first
  202. # line is longer than the window width.
  203. current_buffer = VimBuffer( 'buffer',
  204. contents = [ 'a' * 140, 'b' * 80 ] )
  205. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  206. vim.current.window.width = 120
  207. vim.current.window.options[ 'wrap' ] = True
  208. vimsupport.SetFittingHeightForCurrentWindow()
  209. vim_command.assert_called_once_with( '3wincmd _' )
  210. @patch( 'vim.command' )
  211. def test_SetFittingHeightForCurrentWindow_NoResize(
  212. self, vim_command, *args ):
  213. # Create a two lines buffer whose first
  214. # line is longer than the window width.
  215. current_buffer = VimBuffer( 'buffer',
  216. contents = [ 'a' * 140, 'b' * 80 ],
  217. vars = { 'ycm_no_resize': 1 } )
  218. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  219. vim.current.window.width = 120
  220. vim.current.window.options[ 'wrap' ] = True
  221. vimsupport.SetFittingHeightForCurrentWindow()
  222. vim_command.assert_not_called()
  223. @patch( 'vim.command' )
  224. def test_SetFittingHeightForCurrentWindow_LineWrapOff(
  225. self, vim_command, *args ):
  226. # Create a two lines buffer whose first
  227. # line is longer than the window width.
  228. current_buffer = VimBuffer( 'buffer',
  229. contents = [ 'a' * 140, 'b' * 80 ] )
  230. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  231. vim.current.window.width = 120
  232. vim.current.window.options[ 'wrap' ] = False
  233. vimsupport.SetFittingHeightForCurrentWindow()
  234. vim_command.assert_called_once_with( '2wincmd _' )
  235. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  236. def test_ReplaceChunk_SingleLine_Repl_1( self ):
  237. # Replace with longer range
  238. result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] )
  239. start, end = _BuildLocations( 1, 11, 1, 17 )
  240. vimsupport.ReplaceChunk( start, end, 'pie', result_buffer )
  241. AssertBuffersAreEqualAsBytes( [ 'This is a pie' ], result_buffer )
  242. # and replace again
  243. start, end = _BuildLocations( 1, 10, 1, 11 )
  244. vimsupport.ReplaceChunk( start, end, ' piece of ', result_buffer )
  245. AssertBuffersAreEqualAsBytes( [ 'This is a piece of pie' ], result_buffer )
  246. # and once more, for luck
  247. start, end = _BuildLocations( 1, 1, 1, 5 )
  248. vimsupport.ReplaceChunk( start, end, 'How long', result_buffer )
  249. AssertBuffersAreEqualAsBytes( [ 'How long is a piece of pie' ],
  250. result_buffer )
  251. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  252. def test_ReplaceChunk_SingleLine_Repl_2( self ):
  253. # Replace with shorter range
  254. result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] )
  255. start, end = _BuildLocations( 1, 11, 1, 17 )
  256. vimsupport.ReplaceChunk( start, end, 'test', result_buffer )
  257. AssertBuffersAreEqualAsBytes( [ 'This is a test' ], result_buffer )
  258. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  259. def test_ReplaceChunk_SingleLine_Repl_3( self ):
  260. # Replace with equal range
  261. result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] )
  262. start, end = _BuildLocations( 1, 6, 1, 8 )
  263. vimsupport.ReplaceChunk( start, end, 'be', result_buffer )
  264. AssertBuffersAreEqualAsBytes( [ 'This be a string' ], result_buffer )
  265. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  266. def test_ReplaceChunk_SingleLine_Add_1( self ):
  267. # Insert at start
  268. result_buffer = VimBuffer( 'buffer', contents = [ 'is a string' ] )
  269. start, end = _BuildLocations( 1, 1, 1, 1 )
  270. vimsupport.ReplaceChunk( start, end, 'This ', result_buffer )
  271. AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer )
  272. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  273. def test_ReplaceChunk_SingleLine_Add_2( self ):
  274. # Insert at end
  275. result_buffer = VimBuffer( 'buffer', contents = [ 'This is a ' ] )
  276. start, end = _BuildLocations( 1, 11, 1, 11 )
  277. vimsupport.ReplaceChunk( start, end, 'string', result_buffer )
  278. AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer )
  279. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  280. def test_ReplaceChunk_SingleLine_Add_3( self ):
  281. # Insert in the middle
  282. result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] )
  283. start, end = _BuildLocations( 1, 8, 1, 8 )
  284. vimsupport.ReplaceChunk( start, end, ' not', result_buffer )
  285. AssertBuffersAreEqualAsBytes( [ 'This is not a string' ], result_buffer )
  286. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  287. def test_ReplaceChunk_SingleLine_Del_1( self ):
  288. # Delete from start
  289. result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] )
  290. start, end = _BuildLocations( 1, 1, 1, 6 )
  291. vimsupport.ReplaceChunk( start, end, '', result_buffer )
  292. AssertBuffersAreEqualAsBytes( [ 'is a string' ], result_buffer )
  293. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  294. def test_ReplaceChunk_SingleLine_Del_2( self ):
  295. # Delete from end
  296. result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] )
  297. start, end = _BuildLocations( 1, 10, 1, 18 )
  298. vimsupport.ReplaceChunk( start, end, '', result_buffer )
  299. AssertBuffersAreEqualAsBytes( [ 'This is a' ], result_buffer )
  300. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  301. def test_ReplaceChunk_SingleLine_Del_3( self ):
  302. # Delete from middle
  303. result_buffer = VimBuffer( 'buffer', contents = [ 'This is not a string' ] )
  304. start, end = _BuildLocations( 1, 9, 1, 13 )
  305. vimsupport.ReplaceChunk( start, end, '', result_buffer )
  306. AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer )
  307. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  308. def test_ReplaceChunk_SingleLine_Unicode_ReplaceUnicodeChars( self ):
  309. # Replace Unicode characters.
  310. result_buffer = VimBuffer(
  311. 'buffer', contents = [ 'This Uniçø∂‰ string is in the middle' ] )
  312. start, end = _BuildLocations( 1, 6, 1, 20 )
  313. vimsupport.ReplaceChunk( start, end, 'Unicode ', result_buffer )
  314. AssertBuffersAreEqualAsBytes( [ 'This Unicode string is in the middle' ],
  315. result_buffer )
  316. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  317. def test_ReplaceChunk_SingleLine_Unicode_ReplaceAfterUnicode( self ):
  318. # Replace ASCII characters after Unicode characters in the line.
  319. result_buffer = VimBuffer(
  320. 'buffer', contents = [ 'This Uniçø∂‰ string is in the middle' ] )
  321. start, end = _BuildLocations( 1, 30, 1, 43 )
  322. vimsupport.ReplaceChunk( start, end, 'fåke', result_buffer )
  323. AssertBuffersAreEqualAsBytes( [ 'This Uniçø∂‰ string is fåke' ],
  324. result_buffer )
  325. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  326. def test_ReplaceChunk_SingleLine_Unicode_Grown( self ):
  327. # Replace ASCII characters after Unicode characters in the line.
  328. result_buffer = VimBuffer( 'buffer', contents = [ 'a' ] )
  329. start, end = _BuildLocations( 1, 1, 1, 2 )
  330. vimsupport.ReplaceChunk( start, end, 'å', result_buffer )
  331. AssertBuffersAreEqualAsBytes( [ 'å' ], result_buffer )
  332. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  333. def test_ReplaceChunk_RemoveSingleLine( self ):
  334. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  335. 'aBa',
  336. 'aCa' ] )
  337. start, end = _BuildLocations( 2, 1, 3, 1 )
  338. vimsupport.ReplaceChunk( start, end, '', result_buffer )
  339. # First line is not affected.
  340. AssertBuffersAreEqualAsBytes( [ 'aAa',
  341. 'aCa' ], result_buffer )
  342. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  343. def test_ReplaceChunk_SingleToMultipleLines( self ):
  344. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  345. 'aBa',
  346. 'aCa' ] )
  347. start, end = _BuildLocations( 2, 3, 2, 4 )
  348. vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer )
  349. AssertBuffersAreEqualAsBytes( [ 'aAa',
  350. 'aBcccc',
  351. 'aCa' ], result_buffer )
  352. # now make another change to the second line
  353. start, end = _BuildLocations( 2, 2, 2, 2 )
  354. vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer )
  355. AssertBuffersAreEqualAsBytes( [ 'aAa',
  356. 'aEb',
  357. 'bFBcccc',
  358. 'aCa' ], result_buffer )
  359. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  360. def test_ReplaceChunk_SingleToMultipleLines2( self ):
  361. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  362. 'aBa',
  363. 'aCa' ] )
  364. start, end = _BuildLocations( 2, 2, 2, 2 )
  365. vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nG', result_buffer )
  366. AssertBuffersAreEqualAsBytes( [ 'aAa',
  367. 'aEb',
  368. 'bFb',
  369. 'GBa',
  370. 'aCa' ], result_buffer )
  371. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  372. def test_ReplaceChunk_SingleToMultipleLines3( self ):
  373. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  374. 'aBa',
  375. 'aCa' ] )
  376. start, end = _BuildLocations( 2, 2, 2, 2 )
  377. vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbGb', result_buffer )
  378. AssertBuffersAreEqualAsBytes( [ 'aAa',
  379. 'aEb',
  380. 'bFb',
  381. 'bGbBa',
  382. 'aCa' ], result_buffer )
  383. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  384. def test_ReplaceChunk_SingleToMultipleLinesReplace( self ):
  385. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  386. 'aBa',
  387. 'aCa' ] )
  388. start, end = _BuildLocations( 1, 2, 1, 4 )
  389. vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbGb', result_buffer )
  390. AssertBuffersAreEqualAsBytes( [ 'aEb',
  391. 'bFb',
  392. 'bGb',
  393. 'aBa',
  394. 'aCa' ], result_buffer )
  395. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  396. def test_ReplaceChunk_SingleToMultipleLinesReplace_2( self ):
  397. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  398. 'aBa',
  399. 'aCa' ] )
  400. start, end = _BuildLocations( 1, 4, 1, 4 )
  401. vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer )
  402. AssertBuffersAreEqualAsBytes( [ 'aAacccc',
  403. 'aBa',
  404. 'aCa', ], result_buffer )
  405. # now do a subsequent change (insert in the middle of the first line)
  406. start, end = _BuildLocations( 1, 2, 1, 4 )
  407. vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbGb', result_buffer )
  408. AssertBuffersAreEqualAsBytes( [ 'aEb',
  409. 'bFb',
  410. 'bGbcccc',
  411. 'aBa',
  412. 'aCa' ], result_buffer )
  413. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  414. def test_ReplaceChunk_MultipleLinesToSingleLine( self ):
  415. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  416. 'aBa',
  417. 'aCaaaa' ] )
  418. start, end = _BuildLocations( 3, 4, 3, 5 )
  419. vimsupport.ReplaceChunk( start, end, 'dd\ndd', result_buffer )
  420. AssertBuffersAreEqualAsBytes( [ 'aAa',
  421. 'aBa',
  422. 'aCadd',
  423. 'ddaa' ], result_buffer )
  424. # make another modification applying offsets
  425. start, end = _BuildLocations( 3, 3, 3, 4 )
  426. vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer )
  427. AssertBuffersAreEqualAsBytes( [ 'aAa',
  428. 'aBa',
  429. 'aCccccdd',
  430. 'ddaa' ], result_buffer )
  431. # and another, for luck
  432. start, end = _BuildLocations( 2, 2, 3, 2 )
  433. vimsupport.ReplaceChunk( start, end, 'E', result_buffer )
  434. AssertBuffersAreEqualAsBytes( [ 'aAa',
  435. 'aECccccdd',
  436. 'ddaa' ], result_buffer )
  437. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  438. def test_ReplaceChunk_MultipleLinesToSameMultipleLines( self ):
  439. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  440. 'aBa',
  441. 'aCa',
  442. 'aDe' ] )
  443. start, end = _BuildLocations( 2, 2, 3, 2 )
  444. vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer )
  445. AssertBuffersAreEqualAsBytes( [ 'aAa',
  446. 'aEb',
  447. 'bFCa',
  448. 'aDe' ], result_buffer )
  449. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  450. def test_ReplaceChunk_MultipleLinesToMoreMultipleLines( self ):
  451. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  452. 'aBa',
  453. 'aCa',
  454. 'aDe' ] )
  455. start, end = _BuildLocations( 2, 2, 3, 2 )
  456. vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbG', result_buffer )
  457. AssertBuffersAreEqualAsBytes( [ 'aAa',
  458. 'aEb',
  459. 'bFb',
  460. 'bGCa',
  461. 'aDe' ], result_buffer )
  462. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  463. def test_ReplaceChunk_MultipleLinesToLessMultipleLines( self ):
  464. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  465. 'aBa',
  466. 'aCa',
  467. 'aDe' ] )
  468. start, end = _BuildLocations( 1, 2, 3, 2 )
  469. vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer )
  470. AssertBuffersAreEqualAsBytes( [ 'aEb',
  471. 'bFCa',
  472. 'aDe' ], result_buffer )
  473. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  474. def test_ReplaceChunk_MultipleLinesToEvenLessMultipleLines( self ):
  475. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  476. 'aBa',
  477. 'aCa',
  478. 'aDe' ] )
  479. start, end = _BuildLocations( 1, 2, 4, 2 )
  480. vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer )
  481. AssertBuffersAreEqualAsBytes( [ 'aEb',
  482. 'bFDe' ], result_buffer )
  483. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  484. def test_ReplaceChunk_SpanBufferEdge( self ):
  485. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  486. 'aBa',
  487. 'aCa' ] )
  488. start, end = _BuildLocations( 1, 1, 1, 3 )
  489. vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer )
  490. AssertBuffersAreEqualAsBytes( [ 'bDba',
  491. 'aBa',
  492. 'aCa' ], result_buffer )
  493. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  494. def test_ReplaceChunk_DeleteTextInLine( self ):
  495. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  496. 'aBa',
  497. 'aCa' ] )
  498. start, end = _BuildLocations( 2, 2, 2, 3 )
  499. vimsupport.ReplaceChunk( start, end, '', result_buffer )
  500. AssertBuffersAreEqualAsBytes( [ 'aAa',
  501. 'aa',
  502. 'aCa' ], result_buffer )
  503. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  504. def test_ReplaceChunk_AddTextInLine( self ):
  505. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  506. 'aBa',
  507. 'aCa' ] )
  508. start, end = _BuildLocations( 2, 2, 2, 2 )
  509. vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer )
  510. AssertBuffersAreEqualAsBytes( [ 'aAa',
  511. 'abDbBa',
  512. 'aCa' ], result_buffer )
  513. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  514. def test_ReplaceChunk_ReplaceTextInLine( self ):
  515. result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
  516. 'aBa',
  517. 'aCa' ] )
  518. start, end = _BuildLocations( 2, 2, 2, 3 )
  519. vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer )
  520. AssertBuffersAreEqualAsBytes( [ 'aAa',
  521. 'abDba',
  522. 'aCa' ], result_buffer )
  523. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  524. def test_ReplaceChunk_NewlineChunk( self ):
  525. result_buffer = VimBuffer( 'buffer', contents = [ 'first line',
  526. 'second line' ] )
  527. start, end = _BuildLocations( 1, 11, 2, 1 )
  528. vimsupport.ReplaceChunk( start, end, '\n', result_buffer )
  529. AssertBuffersAreEqualAsBytes( [ 'first line',
  530. 'second line' ], result_buffer )
  531. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  532. def test_ReplaceChunk_BeyondEndOfFile( self ):
  533. result_buffer = VimBuffer( 'buffer', contents = [ 'first line',
  534. 'second line' ] )
  535. start, end = _BuildLocations( 1, 11, 3, 1 )
  536. vimsupport.ReplaceChunk( start, end, '\n', result_buffer )
  537. AssertBuffersAreEqualAsBytes( [ 'first line' ], result_buffer )
  538. @patch( 'vim.current.window.cursor', ( 1, 3 ) )
  539. def test_ReplaceChunk_CursorPosition( self ):
  540. result_buffer = VimBuffer( 'buffer', contents = [ 'bar' ] )
  541. start, end = _BuildLocations( 1, 1, 1, 1 )
  542. vimsupport.ReplaceChunk( start,
  543. end,
  544. 'xyz\nfoo',
  545. result_buffer )
  546. AssertBuffersAreEqualAsBytes( [ 'xyz', 'foobar' ], result_buffer )
  547. # Cursor line is 0-based.
  548. assert_that( vimsupport.CurrentLineAndColumn(), contains_exactly( 1, 6 ) )
  549. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  550. def test_ReplaceChunksInBuffer_SortedChunks( self ):
  551. chunks = [
  552. _BuildChunk( 1, 4, 1, 4, '(' ),
  553. _BuildChunk( 1, 11, 1, 11, ')' )
  554. ]
  555. result_buffer = VimBuffer( 'buffer', contents = [ 'CT<10 >> 2> ct' ] )
  556. vimsupport.ReplaceChunksInBuffer( chunks, result_buffer )
  557. AssertBuffersAreEqualAsBytes( [ 'CT<(10 >> 2)> ct' ], result_buffer )
  558. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  559. def test_ReplaceChunksInBuffer_UnsortedChunks( self ):
  560. chunks = [
  561. _BuildChunk( 1, 11, 1, 11, ')' ),
  562. _BuildChunk( 1, 4, 1, 4, '(' )
  563. ]
  564. result_buffer = VimBuffer( 'buffer', contents = [ 'CT<10 >> 2> ct' ] )
  565. vimsupport.ReplaceChunksInBuffer( chunks, result_buffer )
  566. AssertBuffersAreEqualAsBytes( [ 'CT<(10 >> 2)> ct' ], result_buffer )
  567. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  568. def test_ReplaceChunksInBuffer_LineOverlappingChunks( self ):
  569. chunks = [
  570. _BuildChunk( 1, 11, 2, 1, '\n ' ),
  571. _BuildChunk( 2, 12, 3, 1, '\n ' ),
  572. _BuildChunk( 3, 11, 4, 1, '\n ' )
  573. ]
  574. result_buffer = VimBuffer( 'buffer', contents = [ 'first line',
  575. 'second line',
  576. 'third line',
  577. 'fourth line' ] )
  578. vimsupport.ReplaceChunksInBuffer( chunks, result_buffer )
  579. AssertBuffersAreEqualAsBytes( [ 'first line',
  580. ' second line',
  581. ' third line',
  582. ' fourth line' ], result_buffer )
  583. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  584. def test_ReplaceChunksInBuffer_OutdentChunks( self ):
  585. chunks = [
  586. _BuildChunk( 1, 1, 1, 5, ' ' ),
  587. _BuildChunk( 1, 15, 2, 9, '\n ' ),
  588. _BuildChunk( 2, 20, 3, 3, '\n' )
  589. ]
  590. result_buffer = VimBuffer( 'buffer', contents = [ ' first line',
  591. ' second line',
  592. ' third line' ] )
  593. vimsupport.ReplaceChunksInBuffer( chunks, result_buffer )
  594. AssertBuffersAreEqualAsBytes( [ ' first line',
  595. ' second line',
  596. ' third line' ], result_buffer )
  597. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  598. def test_ReplaceChunksInBuffer_OneLineIndentingChunks( self ):
  599. chunks = [
  600. _BuildChunk( 1, 8, 2, 1, '\n ' ),
  601. _BuildChunk( 2, 9, 2, 10, '\n ' ),
  602. _BuildChunk( 2, 19, 2, 20, '\n ' )
  603. ]
  604. result_buffer = VimBuffer( 'buffer', contents = [ 'class {',
  605. 'method { statement }',
  606. '}' ] )
  607. vimsupport.ReplaceChunksInBuffer( chunks, result_buffer )
  608. AssertBuffersAreEqualAsBytes( [ 'class {',
  609. ' method {',
  610. ' statement',
  611. ' }',
  612. '}' ], result_buffer )
  613. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  614. def test_ReplaceChunksInBuffer_SameLocation( self ):
  615. chunks = [
  616. _BuildChunk( 1, 1, 1, 1, 'this ' ),
  617. _BuildChunk( 1, 1, 1, 1, 'is ' ),
  618. _BuildChunk( 1, 1, 1, 1, 'pure ' )
  619. ]
  620. result_buffer = VimBuffer( 'buffer', contents = [ 'folly' ] )
  621. vimsupport.ReplaceChunksInBuffer( chunks, result_buffer )
  622. AssertBuffersAreEqualAsBytes( [ 'this is pure folly' ], result_buffer )
  623. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  624. @patch( 'ycm.vimsupport.VariableExists', return_value = False )
  625. @patch( 'ycm.vimsupport.SetFittingHeightForCurrentWindow' )
  626. @patch( 'vim.command', new_callable = ExtendedMock )
  627. @patch( 'ycm.vimsupport.GetBufferNumberForFilename',
  628. return_value = 1,
  629. new_callable = ExtendedMock )
  630. @patch( 'ycm.vimsupport.BufferIsVisible',
  631. return_value = True,
  632. new_callable = ExtendedMock )
  633. @patch( 'ycm.vimsupport.OpenFilename' )
  634. @patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
  635. @patch( 'vim.eval', new_callable = ExtendedMock )
  636. def test_ReplaceChunks_SingleFile_Open( self,
  637. vim_eval,
  638. post_vim_message,
  639. open_filename,
  640. buffer_is_visible,
  641. get_buffer_number_for_filename,
  642. *args ):
  643. single_buffer_name = os.path.realpath( 'single_file' )
  644. chunks = [
  645. _BuildChunk( 1, 1, 2, 1, 'replacement', single_buffer_name )
  646. ]
  647. result_buffer = VimBuffer(
  648. single_buffer_name,
  649. contents = [
  650. 'line1',
  651. 'line2',
  652. 'line3'
  653. ]
  654. )
  655. with patch( 'vim.buffers', [ None, result_buffer, None ] ):
  656. vimsupport.ReplaceChunks( chunks )
  657. # Ensure that we applied the replacement correctly
  658. assert_that( result_buffer.GetLines(), contains_exactly(
  659. 'replacementline2',
  660. 'line3',
  661. ) )
  662. # GetBufferNumberForFilename is called twice:
  663. # - once to the check if we would require opening the file (so that we can
  664. # raise a warning)
  665. # - once whilst applying the changes
  666. get_buffer_number_for_filename.assert_has_exact_calls( [
  667. call( single_buffer_name ),
  668. call( single_buffer_name ),
  669. ] )
  670. # BufferIsVisible is called twice for the same reasons as above
  671. buffer_is_visible.assert_has_exact_calls( [
  672. call( 1 ),
  673. call( 1 ),
  674. ] )
  675. # we don't attempt to open any files
  676. open_filename.assert_not_called()
  677. qflist = json.dumps( [ {
  678. 'bufnr': 1,
  679. 'filename': single_buffer_name,
  680. 'lnum': 1,
  681. 'col': 1,
  682. 'text': 'replacement',
  683. 'type': 'F'
  684. } ] )
  685. # But we do set the quickfix list
  686. vim_eval.assert_has_exact_calls( [
  687. call( f'setqflist( { qflist } )' )
  688. ] )
  689. # And it is ReplaceChunks that prints the message showing the number of
  690. # changes
  691. post_vim_message.assert_has_exact_calls( [
  692. call( 'Applied 1 changes', warning = False ),
  693. ] )
  694. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  695. @patch( 'ycm.vimsupport.VariableExists', return_value = False )
  696. @patch( 'ycm.vimsupport.SetFittingHeightForCurrentWindow' )
  697. @patch( 'ycm.vimsupport.GetBufferNumberForFilename',
  698. side_effect = [ -1, -1, 1 ],
  699. new_callable = ExtendedMock )
  700. @patch( 'ycm.vimsupport.BufferIsVisible',
  701. side_effect = [ False, False, True ],
  702. new_callable = ExtendedMock )
  703. @patch( 'ycm.vimsupport.OpenFilename',
  704. new_callable = ExtendedMock )
  705. @patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
  706. @patch( 'ycm.vimsupport.Confirm',
  707. return_value = True,
  708. new_callable = ExtendedMock )
  709. @patch( 'vim.eval', return_value = 10, new_callable = ExtendedMock )
  710. @patch( 'vim.command', new_callable = ExtendedMock )
  711. def test_ReplaceChunks_SingleFile_NotOpen( self,
  712. vim_command,
  713. vim_eval,
  714. confirm,
  715. post_vim_message,
  716. open_filename,
  717. buffer_is_visible,
  718. get_buffer_number_for_filename,
  719. set_fitting_height,
  720. variable_exists ):
  721. single_buffer_name = os.path.realpath( 'single_file' )
  722. chunks = [
  723. _BuildChunk( 1, 1, 2, 1, 'replacement', single_buffer_name )
  724. ]
  725. result_buffer = VimBuffer(
  726. single_buffer_name,
  727. contents = [
  728. 'line1',
  729. 'line2',
  730. 'line3'
  731. ]
  732. )
  733. with patch( 'vim.buffers', [ None, result_buffer, None ] ):
  734. vimsupport.ReplaceChunks( chunks )
  735. # We checked if it was OK to open the file
  736. confirm.assert_has_exact_calls( [
  737. call( vimsupport.FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( 1 ) )
  738. ] )
  739. # Ensure that we applied the replacement correctly
  740. assert_that( result_buffer.GetLines(), contains_exactly(
  741. 'replacementline2',
  742. 'line3',
  743. ) )
  744. # GetBufferNumberForFilename is called 3 times. The return values are set in
  745. # the @patch call above:
  746. # - once to the check if we would require opening the file (so that we can
  747. # raise a warning) (-1 return)
  748. # - once whilst applying the changes (-1 return)
  749. # - finally after calling OpenFilename (1 return)
  750. get_buffer_number_for_filename.assert_has_exact_calls( [
  751. call( single_buffer_name ),
  752. call( single_buffer_name ),
  753. call( single_buffer_name ),
  754. ] )
  755. # BufferIsVisible is called 3 times for the same reasons as above, with the
  756. # return of each one
  757. buffer_is_visible.assert_has_exact_calls( [
  758. call( -1 ),
  759. call( -1 ),
  760. call( 1 ),
  761. ] )
  762. # We open 'single_file' as expected.
  763. open_filename.assert_called_with( single_buffer_name, {
  764. 'focus': True,
  765. 'fix': True,
  766. 'size': 10
  767. } )
  768. # And close it again, then show the quickfix window.
  769. vim_command.assert_has_exact_calls( [
  770. call( 'lclose' ),
  771. call( 'hide' ),
  772. ] )
  773. qflist = json.dumps( [ {
  774. 'bufnr': 1,
  775. 'filename': single_buffer_name,
  776. 'lnum': 1,
  777. 'col': 1,
  778. 'text': 'replacement',
  779. 'type': 'F'
  780. } ] )
  781. # And update the quickfix list
  782. vim_eval.assert_has_exact_calls( [
  783. call( '&previewheight' ),
  784. call( f'setqflist( { qflist } )' )
  785. ] )
  786. # And it is ReplaceChunks that prints the message showing the number of
  787. # changes
  788. post_vim_message.assert_has_exact_calls( [
  789. call( 'Applied 1 changes', warning = False ),
  790. ] )
  791. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  792. @patch( 'ycm.vimsupport.VariableExists', return_value = False )
  793. @patch( 'ycm.vimsupport.SetFittingHeightForCurrentWindow' )
  794. @patch( 'ycm.vimsupport.GetBufferNumberForFilename',
  795. side_effect = [ -1, 1 ],
  796. new_callable = ExtendedMock )
  797. @patch( 'ycm.vimsupport.BufferIsVisible',
  798. side_effect = [ False, True ],
  799. new_callable = ExtendedMock )
  800. @patch( 'ycm.vimsupport.OpenFilename',
  801. new_callable = ExtendedMock )
  802. @patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
  803. @patch( 'ycm.vimsupport.Confirm',
  804. return_value = True,
  805. new_callable = ExtendedMock )
  806. @patch( 'vim.eval', return_value = 10, new_callable = ExtendedMock )
  807. @patch( 'vim.command', new_callable = ExtendedMock )
  808. def test_ReplaceChunks_SingleFile_NotOpen_Silent(
  809. self,
  810. vim_command,
  811. vim_eval,
  812. confirm,
  813. post_vim_message,
  814. open_filename,
  815. buffer_is_visible,
  816. get_buffer_number_for_filename,
  817. set_fitting_height,
  818. variable_exists ):
  819. # This test is the same as ReplaceChunks_SingleFile_NotOpen_test, but we
  820. # pass the silent flag, as used by post-complete actions, and shows the
  821. # stuff we _don't_ call in that case.
  822. single_buffer_name = os.path.realpath( 'single_file' )
  823. chunks = [
  824. _BuildChunk( 1, 1, 2, 1, 'replacement', single_buffer_name )
  825. ]
  826. result_buffer = VimBuffer(
  827. single_buffer_name,
  828. contents = [
  829. 'line1',
  830. 'line2',
  831. 'line3'
  832. ]
  833. )
  834. with patch( 'vim.buffers', [ None, result_buffer, None ] ):
  835. vimsupport.ReplaceChunks( chunks, silent=True )
  836. # We didn't check if it was OK to open the file (silent)
  837. confirm.assert_not_called()
  838. # Ensure that we applied the replacement correctly
  839. assert_that( result_buffer.GetLines(), contains_exactly(
  840. 'replacementline2',
  841. 'line3',
  842. ) )
  843. # GetBufferNumberForFilename is called 2 times. The return values are set in
  844. # the @patch call above:
  845. # - once whilst applying the changes (-1 return)
  846. # - finally after calling OpenFilename (1 return)
  847. get_buffer_number_for_filename.assert_has_exact_calls( [
  848. call( single_buffer_name ),
  849. call( single_buffer_name ),
  850. ] )
  851. # BufferIsVisible is called 2 times for the same reasons as above, with the
  852. # return of each one
  853. buffer_is_visible.assert_has_exact_calls( [
  854. call( -1 ),
  855. call( 1 ),
  856. ] )
  857. # We open 'single_file' as expected.
  858. open_filename.assert_called_with( single_buffer_name, {
  859. 'focus': True,
  860. 'fix': True,
  861. 'size': 10
  862. } )
  863. # And close it again, but don't show the quickfix window
  864. vim_command.assert_has_exact_calls( [
  865. call( 'lclose' ),
  866. call( 'hide' ),
  867. ] )
  868. set_fitting_height.assert_not_called()
  869. # But we _don't_ update the QuickFix list
  870. vim_eval.assert_has_exact_calls( [
  871. call( '&previewheight' ),
  872. ] )
  873. # And we don't print a message either
  874. post_vim_message.assert_not_called()
  875. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  876. @patch( 'ycm.vimsupport.GetBufferNumberForFilename',
  877. side_effect = [ -1, -1, 1 ],
  878. new_callable = ExtendedMock )
  879. @patch( 'ycm.vimsupport.BufferIsVisible',
  880. side_effect = [ False, False, True ],
  881. new_callable = ExtendedMock )
  882. @patch( 'ycm.vimsupport.OpenFilename',
  883. new_callable = ExtendedMock )
  884. @patch( 'ycm.vimsupport.PostVimMessage',
  885. new_callable = ExtendedMock )
  886. @patch( 'ycm.vimsupport.Confirm',
  887. return_value = False,
  888. new_callable = ExtendedMock )
  889. @patch( 'vim.eval',
  890. return_value = 10,
  891. new_callable = ExtendedMock )
  892. @patch( 'vim.command', new_callable = ExtendedMock )
  893. def test_ReplaceChunks_User_Declines_To_Open_File(
  894. self,
  895. vim_command,
  896. vim_eval,
  897. confirm,
  898. post_vim_message,
  899. open_filename,
  900. buffer_is_visible,
  901. get_buffer_number_for_filename ):
  902. # Same as above, except the user selects Cancel when asked if they should
  903. # allow us to open lots of (ahem, 1) file.
  904. single_buffer_name = os.path.realpath( 'single_file' )
  905. chunks = [
  906. _BuildChunk( 1, 1, 2, 1, 'replacement', single_buffer_name )
  907. ]
  908. result_buffer = VimBuffer(
  909. single_buffer_name,
  910. contents = [
  911. 'line1',
  912. 'line2',
  913. 'line3'
  914. ]
  915. )
  916. with patch( 'vim.buffers', [ None, result_buffer, None ] ):
  917. vimsupport.ReplaceChunks( chunks )
  918. # We checked if it was OK to open the file
  919. confirm.assert_has_exact_calls( [
  920. call( vimsupport.FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( 1 ) )
  921. ] )
  922. # Ensure that buffer is not changed
  923. assert_that( result_buffer.GetLines(), contains_exactly(
  924. 'line1',
  925. 'line2',
  926. 'line3',
  927. ) )
  928. # GetBufferNumberForFilename is called once. The return values are set in
  929. # the @patch call above:
  930. # - once to the check if we would require opening the file (so that we can
  931. # raise a warning) (-1 return)
  932. get_buffer_number_for_filename.assert_has_exact_calls( [
  933. call( single_buffer_name ),
  934. ] )
  935. # BufferIsVisible is called once for the above file, which wasn't visible.
  936. buffer_is_visible.assert_has_exact_calls( [
  937. call( -1 ),
  938. ] )
  939. # We don't attempt to open any files or update any quickfix list or anything
  940. # like that
  941. open_filename.assert_not_called()
  942. vim_eval.assert_not_called()
  943. vim_command.assert_not_called()
  944. post_vim_message.assert_not_called()
  945. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  946. @patch( 'ycm.vimsupport.GetBufferNumberForFilename',
  947. side_effect = [ -1, -1, 1 ],
  948. new_callable = ExtendedMock )
  949. # Key difference is here: In the final check, BufferIsVisible returns False
  950. @patch( 'ycm.vimsupport.BufferIsVisible',
  951. side_effect = [ False, False, False ],
  952. new_callable = ExtendedMock )
  953. @patch( 'ycm.vimsupport.OpenFilename',
  954. new_callable = ExtendedMock )
  955. @patch( 'ycm.vimsupport.PostVimMessage',
  956. new_callable = ExtendedMock )
  957. @patch( 'ycm.vimsupport.Confirm',
  958. return_value = True,
  959. new_callable = ExtendedMock )
  960. @patch( 'vim.eval',
  961. return_value = 10,
  962. new_callable = ExtendedMock )
  963. @patch( 'vim.command',
  964. new_callable = ExtendedMock )
  965. def test_ReplaceChunks_User_Aborts_Opening_File(
  966. self,
  967. vim_command,
  968. vim_eval,
  969. confirm,
  970. post_vim_message,
  971. open_filename,
  972. buffer_is_visible,
  973. get_buffer_number_for_filename ):
  974. # Same as above, except the user selects Abort or Quick during the
  975. # "swap-file-found" dialog
  976. single_buffer_name = os.path.realpath( 'single_file' )
  977. chunks = [
  978. _BuildChunk( 1, 1, 2, 1, 'replacement', single_buffer_name )
  979. ]
  980. result_buffer = VimBuffer(
  981. single_buffer_name,
  982. contents = [
  983. 'line1',
  984. 'line2',
  985. 'line3'
  986. ]
  987. )
  988. with patch( 'vim.buffers', [ None, result_buffer, None ] ):
  989. assert_that(
  990. calling( vimsupport.ReplaceChunks ).with_args( chunks ),
  991. raises( RuntimeError,
  992. 'Unable to open file: .+single_file\n'
  993. 'FixIt/Refactor operation aborted prior to completion. '
  994. 'Your files have not been fully updated. '
  995. 'Please use undo commands to revert the applied changes.' ) )
  996. # We checked if it was OK to open the file
  997. confirm.assert_has_exact_calls( [
  998. call( vimsupport.FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( 1 ) )
  999. ] )
  1000. # Ensure that buffer is not changed
  1001. assert_that( result_buffer.GetLines(), contains_exactly(
  1002. 'line1',
  1003. 'line2',
  1004. 'line3',
  1005. ) )
  1006. # We tried to open this file
  1007. open_filename.assert_called_with( single_buffer_name, {
  1008. 'focus': True,
  1009. 'fix': True,
  1010. 'size': 10
  1011. } )
  1012. vim_eval.assert_called_with( "&previewheight" )
  1013. # But raised an exception before issuing the message at the end
  1014. post_vim_message.assert_not_called()
  1015. @patch( 'vim.current.window.cursor', ( 1, 1 ) )
  1016. @patch( 'ycm.vimsupport.VariableExists', return_value = False )
  1017. @patch( 'ycm.vimsupport.SetFittingHeightForCurrentWindow' )
  1018. @patch( 'ycm.vimsupport.GetBufferNumberForFilename', side_effect = [
  1019. 22, # first_file (check)
  1020. -1, # second_file (check)
  1021. 22, # first_file (apply)
  1022. -1, # second_file (apply)
  1023. 19, # second_file (check after open)
  1024. ],
  1025. new_callable = ExtendedMock )
  1026. @patch( 'ycm.vimsupport.BufferIsVisible', side_effect = [
  1027. True, # first_file (check)
  1028. False, # second_file (check)
  1029. True, # first_file (apply)
  1030. False, # second_file (apply)
  1031. True, # side_effect (check after open)
  1032. ],
  1033. new_callable = ExtendedMock )
  1034. @patch( 'ycm.vimsupport.OpenFilename',
  1035. new_callable = ExtendedMock )
  1036. @patch( 'ycm.vimsupport.PostVimMessage',
  1037. new_callable = ExtendedMock )
  1038. @patch( 'ycm.vimsupport.Confirm', return_value = True,
  1039. new_callable = ExtendedMock )
  1040. @patch( 'vim.eval', return_value = 10,
  1041. new_callable = ExtendedMock )
  1042. @patch( 'vim.command',
  1043. new_callable = ExtendedMock )
  1044. def test_ReplaceChunks_MultiFile_Open( self,
  1045. vim_command,
  1046. vim_eval,
  1047. confirm,
  1048. post_vim_message,
  1049. open_filename,
  1050. buffer_is_visible,
  1051. get_buffer_number_for_filename,
  1052. set_fitting_height,
  1053. variable_exists ):
  1054. # Chunks are split across 2 files, one is already open, one isn't
  1055. first_buffer_name = os.path.realpath( '1_first_file' )
  1056. second_buffer_name = os.path.realpath( '2_second_file' )
  1057. chunks = [
  1058. _BuildChunk( 1, 1, 2, 1, 'first_file_replacement ', first_buffer_name ),
  1059. _BuildChunk( 2, 1, 2, 1, 'second_file_replacement ', second_buffer_name ),
  1060. ]
  1061. first_file = VimBuffer(
  1062. first_buffer_name,
  1063. number = 22,
  1064. contents = [
  1065. 'line1',
  1066. 'line2',
  1067. 'line3',
  1068. ]
  1069. )
  1070. second_file = VimBuffer(
  1071. second_buffer_name,
  1072. number = 19,
  1073. contents = [
  1074. 'another line1',
  1075. 'ACME line2',
  1076. ]
  1077. )
  1078. vim_buffers = [ None ] * 23
  1079. vim_buffers[ 22 ] = first_file
  1080. vim_buffers[ 19 ] = second_file
  1081. with patch( 'vim.buffers', vim_buffers ):
  1082. vimsupport.ReplaceChunks( chunks )
  1083. # We checked for the right file names
  1084. get_buffer_number_for_filename.assert_has_exact_calls( [
  1085. call( first_buffer_name ),
  1086. call( second_buffer_name ),
  1087. call( first_buffer_name ),
  1088. call( second_buffer_name ),
  1089. call( second_buffer_name ),
  1090. ] )
  1091. # We checked if it was OK to open the file
  1092. confirm.assert_has_exact_calls( [
  1093. call( vimsupport.FIXIT_OPENING_BUFFERS_MESSAGE_FORMAT.format( 1 ) )
  1094. ] )
  1095. # Ensure that buffers are updated
  1096. assert_that( second_file.GetLines(), contains_exactly(
  1097. 'another line1',
  1098. 'second_file_replacement ACME line2',
  1099. ) )
  1100. assert_that( first_file.GetLines(), contains_exactly(
  1101. 'first_file_replacement line2',
  1102. 'line3',
  1103. ) )
  1104. # We open '2_second_file' as expected.
  1105. open_filename.assert_called_with( second_buffer_name, {
  1106. 'focus': True,
  1107. 'fix': True,
  1108. 'size': 10
  1109. } )
  1110. # And close it again, then show the quickfix window.
  1111. vim_command.assert_has_exact_calls( [
  1112. call( 'lclose' ),
  1113. call( 'hide' ),
  1114. ] )
  1115. qflist = json.dumps( [ {
  1116. 'bufnr': 22,
  1117. 'filename': first_buffer_name,
  1118. 'lnum': 1,
  1119. 'col': 1,
  1120. 'text': 'first_file_replacement ',
  1121. 'type': 'F'
  1122. }, {
  1123. 'bufnr': 19,
  1124. 'filename': second_buffer_name,
  1125. 'lnum': 2,
  1126. 'col': 1,
  1127. 'text': 'second_file_replacement ',
  1128. 'type': 'F'
  1129. } ] )
  1130. # And update the quickfix list with each entry
  1131. vim_eval.assert_has_exact_calls( [
  1132. call( '&previewheight' ),
  1133. call( f'setqflist( { qflist } )' )
  1134. ] )
  1135. # And it is ReplaceChunks that prints the message showing the number of
  1136. # changes
  1137. post_vim_message.assert_has_exact_calls( [
  1138. call( 'Applied 2 changes', warning = False ),
  1139. ] )
  1140. @patch( 'vim.command', new_callable=ExtendedMock )
  1141. @patch( 'vim.current', new_callable=ExtendedMock )
  1142. def test_WriteToPreviewWindow( self, vim_current, vim_command ):
  1143. vim_current.window.options.__getitem__ = MagicMock( return_value = True )
  1144. vimsupport.WriteToPreviewWindow( "test", '' )
  1145. vim_command.assert_has_exact_calls( [
  1146. call( 'silent! pclose!' ),
  1147. call( 'silent! pedit! _TEMP_FILE_' ),
  1148. call( 'silent! wincmd P' ),
  1149. call( 'silent! wincmd p' ) ] )
  1150. vim_current.buffer.__setitem__.assert_called_with(
  1151. slice( None, None, None ), [ 'test' ] )
  1152. vim_current.buffer.options.__setitem__.assert_has_exact_calls( [
  1153. call( 'modifiable', True ),
  1154. call( 'readonly', False ),
  1155. call( 'buftype', 'nofile' ),
  1156. call( 'bufhidden', 'wipe' ),
  1157. call( 'buflisted', False ),
  1158. call( 'swapfile', False ),
  1159. call( 'modifiable', False ),
  1160. call( 'modified', False ),
  1161. call( 'readonly', True ),
  1162. ], any_order = True )
  1163. @patch( 'vim.command', new_callable=ExtendedMock )
  1164. @patch( 'vim.current', new_callable=ExtendedMock )
  1165. def test_WriteToPreviewWindow_Mods( self, vim_current, vim_command ):
  1166. vim_current.window.options.__getitem__ = MagicMock( return_value = True )
  1167. vimsupport.WriteToPreviewWindow( "test", 'tab leftabove' )
  1168. vim_command.assert_has_exact_calls( [
  1169. call( 'silent! pclose!' ),
  1170. call( 'silent! tab leftabove pedit! _TEMP_FILE_' ),
  1171. call( 'silent! wincmd P' ),
  1172. call( 'silent! wincmd p' ) ] )
  1173. vim_current.buffer.__setitem__.assert_called_with(
  1174. slice( None, None, None ), [ 'test' ] )
  1175. vim_current.buffer.options.__setitem__.assert_has_exact_calls( [
  1176. call( 'modifiable', True ),
  1177. call( 'readonly', False ),
  1178. call( 'buftype', 'nofile' ),
  1179. call( 'bufhidden', 'wipe' ),
  1180. call( 'buflisted', False ),
  1181. call( 'swapfile', False ),
  1182. call( 'modifiable', False ),
  1183. call( 'modified', False ),
  1184. call( 'readonly', True ),
  1185. ], any_order = True )
  1186. @patch( 'vim.current' )
  1187. def test_WriteToPreviewWindow_MultiLine( self, vim_current ):
  1188. vim_current.window.options.__getitem__ = MagicMock( return_value = True )
  1189. vimsupport.WriteToPreviewWindow( "test\ntest2", '' )
  1190. vim_current.buffer.__setitem__.assert_called_with(
  1191. slice( None, None, None ), [ 'test', 'test2' ] )
  1192. @patch( 'vim.command', new_callable=ExtendedMock )
  1193. @patch( 'vim.current', new_callable=ExtendedMock )
  1194. def test_WriteToPreviewWindow_JumpFail( self, vim_current, vim_command ):
  1195. vim_current.window.options.__getitem__ = MagicMock( return_value = False )
  1196. vimsupport.WriteToPreviewWindow( "test", '' )
  1197. vim_command.assert_has_exact_calls( [
  1198. call( 'silent! pclose!' ),
  1199. call( 'silent! pedit! _TEMP_FILE_' ),
  1200. call( 'silent! wincmd P' ),
  1201. call( 'redraw' ),
  1202. call( "echo 'test'" ),
  1203. ] )
  1204. vim_current.buffer.__setitem__.assert_not_called()
  1205. vim_current.buffer.options.__setitem__.assert_not_called()
  1206. @patch( 'vim.command', new_callable=ExtendedMock )
  1207. @patch( 'vim.current', new_callable=ExtendedMock )
  1208. def test_WriteToPreviewWindow_JumpFail_MultiLine(
  1209. self, vim_current, vim_command ):
  1210. vim_current.window.options.__getitem__ = MagicMock( return_value = False )
  1211. vimsupport.WriteToPreviewWindow( "test\ntest2", '' )
  1212. vim_command.assert_has_exact_calls( [
  1213. call( 'silent! pclose!' ),
  1214. call( 'silent! pedit! _TEMP_FILE_' ),
  1215. call( 'silent! wincmd P' ),
  1216. call( 'redraw' ),
  1217. call( "echo 'test'" ),
  1218. call( "echo 'test2'" ),
  1219. ] )
  1220. vim_current.buffer.__setitem__.assert_not_called()
  1221. vim_current.buffer.options.__setitem__.assert_not_called()
  1222. def test_BufferIsVisibleForFilename( self ):
  1223. visible_buffer = VimBuffer( 'visible_filename', number = 1 )
  1224. hidden_buffer = VimBuffer( 'hidden_filename', number = 2 )
  1225. with MockVimBuffers( [ visible_buffer, hidden_buffer ],
  1226. [ visible_buffer ] ):
  1227. assert_that( vimsupport.BufferIsVisibleForFilename( 'visible_filename' ) )
  1228. assert_that(
  1229. not vimsupport.BufferIsVisibleForFilename( 'hidden_filename' ) )
  1230. assert_that(
  1231. not vimsupport.BufferIsVisibleForFilename( 'another_filename' ) )
  1232. def test_CloseBuffersForFilename( self ):
  1233. current_buffer = VimBuffer( 'some_filename', number = 2 )
  1234. other_buffer = VimBuffer( 'some_filename', number = 5 )
  1235. with MockVimBuffers( [ current_buffer, other_buffer ],
  1236. [ current_buffer ] ) as vim:
  1237. vimsupport.CloseBuffersForFilename( 'some_filename' )
  1238. assert_that( vim.buffers, empty() )
  1239. @patch( 'vim.command', new_callable = ExtendedMock )
  1240. @patch( 'vim.current', new_callable = ExtendedMock )
  1241. def test_OpenFilename( self, vim_current, vim_command ):
  1242. # Options used to open a logfile.
  1243. options = {
  1244. 'size': vimsupport.GetIntValue( '&previewheight' ),
  1245. 'fix': True,
  1246. 'focus': False,
  1247. 'watch': True,
  1248. 'position': 'end'
  1249. }
  1250. vimsupport.OpenFilename( __file__, options )
  1251. vim_command.assert_has_exact_calls( [
  1252. call( f'12split { __file__ }' ),
  1253. call( f"exec 'au BufEnter <buffer> :silent! checktime { __file__ }'" ),
  1254. call( 'silent! normal! Gzz' ),
  1255. call( 'silent! wincmd p' )
  1256. ] )
  1257. vim_current.buffer.options.__setitem__.assert_has_exact_calls( [
  1258. call( 'autoread', True ),
  1259. ] )
  1260. vim_current.window.options.__setitem__.assert_has_exact_calls( [
  1261. call( 'winfixheight', True )
  1262. ] )
  1263. def test_GetUnsavedAndSpecifiedBufferData_EncodedUnicodeCharsInBuffers(
  1264. self ):
  1265. filepath = os.path.realpath( 'filename' )
  1266. contents = [ ToBytes( 'abc' ), ToBytes( 'fДa' ) ]
  1267. vim_buffer = VimBuffer( filepath, contents = contents )
  1268. with patch( 'vim.buffers', [ vim_buffer ] ):
  1269. assert_that( vimsupport.GetUnsavedAndSpecifiedBufferData( vim_buffer,
  1270. filepath ),
  1271. has_entry( filepath,
  1272. has_entry( 'contents', 'abc\nfДa\n' ) ) )
  1273. def test_GetBufferFilepath_NoBufferName_UnicodeWorkingDirectory( self ):
  1274. vim_buffer = VimBuffer( '', number = 42 )
  1275. unicode_dir = PathToTestFile( 'uni¢od€' )
  1276. with CurrentWorkingDirectory( unicode_dir ):
  1277. assert_that( vimsupport.GetBufferFilepath( vim_buffer ),
  1278. equal_to( os.path.join( unicode_dir, '42' ) ) )
  1279. # NOTE: Vim returns byte offsets for columns, not actual character columns.
  1280. # This makes 'ДД' have 4 columns: column 0, column 2 and column 4.
  1281. @patch( 'vim.current.line', ToBytes( 'ДДaa' ) )
  1282. @patch( 'ycm.vimsupport.CurrentColumn', side_effect = [ 4 ] )
  1283. def test_TextBeforeCursor_EncodedUnicode( *args ):
  1284. assert_that( vimsupport.TextBeforeCursor(), equal_to( 'ДД' ) )
  1285. # NOTE: Vim returns byte offsets for columns, not actual character columns.
  1286. # This makes 'ДД' have 4 columns: column 0, column 2 and column 4.
  1287. @patch( 'vim.current.line', ToBytes( 'aaДД' ) )
  1288. @patch( 'ycm.vimsupport.CurrentColumn', side_effect = [ 2 ] )
  1289. def test_TextAfterCursor_EncodedUnicode( *args ):
  1290. assert_that( vimsupport.TextAfterCursor(), equal_to( 'ДД' ) )
  1291. @patch( 'vim.current.line', ToBytes( 'fДa' ) )
  1292. def test_CurrentLineContents_EncodedUnicode( *args ):
  1293. assert_that( vimsupport.CurrentLineContents(), equal_to( 'fДa' ) )
  1294. @patch( 'vim.eval', side_effect = lambda x: x )
  1295. def test_VimExpressionToPythonType_IntAsUnicode( *args ):
  1296. assert_that( vimsupport.VimExpressionToPythonType( '123' ),
  1297. equal_to( 123 ) )
  1298. @patch( 'vim.eval', side_effect = lambda x: x )
  1299. def test_VimExpressionToPythonType_IntAsBytes( *args ):
  1300. assert_that( vimsupport.VimExpressionToPythonType( ToBytes( '123' ) ),
  1301. equal_to( 123 ) )
  1302. @patch( 'vim.eval', side_effect = lambda x: x )
  1303. def test_VimExpressionToPythonType_StringAsUnicode( *args ):
  1304. assert_that( vimsupport.VimExpressionToPythonType( 'foo' ),
  1305. equal_to( 'foo' ) )
  1306. @patch( 'vim.eval', side_effect = lambda x: x )
  1307. def test_VimExpressionToPythonType_StringAsBytes( *args ):
  1308. assert_that( vimsupport.VimExpressionToPythonType( ToBytes( 'foo' ) ),
  1309. equal_to( 'foo' ) )
  1310. @patch( 'vim.eval', side_effect = lambda x: x )
  1311. def test_VimExpressionToPythonType_ListPassthrough( *args ):
  1312. assert_that( vimsupport.VimExpressionToPythonType( [ 1, 2 ] ),
  1313. equal_to( [ 1, 2 ] ) )
  1314. @patch( 'vim.eval', side_effect = lambda x: x )
  1315. def test_VimExpressionToPythonType_ObjectPassthrough( *args ):
  1316. assert_that( vimsupport.VimExpressionToPythonType( { 1: 2 } ),
  1317. equal_to( { 1: 2 } ) )
  1318. @patch( 'vim.eval', side_effect = lambda x: x )
  1319. def test_VimExpressionToPythonType_GeneratorPassthrough( *args ):
  1320. gen = ( x**2 for x in [ 1, 2, 3 ] )
  1321. assert_that( vimsupport.VimExpressionToPythonType( gen ), equal_to( gen ) )
  1322. @patch( 'vim.eval',
  1323. new_callable = ExtendedMock,
  1324. side_effect = [ None, 2, None ] )
  1325. def test_SelectFromList_LastItem( self, vim_eval ):
  1326. assert_that( vimsupport.SelectFromList( 'test', [ 'a', 'b' ] ),
  1327. equal_to( 1 ) )
  1328. vim_eval.assert_has_exact_calls( [
  1329. call( 'inputsave()' ),
  1330. call( 'inputlist( ["test", "1: a", "2: b"] )' ),
  1331. call( 'inputrestore()' )
  1332. ] )
  1333. @patch( 'vim.eval',
  1334. new_callable = ExtendedMock,
  1335. side_effect = [ None, 1, None ] )
  1336. def test_SelectFromList_FirstItem( self, vim_eval ):
  1337. assert_that( vimsupport.SelectFromList( 'test', [ 'a', 'b' ] ),
  1338. equal_to( 0 ) )
  1339. vim_eval.assert_has_exact_calls( [
  1340. call( 'inputsave()' ),
  1341. call( 'inputlist( ["test", "1: a", "2: b"] )' ),
  1342. call( 'inputrestore()' )
  1343. ] )
  1344. @patch( 'vim.eval', side_effect = [ None, 3, None ] )
  1345. def test_SelectFromList_OutOfRange( self, vim_eval ):
  1346. assert_that( calling( vimsupport.SelectFromList ).with_args( 'test',
  1347. [ 'a', 'b' ] ),
  1348. raises( RuntimeError, vimsupport.NO_SELECTION_MADE_MSG ) )
  1349. @patch( 'vim.eval', side_effect = [ None, 0, None ] )
  1350. def test_SelectFromList_SelectPrompt( self, vim_eval ):
  1351. assert_that( calling( vimsupport.SelectFromList ).with_args( 'test',
  1352. [ 'a', 'b' ] ),
  1353. raises( RuntimeError, vimsupport.NO_SELECTION_MADE_MSG ) )
  1354. @patch( 'vim.eval', side_effect = [ None, -199, None ] )
  1355. def test_SelectFromList_Negative( self, vim_eval ):
  1356. assert_that( calling( vimsupport.SelectFromList ).with_args( 'test',
  1357. [ 'a', 'b' ] ),
  1358. raises( RuntimeError, vimsupport.NO_SELECTION_MADE_MSG ) )
  1359. def test_Filetypes_IntegerFiletype( self ):
  1360. current_buffer = VimBuffer( 'buffer', number = 1, filetype = '42' )
  1361. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
  1362. assert_that( vimsupport.CurrentFiletypes(), contains_exactly( '42' ) )
  1363. assert_that( vimsupport.GetBufferFiletypes( 1 ),
  1364. contains_exactly( '42' ) )
  1365. assert_that( vimsupport.FiletypesForBuffer( current_buffer ),
  1366. contains_exactly( '42' ) )
  1367. @patch( 'ycm.vimsupport.VariableExists', return_value = False )
  1368. @patch( 'ycm.vimsupport.SearchInCurrentBuffer', return_value = 0 )
  1369. @patch( 'vim.current' )
  1370. def test_InsertNamespace_insert( self, vim_current, *args ):
  1371. contents = [ '',
  1372. 'namespace Taqueria {',
  1373. '',
  1374. ' int taco = Math' ]
  1375. vim_current.buffer = VimBuffer( '', contents = contents )
  1376. vim_current.window.cursor = ( 1, 1 )
  1377. vimsupport.InsertNamespace( 'System' )
  1378. expected_buffer = [ 'using System;',
  1379. '',
  1380. 'namespace Taqueria {',
  1381. '',
  1382. ' int taco = Math' ]
  1383. AssertBuffersAreEqualAsBytes( expected_buffer, vim_current.buffer )
  1384. @patch( 'ycm.vimsupport.VariableExists', return_value = False )
  1385. @patch( 'ycm.vimsupport.SearchInCurrentBuffer', return_value = 2 )
  1386. @patch( 'vim.current' )
  1387. def test_InsertNamespace_append( self, vim_current, *args ):
  1388. contents = [ 'namespace Taqueria {',
  1389. ' using System;',
  1390. '',
  1391. ' class Tasty {',
  1392. ' int taco;',
  1393. ' List salad = new List' ]
  1394. vim_current.buffer = VimBuffer( '', contents = contents )
  1395. vim_current.window.cursor = ( 1, 1 )
  1396. vimsupport.InsertNamespace( 'System.Collections' )
  1397. expected_buffer = [ 'namespace Taqueria {',
  1398. ' using System;',
  1399. ' using System.Collections;',
  1400. '',
  1401. ' class Tasty {',
  1402. ' int taco;',
  1403. ' List salad = new List' ]
  1404. AssertBuffersAreEqualAsBytes( expected_buffer, vim_current.buffer )
  1405. @patch( 'vim.command', new_callable = ExtendedMock )
  1406. def test_JumpToLocation_SameFile_SameBuffer_NoSwapFile( self, vim_command ):
  1407. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1408. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1409. vimsupport.JumpToLocation( os.path.realpath( 'uni¢𐍈d€' ),
  1410. 2,
  1411. 5,
  1412. 'aboveleft',
  1413. 'same-buffer' )
  1414. assert_that( vim.current.window.cursor, equal_to( ( 2, 4 ) ) )
  1415. vim_command.assert_has_exact_calls( [
  1416. call( 'normal! m\'' ),
  1417. call( 'normal! zv' ),
  1418. call( 'normal! zz' )
  1419. ] )
  1420. @patch( 'vim.command', new_callable = ExtendedMock )
  1421. def test_JumpToLocation_DifferentFile_SameBuffer_Unmodified(
  1422. self, vim_command ):
  1423. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1424. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1425. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1426. vimsupport.JumpToLocation( target_name,
  1427. 2,
  1428. 5,
  1429. 'belowright',
  1430. 'same-buffer' )
  1431. assert_that( vim.current.window.cursor, equal_to( ( 2, 4 ) ) )
  1432. vim_command.assert_has_exact_calls( [
  1433. call( 'normal! m\'' ),
  1434. call( f'keepjumps belowright edit { target_name }' ),
  1435. call( 'normal! zv' ),
  1436. call( 'normal! zz' )
  1437. ] )
  1438. @patch( 'vim.command', new_callable = ExtendedMock )
  1439. def test_JumpToLocation_SameFile_NoLineCol( self, vim_command ):
  1440. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1441. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1442. vim.current.window.cursor = ( 99, 99 )
  1443. target_name = os.path.realpath( 'uni¢𐍈d€' )
  1444. vimsupport.JumpToLocation( target_name,
  1445. None,
  1446. None,
  1447. 'belowright',
  1448. 'same-buffer' )
  1449. assert_that( vim.current.window.cursor, equal_to( ( 99, 99 ) ) )
  1450. vim_command.assert_has_exact_calls( [
  1451. call( 'normal! m\'' ),
  1452. ] )
  1453. @patch( 'vim.command', new_callable = ExtendedMock )
  1454. def test_JumpToLocation_SameFile_NoLine( self, vim_command ):
  1455. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1456. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1457. vim.current.window.cursor = ( 99, 99 )
  1458. target_name = os.path.realpath( 'uni¢𐍈d€' )
  1459. vimsupport.JumpToLocation( target_name,
  1460. None,
  1461. 1,
  1462. 'belowright',
  1463. 'same-buffer' )
  1464. assert_that( vim.current.window.cursor, equal_to( ( 99, 99 ) ) )
  1465. vim_command.assert_has_exact_calls( [
  1466. call( 'normal! m\'' ),
  1467. ] )
  1468. @patch( 'vim.command', new_callable = ExtendedMock )
  1469. def test_JumpToLocation_SameFile_NoCol( self, vim_command ):
  1470. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1471. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1472. vim.current.window.cursor = ( 99, 99 )
  1473. target_name = os.path.realpath( 'uni¢𐍈d€' )
  1474. vimsupport.JumpToLocation( target_name,
  1475. 1,
  1476. None,
  1477. 'belowright',
  1478. 'same-buffer' )
  1479. assert_that( vim.current.window.cursor, equal_to( ( 99, 99 ) ) )
  1480. vim_command.assert_has_exact_calls( [
  1481. call( 'normal! m\'' ),
  1482. ] )
  1483. @patch( 'vim.command', new_callable = ExtendedMock )
  1484. def test_JumpToLocation_DifferentFile_NoLineCol( self, vim_command ):
  1485. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1486. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1487. vim.current.window.cursor = ( 99, 99 )
  1488. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1489. vimsupport.JumpToLocation( target_name,
  1490. None,
  1491. None,
  1492. 'belowright',
  1493. 'same-buffer' )
  1494. assert_that( vim.current.window.cursor, equal_to( ( 99, 99 ) ) )
  1495. vim_command.assert_has_exact_calls( [
  1496. call( 'normal! m\'' ),
  1497. call( f'keepjumps belowright edit { target_name }' ),
  1498. ] )
  1499. @patch( 'vim.command', new_callable = ExtendedMock )
  1500. def test_JumpToLocation_DifferentFile_NoLine( self, vim_command ):
  1501. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1502. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1503. vim.current.window.cursor = ( 99, 99 )
  1504. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1505. vimsupport.JumpToLocation( target_name,
  1506. None,
  1507. 1,
  1508. 'belowright',
  1509. 'same-buffer' )
  1510. assert_that( vim.current.window.cursor, equal_to( ( 99, 99 ) ) )
  1511. vim_command.assert_has_exact_calls( [
  1512. call( 'normal! m\'' ),
  1513. call( f'keepjumps belowright edit { target_name }' ),
  1514. ] )
  1515. @patch( 'vim.command', new_callable = ExtendedMock )
  1516. def test_JumpToLocation_DifferentFile_NoCol( self, vim_command ):
  1517. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1518. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1519. vim.current.window.cursor = ( 99, 99 )
  1520. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1521. vimsupport.JumpToLocation( target_name,
  1522. 1,
  1523. None,
  1524. 'belowright',
  1525. 'same-buffer' )
  1526. assert_that( vim.current.window.cursor, equal_to( ( 99, 99 ) ) )
  1527. vim_command.assert_has_exact_calls( [
  1528. call( 'normal! m\'' ),
  1529. call( f'keepjumps belowright edit { target_name }' ),
  1530. ] )
  1531. @patch( 'vim.command', new_callable = ExtendedMock )
  1532. def test_JumpToLocation_DifferentFile_SameBuffer_Modified_CannotHide(
  1533. self, vim_command ):
  1534. current_buffer = VimBuffer( 'uni¢𐍈d€', modified = True )
  1535. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1536. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1537. vimsupport.JumpToLocation( target_name, 2, 5, 'botright', 'same-buffer' )
  1538. assert_that( vim.current.window.cursor, equal_to( ( 2, 4 ) ) )
  1539. vim_command.assert_has_exact_calls( [
  1540. call( 'normal! m\'' ),
  1541. call( f'keepjumps botright split { target_name }' ),
  1542. call( 'normal! zv' ),
  1543. call( 'normal! zz' )
  1544. ] )
  1545. @patch( 'vim.command', new_callable = ExtendedMock )
  1546. def test_JumpToLocation_DifferentFile_SameBuffer_Modified_CanHide(
  1547. self, vim_command ):
  1548. current_buffer = VimBuffer( 'uni¢𐍈d€', modified = True, bufhidden = "hide" )
  1549. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1550. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1551. vimsupport.JumpToLocation( target_name, 2, 5, 'leftabove', 'same-buffer' )
  1552. assert_that( vim.current.window.cursor, equal_to( ( 2, 4 ) ) )
  1553. vim_command.assert_has_exact_calls( [
  1554. call( 'normal! m\'' ),
  1555. call( f'keepjumps leftabove edit { target_name }' ),
  1556. call( 'normal! zv' ),
  1557. call( 'normal! zz' )
  1558. ] )
  1559. @patch( 'vim.command',
  1560. side_effect = [ None, VimError( 'Unknown code' ), None ] )
  1561. def test_JumpToLocation_DifferentFile_SameBuffer_SwapFile_Unexpected(
  1562. self, vim_command ):
  1563. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1564. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
  1565. assert_that(
  1566. calling( vimsupport.JumpToLocation ).with_args(
  1567. os.path.realpath( 'different_uni¢𐍈d€' ),
  1568. 2,
  1569. 5,
  1570. 'rightbelow',
  1571. 'same-buffer' ),
  1572. raises( VimError, 'Unknown code' )
  1573. )
  1574. @patch( 'vim.command',
  1575. new_callable = ExtendedMock,
  1576. side_effect = [ None, VimError( 'E325' ), None ] )
  1577. def test_JumpToLocation_DifferentFile_SameBuffer_SwapFile_Quit(
  1578. self, vim_command ):
  1579. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1580. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
  1581. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1582. vimsupport.JumpToLocation( target_name, 2, 5, 'topleft', 'same-buffer' )
  1583. vim_command.assert_has_exact_calls( [
  1584. call( 'normal! m\'' ),
  1585. call( f'keepjumps topleft edit { target_name }' )
  1586. ] )
  1587. @patch( 'vim.command',
  1588. new_callable = ExtendedMock,
  1589. side_effect = [ None, KeyboardInterrupt, None ] )
  1590. def test_JumpToLocation_DifferentFile_SameBuffer_SwapFile_Abort(
  1591. self, vim_command ):
  1592. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1593. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
  1594. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1595. vimsupport.JumpToLocation( target_name, 2, 5, 'vertical', 'same-buffer' )
  1596. vim_command.assert_has_exact_calls( [
  1597. call( 'normal! m\'' ),
  1598. call( f'keepjumps vertical edit { target_name }' )
  1599. ] )
  1600. @patch( 'vim.command', new_callable = ExtendedMock )
  1601. def test_JumpToLocation_DifferentFile_Split_CurrentTab_NotAlreadyOpened(
  1602. self, vim_command ):
  1603. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1604. current_window = MagicMock( buffer = current_buffer )
  1605. current_tab = MagicMock( windows = [ current_window ] )
  1606. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ) as vim:
  1607. vim.current.tabpage = current_tab
  1608. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1609. vimsupport.JumpToLocation( target_name,
  1610. 2,
  1611. 5,
  1612. 'aboveleft',
  1613. 'split-or-existing-window' )
  1614. vim_command.assert_has_exact_calls( [
  1615. call( 'normal! m\'' ),
  1616. call( f'keepjumps aboveleft split { target_name }' ),
  1617. call( 'normal! zv' ),
  1618. call( 'normal! zz' )
  1619. ] )
  1620. @patch( 'vim.command', new_callable = ExtendedMock )
  1621. def test_JumpToLocation_DifferentFile_Split_CurrentTab_AlreadyOpened(
  1622. self, vim_command ):
  1623. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1624. different_buffer = VimBuffer( 'different_uni¢𐍈d€' )
  1625. current_window = MagicMock( buffer = current_buffer )
  1626. different_window = MagicMock( buffer = different_buffer )
  1627. current_tab = MagicMock( windows = [ current_window, different_window ] )
  1628. with MockVimBuffers( [ current_buffer, different_buffer ],
  1629. [ current_buffer ] ) as vim:
  1630. vim.current.tabpage = current_tab
  1631. vimsupport.JumpToLocation( os.path.realpath( 'different_uni¢𐍈d€' ),
  1632. 2,
  1633. 5,
  1634. 'belowright',
  1635. 'split-or-existing-window' )
  1636. assert_that( vim.current.tabpage, equal_to( current_tab ) )
  1637. assert_that( vim.current.window, equal_to( different_window ) )
  1638. assert_that( vim.current.window.cursor, equal_to( ( 2, 4 ) ) )
  1639. vim_command.assert_has_exact_calls( [
  1640. call( 'normal! m\'' ),
  1641. call( 'normal! zv' ),
  1642. call( 'normal! zz' )
  1643. ] )
  1644. @WindowsAndMacOnly
  1645. @patch( 'vim.command', new_callable = ExtendedMock )
  1646. def test_JumpToLocation_DifferentFile_Split_CurrentTab_AlreadyOpened_Case(
  1647. self, vim_command ):
  1648. current_buffer = VimBuffer( 'current_buffer' )
  1649. different_buffer = VimBuffer( 'AnotHer_buFfeR' )
  1650. current_window = MagicMock( buffer = current_buffer )
  1651. different_window = MagicMock( buffer = different_buffer )
  1652. current_tab = MagicMock( windows = [ current_window, different_window ] )
  1653. with MockVimBuffers( [ current_buffer, different_buffer ],
  1654. [ current_buffer ] ) as vim:
  1655. vim.current.tabpage = current_tab
  1656. vimsupport.JumpToLocation( os.path.realpath( 'anOther_BuffEr' ),
  1657. 4,
  1658. 1,
  1659. 'belowright',
  1660. 'split-or-existing-window' )
  1661. assert_that( vim.current.tabpage, equal_to( current_tab ) )
  1662. assert_that( vim.current.window, equal_to( different_window ) )
  1663. assert_that( vim.current.window.cursor, equal_to( ( 4, 0 ) ) )
  1664. vim_command.assert_has_exact_calls( [
  1665. call( 'normal! m\'' ),
  1666. call( 'normal! zv' ),
  1667. call( 'normal! zz' )
  1668. ] )
  1669. @patch( 'vim.command', new_callable = ExtendedMock )
  1670. def test_JumpToLocation_DifferentFile_Split_AllTabs_NotAlreadyOpened(
  1671. self, vim_command ):
  1672. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1673. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
  1674. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1675. vimsupport.JumpToLocation( target_name,
  1676. 2,
  1677. 5,
  1678. 'tab',
  1679. 'split-or-existing-window' )
  1680. vim_command.assert_has_exact_calls( [
  1681. call( 'normal! m\'' ),
  1682. call( f'keepjumps tab split { target_name }' ),
  1683. call( 'normal! zv' ),
  1684. call( 'normal! zz' )
  1685. ] )
  1686. @patch( 'vim.command', new_callable = ExtendedMock )
  1687. def test_JumpToLocation_DifferentFile_Split_AllTabs_AlreadyOpened(
  1688. self, vim_command ):
  1689. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1690. different_buffer = VimBuffer( 'different_uni¢𐍈d€' )
  1691. current_window = MagicMock( buffer = current_buffer )
  1692. different_window = MagicMock( buffer = different_buffer )
  1693. current_tab = MagicMock( windows = [ current_window, different_window ] )
  1694. with MockVimBuffers( [ current_buffer, different_buffer ],
  1695. [ current_buffer ] ) as vim:
  1696. with patch( 'vim.tabpages', [ current_tab ] ):
  1697. vimsupport.JumpToLocation( os.path.realpath( 'different_uni¢𐍈d€' ),
  1698. 2,
  1699. 5,
  1700. 'tab',
  1701. 'split-or-existing-window' )
  1702. assert_that( vim.current.tabpage, equal_to( current_tab ) )
  1703. assert_that( vim.current.window, equal_to( different_window ) )
  1704. assert_that( vim.current.window.cursor, equal_to( ( 2, 4 ) ) )
  1705. vim_command.assert_has_exact_calls( [
  1706. call( 'normal! m\'' ),
  1707. call( 'normal! zv' ),
  1708. call( 'normal! zz' )
  1709. ] )
  1710. @patch( 'vim.command', new_callable = ExtendedMock )
  1711. def test_JumpToLocation_DifferentFile_NewOrExistingTab_NotAlreadyOpened(
  1712. self, vim_command ):
  1713. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1714. with MockVimBuffers( [ current_buffer ], [ current_buffer ] ):
  1715. target_name = os.path.realpath( 'different_uni¢𐍈d€' )
  1716. vimsupport.JumpToLocation( target_name,
  1717. 2,
  1718. 5,
  1719. 'aboveleft vertical',
  1720. 'new-or-existing-tab' )
  1721. vim_command.assert_has_exact_calls( [
  1722. call( 'normal! m\'' ),
  1723. call( f'keepjumps aboveleft vertical tabedit { target_name }' ),
  1724. call( 'normal! zv' ),
  1725. call( 'normal! zz' )
  1726. ] )
  1727. @patch( 'vim.command', new_callable = ExtendedMock )
  1728. def test_JumpToLocation_DifferentFile_NewOrExistingTab_AlreadyOpened(
  1729. self, vim_command ):
  1730. current_buffer = VimBuffer( 'uni¢𐍈d€' )
  1731. different_buffer = VimBuffer( 'different_uni¢𐍈d€' )
  1732. current_window = MagicMock( buffer = current_buffer )
  1733. different_window = MagicMock( buffer = different_buffer )
  1734. current_tab = MagicMock( windows = [ current_window, different_window ] )
  1735. with MockVimBuffers( [ current_buffer, different_buffer ],
  1736. [ current_buffer ] ) as vim:
  1737. with patch( 'vim.tabpages', [ current_tab ] ):
  1738. vimsupport.JumpToLocation( os.path.realpath( 'different_uni¢𐍈d€' ),
  1739. 2,
  1740. 5,
  1741. 'belowright tab',
  1742. 'new-or-existing-tab' )
  1743. assert_that( vim.current.tabpage, equal_to( current_tab ) )
  1744. assert_that( vim.current.window, equal_to( different_window ) )
  1745. assert_that( vim.current.window.cursor, equal_to( ( 2, 4 ) ) )
  1746. vim_command.assert_has_exact_calls( [
  1747. call( 'normal! m\'' ),
  1748. call( 'normal! zv' ),
  1749. call( 'normal! zz' )
  1750. ] )
  1751. @patch( 'ycm.tests.test_utils.VIM_VERSION', Version( 7, 4, 1578 ) )
  1752. def test_VimVersionAtLeast( self ):
  1753. assert_that( vimsupport.VimVersionAtLeast( '7.3.414' ) )
  1754. assert_that( vimsupport.VimVersionAtLeast( '7.4.1578' ) )
  1755. assert_that( not vimsupport.VimVersionAtLeast( '7.4.1579' ) )
  1756. assert_that( not vimsupport.VimVersionAtLeast( '7.4.1898' ) )
  1757. assert_that( not vimsupport.VimVersionAtLeast( '8.1.278' ) )