[pycharm] PY-72340 Jupyter(feat): Support "File <file>:<line>" format for highlight of stacktraces, resolve path with ~

GitOrigin-RevId: 8ba99b238db19eaf1cf714df2ac388a649a61d88
This commit is contained in:
Nikita.Ashihmin
2024-05-02 23:21:04 +04:00
committed by intellij-monorepo-bot
parent 204288e1fa
commit 7032ef2e1c
4 changed files with 101 additions and 92 deletions

View File

@@ -1,32 +1,36 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run;
package com.jetbrains.python.run
import com.jetbrains.python.traceBackParsers.LinkInTrace;
import com.jetbrains.python.traceBackParsers.TraceBackParserAdapter;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.jetbrains.python.traceBackParsers.LinkInTrace
import com.jetbrains.python.traceBackParsers.TraceBackParserAdapter
import java.util.regex.Matcher
import java.util.regex.Pattern
/**
* Finds links in default python traceback
*
* @author Ilya.Kazakevich
*/
public class PyTracebackParser extends TraceBackParserAdapter {
open class PyTracebackParser : TraceBackParserAdapter(Pattern.compile(
"(File \"(?<file>[^0-9][^\"]{0,200})\", line (?<line>\\d{1,8}))|(File (?<file2>[^0-9][^\"]{0,200}):(?<line2>\\d{1,8}))")) {
override fun findLinkInTrace(line: String, matchedMatcher: Matcher): LinkInTrace {
val file1 = matchedMatcher.group("file")
val file2 = matchedMatcher.group("file2")
val fileName = (file1 ?: file2).replace('\\', '/')
val lineNumber1 = matchedMatcher.group("line")
val lineNumber2 = matchedMatcher.group("line2")
val lineNumber = (lineNumber1 ?: lineNumber2).toInt()
public PyTracebackParser() {
// File name can't start with number, can't be more then 200 chars long (its insane) and line number is also limited to int maxvalue
super(Pattern.compile("File \"([^0-9][^\"]{0,200})\", line (\\d{1,8})"));
if (file1 != null && lineNumber1 != null) {
val startPos = line.indexOf('\"') + 1
val endPos = line.indexOf('\"', startPos)
return LinkInTrace(fileName, lineNumber, startPos, endPos)
}
else {
val startPos = matchedMatcher.start("file2")
val endPos = matchedMatcher.end("line2")
return LinkInTrace(fileName, lineNumber, startPos, endPos)
}
}
@Override
protected @NotNull LinkInTrace findLinkInTrace(final @NotNull String line, final @NotNull Matcher matchedMatcher) {
final String fileName = matchedMatcher.group(1).replace('\\', '/');
final int lineNumber = Integer.parseInt(matchedMatcher.group(2));
final int startPos = line.indexOf('\"') + 1;
final int endPos = line.indexOf('\"', startPos);
return new LinkInTrace(fileName, lineNumber, startPos, endPos);
}
}
}

View File

@@ -1,71 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.python.traceBackParsers.LinkInTrace;
import com.jetbrains.python.traceBackParsers.TraceBackParser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public class PythonTracebackFilter implements Filter {
private final Project myProject;
private final String myWorkingDirectory;
public PythonTracebackFilter(final Project project) {
myProject = project;
myWorkingDirectory = project.getBasePath();
}
public PythonTracebackFilter(final Project project, final @Nullable String workingDirectory) {
myProject = project;
myWorkingDirectory = workingDirectory;
}
@Override
public final @Nullable Result applyFilter(final @NotNull String line, final int entireLength) {
for (final TraceBackParser parser : TraceBackParser.PARSERS) {
final LinkInTrace linkInTrace = parser.findLinkInTrace(line);
if (linkInTrace == null) {
continue;
}
final int lineNumber = linkInTrace.getLineNumber();
final VirtualFile vFile = findFileByName(linkInTrace.getFileName());
if (vFile != null) {
if (!vFile.isDirectory()) {
var extension = vFile.getExtension();
if (extension != null && !extension.equals("py")) {
return null;
}
}
final OpenFileHyperlinkInfo hyperlink = new OpenFileHyperlinkInfo(myProject, vFile, lineNumber - 1);
final int textStartOffset = entireLength - line.length();
final int startPos = linkInTrace.getStartPos();
final int endPos = linkInTrace.getEndPos();
return new Result(startPos + textStartOffset, endPos + textStartOffset, hyperlink);
}
}
return null;
}
protected @Nullable VirtualFile findFileByName(final @NotNull String fileName) {
VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fileName);
if (vFile == null && !StringUtil.isEmptyOrSpaces(myWorkingDirectory)) {
vFile = LocalFileSystem.getInstance().findFileByIoFile(new File(myWorkingDirectory, fileName));
}
return vFile;
}
protected Project getProject() {
return myProject;
}
}

View File

@@ -0,0 +1,66 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run
import com.intellij.execution.filters.Filter
import com.intellij.execution.filters.OpenFileHyperlinkInfo
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.SystemProperties
import com.jetbrains.python.traceBackParsers.TraceBackParser
import java.io.File
open class PythonTracebackFilter : Filter {
protected val project: Project
private val myWorkingDirectory: String?
constructor(project: Project) {
this.project = project
myWorkingDirectory = project.basePath
}
constructor(project: Project, workingDirectory: String?) {
this.project = project
myWorkingDirectory = workingDirectory
}
override fun applyFilter(line: String, entireLength: Int): Filter.Result? {
for (parser in TraceBackParser.PARSERS) {
val linkInTrace = parser.findLinkInTrace(line)
if (linkInTrace == null) {
continue
}
val lineNumber = linkInTrace.lineNumber
val vFile = findFileByName(linkInTrace.fileName)
if (vFile != null) {
if (!vFile.isDirectory) {
val extension = vFile.extension
if (extension != null && extension != "py") {
return null
}
}
val hyperlink = OpenFileHyperlinkInfo(project, vFile, lineNumber - 1)
val textStartOffset = entireLength - line.length
val startPos = linkInTrace.startPos
val endPos = linkInTrace.endPos
return Filter.Result(startPos + textStartOffset, endPos + textStartOffset, hyperlink)
}
}
return null
}
protected open fun findFileByName(fileName: String): VirtualFile? {
val preparedName = if (fileName.startsWith("~")) {
fileName.replaceFirst("~".toRegex(), SystemProperties.getUserHome())
}
else {
fileName
}
var vFile = LocalFileSystem.getInstance().findFileByPath(preparedName)
if (vFile == null && !myWorkingDirectory.isNullOrBlank()) {
vFile = LocalFileSystem.getInstance().findFileByIoFile(File(myWorkingDirectory, preparedName))
}
return vFile
}
}

View File

@@ -39,6 +39,16 @@ public class PyTracebackParserTest extends TestCase {
Assert.assertEquals("Bad end pos", 16, linkInTrace.getEndPos());
}
public void testLineWithLink2() {
final LinkInTrace linkInTrace = new PyTracebackParser().findLinkInTrace(
"File ~/.pyenv/versions/3.10.11/lib/python3.10/threading.py:324, in Condition.wait(self, timeout)");
Assert.assertNotNull("Failed to parse line", linkInTrace);
Assert.assertEquals("Bad file name", "~/.pyenv/versions/3.10.11/lib/python3.10/threading.py", linkInTrace.getFileName());
Assert.assertEquals("Bad line number", 324, linkInTrace.getLineNumber());
Assert.assertEquals("Bad start pos", 5, linkInTrace.getStartPos());
Assert.assertEquals("Bad end pos", 62, linkInTrace.getEndPos());
}
/**
* lines with out of file references should not have links
*/