mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-141008 QuickDocumentation (Ctrl-Q) shows "JavaScript is disabled on your browser" sometimes
This commit is contained in:
@@ -24,7 +24,6 @@ import com.intellij.lang.java.JavaDocumentationProvider;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.NullableComputable;
|
||||
import com.intellij.openapi.util.Trinity;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -54,10 +53,10 @@ import java.util.regex.Pattern;
|
||||
public class JavaDocExternalFilter extends AbstractExternalFilter {
|
||||
private final Project myProject;
|
||||
|
||||
private static final Trinity<Pattern, Pattern, Boolean> ourPackageInfoSettings = Trinity.create(
|
||||
private static final ParseSettings ourPackageInfoSettings = new ParseSettings(
|
||||
Pattern.compile("package\\s+[^\\s]+\\s+description", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("START OF BOTTOM NAVBAR", Pattern.CASE_INSENSITIVE),
|
||||
Boolean.TRUE
|
||||
true, false
|
||||
);
|
||||
|
||||
protected static @NonNls final Pattern ourHTMLsuffix = Pattern.compile("[.][hH][tT][mM][lL]?");
|
||||
@@ -168,7 +167,7 @@ public class JavaDocExternalFilter extends AbstractExternalFilter {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Trinity<Pattern, Pattern, Boolean> getParseSettings(@NotNull String url) {
|
||||
protected ParseSettings getParseSettings(@NotNull String url) {
|
||||
return url.endsWith(JavaDocumentationProvider.PACKAGE_SUMMARY_FILE) ? ourPackageInfoSettings : super.getParseSettings(url);
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<HTML><base href="placeholder"><style type="text/css"> ul.inheritance {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
ul.inheritance li {
|
||||
display:inline;
|
||||
list-style:none;
|
||||
}
|
||||
ul.inheritance li ul.inheritance {
|
||||
margin-left:15px;
|
||||
padding-left:15px;
|
||||
padding-top:1px;
|
||||
}
|
||||
</style><a name="param()">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="blockListLast">
|
||||
<h3><a href="psi_element://com.jetbrains.TestAnnotation"><code>com.jetbrains.TestAnnotation</code></a></h3>
|
||||
<pre>public abstract java.lang.String param</pre>
|
||||
<div class="block">Some info</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</HTML>
|
||||
Binary file not shown.
Binary file not shown.
+89
-50
@@ -30,30 +30,34 @@ import com.intellij.openapi.roots.ModuleRootModificationUtil;
|
||||
import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.roots.libraries.Library;
|
||||
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.openapi.util.ActionCallback;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.JarFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiFileFactory;
|
||||
import com.intellij.testFramework.EditorTestUtil;
|
||||
import com.intellij.testFramework.PlatformTestCase;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.html.HTML;
|
||||
import javax.swing.text.html.HTMLDocument;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class JavaExternalDocumentationTest extends PlatformTestCase {
|
||||
public void testImagesInsideJavadocJar() throws Exception {
|
||||
|
||||
public static final Pattern BASE_URL_PATTERN = Pattern.compile("(<base href=\")([^\"]*)");
|
||||
public static final Pattern IMG_URL_PATTERN = Pattern.compile("<img src=\"([^\"]*)");
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
final VirtualFile libClasses = getJarFile("library.jar");
|
||||
final VirtualFile libJavadocJar = getJarFile("library-javadoc.jar");
|
||||
|
||||
@@ -68,32 +72,33 @@ public class JavaExternalDocumentationTest extends PlatformTestCase {
|
||||
assertSize(1, modules);
|
||||
ModuleRootModificationUtil.addDependency(modules[0], library);
|
||||
});
|
||||
}
|
||||
|
||||
PsiFile psiFile =
|
||||
PsiFileFactory.getInstance(myProject).createFileFromText(JavaLanguage.INSTANCE, "class Foo { com.jetbrains.Test field; }");
|
||||
Document document = PsiDocumentManager.getInstance(myProject).getDocument(psiFile);
|
||||
assertNotNull(document);
|
||||
Editor editor = EditorFactory.getInstance().createEditor(document, myProject);
|
||||
try {
|
||||
editor.getCaretModel().moveToOffset(document.getText().indexOf("Test"));
|
||||
DocumentationManager documentationManager = DocumentationManager.getInstance(myProject);
|
||||
documentationManager.showJavaDocInfo(editor, psiFile, false);
|
||||
waitTillDone(documentationManager.getLastAction());
|
||||
JBPopup popup = documentationManager.getDocInfoHint();
|
||||
assertNotNull(popup);
|
||||
DocumentationComponent documentationComponent = (DocumentationComponent)popup.getContent().getComponent(0);
|
||||
try {
|
||||
byte[] imageData = getImageDataFromDocumentationComponent(documentationComponent);
|
||||
assertEquals(228, imageData.length);
|
||||
}
|
||||
finally {
|
||||
Disposer.dispose(documentationComponent);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
EditorFactory.getInstance().releaseEditor(editor);
|
||||
public void testImagesInsideJavadocJar() throws Exception {
|
||||
String text = getDocumentationText("class Foo { com.jetbrains.<caret>Test field; }");
|
||||
Matcher baseUrlmatcher = BASE_URL_PATTERN.matcher(text);
|
||||
assertTrue(baseUrlmatcher.find());
|
||||
String baseUrl = baseUrlmatcher.group(2);
|
||||
Matcher imgMatcher = IMG_URL_PATTERN.matcher(text);
|
||||
assertTrue(imgMatcher.find());
|
||||
String relativeUrl = imgMatcher.group(1);
|
||||
URL imageUrl = new URL(new URL(baseUrl), relativeUrl);
|
||||
try (InputStream stream = imageUrl.openStream()) {
|
||||
assertEquals(228, FileUtil.loadBytes(stream).length);
|
||||
}
|
||||
}
|
||||
|
||||
// We're guessing style of references in javadoc by bytecode version of library class file
|
||||
// but displaying quick doc should work even if javadoc was generated using a JDK not corresponding to bytecode version
|
||||
public void testReferenceStyleDoesntMatchBytecodeVersion() throws Exception {
|
||||
String actualText = getDocumentationText("@com.jetbrains.TestAnnotation(<caret>param = \"foo\") class Foo {}");
|
||||
String expectedText = FileUtil.loadFile(getDataFile(getTestName(false) + ".html"));
|
||||
assertEquals(expectedText, replaceBaseUrlWithPlaceholder(actualText));
|
||||
}
|
||||
|
||||
private static String replaceBaseUrlWithPlaceholder(String actualText) {
|
||||
return BASE_URL_PATTERN.matcher(actualText).replaceAll("$1placeholder");
|
||||
}
|
||||
|
||||
private static void waitTillDone(ActionCallback actionCallback) throws InterruptedException {
|
||||
long start = System.currentTimeMillis();
|
||||
@@ -106,40 +111,74 @@ public class JavaExternalDocumentationTest extends PlatformTestCase {
|
||||
fail("Timed out waiting for documentation to show");
|
||||
}
|
||||
|
||||
private static File getDataFile(String name) {
|
||||
return new File(JavaTestUtil.getJavaTestDataPath() + "/codeInsight/documentation/" + name);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static VirtualFile getJarFile(String name) {
|
||||
VirtualFile file = getVirtualFile(new File(JavaTestUtil.getJavaTestDataPath() + "/codeInsight/documentation/" + name));
|
||||
VirtualFile file = getVirtualFile(getDataFile(name));
|
||||
assertNotNull(file);
|
||||
VirtualFile jarFile = JarFileSystem.getInstance().getJarRootForLocalFile(file);
|
||||
assertNotNull(jarFile);
|
||||
return jarFile;
|
||||
}
|
||||
|
||||
private static byte[] getImageDataFromDocumentationComponent(DocumentationComponent documentationComponent) throws Exception {
|
||||
JEditorPane editorPane = (JEditorPane)documentationComponent.getComponent();
|
||||
final HTMLDocument document = (HTMLDocument)editorPane.getDocument();
|
||||
final Ref<byte[]> result = new Ref<>();
|
||||
document.render(() -> {
|
||||
|
||||
private String getDocumentationText(String sourceEditorText) throws Exception {
|
||||
int caretPosition = sourceEditorText.indexOf(EditorTestUtil.CARET_TAG);
|
||||
if (caretPosition >= 0) {
|
||||
sourceEditorText = sourceEditorText.substring(0, caretPosition) +
|
||||
sourceEditorText.substring(caretPosition + EditorTestUtil.CARET_TAG.length());
|
||||
}
|
||||
PsiFile psiFile = PsiFileFactory.getInstance(myProject).createFileFromText(JavaLanguage.INSTANCE, sourceEditorText);
|
||||
Document document = PsiDocumentManager.getInstance(myProject).getDocument(psiFile);
|
||||
assertNotNull(document);
|
||||
Editor editor = EditorFactory.getInstance().createEditor(document, myProject);
|
||||
try {
|
||||
if (caretPosition >= 0) {
|
||||
editor.getCaretModel().moveToOffset(caretPosition);
|
||||
}
|
||||
DocumentationManager documentationManager = DocumentationManager.getInstance(myProject);
|
||||
MockDocumentationComponent documentationComponent = new MockDocumentationComponent(documentationManager);
|
||||
try {
|
||||
HTMLDocument.Iterator it = document.getIterator(HTML.Tag.IMG);
|
||||
assertTrue(it.isValid());
|
||||
String relativeUrl = (String)it.getAttributes().getAttribute(HTML.Attribute.SRC);
|
||||
it.next();
|
||||
assertFalse(it.isValid());
|
||||
URL imageUrl = new URL(document.getBase(), relativeUrl);
|
||||
try (InputStream stream = imageUrl.openStream()) {
|
||||
result.set(FileUtil.loadBytes(stream));
|
||||
}
|
||||
documentationManager.setDocumentationComponent(documentationComponent);
|
||||
documentationManager.showJavaDocInfo(editor, psiFile, false);
|
||||
waitTillDone(documentationManager.getLastAction());
|
||||
return documentationComponent.getText();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
finally {
|
||||
Disposer.dispose(documentationComponent);
|
||||
}
|
||||
});
|
||||
return result.get();
|
||||
}
|
||||
finally {
|
||||
EditorFactory.getInstance().releaseEditor(editor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRunInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class MockDocumentationComponent extends DocumentationComponent {
|
||||
private String myText;
|
||||
|
||||
public MockDocumentationComponent(DocumentationManager manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(String text, PsiElement element, boolean clean, boolean clearHistory) {
|
||||
myText = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(PsiElement _element, String text, boolean clearHistory, String effectiveExternalUrl, String ref) {
|
||||
myText = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return myText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-17
@@ -21,7 +21,6 @@ import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.Trinity;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.util.text.StringUtilRt;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
@@ -179,10 +178,10 @@ public abstract class AbstractExternalFilter {
|
||||
}
|
||||
|
||||
protected void doBuildFromStream(final String url, Reader input, final StringBuilder data, boolean searchForEncoding, boolean matchStart) throws IOException {
|
||||
Trinity<Pattern, Pattern, Boolean> settings = getParseSettings(url);
|
||||
@NonNls Pattern startSection = settings.first;
|
||||
@NonNls Pattern endSection = settings.second;
|
||||
boolean useDt = settings.third;
|
||||
ParseSettings settings = getParseSettings(url);
|
||||
@NonNls Pattern startSection = settings.startPattern;
|
||||
@NonNls Pattern endSection = settings.endPattern;
|
||||
boolean useDt = settings.useDt;
|
||||
@NonNls String greatestEndSection = "<!-- ========= END OF CLASS DATA ========= -->";
|
||||
|
||||
data.append(HTML);
|
||||
@@ -236,7 +235,7 @@ public abstract class AbstractExternalFilter {
|
||||
|
||||
if (read == null) {
|
||||
data.setLength(0);
|
||||
if (matchStart && input instanceof MyReader) {
|
||||
if (matchStart && !settings.forcePatternSearch && input instanceof MyReader) {
|
||||
try {
|
||||
final MyReader reader = contentEncoding != null ? new MyReader(((MyReader)input).myInputStream, contentEncoding)
|
||||
: new MyReader(((MyReader)input).myInputStream, ((MyReader)input).getEncoding());
|
||||
@@ -299,26 +298,19 @@ public abstract class AbstractExternalFilter {
|
||||
data.append(HTML_CLOSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides what settings should be used for parsing content represented by the given url.
|
||||
*
|
||||
* @param url url which points to the target content
|
||||
* @return following data: (start interested data boundary pattern; end interested data boundary pattern;
|
||||
* replace table data by <dt>)
|
||||
*/
|
||||
@NotNull
|
||||
protected Trinity<Pattern, Pattern, Boolean> getParseSettings(@NotNull String url) {
|
||||
protected ParseSettings getParseSettings(@NotNull String url) {
|
||||
Pattern startSection = ourClassDataStartPattern;
|
||||
Pattern endSection = ourClassDataEndPattern;
|
||||
boolean useDt = true;
|
||||
boolean anchorPresent = false;
|
||||
|
||||
Matcher anchorMatcher = ourAnchorSuffix.matcher(url);
|
||||
if (anchorMatcher.find()) {
|
||||
useDt = false;
|
||||
anchorPresent = true;
|
||||
startSection = Pattern.compile(Pattern.quote("<a name=\"" + anchorMatcher.group(1) + "\""), Pattern.CASE_INSENSITIVE);
|
||||
endSection = ourNonClassDataEndPattern;
|
||||
}
|
||||
return Trinity.create(startSection, endSection, useDt);
|
||||
return new ParseSettings(startSection, endSection, !anchorPresent, anchorPresent);
|
||||
}
|
||||
|
||||
private static boolean reachTheEnd(StringBuilder data, String read, StringBuilder classDetails) {
|
||||
@@ -441,4 +433,35 @@ public abstract class AbstractExternalFilter {
|
||||
myInputStream = in;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings used for parsing of external documentation
|
||||
*/
|
||||
protected static class ParseSettings {
|
||||
@NotNull
|
||||
/**
|
||||
* Pattern defining the start of target fragment
|
||||
*/
|
||||
private final Pattern startPattern;
|
||||
@NotNull
|
||||
/**
|
||||
* Pattern defining the end of target fragment
|
||||
*/
|
||||
private final Pattern endPattern;
|
||||
/**
|
||||
* If <code>false</code>, and line matching start pattern is not found, whole document will be processed
|
||||
*/
|
||||
private final boolean forcePatternSearch;
|
||||
/**
|
||||
* Replace table data by <dt>
|
||||
*/
|
||||
private final boolean useDt;
|
||||
|
||||
public ParseSettings(@NotNull Pattern startPattern, @NotNull Pattern endPattern, boolean useDt, boolean forcePatternSearch) {
|
||||
this.startPattern = startPattern;
|
||||
this.endPattern = endPattern;
|
||||
this.useDt = useDt;
|
||||
this.forcePatternSearch = forcePatternSearch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -103,6 +103,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
|
||||
private boolean myCloseOnSneeze;
|
||||
|
||||
private ActionCallback myLastAction;
|
||||
private DocumentationComponent myTestDocumentationComponent;
|
||||
|
||||
@Override
|
||||
protected String getToolwindowId() {
|
||||
@@ -431,7 +432,8 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
|
||||
PopupUpdateProcessor updateProcessor,
|
||||
final PsiElement originalElement,
|
||||
@Nullable final Runnable closeCallback) {
|
||||
final DocumentationComponent component = new DocumentationComponent(this);
|
||||
final DocumentationComponent component = myTestDocumentationComponent == null ? new DocumentationComponent(this) :
|
||||
myTestDocumentationComponent;
|
||||
component.setNavigateCallback(new Consumer<PsiElement>() {
|
||||
@Override
|
||||
public void consume(PsiElement psiElement) {
|
||||
@@ -1063,6 +1065,11 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
|
||||
public ActionCallback getLastAction() {
|
||||
return myLastAction;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void setDocumentationComponent(DocumentationComponent documentationComponent) {
|
||||
myTestDocumentationComponent = documentationComponent;
|
||||
}
|
||||
|
||||
private interface DocumentationCollector {
|
||||
@Nullable
|
||||
|
||||
Reference in New Issue
Block a user