From edffd7aaaf87c5999a38b8f8c7f5b1eab74407f2 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Mon, 5 Jan 2026 23:49:49 +0200 Subject: [PATCH] JSDoc tag rendering for HoverInfo --- .../interpreter/hover/TokenHoverInfo.java | 166 +++++++++++++++++- .../interpreter/hover/TokenHoverRenderer.java | 57 +++++- 2 files changed, 216 insertions(+), 7 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java index 18faeaadf..40da28d3f 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java @@ -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 documentation = new ArrayList<>(); + + /** JSDoc-formatted documentation with colored segments (sections like Params, Returns) */ + private List jsDocLines = new ArrayList<>(); /** Error messages (shown in red) */ private List 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 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 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 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 getDocumentation() { return documentation; } + + public List getJSDocLines() { + return jsDocLines; + } public List getErrors() { return errors; } public List 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() { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverRenderer.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverRenderer.java index 4903ddb9a..4af1352cb 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverRenderer.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverRenderer.java @@ -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 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 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 wrappedLines = wrapText(line, maxWidth); @@ -391,6 +423,7 @@ public class TokenHoverRenderer { String packageName = info.getPackageName(); List declaration = info.getDeclaration(); List docs = info.getDocumentation(); + List jsDocLines = info.getJSDocLines(); List 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;