JSDoc tag rendering for HoverInfo

This commit is contained in:
bigguy345 committed 2026-01-05 23:49:49 +02:00
1 parent 9b1ffb4992
commit edffd7aaaf
2 files changed
+216 -7

No files matched your search

@@ -5,6 +5,10 @@ import noppes.npcs.client.gui.util.script.interpreter.field.AssignmentInfo;
import noppes.npcs.client.gui.util.script.interpreter.field.EnumConstantInfo;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldAccessInfo;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocInfo;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocParamTag;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocReturnTag;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocTypeTag;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodCallInfo;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.token.Token;
@@ -46,6 +50,9 @@ public class TokenHoverInfo {
/** Javadoc/documentation comment lines */
private List<String> documentation = new ArrayList<>();
/** JSDoc-formatted documentation with colored segments (sections like Params, Returns) */
private List<DocumentationLine> jsDocLines = new ArrayList<>();
/** Error messages (shown in red) */
private List<String> errors = new ArrayList<>();
@@ -56,6 +63,32 @@ public class TokenHoverInfo {
/** The token this info was built from */
private final Token token;
// ==================== DOCUMENTATION LINE ====================
/**
* A line of documentation that may contain colored segments.
* Used for JSDoc-style rendering with "Params:", parameter names, etc.
*/
public static class DocumentationLine {
public final List<TextSegment> segments;
public DocumentationLine() {
this.segments = new ArrayList<>();
}
public void addSegment(String text, int color) {
segments.add(new TextSegment(text, color));
}
public void addText(String text) {
segments.add(new TextSegment(text, TextSegment.COLOR_DEFAULT));
}
public boolean isEmpty() {
return segments.isEmpty() || segments.stream().allMatch(s -> s.text == null || s.text.isEmpty());
}
}
// ==================== TEXT SEGMENT ====================
/**
@@ -712,7 +745,10 @@ public class TokenHoverInfo {
iconIndicator = "f";
// Add documentation if available
if (fieldInfo.getDocumentation() != null && !fieldInfo.getDocumentation().isEmpty()) {
JSDocInfo jsDoc = fieldInfo.getJSDocInfo();
if (jsDoc != null) {
formatJSDocumentation(jsDoc, null);
} else if (fieldInfo.getDocumentation() != null && !fieldInfo.getDocumentation().isEmpty()) {
String[] docLines = fieldInfo.getDocumentation().split("\n");
for (String line : docLines) {
documentation.add(line);
@@ -1035,8 +1071,12 @@ public class TokenHoverInfo {
}
addSegment(")", TokenType.DEFAULT.getHexColor());
// Add documentation if available
if (methodInfo.getDocumentation() != null && !methodInfo.getDocumentation().isEmpty()) {
// Add documentation if available - use JSDoc formatting if we have JSDocInfo
JSDocInfo jsDoc = methodInfo.getJSDocInfo();
if (jsDoc != null) {
formatJSDocumentation(jsDoc, methodInfo.getParameters());
} else if (methodInfo.getDocumentation() != null && !methodInfo.getDocumentation().isEmpty()) {
// Fallback to raw documentation
String[] docLines = methodInfo.getDocumentation().split("\n");
for (String line : docLines) {
documentation.add(line);
@@ -1044,6 +1084,116 @@ public class TokenHoverInfo {
}
}
/**
* Format JSDoc information in IntelliJ-style with "Params:" and "Returns:" sections.
* Creates colored documentation lines with parameter names highlighted.
*/
private void formatJSDocumentation(JSDocInfo jsDoc, List<FieldInfo> methodParams) {
// Add description if available (without @tags)
String description = jsDoc.getDescription();
if (description != null && !description.isEmpty()) {
// Clean up the description - remove leading/trailing whitespace and asterisks
String[] descLines = description.split("\n");
for (String line : descLines) {
line = line.trim();
if (line.startsWith("*")) {
line = line.substring(1).trim();
}
if (!line.isEmpty() && !line.startsWith("@")) {
documentation.add(line);
}
}
}
// Add Returns section if there's a @return tag
JSDocTypeTag typeTag = jsDoc.getTypeTag();
if (typeTag != null) {
// "Type:" header
DocumentationLine typeLine = new DocumentationLine();
typeLine.addSegment("Type:", TokenType.JSDOC_TAG.getHexColor());
// Type if available
if (typeTag.hasType()) {
typeLine.addText(" ");
typeLine.addSegment("{", TokenType.JSDOC_TYPE.getHexColor());
typeLine.addSegment(typeTag.getTypeName(), TokenType.getColor(typeTag.getTypeInfo()));
typeLine.addSegment("}", TokenType.JSDOC_TYPE.getHexColor());
}
// Description if available
String typeDesc = typeTag.getDescription();
if (typeDesc != null && !typeDesc.isEmpty()) {
typeLine.addText(" - ");
typeLine.addText(typeDesc.trim());
}
jsDocLines.add(typeLine);
}
// Add Params section if there are @param tags
List<JSDocParamTag> paramTags = jsDoc.getParamTags();
if (paramTags != null && !paramTags.isEmpty()) {
// "Params:" header
DocumentationLine paramsHeader = new DocumentationLine();
paramsHeader.addSegment("Params:", TokenType.JSDOC_TAG.getHexColor());
jsDocLines.add(paramsHeader);
// Add each parameter
for (JSDocParamTag paramTag : paramTags) {
DocumentationLine paramLine = new DocumentationLine();
// Indent and parameter name
String paramName = paramTag.getParamName();
boolean paramExists = methodParams != null && methodParams.stream()
.anyMatch(p -> p.getName().equals(paramName));
paramLine.addSegment(paramName,
paramExists ? TokenType.PARAMETER.getHexColor() : TokenType.UNDEFINED_VAR.getHexColor());
// Type if available
if (paramTag.hasType()) {
paramLine.addSegment(" {", TokenType.JSDOC_TYPE.getHexColor());
paramLine.addSegment(paramTag.getTypeName(), TokenType.getColor(paramTag.getTypeInfo()));
paramLine.addSegment("}", TokenType.JSDOC_TYPE.getHexColor());
}
// Description if available
String paramDesc = paramTag.getDescription();
if (paramDesc != null && !paramDesc.isEmpty()) {
paramLine.addText(" - ");
paramLine.addText(paramDesc.trim());
}
jsDocLines.add(paramLine);
}
}
// Add Returns section if there's a @return tag
JSDocReturnTag returnTag = jsDoc.getReturnTag();
if (returnTag != null) {
DocumentationLine returnLine = new DocumentationLine();
//"Returns:" header
returnLine.addSegment("Returns:", TokenType.JSDOC_TAG.getHexColor());
// Type if available
if (returnTag.hasType()) {
returnLine.addText(" ");
returnLine.addSegment("{", TokenType.JSDOC_TYPE.getHexColor());
returnLine.addSegment(returnTag.getTypeName(), TokenType.getColor(returnTag.getTypeInfo()));
returnLine.addSegment("}", TokenType.JSDOC_TYPE.getHexColor());
}
// Description if available
String returnDesc = returnTag.getDescription();
if (returnDesc != null && !returnDesc.isEmpty()) {
returnLine.addText(" - ");
returnLine.addText(returnDesc.trim());
}
jsDocLines.add(returnLine);
}
}
private void extractJavadoc(Method method) {
// Java reflection doesn't provide Javadoc at runtime
@@ -1242,12 +1392,20 @@ public class TokenHoverInfo {
return declaration;
}
public List<String> getDocumentation() { return documentation; }
public List<DocumentationLine> getJSDocLines() {
return jsDocLines;
}
public List<String> getErrors() { return errors; }
public List<String> getAdditionalInfo() { return additionalInfo; }
public Token getToken() { return token; }
public boolean hasContent() {
return !declaration.isEmpty() || !errors.isEmpty() || !documentation.isEmpty();
return !declaration.isEmpty() || !errors.isEmpty() || !documentation.isEmpty() || !jsDocLines.isEmpty();
}
public boolean hasJSDocContent() {
return !jsDocLines.isEmpty();
}
public boolean hasErrors() {
@@ -203,7 +203,8 @@ public class TokenHoverRenderer {
currentY = drawWrappedSegments(textX, currentY, wrapWidth, declaration);
currentY += LINE_SPACING;
}
// Draw documentation
// Draw documentation (plain text)
if (!docs.isEmpty()) {
// Draw separator line before documentation
Gui.drawRect(textX, currentY, x + boxWidth - PADDING, currentY + SEPARATOR_HEIGHT, BORDER_COLOR);
@@ -217,6 +218,26 @@ public class TokenHoverRenderer {
}
}
// Draw JSDoc-formatted documentation with colored segments
List<TokenHoverInfo.DocumentationLine> jsDocLines = info.getJSDocLines();
if (!jsDocLines.isEmpty()) {
// Draw separator line if there was plain documentation or declaration
if (docs.isEmpty() && !declaration.isEmpty()) {
Gui.drawRect(textX, currentY, x + boxWidth - PADDING, currentY + SEPARATOR_HEIGHT, BORDER_COLOR);
currentY += SEPARATOR_HEIGHT + SEPARATOR_SPACING;
}
for (TokenHoverInfo.DocumentationLine docLine : jsDocLines) {
if (!docLine.isEmpty()) {
currentY = drawWrappedSegments(textX, currentY, wrapWidth, docLine.segments);
currentY += LINE_SPACING;
} else {
// Empty line - just add spacing
currentY += lineHeight + LINE_SPACING;
}
}
}
// Draw additional info
if (!additionalInfo.isEmpty()) {
@@ -368,6 +389,17 @@ public class TokenHoverRenderer {
}
}
// JSDoc-formatted documentation lines
List<TokenHoverInfo.DocumentationLine> jsDocLines = info.getJSDocLines();
if (jsDocLines != null) {
for (TokenHoverInfo.DocumentationLine docLine : jsDocLines) {
if (!docLine.isEmpty()) {
int lineLongest = calculateSegmentsLongestLine(maxWidth, docLine.segments);
longestLineWidth = Math.max(longestLineWidth, lineLongest);
}
}
}
// Additional info
for (String line : info.getAdditionalInfo()) {
List<String> wrappedLines = wrapText(line, maxWidth);
@@ -391,6 +423,7 @@ public class TokenHoverRenderer {
String packageName = info.getPackageName();
List<TokenHoverInfo.TextSegment> declaration = info.getDeclaration();
List<String> docs = info.getDocumentation();
List<TokenHoverInfo.DocumentationLine> jsDocLines = info.getJSDocLines();
List<String> additionalInfo = info.getAdditionalInfo();
// Errors
@@ -402,7 +435,7 @@ public class TokenHoverRenderer {
}
boolean onlyErrors = errors != null && !errors.isEmpty() && (packageName == null || packageName.isEmpty()) && (declaration == null ||
declaration.isEmpty()) && (docs == null || docs.isEmpty()) && (additionalInfo == null || additionalInfo.isEmpty());
declaration.isEmpty()) && (docs == null || docs.isEmpty()) && (jsDocLines == null || jsDocLines.isEmpty()) && (additionalInfo == null || additionalInfo.isEmpty());
if (!onlyErrors) //Add error separator height
totalHeight += (SEPARATOR_SPACING + SEPARATOR_HEIGHT) * 2 - 5;
@@ -423,7 +456,7 @@ public class TokenHoverRenderer {
totalHeight += LINE_SPACING;
}
// Documentation
// Documentation (plain text)
if (!docs.isEmpty()) {
// Add space for separator line and spacing
totalHeight += SEPARATOR_SPACING + SEPARATOR_HEIGHT;
@@ -433,6 +466,24 @@ public class TokenHoverRenderer {
}
}
// JSDoc-formatted documentation lines
if (jsDocLines != null && !jsDocLines.isEmpty()) {
// Add separator if there was plain documentation or declaration but no plain docs
if (docs.isEmpty() && !declaration.isEmpty()) {
// totalHeight += SEPARATOR_SPACING + SEPARATOR_HEIGHT;
}
for (TokenHoverInfo.DocumentationLine docLine : jsDocLines) {
if (!docLine.isEmpty()) {
totalHeight += calculateSegmentsHeight(contentWidth, docLine.segments);
//. totalHeight += LINE_SPACING;
} else {
// Empty line
// totalHeight += lineHeight + LINE_SPACING;
}
}
}
// Additional info
if (!additionalInfo.isEmpty()) {
totalHeight += LINE_SPACING;