Auto merge of #3390 - micbou:goto-normcase, r=micbou

[READY] Ignore case when comparing paths on Windows and macOS

There is an issue on Windows where jumping to a file that is already open in a window doesn't go to that window if the case differs between the path of the file and the path of the buffer in the window (with `g:ycm_goto_command_buffer` sets to `'split-or-existing-window'`). This shouldn't happen since paths are case-insensitive on this platform. We need to ignore the case when comparing paths on Windows.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/3390)
<!-- Reviewable:end -->
This commit is contained in:
zzbot 2019-05-05 02:51:35 -07:00 committed by GitHub
commit 2e67594efc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 14 deletions

View File

@ -34,7 +34,12 @@ import os
import re
import sys
from ycmd.utils import GetCurrentDirectory, ToBytes, ToUnicode
try:
from unittest import skipIf
except ImportError:
from unittest2 import skipIf
from ycmd.utils import GetCurrentDirectory, OnMac, OnWindows, ToBytes, ToUnicode
BUFNR_REGEX = re.compile( '^bufnr\\(\'(?P<buffer_filename>.+)\', ([01])\\)$' )
@ -102,6 +107,9 @@ REDIR = {
'output': ''
}
WindowsAndMacOnly = skipIf( not OnWindows() or not OnMac(),
'Windows and macOS only' )
@contextlib.contextmanager
def CurrentWorkingDirectory( path ):
@ -293,7 +301,7 @@ def _MockVimEval( value ):
if value == REDIR[ 'variable' ]:
return REDIR[ 'output' ]
raise VimError( 'Unexpected evaluation: {0}'.format( value ) )
raise VimError( 'Unexpected evaluation: {}'.format( value ) )
def _MockWipeoutBuffer( buffer_number ):
@ -444,6 +452,11 @@ class VimBuffer( object ):
raise ValueError( 'Unexpected mark: {name}'.format( name = name ) )
def __repr__( self ):
return "VimBuffer( name = '{}', number = {} )".format( self.name,
self.number )
class VimBuffers( object ):
"""An object that looks like a vim.buffers object."""
@ -483,6 +496,13 @@ class VimWindow( object ):
self.options = {}
def __repr__( self ):
return "VimWindow( number = {}, buffer = {}, cursor = {} )".format(
self.number,
self.buffer,
self.cursor )
class VimWindows( object ):
"""An object that looks like a vim.windows object."""
@ -534,8 +554,8 @@ class VimMatch( object ):
def __repr__( self ):
return "VimMatch( group = '{0}', pattern = '{1}' )".format( self.group,
self.pattern )
return "VimMatch( group = '{}', pattern = '{}' )".format( self.group,
self.pattern )
def __getitem__( self, key ):
@ -562,11 +582,11 @@ class VimSign( object ):
def __repr__( self ):
return ( "VimSign( id = {0}, line = {1}, "
"name = '{2}', bufnr = {3} )".format( self.id,
self.line,
self.name,
self.bufnr ) )
return ( "VimSign( id = {}, line = {}, "
"name = '{}', bufnr = {} )".format( self.id,
self.line,
self.name,
self.bufnr ) )
def __getitem__( self, key ):
@ -692,7 +712,7 @@ def ExpectedFailure( reason, *exception_matchers ):
# Failed for the right reason
raise nose.SkipTest( reason )
else:
raise AssertionError( 'Test was expected to fail: {0}'.format(
raise AssertionError( 'Test was expected to fail: {}'.format(
reason ) )
return Wrapper

View File

@ -27,7 +27,7 @@ from builtins import * # noqa
from ycm.tests import PathToTestFile
from ycm.tests.test_utils import ( CurrentWorkingDirectory, ExtendedMock,
MockVimBuffers, MockVimModule, Version,
VimBuffer, VimError )
VimBuffer, VimError, WindowsAndMacOnly )
MockVimModule()
from ycm import vimsupport
@ -1858,6 +1858,35 @@ def JumpToLocation_DifferentFile_Split_CurrentTab_AlreadyOpened_test(
] )
@WindowsAndMacOnly
@patch( 'vim.command', new_callable = ExtendedMock )
def JumpToLocation_DifferentFile_Split_CurrentTab_AlreadyOpened_Case_test(
vim_command ):
current_buffer = VimBuffer( 'current_buffer' )
different_buffer = VimBuffer( 'AnotHer_buFfeR' )
current_window = MagicMock( buffer = current_buffer )
different_window = MagicMock( buffer = different_buffer )
current_tab = MagicMock( windows = [ current_window, different_window ] )
with MockVimBuffers( [ current_buffer, different_buffer ],
[ current_buffer ] ) as vim:
vim.current.tabpage = current_tab
vimsupport.JumpToLocation( os.path.realpath( 'anOther_BuffEr' ),
4,
1,
'belowright',
'split-or-existing-window' )
assert_that( vim.current.tabpage, equal_to( current_tab ) )
assert_that( vim.current.window, equal_to( different_window ) )
assert_that( vim.current.window.cursor, equal_to( ( 4, 0 ) ) )
vim_command.assert_has_exact_calls( [
call( 'normal! m\'' ),
call( 'normal! zz' )
] )
@patch( 'vim.command', new_callable = ExtendedMock )
def JumpToLocation_DifferentFile_Split_AllTabs_NotAlreadyOpened_test(
vim_command ):

View File

@ -28,8 +28,13 @@ import os
import json
import re
from collections import defaultdict, namedtuple
from ycmd.utils import ( ByteOffsetToCodepointOffset, GetCurrentDirectory,
JoinLinesAsUnicode, ToBytes, ToUnicode )
from ycmd.utils import ( ByteOffsetToCodepointOffset,
GetCurrentDirectory,
JoinLinesAsUnicode,
OnMac,
OnWindows,
ToBytes,
ToUnicode )
BUFFER_COMMAND_MAP = { 'same-buffer' : 'edit',
'split' : 'split',
@ -460,10 +465,21 @@ def EscapeFilepathForVimCommand( filepath ):
return GetVariableValue( to_eval )
def ComparePaths( path1, path2 ):
# Assume that the file system is case-insensitive on Windows and macOS and
# case-sensitive on other platforms. While this is not necessarily true, being
# completely correct here is not worth the trouble as this assumption
# represents the overwhelming use case and detecting the case sensitivity of a
# file system is tricky.
if OnWindows() or OnMac():
return path1.lower() == path2.lower()
return path1 == path2
# Both |line| and |column| need to be 1-based
def TryJumpLocationInTab( tab, filename, line, column ):
for win in tab.windows:
if GetBufferFilepath( win.buffer ) == filename:
if ComparePaths( GetBufferFilepath( win.buffer ), filename ):
vim.current.tabpage = tab
vim.current.window = win
vim.current.window.cursor = ( line, column - 1 )