diff --git a/python/helpers/pydev/pydevd_file_utils.py b/python/helpers/pydev/pydevd_file_utils.py index 2e63f4b26569..d0237d224745 100644 --- a/python/helpers/pydev/pydevd_file_utils.py +++ b/python/helpers/pydev/pydevd_file_utils.py @@ -46,6 +46,7 @@ from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding import os.path import sys import traceback +import types os_normcase = os.path.normcase basename = os.path.basename @@ -72,11 +73,31 @@ PATHS_FROM_ECLIPSE_TO_PYTHON = [] normcase = os_normcase # May be rebound on set_ide_os - +import types +str_to_unicode = types.UnicodeType +def convert_to_long_pathname(filename): + return filename +if os.name == 'nt': + try: + import ctypes + except ImportError: + pass + else: + def convert_to_long_pathname(filename): + buf = ctypes.create_unicode_buffer(260) + GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW + rv = GetLongPathName(str_to_unicode(filename), buf , 260) + if rv == 0 or rv > 260: + return filename + else: + return buf.value.encode(getfilesystemencoding()) def norm_case(filename): # `normcase` doesn't lower case on Python 2 for non-English locale, but Java side does it, # so we should do it manually + if '~' in filename: + filename = convert_to_long_pathname(filename) + filename = os_normcase(filename) enc = getfilesystemencoding() if IS_PY3K or enc is None or enc.lower() == "utf-8": @@ -143,7 +164,7 @@ def _NormPaths(filename): def _NormPath(filename, normpath): - r = normcase(normpath(filename)) + r = normpath(filename) #cache it for fast access later ind = r.find('.zip') if ind == -1: @@ -156,7 +177,9 @@ def _NormPath(filename, normpath): inner_path = r[ind:] if inner_path.startswith('/') or inner_path.startswith('\\'): inner_path = inner_path[1:] - r = join(zip_path, inner_path) + r = join(normcase(zip_path), inner_path) + else: + r = normcase(r) return r @@ -366,11 +389,11 @@ def get_abs_path_real_path_and_base_from_frame(frame): except: #This one is just internal (so, does not need any kind of client-server translation) f = frame.f_code.co_filename - if f is not None and f.startswith('build/bdist.'): + if f is not None and f.startswith (('build/bdist.','build\\bdist.')): # files from eggs in Python 2.7 have paths like build/bdist.linux-x86_64/egg/ f = frame.f_globals['__file__'] - if f.endswith('.pyc'): - f = f[:-1] + if f is not None and f.endswith('.pyc'): + f = f[:-1] ret = get_abs_path_real_path_and_base_from_file(f) # Also cache based on the frame.f_code.co_filename (if we had it inside build/bdist it can make a difference). NORM_PATHS_AND_BASE_CONTAINER[frame.f_code.co_filename] = ret diff --git a/python/src/com/jetbrains/python/debugger/PyLocalPositionConverter.java b/python/src/com/jetbrains/python/debugger/PyLocalPositionConverter.java index e071549a44c6..1afaec0a7179 100644 --- a/python/src/com/jetbrains/python/debugger/PyLocalPositionConverter.java +++ b/python/src/com/jetbrains/python/debugger/PyLocalPositionConverter.java @@ -47,7 +47,7 @@ public class PyLocalPositionConverter implements PyPositionConverter { return null; } if (SystemInfo.isWindows) { - file = file.toLowerCase(); + file = winNormCase(file); } return super.normalize(file); } @@ -64,7 +64,7 @@ public class PyLocalPositionConverter implements PyPositionConverter { return null; } if (SystemInfo.isWindows && isWindowsPath(file)) { - file = file.toLowerCase(); + file = winNormCase(file); } return super.normalize(file); } @@ -165,6 +165,19 @@ public class PyLocalPositionConverter implements PyPositionConverter { } } + private static String winNormCase(String file) { + int ind = -1; + for (String ext : EGG_EXTENSIONS) { + ind = file.indexOf(ext); + if (ind != -1) break; + } + if (ind != -1) { + return file.substring(0, ind + 4).toLowerCase() + file.substring(ind + 4); + } + else { + return file.toLowerCase(); + } + } @Nullable protected static XSourcePosition createXSourcePosition(@Nullable VirtualFile vFile, int line) { if (vFile != null) { diff --git a/python/testData/debug/long_name_win_test.py b/python/testData/debug/long_name_win_test.py new file mode 100644 index 000000000000..e61b3fcbeaf7 --- /dev/null +++ b/python/testData/debug/long_name_win_test.py @@ -0,0 +1,3 @@ +##intentionally named differently so that so that short name won't clash with other files +x = 5 + 5 +print (x) \ No newline at end of file diff --git a/python/testData/debug/test_winegg.py b/python/testData/debug/test_winegg.py new file mode 100644 index 000000000000..092aed5176fe --- /dev/null +++ b/python/testData/debug/test_winegg.py @@ -0,0 +1,8 @@ +from eggxample import MIXED_case, lower_case + +x = lower_case.add(7, 9) +print(x) + +x = MIXED_case.add(7, 10) +print(x) + diff --git a/python/testData/debug/wintestegg-0.1.egg b/python/testData/debug/wintestegg-0.1.egg new file mode 100644 index 000000000000..068e05ca747f Binary files /dev/null and b/python/testData/debug/wintestegg-0.1.egg differ diff --git a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java index d883defa7b82..2d6364a70f88 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java @@ -4,6 +4,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.testFramework.UsefulTestCase; import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; import com.intellij.xdebugger.XDebuggerTestUtil; @@ -23,6 +25,7 @@ import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; import com.jetbrains.python.sdkTools.SdkCreationType; import org.jetbrains.annotations.NotNull; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -539,6 +542,87 @@ public class PythonDebuggerTest extends PyEnvTestCase { }); } + + public void testWinEggDebug() throws Exception { + if (!SystemInfo.isWindows) + { + return; // Only needs to run on windows + } + runPythonTest(new PyDebuggerTask("/debug", "test_winegg.py") { + @Override + public void before() throws Exception { + String egg = getFilePath("wintestegg-0.1.egg"); + toggleBreakpointInEgg(egg, "eggxample/lower_case.py", 2); + toggleBreakpointInEgg(egg, "eggxample/MIXED_case.py", 2); + + PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(getRunConfiguration().getSdkHome()); + if (flavor != null) { + flavor.initPythonPath(Lists.newArrayList(egg), getRunConfiguration().getEnvs()); + } + else { + getRunConfiguration().getEnvs().put("PYTHONPATH", egg); + } + } + + @Override + public void testing() throws Exception { + waitForPause(); + eval("ret").hasValue("16"); + resume(); + + waitForPause(); + eval("ret").hasValue("17"); + resume(); + } + + @NotNull + @Override + public Set getTags() { + return ImmutableSet.of("-jython"); //TODO: fix that for Jython if anybody needs it + } + + + }); + } + + public void testWinLongName() throws Exception { + if (!SystemInfo.isWindows) + { + return; // Only needs to run on windows + } + runPythonTest(new PyDebuggerTask("/debug", "long_n~1.py") { + @Override + public void before() throws Exception { + + String scriptPath = getScriptPath(); + String longPath = FileUtil + .toSystemDependentName((new File(scriptPath).getCanonicalPath())); + LocalFileSystem.getInstance().refreshAndFindFileByPath(longPath); + toggleBreakpoint(longPath, 2); + + + } + + @Override + public void testing() throws Exception { + waitForPause(); + eval("x").hasValue("10"); + resume(); + + } + + @NotNull + @Override + public Set getTags() { + return ImmutableSet.of("-jython"); //TODO: fix that for Jython if anybody needs it + } + + + }); + } + + + public void testStepOverConditionalBreakpoint() throws Exception { runPythonTest(new PyDebuggerTask("/debug", "test_stepOverCondition.py") { @Override