diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index e2a01b4..7d656ea 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -98,6 +98,9 @@ + + + diff --git a/src/cz/jiripudil/intellij/nette/tester/console/ActualExpectedPathDetector.java b/src/cz/jiripudil/intellij/nette/tester/console/ActualExpectedPathDetector.java new file mode 100644 index 0000000..15f9579 --- /dev/null +++ b/src/cz/jiripudil/intellij/nette/tester/console/ActualExpectedPathDetector.java @@ -0,0 +1,46 @@ +package cz.jiripudil.intellij.nette.tester.console; + +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.Nullable; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class ActualExpectedPathDetector { + /** + * @see diff line + * @see argument escaping + */ + private final static Pattern DIFF_LINE_REGEX; + + static { + String unixArg = "'(?:[^']|'\\\\'')+'"; + String winArg = "\"[^\"]+\""; + String unquotedArg = "[a-z0-9._=/:-]+"; + String arg = "(" + unixArg + "|" + winArg + "|" + unquotedArg + ")"; + DIFF_LINE_REGEX = Pattern.compile("^diff " + arg + " " + arg + "$"); + } + + @Nullable + public static Pair detectPaths(final String diffLine) { + Matcher matcher = DIFF_LINE_REGEX.matcher(diffLine); + if (matcher.find()) { + return Pair.create(unquoteArg(matcher.group(1)), unquoteArg(matcher.group(2))); + } + return null; + } + + private static String unquoteArg(String arg) { + if (arg.startsWith("'")) { + return StringUtil.unquoteString(arg).replace("'\\''", "'"); + } + + if (arg.startsWith("\"")) { + return StringUtil.unquoteString(arg); + } + + return arg; + } +} diff --git a/src/cz/jiripudil/intellij/nette/tester/console/FilterProvider.java b/src/cz/jiripudil/intellij/nette/tester/console/FilterProvider.java new file mode 100644 index 0000000..bd0a601 --- /dev/null +++ b/src/cz/jiripudil/intellij/nette/tester/console/FilterProvider.java @@ -0,0 +1,18 @@ +package cz.jiripudil.intellij.nette.tester.console; + +import com.intellij.execution.filters.ConsoleFilterProvider; +import com.intellij.execution.filters.Filter; +import com.intellij.openapi.project.Project; +import cz.jiripudil.intellij.nette.tester.console.filters.MakeDiffLinkTextClickableFilter; +import org.jetbrains.annotations.NotNull; + + +public class FilterProvider implements ConsoleFilterProvider { + @NotNull + @Override + public Filter[] getDefaultFilters(@NotNull Project project) { + return new Filter[]{ + new MakeDiffLinkTextClickableFilter(), + }; + } +} diff --git a/src/cz/jiripudil/intellij/nette/tester/console/InputFilterProvider.java b/src/cz/jiripudil/intellij/nette/tester/console/InputFilterProvider.java new file mode 100644 index 0000000..a5a9bee --- /dev/null +++ b/src/cz/jiripudil/intellij/nette/tester/console/InputFilterProvider.java @@ -0,0 +1,18 @@ +package cz.jiripudil.intellij.nette.tester.console; + +import com.intellij.execution.filters.ConsoleInputFilterProvider; +import com.intellij.execution.filters.InputFilter; +import com.intellij.openapi.project.Project; +import cz.jiripudil.intellij.nette.tester.console.filters.InsertDiffLinkTextInputFilter; +import org.jetbrains.annotations.NotNull; + + +public class InputFilterProvider implements ConsoleInputFilterProvider { + @NotNull + @Override + public InputFilter[] getDefaultFilters(@NotNull Project project) { + return new InputFilter[]{ + new InsertDiffLinkTextInputFilter(), + }; + } +} diff --git a/src/cz/jiripudil/intellij/nette/tester/console/filters/InsertDiffLinkTextInputFilter.java b/src/cz/jiripudil/intellij/nette/tester/console/filters/InsertDiffLinkTextInputFilter.java new file mode 100644 index 0000000..7309736 --- /dev/null +++ b/src/cz/jiripudil/intellij/nette/tester/console/filters/InsertDiffLinkTextInputFilter.java @@ -0,0 +1,28 @@ +package cz.jiripudil.intellij.nette.tester.console.filters; + +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.filters.InputFilter; +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.openapi.util.Pair; +import cz.jiripudil.intellij.nette.tester.console.ActualExpectedPathDetector; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +/** + * Adds text {@link InsertDiffLinkTextInputFilter#DIFF_LINK_TEXT} after a line with + * diff shell command (see: {@link ActualExpectedPathDetector#DIFF_LINE_REGEX}). + */ +public class InsertDiffLinkTextInputFilter implements InputFilter { + final static String DIFF_LINK_TEXT = ExecutionBundle.message("junit.click.to.see.diff.link"); + + @Nullable + @Override + public List> applyFilter(String text, ConsoleViewContentType contentType) { + if (ActualExpectedPathDetector.detectPaths(text) != null) { + return Collections.singletonList(Pair.create(text + DIFF_LINK_TEXT + "\n", contentType)); + } + return null; + } +} diff --git a/src/cz/jiripudil/intellij/nette/tester/console/filters/MakeDiffLinkTextClickableFilter.java b/src/cz/jiripudil/intellij/nette/tester/console/filters/MakeDiffLinkTextClickableFilter.java new file mode 100644 index 0000000..eea9132 --- /dev/null +++ b/src/cz/jiripudil/intellij/nette/tester/console/filters/MakeDiffLinkTextClickableFilter.java @@ -0,0 +1,71 @@ +package cz.jiripudil.intellij.nette.tester.console.filters; + +import com.intellij.execution.filters.Filter; +import com.intellij.execution.filters.HyperlinkInfo; +import com.intellij.execution.testframework.stacktrace.DiffHyperlink; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.FileUtil; +import cz.jiripudil.intellij.nette.tester.console.ActualExpectedPathDetector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; + +/** + * Catches text {@link InsertDiffLinkTextInputFilter#DIFF_LINK_TEXT} and makes + * it clickable if previous line contains diff shell command (see: {@link ActualExpectedPathDetector#DIFF_LINE_REGEX}). + */ +public class MakeDiffLinkTextClickableFilter implements Filter { + private String expectedPath; + private String actualPath; + + @Nullable + @Override + public Result applyFilter(String line, int endPoint) { + if (line.equals(InsertDiffLinkTextInputFilter.DIFF_LINK_TEXT + "\n")) { + if (expectedPath != null && actualPath != null) { + Result result = new Result(endPoint - line.length(), endPoint, new LazyDiffHyperlinkInfo(expectedPath, actualPath)); + expectedPath = actualPath = null; + return result; + } + return null; + } + + Pair paths = ActualExpectedPathDetector.detectPaths(line); + if (paths != null) { + expectedPath = paths.first; + actualPath = paths.second; + } + + return null; + } + + private static class LazyDiffHyperlinkInfo implements HyperlinkInfo { + private String expectedPath; + private String actualPath; + private DiffHyperlink.DiffHyperlinkInfo link; + + LazyDiffHyperlinkInfo(@NotNull String expectedPath, @NotNull String actualPath) { + this.expectedPath = expectedPath; + this.actualPath = actualPath; + } + + @Override + public void navigate(Project project) { + if (link == null) { + String expected, actual; + try { + expected = FileUtil.loadFile(new File(expectedPath)); + actual = FileUtil.loadFile(new File(actualPath)); + } catch (IOException e) { + return; + } + link = new DiffHyperlink(expected, actual, expectedPath, actualPath, false).new DiffHyperlinkInfo(); + } + + link.navigate(project); + } + } +} diff --git a/test/diff-link-integration-test.php b/test/diff-link-integration-test.php new file mode 100644 index 0000000..0d03157 --- /dev/null +++ b/test/diff-link-integration-test.php @@ -0,0 +1,33 @@ +