vimsupport_test.py 78 KB

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