mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-66333 Quick documentation lookup on mouse hover
1. Preserve old 'mouse hover' text, just use doc links there; 2. Limit 'mouse hover' text by the header width; 3. Test data is updated;
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2011 JetBrains s.r.o.
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,8 +32,10 @@ class Foo {{
|
||||
}}
|
||||
'''
|
||||
def ref = myFixture.file.findReferenceAt(myFixture.editor.caretModel.offset)
|
||||
assert CtrlMouseHandler.getInfo(ref.resolve(), ref.element) == """Bar
|
||||
java.util.List<java.lang.String> foo (java.lang.String param)"""
|
||||
assertEquals (
|
||||
'<a href="psi_element://Bar">Bar</a><br/> <a href="psi_element://java.util.List">List</a><java.lang.String> foo (java.lang.String param)',
|
||||
CtrlMouseHandler.getInfo(ref.resolve(), ref.element)
|
||||
)
|
||||
}
|
||||
|
||||
public void testGenericField() {
|
||||
@@ -45,8 +47,9 @@ class Foo {{
|
||||
}}
|
||||
'''
|
||||
def ref = myFixture.file.findReferenceAt(myFixture.editor.caretModel.offset)
|
||||
assert CtrlMouseHandler.getInfo(ref.resolve(), ref.element) == """Bar
|
||||
java.lang.Integer field"""
|
||||
assertEquals(
|
||||
'<a href="psi_element://Bar">Bar</a><br/> java.lang.Integer field',
|
||||
CtrlMouseHandler.getInfo(ref.resolve(), ref.element)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,8 +85,7 @@ import java.util.List;
|
||||
|
||||
public class CtrlMouseHandler extends AbstractProjectComponent {
|
||||
|
||||
private static final int ourQuickDocRowsNumber = getIntProperty("quick.doc.desired.rows.number", 2);
|
||||
private static final int ourQuickDocSymbolsInRowNumber = getIntProperty("quick.doc.desired.symbols.in.row.number", 80);
|
||||
private static final int ourQuickDocRowsNumber = getIntProperty("quick.doc.desired.rows.number", 1);
|
||||
|
||||
private final TextAttributes ourReferenceAttributes;
|
||||
private HighlightersSet myHighlighter;
|
||||
@@ -264,7 +263,7 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
|
||||
if (result != null) {
|
||||
String fullText = documentationProvider.generateDoc(element, atPointer);
|
||||
String qName = element instanceof PsiQualifiedNamedElement ? ((PsiQualifiedNamedElement)element).getQualifiedName() : null;
|
||||
String text = DocPreviewUtil.buildPreview(result, qName, fullText, ourQuickDocRowsNumber, ourQuickDocSymbolsInRowNumber);
|
||||
String text = DocPreviewUtil.buildPreview(result, qName, fullText, ourQuickDocRowsNumber);
|
||||
return new DocInfo(text, documentationProvider, atPointer);
|
||||
}
|
||||
return DocInfo.EMPTY;
|
||||
|
||||
@@ -18,12 +18,16 @@ package com.intellij.codeInsight.navigation;
|
||||
import com.intellij.lang.documentation.DocumentationProvider;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.containers.Stack;
|
||||
import gnu.trove.TObjectIntHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Provides utility methods for building documentation preview.
|
||||
@@ -35,15 +39,26 @@ import java.util.Set;
|
||||
*/
|
||||
public class DocPreviewUtil {
|
||||
|
||||
private static final Set<String> TAGS_TO_ADD_LF = new HashSet<String>(Arrays.asList("p", "blockquote", "pre"));
|
||||
private static final Set<String> TAGS_TO_IGNORE = new HashSet<String>(Arrays.asList("style", "b", "small"));
|
||||
private static final Set<String> TAGS_TO_ADD_LF = new HashSet<String>();
|
||||
private static final Set<String> TAGS_TO_IGNORE = new HashSet<String>();
|
||||
static {
|
||||
for (String tag : new String[] {"p", "blockquote", "pre"}) {
|
||||
TAGS_TO_ADD_LF.add(tag);
|
||||
TAGS_TO_ADD_LF.add(tag.toUpperCase());
|
||||
}
|
||||
|
||||
for (String tag : new String[] {"style", "b", "small"}) {
|
||||
TAGS_TO_IGNORE.add(tag);
|
||||
TAGS_TO_IGNORE.add(tag.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
private DocPreviewUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to build a documentation preview from the given arguments. Basically, takes given 'full documentation', wraps it according
|
||||
* to the given 'desired rows and columns per-row' arguments and returns a result.
|
||||
* Allows to build a documentation preview from the given arguments. Basically, takes given 'full documentation', cuts it according
|
||||
* to the given 'desired rows' argument and returns a result.
|
||||
*
|
||||
* @param header target documentation header. Is expected to be a result of the
|
||||
* {@link DocumentationProvider#getQuickNavigateInfo(PsiElement, PsiElement)} call
|
||||
@@ -52,16 +67,10 @@ public class DocPreviewUtil {
|
||||
* element with the given qualified name is added to the preview's end if the qName is provided then
|
||||
* @param fullText full documentation text (if available)
|
||||
* @param desiredRowsNumber maximum number of rows to use at the preview's body ('header' text is not count here)
|
||||
* @param desiredSymbolsInRowNumber desired max number of columns per row
|
||||
* @return preview text to use for the given arguments
|
||||
*/
|
||||
@NotNull
|
||||
public static String buildPreview(@NotNull String header,
|
||||
@Nullable String qName,
|
||||
@Nullable String fullText,
|
||||
int desiredRowsNumber,
|
||||
int desiredSymbolsInRowNumber)
|
||||
{
|
||||
public static String buildPreview(@NotNull String header, @Nullable String qName, @Nullable String fullText, int desiredRowsNumber) {
|
||||
if (fullText == null) {
|
||||
return header;
|
||||
}
|
||||
@@ -76,237 +85,283 @@ public class DocPreviewUtil {
|
||||
if (bodyEnd < 0) {
|
||||
return header;
|
||||
}
|
||||
|
||||
String body = fullText.substring(bodyStart, bodyEnd);
|
||||
|
||||
int headerEnd = fullText.indexOf("</PRE>");
|
||||
if (headerEnd < 0) {
|
||||
return header;
|
||||
}
|
||||
|
||||
String headerWithLinks = fullText.substring(bodyStart, headerEnd);
|
||||
String docText = fullText.substring(headerEnd + "</PRE>".length(), bodyEnd);
|
||||
|
||||
// The algorithm is:
|
||||
// 1. Process full text body as follows:
|
||||
// 1.1. Count non-markup symbols until desired row symbols number is exceeded;
|
||||
// 1.2. Insert <br> after that to start a new row;
|
||||
// 1.3. Stop processing as soon as the desired rows number is reached or the text is finished;
|
||||
// 2. Add closing tags for all non-matched open tags;
|
||||
// 1. Get given header text and replace meaningful symbols with the links to those symbols;
|
||||
// 2. Calculate max symbols per-row to use for the result as a number of non-markup symbols at the longest header line;
|
||||
// 3. Process full text body as follows:
|
||||
// 2.1. Count non-markup symbols until desired row symbols number is exceeded;
|
||||
// 2.2. Insert <br> after that to start a new row (if max rows number is not reached);
|
||||
// 3.3. Stop processing as soon as the desired rows number is reached or the text is finished;
|
||||
// 4. Add a link to the full documentation if it's not placed inside the resulted text;
|
||||
// 5. Add closing tags for all non-matched open tags;
|
||||
|
||||
final Context context = new Context(desiredRowsNumber, desiredSymbolsInRowNumber);
|
||||
int startParseOffset = 0;
|
||||
|
||||
//region 1. Prepare header to use
|
||||
final Context context = new Context(desiredRowsNumber);
|
||||
|
||||
// Include information about the library/module location.
|
||||
int bracket = header.indexOf(']');
|
||||
int lf = header.indexOf('\n');
|
||||
if (bracket > 0 && (lf < 0 || bracket < lf)) {
|
||||
context.buffer.append(header.substring(0, bracket + 1)).append(" ");
|
||||
}
|
||||
|
||||
// Include information that is available at the given header (it's not count to the given rows/columns arguments).
|
||||
startParseOffset = process(body, startParseOffset, body.length(), getHeaderParser(context, header));
|
||||
|
||||
//endregion
|
||||
|
||||
//region Parse body
|
||||
startParseOffset = process(body, startParseOffset, body.length(), getBodyParser(context));
|
||||
//endregion
|
||||
|
||||
if (qName != null && startParseOffset < body.length()) {
|
||||
context.buffer.append(String.format("<a href='psi_element://%s'><more></a>", qName));
|
||||
}
|
||||
int columnsPerRow = parseHeader(header, headerWithLinks, context);
|
||||
process(docText, new BodyCallback(context, qName, columnsPerRow));
|
||||
|
||||
//region Add closing tags
|
||||
while (!context.openTags.isEmpty()) {
|
||||
context.buffer.append("</").append(context.openTags.pop()).append('>');
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
return context.buffer.toString();
|
||||
}
|
||||
|
||||
private static int parseHeader(@NotNull String headerTemplate, @NotNull String headerWithLinks, @NotNull Context context) {
|
||||
|
||||
//region Build links info.
|
||||
Map<String/*qName*/, String/*address*/> links = new HashMap<String, String>();
|
||||
process(headerWithLinks, new LinksCollector(links));
|
||||
//endregion
|
||||
|
||||
|
||||
//region Apply links info to the header template.
|
||||
String headerToUse = headerTemplate.replace("\n", "<br/>");
|
||||
for (Map.Entry<String, String> entry : links.entrySet()) {
|
||||
String visibleName = entry.getKey();
|
||||
int i = visibleName.lastIndexOf('.');
|
||||
if (i > 0 && i < visibleName.length() - 1) {
|
||||
visibleName = visibleName.substring(i + 1);
|
||||
}
|
||||
headerToUse = headerToUse.replace(entry.getKey(), String.format("<a href=\"%s\">%s</a>", entry.getValue(), visibleName));
|
||||
}
|
||||
context.buffer.append(headerToUse);
|
||||
//endregion
|
||||
|
||||
//region Update 'columns-per-row' if the header is wide.
|
||||
MaxColumnCalculator calculator = new MaxColumnCalculator();
|
||||
process(headerToUse, calculator);
|
||||
return calculator.maxColumn;
|
||||
//endregion
|
||||
}
|
||||
|
||||
private enum State {TEXT, INSIDE_OPEN_TAG, INSIDE_CLOSE_TAG}
|
||||
|
||||
@SuppressWarnings("AssignmentToForLoopParameter")
|
||||
private static int process(@NotNull String text, int start, int end, @NotNull Callback callback) {
|
||||
private static int process(@NotNull String text, @NotNull Callback callback) {
|
||||
State state = State.TEXT;
|
||||
int dataStartOffset = start;
|
||||
int tagNameStartOffset = start;
|
||||
int dataStartOffset = 0;
|
||||
int tagNameStartOffset = 0;
|
||||
String tagName = null;
|
||||
for (; start < end; start++) {
|
||||
char c = text.charAt(start);
|
||||
int i = 0;
|
||||
for (; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
switch (state) {
|
||||
case TEXT:
|
||||
if (c == '<') {
|
||||
if (start > dataStartOffset) {
|
||||
if (!callback.onText(text.substring(dataStartOffset, start).replace(" ", " "))) {
|
||||
if (i > dataStartOffset) {
|
||||
if (!callback.onText(text.substring(dataStartOffset, i).replace(" ", " "))) {
|
||||
return dataStartOffset;
|
||||
}
|
||||
}
|
||||
dataStartOffset = start;
|
||||
if (start < text.length() - 1 && text.charAt(start + 1) == '/') {
|
||||
dataStartOffset = i;
|
||||
if (i < text.length() - 1 && text.charAt(i + 1) == '/') {
|
||||
state = State.INSIDE_CLOSE_TAG;
|
||||
tagNameStartOffset = ++start + 1;
|
||||
tagNameStartOffset = ++i + 1;
|
||||
}
|
||||
else {
|
||||
state = State.INSIDE_OPEN_TAG;
|
||||
tagNameStartOffset = start + 1;
|
||||
tagNameStartOffset = i + 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case INSIDE_OPEN_TAG:
|
||||
if (c == ' ') {
|
||||
tagName = text.substring(tagNameStartOffset, start);
|
||||
tagName = text.substring(tagNameStartOffset, i);
|
||||
}
|
||||
else if (c == '/') {
|
||||
if (start < text.length() - 1 && text.charAt(start + 1) == '>') {
|
||||
if (i < text.length() - 1 && text.charAt(i + 1) == '>') {
|
||||
if (tagName == null) {
|
||||
tagName = text.substring(tagNameStartOffset, start);
|
||||
tagName = text.substring(tagNameStartOffset, i);
|
||||
}
|
||||
if (!callback.onStandaloneTag(tagName, text.substring(dataStartOffset, start + 2))) {
|
||||
if (!callback.onStandaloneTag(tagName, text.substring(dataStartOffset, i + 2))) {
|
||||
return dataStartOffset;
|
||||
}
|
||||
tagName = null;
|
||||
state = State.TEXT;
|
||||
dataStartOffset = ++start + 1;
|
||||
dataStartOffset = ++i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (c == '>') {
|
||||
if (tagName == null) {
|
||||
tagName = text.substring(tagNameStartOffset, start);
|
||||
tagName = text.substring(tagNameStartOffset, i);
|
||||
}
|
||||
if (!callback.onOpenTag(tagName, text.substring(dataStartOffset, start + 1))) {
|
||||
if (!callback.onOpenTag(tagName, text.substring(dataStartOffset, i + 1))) {
|
||||
return dataStartOffset;
|
||||
}
|
||||
tagName = null;
|
||||
state = State.TEXT;
|
||||
dataStartOffset = start + 1;
|
||||
dataStartOffset = i + 1;
|
||||
}
|
||||
break;
|
||||
case INSIDE_CLOSE_TAG:
|
||||
if (c == '>') {
|
||||
if (tagName == null) {
|
||||
tagName = text.substring(tagNameStartOffset, start);
|
||||
tagName = text.substring(tagNameStartOffset, i);
|
||||
}
|
||||
if (!callback.onCloseTag(tagName, text.substring(dataStartOffset, start + 1))) {
|
||||
if (!callback.onCloseTag(tagName, text.substring(dataStartOffset, i + 1))) {
|
||||
return dataStartOffset;
|
||||
}
|
||||
tagName = null;
|
||||
state = State.TEXT;
|
||||
dataStartOffset = start + 1;
|
||||
dataStartOffset = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Callback getHeaderParser(@NotNull Context context, @NotNull final String header) {
|
||||
return new AbstractCallback(context, false) {
|
||||
|
||||
private boolean myStop;
|
||||
|
||||
@Override
|
||||
public boolean onOpenTag(@NotNull String name, @NotNull String text) {
|
||||
return !myStop && super.onOpenTag(name, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onText(@NotNull String text) {
|
||||
boolean addLf = false;
|
||||
for (String s : text.split("\n")) {
|
||||
if (addLf) {
|
||||
newLine();
|
||||
}
|
||||
else {
|
||||
addLf = true;
|
||||
}
|
||||
|
||||
if (s.length() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!header.contains(s) && s.startsWith("java.lang.")) {
|
||||
s = s.substring("java.lang.".length());
|
||||
}
|
||||
|
||||
if (myStop || !header.contains(s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.endsWith(s)) {
|
||||
myStop = true;
|
||||
}
|
||||
|
||||
addText(s);
|
||||
}
|
||||
if (text.endsWith("\n")) {
|
||||
newLine();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBreakBeforeText(@NotNull String text) {
|
||||
// Don't allow line break before the closing type parameter bracket.
|
||||
return !text.startsWith(">") && !text.startsWith(",");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Callback getBodyParser(@NotNull final Context context) {
|
||||
return new AbstractCallback(context, true) {
|
||||
|
||||
@Override
|
||||
public boolean onText(@NotNull String text) {
|
||||
return addText(text);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class Context {
|
||||
if (dataStartOffset < text.length()) {
|
||||
callback.onText(text.substring(dataStartOffset, text.length()).replace(" ", " "));
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private static class Context {
|
||||
|
||||
@NotNull public final Stack<String> openTags = new Stack<String>();
|
||||
@NotNull public final StringBuilder buffer = new StringBuilder();
|
||||
public final int rows;
|
||||
public final int columnsPerRow;
|
||||
public int currentRow;
|
||||
public int currentColumn;
|
||||
|
||||
public Context(int rows, int columnsPerRow) {
|
||||
public Context(int rows) {
|
||||
this.rows = rows;
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
}
|
||||
|
||||
private interface Callback {
|
||||
boolean onOpenTag(@NotNull String name, @NotNull String text);
|
||||
|
||||
boolean onCloseTag(@NotNull String name, @NotNull String text);
|
||||
|
||||
boolean onStandaloneTag(@NotNull String name, @NotNull String text);
|
||||
|
||||
boolean onText(@NotNull String text);
|
||||
}
|
||||
|
||||
private static abstract class AbstractCallback implements Callback {
|
||||
|
||||
@NotNull protected final Context myContext;
|
||||
private final boolean myCountRows;
|
||||
private boolean myScheduleNewLine;
|
||||
private boolean myInsidePre;
|
||||
private static class LinksCollector implements Callback {
|
||||
|
||||
protected AbstractCallback(@NotNull Context context, boolean countRows) {
|
||||
myContext = context;
|
||||
myCountRows = countRows;
|
||||
private static final Pattern HREF_PATTERN = Pattern.compile("href=[\"']([^\"']+)");
|
||||
|
||||
@NotNull private final Map<String, String> myLinks;
|
||||
private String myHref;
|
||||
|
||||
LinksCollector(@NotNull Map<String, String> links) {
|
||||
myLinks = links;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOpenTag(@NotNull String name, @NotNull String text) {
|
||||
if (!"a".equals(name)) {
|
||||
return true;
|
||||
}
|
||||
Matcher matcher = HREF_PATTERN.matcher(text);
|
||||
if (matcher.find()) {
|
||||
myHref = matcher.group(1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCloseTag(@NotNull String name, @NotNull String text) {
|
||||
if ("a".equals(name)) {
|
||||
myHref = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStandaloneTag(@NotNull String name, @NotNull String text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onText(@NotNull String text) {
|
||||
if (myHref != null) {
|
||||
myLinks.put(text, myHref);
|
||||
myHref = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MaxColumnCalculator implements Callback {
|
||||
|
||||
private static final TObjectIntHashMap<String> SUBSTITUTIONS = new TObjectIntHashMap<String>();
|
||||
static {
|
||||
SUBSTITUTIONS.put("<", 1);
|
||||
SUBSTITUTIONS.put(">", 1);
|
||||
SUBSTITUTIONS.put(" ", 1);
|
||||
}
|
||||
|
||||
public int maxColumn;
|
||||
private int myCurrentColumn;
|
||||
|
||||
@Override
|
||||
public boolean onOpenTag(@NotNull String name, @NotNull String text) {
|
||||
if ("br".equals(name)) {
|
||||
myCurrentColumn = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCloseTag(@NotNull String name, @NotNull String text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStandaloneTag(@NotNull String name, @NotNull String text) {
|
||||
return onOpenTag(name, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onText(@NotNull String text) {
|
||||
int length = text.length();
|
||||
if (SUBSTITUTIONS.containsKey(text)) {
|
||||
length = SUBSTITUTIONS.get(text);
|
||||
}
|
||||
myCurrentColumn += length;
|
||||
maxColumn = Math.max(maxColumn, myCurrentColumn);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class BodyCallback implements Callback {
|
||||
|
||||
@NotNull protected final Context myContext;
|
||||
private boolean myScheduleNewLine;
|
||||
private boolean myInsidePre;
|
||||
private boolean myDocAdded;
|
||||
private int myCurrentRow;
|
||||
private int myCurrentColumn;
|
||||
@Nullable private String myQName;
|
||||
private int myColumnsPerRow;
|
||||
|
||||
|
||||
protected BodyCallback(@NotNull Context context, @Nullable String qName, final int columnsPerRow) {
|
||||
myContext = context;
|
||||
myQName = qName;
|
||||
myColumnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
public boolean onOpenTag(@NotNull String name, @NotNull String text) {
|
||||
if ("pre".equals(name)) {
|
||||
myInsidePre = true;
|
||||
}
|
||||
if (!processDelayedLfTag(name)) {
|
||||
return myContext.currentRow < myContext.rows;
|
||||
return myCurrentRow < myContext.rows;
|
||||
}
|
||||
|
||||
if (!TAGS_TO_IGNORE.contains(name)) {
|
||||
myContext.buffer.append(text);
|
||||
addText(text, false);
|
||||
myContext.openTags.push(name);
|
||||
}
|
||||
return true;
|
||||
@@ -324,120 +379,123 @@ public class DocPreviewUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCloseTag(@NotNull String name, @NotNull String text) {
|
||||
if ("pre".equals(name)) {
|
||||
myInsidePre = false;
|
||||
}
|
||||
|
||||
if (!processDelayedLfTag(name)) {
|
||||
return myContext.currentRow < myContext.rows;
|
||||
return myCurrentRow < myContext.rows;
|
||||
}
|
||||
|
||||
|
||||
if (!TAGS_TO_IGNORE.contains(name)) {
|
||||
myContext.buffer.append(text);
|
||||
addText(text, false);
|
||||
myContext.openTags.remove(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStandaloneTag(@NotNull String name, @NotNull String text) {
|
||||
if (!processDelayedLfTag(name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TAGS_TO_IGNORE.contains(name)) {
|
||||
myContext.buffer.append(text);
|
||||
addText(text, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onText(@NotNull String text) {
|
||||
myContext.buffer.append(text);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean canBreakBeforeText(@NotNull String text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean addText(@NotNull String text) {
|
||||
boolean addSpace = false;
|
||||
if (!text.isEmpty() && (text.startsWith(" ") || text.startsWith("\t"))) {
|
||||
myContext.buffer.append(text.charAt(0));
|
||||
myContext.currentColumn++;
|
||||
if (!text.isEmpty() && (text.startsWith(" ") || text.startsWith("\t")) && !addText(String.valueOf(text.charAt(0)), true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String tailText = (!text.isEmpty() && (text.endsWith(" ") || text.endsWith("\t"))) ? text.substring(text.length() - 1) : null;
|
||||
String tailText = (!text.isEmpty() && (text.endsWith(" ") || text.endsWith("\t"))) ? text.substring(text.length() - 1) : null;
|
||||
|
||||
text = text.trim();
|
||||
if (myInsidePre && text.contains("\n")) {
|
||||
boolean addLf = false;
|
||||
for (String s : text.split("\n")) {
|
||||
if (addLf) {
|
||||
newLine();
|
||||
if (myContext.currentRow >= myContext.rows) {
|
||||
if (!newLine()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
addLf = true;
|
||||
}
|
||||
addText(s);
|
||||
if (myContext.currentRow >= myContext.rows) {
|
||||
return false;
|
||||
}
|
||||
if (!onText(s)) return false;
|
||||
}
|
||||
return myContext.currentRow < myContext.rows;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
for (String s : text.split(" ")) {
|
||||
s = s.trim();
|
||||
if (s.length() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (myScheduleNewLine && canBreakBeforeText(s)) {
|
||||
newLine();
|
||||
if (!newLine()) return false;
|
||||
addSpace = false;
|
||||
if (myContext.currentRow >= myContext.rows) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (addSpace) {
|
||||
myContext.buffer.append(" ");
|
||||
myContext.currentColumn++;
|
||||
if (!addText(" ", true)) return false;
|
||||
}
|
||||
else {
|
||||
addSpace = true;
|
||||
}
|
||||
|
||||
myContext.currentColumn += s.length();
|
||||
myContext.buffer.append(s);
|
||||
if (myContext.currentColumn < myContext.columnsPerRow) {
|
||||
continue;
|
||||
|
||||
if (!addText(s, true)) return false;
|
||||
}
|
||||
return !(tailText != null && !addText(tailText, true));
|
||||
}
|
||||
|
||||
private static boolean canBreakBeforeText(@NotNull String text) {
|
||||
return !text.startsWith(">") && !text.startsWith(",");
|
||||
}
|
||||
|
||||
private boolean newLine() {
|
||||
if (onLastRow()) {
|
||||
if (myQName != null) {
|
||||
myContext.buffer.append(String.format(" <a href='psi_element://%s'>...</a>", myQName));
|
||||
}
|
||||
myScheduleNewLine = true;
|
||||
}
|
||||
if (tailText != null) {
|
||||
myContext.buffer.append(tailText);
|
||||
myContext.currentColumn += tailText.length();
|
||||
return false;
|
||||
}
|
||||
addText("<br/>", false);
|
||||
myCurrentColumn = 0;
|
||||
myScheduleNewLine = false;
|
||||
myCurrentRow++;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void newLine() {
|
||||
myContext.buffer.append("<br/>");
|
||||
myContext.currentColumn = 0;
|
||||
myScheduleNewLine = false;
|
||||
if (myCountRows) {
|
||||
myContext.currentRow++;
|
||||
private boolean addText(@NotNull String text, boolean countColumns) {
|
||||
if (!myDocAdded) {
|
||||
myContext.buffer.append("<br/>");
|
||||
myDocAdded = true;
|
||||
}
|
||||
|
||||
if (!countColumns) {
|
||||
myContext.buffer.append(text);
|
||||
return true;
|
||||
}
|
||||
|
||||
int remainingColumns = myColumnsPerRow - myCurrentColumn;
|
||||
if (onLastRow()) {
|
||||
remainingColumns -= " ...".length();
|
||||
}
|
||||
if (remainingColumns < text.length() && !newLine()) return false;
|
||||
|
||||
myContext.buffer.append(text);
|
||||
myCurrentColumn += text.length();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onLastRow() {
|
||||
return myCurrentRow >= myContext.rows - 1;
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {TEXT, INSIDE_OPEN_TAG, INSIDE_CLOSE_TAG}
|
||||
}
|
||||
|
||||
+21
-2
@@ -19,6 +19,7 @@ package com.intellij.codeInsight.navigation;
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
import static org.junit.Assert.assertTrue
|
||||
|
||||
/**
|
||||
* @author Denis Zhdanov
|
||||
@@ -103,10 +104,28 @@ implements <a href="psi_element://java.io.Serializable"><code>java.io.Serializab
|
||||
'''
|
||||
|
||||
def expected = '''\
|
||||
[< 1.7 >] java.lang<PRE>public final class java.lang.String<br/>extends <a href="psi_element://java.lang.Object"><code>Object</code></a><br/>implements <a href="psi_element://java.io.Serializable"><code>java.io.Serializable</code></a>, <a href="psi_element://java.lang.Comparable"><code>java.lang.Comparable</code></a><<a href="psi_element://java.lang.String"><code>java.lang.String<br/></code></a>>, <a href="psi_element://java.lang.CharSequence"><code>java.lang.CharSequence</code></a></PRE><br/>The <code>String</code> class represents character strings. All string literals<br/>in Java programs, such as <code>"abc"</code>, are implemented as instances<br/><a href='psi_element://java.lang.String'><more></a>\
|
||||
java.lang<br/> public final class <a href="psi_element://java.lang.String">String</a> extends Object<br/> implements <a href="psi_element://java.io.Serializable">Serializable</a>, <a href="psi_element://java.lang.Comparable">Comparable</a><<a href="psi_element://java.lang.String">String</a>>, <a href="psi_element://java.lang.CharSequence">CharSequence</a><br/>The <code>String</code> class represents character strings. All string <br/>literals in Java programs, such as <code>"abc"</code>, are implemented as <br/>instances of this class. <br/>Strings are constant; their values cannot be changed after <br/>they are created. String buffers support mutable strings. <br/>Because String objects are immutable they can be shared. For <br/>example: <br/>String str = "abc"; <br/>is equivalent to: <br/>char data[] = {'a', 'b', 'c'}; <a href='psi_element://java.lang.String'>...</a>\
|
||||
'''
|
||||
|
||||
def actual = DocPreviewUtil.buildPreview(header, "java.lang.String", fullText, 2, 60)
|
||||
def actual = DocPreviewUtil.buildPreview(header, "java.lang.String", fullText, 10)
|
||||
assertTrue(actual.endsWith(expected)) // Can't check for equals() because jdk name might differ on different machines.
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldTypeSubstitution() {
|
||||
def header = '''\
|
||||
Bar
|
||||
java.util.List<java.lang.String> foo (java.lang.String param)\
|
||||
'''
|
||||
|
||||
def fullText = '''\
|
||||
<html><head> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><small><b><a href="psi_element://Bar"><code>Bar</code></a></b></small><PRE><a href="psi_element://java.util.List"><code>java.util.List</code></a><T> <b>foo</b>(T param)</PRE></body></html>\
|
||||
'''
|
||||
|
||||
def expected = '''\
|
||||
<a href="psi_element://Bar">Bar</a><br/> <a href="psi_element://java.util.List">List</a><java.lang.String> foo (java.lang.String param)\
|
||||
'''
|
||||
def actual = DocPreviewUtil.buildPreview(header, "java.lang.String", fullText, 2)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2011 JetBrains s.r.o.
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,8 +29,10 @@ class Bar<T> { java.util.List<T> foo(T param); }
|
||||
new Bar<String>().f<caret>oo();
|
||||
'''
|
||||
def ref = myFixture.file.findReferenceAt(myFixture.editor.caretModel.offset)
|
||||
assert CtrlMouseHandler.getInfo(ref.resolve(), ref.element) == """Bar
|
||||
java.util.List<java.lang.String> foo (java.lang.String param)"""
|
||||
assertEquals(
|
||||
"""<a href="psi_element://Bar">Bar</a><br/>java.util.<a href="psi_element://java.util.List">List</a><java.lang.String> foo (java.lang.String param)""",
|
||||
CtrlMouseHandler.getInfo(ref.resolve(), ref.element)
|
||||
)
|
||||
}
|
||||
|
||||
public void testGenericField() {
|
||||
@@ -39,8 +41,9 @@ class Bar<T> { T field; }
|
||||
new Bar<Integer>().fi<caret>eld
|
||||
'''
|
||||
def ref = myFixture.file.findReferenceAt(myFixture.editor.caretModel.offset)
|
||||
assert CtrlMouseHandler.getInfo(ref.resolve(), ref.element) == """Bar
|
||||
java.lang.Integer getField ()"""
|
||||
assertEquals(
|
||||
"""<a href="psi_element://Bar">Bar</a><br/>java.lang.Integer getField ()""",
|
||||
CtrlMouseHandler.getInfo(ref.resolve(), ref.element)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user