mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 20:07:32 +00:00
Refactored error curly underline rendering to its own helper class
This commit is contained in:
1 parent
e2a5d254a9
commit
534ec7b2c5
2 files changed
+281
-388
No files matched your search
+272
@@ -0,0 +1,272 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter;
|
||||
|
||||
import noppes.npcs.client.ClientProxy;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Helper class for rendering error underlines in script editor lines.
|
||||
* Handles the repetitive calculation of underline positions and drawing of curly underlines.
|
||||
*/
|
||||
public class ErrorUnderlineRenderer {
|
||||
private static final int ERROR_COLOR = 0xFF5555;
|
||||
|
||||
// Holds the calculation result for an underline
|
||||
private static class UnderlinePosition {
|
||||
final int x;
|
||||
final int width;
|
||||
|
||||
UnderlinePosition(int x, int width) {
|
||||
this.x = x;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return width > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw error underlines for all validation errors in the document that intersect the given line.
|
||||
*
|
||||
* @param doc The script document containing all errors
|
||||
* @param lineStartX X coordinate where the line starts rendering
|
||||
* @param baselineY Y coordinate for the underline baseline
|
||||
* @param lineText The text content of the line
|
||||
* @param lineStart Global offset where this line starts
|
||||
* @param lineEnd Global offset where this line ends
|
||||
*/
|
||||
public static void drawErrorUnderlines(
|
||||
ScriptDocument doc,
|
||||
int lineStartX, int baselineY,
|
||||
String lineText, int lineStart, int lineEnd) {
|
||||
|
||||
if (doc == null)
|
||||
return;
|
||||
|
||||
|
||||
// Check all method calls in the document
|
||||
for (MethodCallInfo call : doc.getMethodCalls()) {
|
||||
// Skip method declarations that were erroneously recorded as calls
|
||||
boolean isDeclaration = false;
|
||||
int methodStart = call.getMethodNameStart();
|
||||
for (MethodInfo mi : doc.getMethods()) {
|
||||
if (mi.getDeclarationOffset() <= methodStart && mi.getBodyStart() >= methodStart) {
|
||||
isDeclaration = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isDeclaration)
|
||||
continue;
|
||||
|
||||
// Skip if this call doesn't intersect this line
|
||||
if (call.getCloseParenOffset() < lineStart || call.getOpenParenOffset() > lineEnd)
|
||||
continue;
|
||||
|
||||
// Handle arg count errors (underline the method name)
|
||||
if (call.hasArgCountError()) {
|
||||
int methodEnd = methodStart + call.getMethodName().length();
|
||||
drawUnderlineForSpan(methodStart, methodEnd, lineStartX, baselineY,
|
||||
lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
|
||||
// Handle return type mismatch (underline the method name) - currently commented out
|
||||
// if (call.hasReturnTypeMismatch()) { ... }
|
||||
|
||||
// Handle arg type errors (underline specific arguments)
|
||||
if (call.hasArgTypeError()) {
|
||||
for (MethodCallInfo.ArgumentTypeError error : call.getArgumentTypeErrors()) {
|
||||
MethodCallInfo.Argument arg = error.getArg();
|
||||
drawUnderlineForSpan(arg.getStartOffset(), arg.getEndOffset(),
|
||||
lineStartX, baselineY, lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check all field accesses in the document (currently commented out)
|
||||
// for (FieldAccessInfo access : doc.getFieldAccesses()) { ... }
|
||||
|
||||
// Check all errored assignments in the document
|
||||
for (AssignmentInfo assign : doc.getAllErroredAssignments()) {
|
||||
int underlineStart, underlineEnd;
|
||||
|
||||
if (assign.isLhsError()) {
|
||||
underlineStart = assign.getLhsStart();
|
||||
underlineEnd = assign.getLhsEnd();
|
||||
} else if (assign.isRhsError()) {
|
||||
underlineStart = assign.getRhsStart();
|
||||
underlineEnd = assign.getRhsEnd();
|
||||
} else if (assign.isFullLineError()) {
|
||||
underlineStart = assign.getStatementStart();
|
||||
underlineEnd = assign.getRhsEnd();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
drawUnderlineForSpan(underlineStart, underlineEnd, lineStartX, baselineY,
|
||||
lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
|
||||
// Check all method declarations for errors
|
||||
for (MethodInfo method : doc.getMethods()) {
|
||||
if (!method.isDeclaration() || !method.hasError())
|
||||
continue;
|
||||
|
||||
// Handle return statement type errors
|
||||
if (method.hasReturnStatementErrors()) {
|
||||
for (MethodInfo.ReturnStatementError returnError : method.getReturnStatementErrors()) {
|
||||
drawUnderlineForSpan(returnError.getStartOffset(), returnError.getEndOffset(),
|
||||
lineStartX, baselineY, lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle missing return error (underline the method name)
|
||||
if (method.hasMissingReturnError()) {
|
||||
int methodNameStart = method.getNameOffset();
|
||||
int methodNameEnd = methodNameStart + method.getName().length();
|
||||
drawUnderlineForSpan(methodNameStart, methodNameEnd, lineStartX, baselineY,
|
||||
lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
|
||||
// Handle duplicate method error (underline from full declaration start to closing paren)
|
||||
if (method.getErrorType() == MethodInfo.ErrorType.DUPLICATE_METHOD) {
|
||||
drawUnderlineForSpan(method.getFullDeclarationOffset(), method.getDeclarationEnd(),
|
||||
lineStartX, baselineY, lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
|
||||
// Handle parameter errors
|
||||
if (method.hasParameterErrors()) {
|
||||
for (MethodInfo.ParameterError paramError : method.getParameterErrors()) {
|
||||
FieldInfo param = paramError.getParameter();
|
||||
if (param == null || param.getDeclarationOffset() < 0)
|
||||
continue;
|
||||
|
||||
int paramStart = param.getDeclarationOffset();
|
||||
int paramEnd = paramStart + param.getName().length();
|
||||
drawUnderlineForSpan(paramStart, paramEnd, lineStartX, baselineY,
|
||||
lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate underline position for a span of text within a line.
|
||||
* Handles clipping to line boundaries and pixel width calculation.
|
||||
*
|
||||
* @param spanStart Global offset where the span starts
|
||||
* @param spanEnd Global offset where the span ends
|
||||
* @param lineStartX X coordinate where the line starts rendering
|
||||
* @param lineText The text content of the line
|
||||
* @param lineStart Global offset where this line starts
|
||||
* @param lineEnd Global offset where this line ends
|
||||
* @return UnderlinePosition with x and width, or null if span doesn't intersect line
|
||||
*/
|
||||
private static UnderlinePosition calculateUnderlinePosition(
|
||||
int spanStart, int spanEnd,
|
||||
int lineStartX, String lineText, int lineStart, int lineEnd) {
|
||||
|
||||
// Skip if span doesn't intersect this line
|
||||
if (spanEnd < lineStart || spanStart > lineEnd)
|
||||
return null;
|
||||
|
||||
// Clip to line boundaries
|
||||
int clipStart = Math.max(spanStart, lineStart);
|
||||
int clipEnd = Math.min(spanEnd, lineEnd);
|
||||
|
||||
if (clipStart >= clipEnd)
|
||||
return null;
|
||||
|
||||
// Convert to line-local coordinates
|
||||
int lineLocalStart = clipStart - lineStart;
|
||||
int lineLocalEnd = clipEnd - lineStart;
|
||||
|
||||
// Bounds check
|
||||
if (lineLocalStart < 0 || lineLocalStart >= lineText.length())
|
||||
return null;
|
||||
|
||||
// Compute pixel position
|
||||
String beforeSpan = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeSpan);
|
||||
|
||||
int spanWidth;
|
||||
if (lineLocalEnd > lineText.length()) {
|
||||
// Span extends past line end
|
||||
spanWidth = ClientProxy.Font.width(lineText.substring(lineLocalStart));
|
||||
} else {
|
||||
// Span is fully on this line (or clipped)
|
||||
String spanTextOnLine = lineText.substring(lineLocalStart, lineLocalEnd);
|
||||
spanWidth = ClientProxy.Font.width(spanTextOnLine);
|
||||
}
|
||||
|
||||
return new UnderlinePosition(lineStartX + beforeWidth, spanWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw an underline for a simple span if it intersects the line.
|
||||
*
|
||||
* @param spanStart Global offset where the span starts
|
||||
* @param spanEnd Global offset where the span ends
|
||||
* @param lineStartX X coordinate where the line starts rendering
|
||||
* @param baselineY Y coordinate for the underline baseline
|
||||
* @param lineText The text content of the line
|
||||
* @param lineStart Global offset where this line starts
|
||||
* @param lineEnd Global offset where this line ends
|
||||
* @param color Underline color
|
||||
*/
|
||||
public static void drawUnderlineForSpan(
|
||||
int spanStart, int spanEnd,
|
||||
int lineStartX, int baselineY,
|
||||
String lineText, int lineStart, int lineEnd,
|
||||
int color) {
|
||||
|
||||
UnderlinePosition pos = calculateUnderlinePosition(
|
||||
spanStart, spanEnd, lineStartX, lineText, lineStart, lineEnd);
|
||||
|
||||
if (pos != null && pos.isValid()) {
|
||||
drawCurlyUnderline(pos.x, baselineY, pos.width, color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a curly/wavy underline (like IDE error highlighting).
|
||||
* @param x Start X position
|
||||
* @param y Y position (bottom of text)
|
||||
* @param width Width of the underline
|
||||
* @param color Color in ARGB format (e.g., 0xFFFF5555 for red)
|
||||
*/
|
||||
public static void drawCurlyUnderline(int x, int y, int width, int color) {
|
||||
if (width <= 0)
|
||||
return;
|
||||
|
||||
float a = ((color >> 24) & 0xFF) / 255f;
|
||||
float r = ((color >> 16) & 0xFF) / 255f;
|
||||
float g = ((color >> 8) & 0xFF) / 255f;
|
||||
float b = (color & 0xFF) / 255f;
|
||||
|
||||
// If alpha is 0, assume full opacity
|
||||
if (a == 0)
|
||||
a = 1.0f;
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glColor4f(r, g, b, a);
|
||||
GL11.glLineWidth(1.0f);
|
||||
|
||||
GL11.glBegin(GL11.GL_LINE_STRIP);
|
||||
// Wave parameters: 2 pixels amplitude, 4 pixels wavelength
|
||||
int waveHeight = 1;
|
||||
float waveLength = 4f;
|
||||
for (float i = -0.5f; i <= width - 1; i += 0.125f) {
|
||||
// Create a sine-like wave pattern
|
||||
double phase = (double) i / waveLength * Math.PI * 2;
|
||||
float yOffset = (float) (Math.sin(phase) * waveHeight) - 0.25f;
|
||||
GL11.glVertex2f(x + i + 2f, y + yOffset);
|
||||
}
|
||||
GL11.glEnd();
|
||||
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
}
|
||||
@@ -261,399 +261,20 @@ public class ScriptLine {
|
||||
|
||||
/**
|
||||
* Draw underlines for all validation errors (method calls and field accesses) that intersect this line.
|
||||
* Uses the same token-walking logic as Token.drawUnderline but applies it
|
||||
* to ALL validatable items in the document, enabling multi-line underlines.
|
||||
* Delegates to ErrorUnderlineRenderer for the actual rendering logic.
|
||||
*/
|
||||
private void drawErrorUnderlines(int lineStartX, int baselineY) {
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
ScriptDocument doc = parent;
|
||||
String lineText = getText();
|
||||
int lineStart = getGlobalStart();
|
||||
int lineEnd = getGlobalEnd();
|
||||
|
||||
// Check all method calls in the document
|
||||
for (MethodCallInfo call : doc.getMethodCalls()) {
|
||||
// Skip method declarations that were erroneously recorded as calls.
|
||||
boolean isDeclaration = false;
|
||||
int methodStart = call.getMethodNameStart();
|
||||
for (MethodInfo mi : doc.getMethods()) {
|
||||
//SKIP method declarations cause for some reason their underlines are drawn here
|
||||
if (mi.getDeclarationOffset() <= methodStart && mi.getBodyStart() >= methodStart) {
|
||||
isDeclaration = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isDeclaration)
|
||||
continue;
|
||||
// Skip if this call doesn't intersect this line
|
||||
if (call.getCloseParenOffset() < lineStart || call.getOpenParenOffset() > lineEnd)
|
||||
continue;
|
||||
|
||||
// Handle arg count errors (underline the method name)
|
||||
if (call.hasArgCountError()) {
|
||||
// Check if method name is on this line
|
||||
if (methodStart >= lineStart && methodStart < lineEnd) {
|
||||
int lineLocalStart = methodStart - lineStart;
|
||||
if (lineLocalStart >= 0 && lineLocalStart < lineText.length()) {
|
||||
String beforeMethod = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeMethod);
|
||||
int methodWidth = ClientProxy.Font.width(call.getMethodName());
|
||||
drawCurlyUnderline(lineStartX + beforeWidth, baselineY, methodWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle return type mismatch (underline the method name)
|
||||
if (call.hasReturnTypeMismatch()) {
|
||||
// Check if method name is on this line
|
||||
if (methodStart >= lineStart && methodStart < lineEnd) {
|
||||
int lineLocalStart = methodStart - lineStart;
|
||||
if (lineLocalStart >= 0 && lineLocalStart < lineText.length()) {
|
||||
String beforeMethod = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeMethod);
|
||||
int methodWidth = ClientProxy.Font.width(call.getMethodName());
|
||||
// drawCurlyUnderline(lineStartX + beforeWidth, baselineY, methodWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle arg type errors (underline specific arguments)
|
||||
if (call.hasArgTypeError()) {
|
||||
for (MethodCallInfo.ArgumentTypeError error : call.getArgumentTypeErrors()) {
|
||||
MethodCallInfo.Argument arg = error.getArg();
|
||||
int argStart = arg.getStartOffset();
|
||||
int argEnd = arg.getEndOffset();
|
||||
|
||||
// Clip argument to this line's bounds
|
||||
int clipStart = Math.max(argStart, lineStart);
|
||||
int clipEnd = Math.min(argEnd, lineEnd);
|
||||
|
||||
// Skip if argument doesn't intersect this line
|
||||
if (clipStart >= clipEnd)
|
||||
continue;
|
||||
|
||||
// Convert to line-local coordinates
|
||||
int lineLocalStart = clipStart - lineStart;
|
||||
int lineLocalEnd = clipEnd - lineStart;
|
||||
|
||||
// Bounds check
|
||||
if (lineLocalStart < 0 || lineLocalStart >= lineText.length())
|
||||
continue;
|
||||
|
||||
// Compute pixel position using same logic as Token.drawUnderline
|
||||
String beforeArg = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeArg);
|
||||
|
||||
int argWidth;
|
||||
if (lineLocalEnd > lineText.length()) {
|
||||
// Argument extends past line end
|
||||
argWidth = ClientProxy.Font.width(lineText.substring(lineLocalStart));
|
||||
} else {
|
||||
// Argument is fully on this line (or clipped)
|
||||
String argTextOnLine = lineText.substring(lineLocalStart, lineLocalEnd);
|
||||
argWidth = ClientProxy.Font.width(argTextOnLine);
|
||||
}
|
||||
|
||||
if (argWidth > 0) {
|
||||
drawCurlyUnderline(lineStartX + beforeWidth, baselineY, argWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check all field accesses in the document
|
||||
for (FieldAccessInfo access : doc.getFieldAccesses()) {
|
||||
// Skip if this access doesn't intersect this line
|
||||
int fieldStart = access.getFieldNameStart();
|
||||
int fieldEnd = access.getFieldNameEnd();
|
||||
|
||||
if (fieldEnd < lineStart || fieldStart > lineEnd)
|
||||
continue;
|
||||
|
||||
// Handle type mismatch errors (underline the field name)
|
||||
if (access.hasTypeMismatch()) {
|
||||
// Check if field name is on this line
|
||||
if (fieldStart >= lineStart && fieldStart < lineEnd) {
|
||||
int lineLocalStart = fieldStart - lineStart;
|
||||
if (lineLocalStart >= 0 && lineLocalStart < lineText.length()) {
|
||||
String beforeField = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeField);
|
||||
int fieldWidth = ClientProxy.Font.width(access.getFieldName());
|
||||
// drawCurlyUnderline(lineStartX + beforeWidth, baselineY, fieldWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check all errored assignments in the document (stored in FieldInfo objects)
|
||||
for (AssignmentInfo assign : doc.getAllErroredAssignments()) {
|
||||
// Determine what to underline based on error type
|
||||
// LHS errors (final reassignment, access errors) underline the LHS
|
||||
// RHS errors (type mismatch) underline the RHS
|
||||
|
||||
int underlineStart, underlineEnd;
|
||||
String underlineText;
|
||||
|
||||
if (assign.isLhsError()) {
|
||||
// Underline the LHS (target variable)
|
||||
underlineStart = assign.getLhsStart();
|
||||
underlineEnd = assign.getLhsEnd();
|
||||
underlineText = assign.getTargetName();
|
||||
} else if (assign.isRhsError()) {
|
||||
// Underline the RHS (source expression)
|
||||
underlineStart = assign.getRhsStart();
|
||||
underlineEnd = assign.getRhsEnd();
|
||||
underlineText = assign.getSourceExpr();
|
||||
} else if (assign.isFullLineError()) {
|
||||
underlineStart = assign.getStatementStart();
|
||||
underlineEnd = assign.getRhsEnd();
|
||||
} else {
|
||||
// Skip assignments with no displayable error
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if this doesn't intersect this line
|
||||
if (underlineEnd < lineStart || underlineStart > lineEnd)
|
||||
continue;
|
||||
|
||||
// Clip to line boundaries
|
||||
int clipStart = Math.max(underlineStart, lineStart);
|
||||
int clipEnd = Math.min(underlineEnd, lineEnd);
|
||||
|
||||
if (clipStart >= clipEnd)
|
||||
continue;
|
||||
|
||||
// Convert to line-local coordinates
|
||||
int lineLocalStart = clipStart - lineStart;
|
||||
int lineLocalEnd = clipEnd - lineStart;
|
||||
|
||||
// Bounds check
|
||||
if (lineLocalStart < 0 || lineLocalStart >= lineText.length())
|
||||
continue;
|
||||
|
||||
// Compute pixel position
|
||||
String beforeUnderline = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeUnderline);
|
||||
|
||||
int underlineWidth;
|
||||
if (lineLocalEnd > lineText.length()) {
|
||||
// Extends past line end
|
||||
underlineWidth = ClientProxy.Font.width(lineText.substring(lineLocalStart));
|
||||
} else {
|
||||
String underlineOnLine = lineText.substring(lineLocalStart, lineLocalEnd);
|
||||
underlineWidth = ClientProxy.Font.width(underlineOnLine);
|
||||
}
|
||||
|
||||
if (underlineWidth > 0) {
|
||||
drawCurlyUnderline(lineStartX + beforeWidth, baselineY, underlineWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
|
||||
// Check all method declarations for errors (missing return, parameter errors, return type errors)
|
||||
for (MethodInfo method : doc.getMethods()) {
|
||||
if (!method.isDeclaration() || !method.hasError())
|
||||
continue;
|
||||
|
||||
// Handle return statement type errors (underline the entire return statement)
|
||||
if (method.hasReturnStatementErrors()) {
|
||||
for (MethodInfo.ReturnStatementError returnError : method.getReturnStatementErrors()) {
|
||||
int returnStart = returnError.getStartOffset();
|
||||
int returnEnd = returnError.getEndOffset();
|
||||
|
||||
//error 247, 258
|
||||
//line 235, 259
|
||||
// Skip if return statement doesn't intersect this line
|
||||
if (returnEnd < lineStart || returnStart > lineEnd)
|
||||
continue;
|
||||
|
||||
// Clip to line boundaries
|
||||
int clipStart = Math.max(returnStart, lineStart);
|
||||
int clipEnd = Math.min(returnEnd, lineEnd);
|
||||
|
||||
if (clipStart >= clipEnd)
|
||||
continue;
|
||||
|
||||
// Convert to line-local coordinates
|
||||
int lineLocalStart = clipStart - lineStart;
|
||||
int lineLocalEnd = clipEnd - lineStart;
|
||||
|
||||
// Bounds check
|
||||
if (lineLocalStart < 0 || lineLocalStart >= lineText.length())
|
||||
continue;
|
||||
|
||||
// Compute pixel position
|
||||
String beforeReturn = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeReturn);
|
||||
|
||||
int returnWidth;
|
||||
if (lineLocalEnd > lineText.length()) {
|
||||
returnWidth = ClientProxy.Font.width(lineText.substring(lineLocalStart));
|
||||
} else {
|
||||
String returnTextOnLine = lineText.substring(lineLocalStart, lineLocalEnd);
|
||||
returnWidth = ClientProxy.Font.width(returnTextOnLine);
|
||||
}
|
||||
|
||||
if (returnWidth > 0) {
|
||||
drawCurlyUnderline(lineStartX + beforeWidth, baselineY, returnWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle missing return error (underline the method name)
|
||||
if (method.hasMissingReturnError()) {
|
||||
int methodNameStart = method.getNameOffset();
|
||||
int methodNameEnd = methodNameStart + method.getName().length();
|
||||
|
||||
// Skip if method name doesn't intersect this line
|
||||
if (methodNameEnd < lineStart || methodNameStart > lineEnd)
|
||||
continue;
|
||||
|
||||
// Check if method name is on this line
|
||||
if (methodNameStart >= lineStart && methodNameStart < lineEnd) {
|
||||
int lineLocalStart = methodNameStart - lineStart;
|
||||
if (lineLocalStart >= 0 && lineLocalStart < lineText.length()) {
|
||||
String beforeMethod = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeMethod);
|
||||
int methodWidth = ClientProxy.Font.width(method.getName());
|
||||
drawCurlyUnderline(lineStartX + beforeWidth, baselineY, methodWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle duplicate method error (underline from full declaration start to closing paren)
|
||||
if (method.getErrorType() == MethodInfo.ErrorType.DUPLICATE_METHOD) {
|
||||
int declStart = method.getFullDeclarationOffset();
|
||||
int declEnd = method.getDeclarationEnd();
|
||||
|
||||
// Skip if declaration doesn't intersect this line
|
||||
if (declEnd < lineStart || declStart > lineEnd)
|
||||
continue;
|
||||
|
||||
// Clip to line boundaries
|
||||
int clipStart = Math.max(declStart, lineStart);
|
||||
int clipEnd = Math.min(declEnd, lineEnd);
|
||||
|
||||
if (clipStart >= clipEnd)
|
||||
continue;
|
||||
|
||||
// Convert to line-local coordinates
|
||||
int lineLocalStart = clipStart - lineStart;
|
||||
int lineLocalEnd = clipEnd - lineStart;
|
||||
|
||||
// Bounds check
|
||||
if (lineLocalStart < 0 || lineLocalStart >= lineText.length())
|
||||
continue;
|
||||
|
||||
// Compute pixel position
|
||||
String beforeDecl = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeDecl);
|
||||
|
||||
int declWidth;
|
||||
if (lineLocalEnd > lineText.length()) {
|
||||
declWidth = ClientProxy.Font.width(lineText.substring(lineLocalStart));
|
||||
} else {
|
||||
String declTextOnLine = lineText.substring(lineLocalStart, lineLocalEnd);
|
||||
declWidth = ClientProxy.Font.width(declTextOnLine);
|
||||
}
|
||||
|
||||
if (declWidth > 0) {
|
||||
drawCurlyUnderline(lineStartX + beforeWidth, baselineY, declWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle parameter errors (underline specific parameters)
|
||||
if (method.hasParameterErrors()) {
|
||||
for (MethodInfo.ParameterError paramError : method.getParameterErrors()) {
|
||||
FieldInfo param = paramError.getParameter();
|
||||
if (param == null || param.getDeclarationOffset() < 0)
|
||||
continue;
|
||||
|
||||
int paramStart = param.getDeclarationOffset();
|
||||
int paramEnd = paramStart + param.getName().length();
|
||||
|
||||
// Skip if parameter doesn't intersect this line
|
||||
if (paramEnd < lineStart || paramStart > lineEnd)
|
||||
continue;
|
||||
|
||||
// Clip to line boundaries
|
||||
int clipStart = Math.max(paramStart, lineStart);
|
||||
int clipEnd = Math.min(paramEnd, lineEnd);
|
||||
|
||||
if (clipStart >= clipEnd)
|
||||
continue;
|
||||
|
||||
// Convert to line-local coordinates
|
||||
int lineLocalStart = clipStart - lineStart;
|
||||
int lineLocalEnd = clipEnd - lineStart;
|
||||
|
||||
// Bounds check
|
||||
if (lineLocalStart < 0 || lineLocalStart >= lineText.length())
|
||||
continue;
|
||||
|
||||
// Compute pixel position
|
||||
String beforeParam = lineText.substring(0, lineLocalStart);
|
||||
int beforeWidth = ClientProxy.Font.width(beforeParam);
|
||||
|
||||
int paramWidth;
|
||||
if (lineLocalEnd > lineText.length()) {
|
||||
paramWidth = ClientProxy.Font.width(lineText.substring(lineLocalStart));
|
||||
} else {
|
||||
String paramTextOnLine = lineText.substring(lineLocalStart, lineLocalEnd);
|
||||
paramWidth = ClientProxy.Font.width(paramTextOnLine);
|
||||
}
|
||||
|
||||
if (paramWidth > 0) {
|
||||
drawCurlyUnderline(lineStartX + beforeWidth, baselineY, paramWidth, 0xFF5555);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a curly/wavy underline (like IDE error highlighting).
|
||||
* @param x Start X position
|
||||
* @param y Y position (bottom of text)
|
||||
* @param width Width of the underline
|
||||
* @param color Color in ARGB format (e.g., 0xFFFF5555 for red)
|
||||
*/
|
||||
protected static void drawCurlyUnderline(int x, int y, int width, int color) {
|
||||
if (width <= 0)
|
||||
return;
|
||||
|
||||
float a = ((color >> 24) & 0xFF) / 255f;
|
||||
float r = ((color >> 16) & 0xFF) / 255f;
|
||||
float g = ((color >> 8) & 0xFF) / 255f;
|
||||
float b = (color & 0xFF) / 255f;
|
||||
|
||||
// If alpha is 0, assume full opacity
|
||||
if (a == 0)
|
||||
a = 1.0f;
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glColor4f(r, g, b, a);
|
||||
GL11.glLineWidth(1.0f);
|
||||
|
||||
GL11.glBegin(GL11.GL_LINE_STRIP);
|
||||
// Wave parameters: 2 pixels amplitude, 4 pixels wavelength
|
||||
int waveHeight = 1;
|
||||
float waveLength = 4f;
|
||||
for (float i = -0.5f; i <= width - 1; i += 0.125f) {
|
||||
// Create a sine-like wave pattern
|
||||
double phase = (double) i / waveLength * Math.PI * 2;
|
||||
float yOffset = (float) (Math.sin(phase) * waveHeight) - 0.25f;
|
||||
GL11.glVertex2f(x + i + 2f, y + yOffset);
|
||||
}
|
||||
GL11.glEnd();
|
||||
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glPopMatrix();
|
||||
ErrorUnderlineRenderer.drawErrorUnderlines(
|
||||
parent,
|
||||
lineStartX,
|
||||
baselineY,
|
||||
getText(),
|
||||
getGlobalStart(),
|
||||
getGlobalEnd()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user