[console, terminal] IJPL-201536 support links to local files as file:/path/to/file

GitOrigin-RevId: 73ec8073c6e99b45f6bdf89c722e05c1af0fca3e
This commit is contained in:
Sergey Simonchik
2025-10-14 00:07:03 +00:00
committed by intellij-monorepo-bot
parent 04378d0238
commit 20e45655eb
3 changed files with 33 additions and 7 deletions
@@ -28,6 +28,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UrlFilter implements Filter, DumbAware {
private static final String FILE_MINIMAL_PROTOCOL_PREFIX = "file:/";
private final Project myProject;
public UrlFilter() {
@@ -55,7 +58,7 @@ public class UrlFilter implements Filter, DumbAware {
int textStartOffset = entireLength - line.length();
List<ResultItem> resultList = new ArrayList<>();
if (line.contains(LocalFileSystem.PROTOCOL_PREFIX)) {
if (line.contains(FILE_MINIMAL_PROTOCOL_PREFIX)) {
resultList.addAll(findMatchingItems(line, URLUtil.FILE_URL_PATTERN_OPTIMIZED, textStartOffset));
}
@@ -89,17 +92,21 @@ public class UrlFilter implements Filter, DumbAware {
}
private @Nullable HyperlinkInfo buildFileHyperlinkInfo(@NotNull String url) {
if (myProject != null && !url.endsWith(".html") && url.startsWith(LocalFileSystem.PROTOCOL_PREFIX)) {
if (myProject != null && !url.endsWith(".html")) {
String prefix = findFileProtocolPrefix(url);
if (prefix == null) {
return null;
}
int documentLine = -1, documentColumn = -1;
int filePathEndIndex = url.length();
final int lastColonInd = url.lastIndexOf(':');
if (lastColonInd > LocalFileSystem.PROTOCOL_PREFIX.length() && lastColonInd < url.length() - 1) {
if (lastColonInd > prefix.length() && lastColonInd < url.length() - 1) {
int lastValue = StringUtil.parseInt(url.substring(lastColonInd + 1), Integer.MIN_VALUE);
if (lastValue != Integer.MIN_VALUE) {
documentLine = lastValue - 1;
filePathEndIndex = lastColonInd;
int preLastColonInd = url.lastIndexOf(':', lastColonInd - 1);
if (preLastColonInd > LocalFileSystem.PROTOCOL_PREFIX.length()) {
if (preLastColonInd > prefix.length()) {
int preLastValue = StringUtil.parseInt(url.substring(preLastColonInd + 1, lastColonInd), Integer.MIN_VALUE);
if (preLastValue != Integer.MIN_VALUE) {
documentLine = preLastValue - 1;
@@ -109,12 +116,22 @@ public class UrlFilter implements Filter, DumbAware {
}
}
}
String filePath = toWindowsPath(decode(url.substring(LocalFileSystem.PROTOCOL_PREFIX.length(), filePathEndIndex)));
String filePath = toWindowsPath(decode(url.substring(prefix.length(), filePathEndIndex)));
return new FileUrlHyperlinkInfo(myProject, filePath, documentLine, documentColumn, url, true);
}
return null;
}
private static @Nullable String findFileProtocolPrefix(@NotNull String url) {
if (url.startsWith("file:///")) {
return LocalFileSystem.PROTOCOL_PREFIX;
}
if (url.startsWith(FILE_MINIMAL_PROTOCOL_PREFIX) && !url.startsWith(LocalFileSystem.PROTOCOL_PREFIX)) {
return "file:";
}
return null;
}
private static @NotNull String toWindowsPath(@NotNull String path) {
if (path.length() >= 4 && path.charAt(0) == '/' && OSAgnosticPathUtil.isDriveLetter(path.charAt(1)) &&
path.charAt(2) == ':' && PathUtilRt.isSeparator(path.charAt(3))) {
@@ -29,6 +29,15 @@ public class UrlFilterTest extends BasePlatformTestCase {
4, 52, "//wsl$/Ubuntu-20.04/projects/report.txt", 4, -1);
}
public void testMinimalFileProtocol() {
assertFileHyperlink("Click file:/path/to/file.diff:2:4 to see the difference", 6, 33, "/path/to/file.diff", 2, 4);
assertFileHyperlink("file:/path/to/file.txt", 0, 22, "/path/to/file.txt", -1, -1);
}
public void testNoFileHyperlink() {
assertBrowserHyperlink("file://path/to/file.txt", 0, 23);
}
public void testSingleBrowserHyperlink() {
assertBrowserHyperlink("http://test.com", 0, 15);
assertBrowserHyperlink(" at http://test.com", 4, 19);
@@ -39,7 +39,7 @@ public final class URLUtil {
public static final Pattern DATA_URI_PATTERN_OPTIMIZED = Pattern.compile("data:[^,;]+/[^,;]+(?:;charset[=:][^,;]+)?(;base64)?,(.+)");
public static final Pattern URL_PATTERN_OPTIMIZED = Pattern.compile("\\b(?:mailto:|(?:news|(?:ht|f)tps?)://|(?<![\\p{L}0-9_.])www\\.)[-A-Za-z0-9+$&@#/%?=~_|!:,.;]*[-A-Za-z0-9+$&@#/%=~_|]");
public static final Pattern URL_WITH_PARENS_PATTERN_OPTIMIZED = Pattern.compile("\\b(?:mailto:|(?:news|(?:ht|f)tps?)://|(?<![\\p{L}0-9_.])www\\.)[-A-Za-z0-9+$&@#/%?=~_|!:,.;()]*[-A-Za-z0-9+$&@#/%=~_|()]");
public static final Pattern FILE_URL_PATTERN_OPTIMIZED = Pattern.compile("\\bfile:///[-A-Za-z0-9+$&@#/%?=~_|!:,.;]*[-A-Za-z0-9+$&@#/%=~_|]");
public static final Pattern FILE_URL_PATTERN_OPTIMIZED = Pattern.compile("\\bfile:(?:///|/)[-A-Za-z0-9+$&@#/%?=~_|!:,.;]*[-A-Za-z0-9+$&@#/%=~_|]");
public static final Pattern HREF_PATTERN = Pattern.compile("<a(?:\\s+href\\s*=\\s*[\"']([^\"']*)[\"'])?\\s*>([^<]*)</a>");
@@ -49,7 +49,7 @@ public final class URLUtil {
* If {@code false}, then the line contains no URL, otherwise the heavier {@link #URL_PATTERN} check should be used.
*/
public static boolean canContainUrl(@NotNull String line) {
return line.contains("mailto:") || line.contains(SCHEME_SEPARATOR) || line.contains("www.");
return line.contains("mailto:") || line.contains(SCHEME_SEPARATOR) || line.contains("www.") || line.contains("file:/");
}
/**