diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/FieldInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/FieldInfo.java index 2415c3fae..c123af759 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/FieldInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/FieldInfo.java @@ -18,12 +18,17 @@ public final class FieldInfo { private final int declarationOffset; // Where this field was declared in the source private final boolean resolved; private final String documentation; // Javadoc/comment documentation for this field + + // Initialization value range (for displaying "= value" in hover info) + private final int initStart; // Position of '=' or -1 if no initializer + private final int initEnd; // Position after initializer (before ';') or -1 // For local/parameter fields, track the containing method private final MethodInfo containingMethod; private FieldInfo(String name, Scope scope, TypeInfo declaredType, - int declarationOffset, boolean resolved, MethodInfo containingMethod, String documentation) { + int declarationOffset, boolean resolved, MethodInfo containingMethod, + String documentation, int initStart, int initEnd) { this.name = name; this.scope = scope; this.declaredType = declaredType; @@ -31,34 +36,44 @@ public final class FieldInfo { this.resolved = resolved; this.containingMethod = containingMethod; this.documentation = documentation; + this.initStart = initStart; + this.initEnd = initEnd; } // Factory methods public static FieldInfo globalField(String name, TypeInfo type, int declOffset) { - return new FieldInfo(name, Scope.GLOBAL, type, declOffset, type != null && type.isResolved(), null, null); + return new FieldInfo(name, Scope.GLOBAL, type, declOffset, type != null && type.isResolved(), null, null, -1, -1); } public static FieldInfo globalField(String name, TypeInfo type, int declOffset, String documentation) { - return new FieldInfo(name, Scope.GLOBAL, type, declOffset, type != null && type.isResolved(), null, documentation); + return new FieldInfo(name, Scope.GLOBAL, type, declOffset, type != null && type.isResolved(), null, documentation, -1, -1); + } + + public static FieldInfo globalField(String name, TypeInfo type, int declOffset, String documentation, int initStart, int initEnd) { + return new FieldInfo(name, Scope.GLOBAL, type, declOffset, type != null && type.isResolved(), null, documentation, initStart, initEnd); } public static FieldInfo localField(String name, TypeInfo type, int declOffset, MethodInfo method) { - return new FieldInfo(name, Scope.LOCAL, type, declOffset, type != null && type.isResolved(), method, null); + return new FieldInfo(name, Scope.LOCAL, type, declOffset, type != null && type.isResolved(), method, null, -1, -1); + } + + public static FieldInfo localField(String name, TypeInfo type, int declOffset, MethodInfo method, int initStart, int initEnd) { + return new FieldInfo(name, Scope.LOCAL, type, declOffset, type != null && type.isResolved(), method, null, initStart, initEnd); } public static FieldInfo parameter(String name, TypeInfo type, int declOffset, MethodInfo method) { - return new FieldInfo(name, Scope.PARAMETER, type, declOffset, type != null && type.isResolved(), method, null); + return new FieldInfo(name, Scope.PARAMETER, type, declOffset, type != null && type.isResolved(), method, null, -1, -1); } public static FieldInfo unresolved(String name, Scope scope) { - return new FieldInfo(name, scope, null, -1, false, null, null); + return new FieldInfo(name, scope, null, -1, false, null, null, -1, -1); } /** * Create a FieldInfo from reflection data for method parameters. */ public static FieldInfo reflectionParam(String name, TypeInfo type) { - return new FieldInfo(name, Scope.PARAMETER, type, -1, true, null, null); + return new FieldInfo(name, Scope.PARAMETER, type, -1, true, null, null, -1, -1); } /** @@ -67,7 +82,7 @@ public final class FieldInfo { public static FieldInfo fromReflection(java.lang.reflect.Field field, TypeInfo containingType) { String name = field.getName(); TypeInfo type = TypeInfo.fromClass(field.getType()); - return new FieldInfo(name, Scope.GLOBAL, type, -1, true, null, null); + return new FieldInfo(name, Scope.GLOBAL, type, -1, true, null, null, -1, -1); } // Getters @@ -79,6 +94,9 @@ public final class FieldInfo { public boolean isResolved() { return resolved; } public MethodInfo getContainingMethod() { return containingMethod; } public String getDocumentation() { return documentation; } + public int getInitStart() { return initStart; } + public int getInitEnd() { return initEnd; } + public boolean hasInitializer() { return initStart >= 0 && initEnd > initStart; } public boolean isGlobal() { return scope == Scope.GLOBAL; } public boolean isLocal() { return scope == Scope.LOCAL; } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java index f520fcc11..8acc8532d 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java @@ -583,8 +583,28 @@ public class ScriptDocument { typeInfo = resolveType(typeName); } + // Extract initialization range if there's an '=' delimiter + int initStart = -1; + int initEnd = -1; + if ("=".equals(delimiter)) { + initStart = bodyStart + m.start(3); // Absolute position of '=' + // Find the semicolon or comma that ends this declaration + int searchPos = bodyStart + m.end(3); + int depth = 0; // Track nested parens/brackets/braces + while (searchPos < text.length()) { + char c = text.charAt(searchPos); + if (c == '(' || c == '[' || c == '{') depth++; + else if (c == ')' || c == ']' || c == '}') depth--; + else if ((c == ';' || c == ',') && depth == 0) { + initEnd = searchPos; // Position of ';' or ',' (exclusive) + break; + } + searchPos++; + } + } + int declPos = bodyStart + m.start(2); - FieldInfo fieldInfo = FieldInfo.localField(varName, typeInfo, declPos, method); + FieldInfo fieldInfo = FieldInfo.localField(varName, typeInfo, declPos, method, initStart, initEnd); locals.put(varName, fieldInfo); } } @@ -776,6 +796,7 @@ public class ScriptDocument { while (m.find()) { String typeNameRaw = m.group(1); String fieldName = m.group(2); + String delimiter = m.group(3); int position = m.start(2); // Skip if the field name itself is excluded @@ -798,8 +819,28 @@ public class ScriptDocument { // Extract documentation before this field String documentation = extractDocumentationBefore(m.start()); + // Extract initialization range if there's an '=' delimiter + int initStart = -1; + int initEnd = -1; + if ("=".equals(delimiter)) { + initStart = m.start(3); // Position of '=' + // Find the semicolon that ends this declaration + int searchPos = m.end(3); + int depth = 0; // Track nested parens/brackets/braces + while (searchPos < text.length()) { + char c = text.charAt(searchPos); + if (c == '(' || c == '[' || c == '{') depth++; + else if (c == ')' || c == ']' || c == '}') depth--; + else if (c == ';' && depth == 0) { + initEnd = searchPos; // Position of ';' (exclusive) + break; + } + searchPos++; + } + } + TypeInfo typeInfo = resolveType(typeName); - FieldInfo fieldInfo = FieldInfo.globalField(fieldName, typeInfo, position, documentation); + FieldInfo fieldInfo = FieldInfo.globalField(fieldName, typeInfo, position, documentation, initStart, initEnd); globalFields.put(fieldName, fieldInfo); } } @@ -3061,6 +3102,31 @@ public class ScriptDocument { } return -1; } + + /** + * Get all tokens that fall within a given range [start, end). + * Returns tokens in order. Tokens that span across the range boundaries + * are included if any part of them is within the range. + */ + public List getTokensInRange(int start, int end) { + List result = new ArrayList<>(); + if (start < 0 || end <= start) return result; + + for (ScriptLine line : lines) { + // Skip lines entirely before the range + if (line.getGlobalEnd() <= start) continue; + // Stop if line is entirely after the range + if (line.getGlobalStart() >= end) break; + + for (Token token : line.getTokens()) { + // Check if token overlaps with range + if (token.getGlobalEnd() > start && token.getGlobalStart() < end) { + result.add(token); + } + } + } + return result; + } /** * Find the expected type for an expression at the given position by looking for assignment context. 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 dfa017670..6facd698f 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 @@ -464,6 +464,9 @@ public class TokenHoverInfo { // Field name addSegment(fieldInfo.getName(), TokenType.GLOBAL_FIELD.getHexColor()); + + // Add initialization value if available + addInitializationTokens(token, fieldInfo); } private void extractLocalFieldInfo(Token token) { @@ -489,6 +492,9 @@ public class TokenHoverInfo { addSegment(fieldInfo.getName(), TokenType.LOCAL_FIELD.getHexColor()); + // Add initialization value if available + addInitializationTokens(token, fieldInfo); + // Show it's a local variable additionalInfo.add("Local variable"); } @@ -750,6 +756,31 @@ public class TokenHoverInfo { } return TokenType.IMPORTED_CLASS.getHexColor(); } + + /** + * Add initialization tokens from the field's initializer to the declaration. + * Fetches the tokens in the initialization range and adds them with their proper coloring. + */ + private void addInitializationTokens(Token token, FieldInfo fieldInfo) { + if (!fieldInfo.hasInitializer()) return; + + ScriptLine line = token.getParentLine(); + if (line == null || line.getParent() == null) return; + + ScriptDocument doc = line.getParent(); + // Include the semicolon by extending range by 1 + List initTokens = doc.getTokensInRange(fieldInfo.getInitStart(), fieldInfo.getInitEnd() + 1); + + if (initTokens.isEmpty()) return; + + // Add space before '=' for readability + addSegment(" ", TokenType.DEFAULT.getHexColor()); + + // Add each token with its proper color + for (Token initToken : initTokens) { + addSegment(initToken.getText(), initToken.getType().getHexColor()); + } + } // ==================== GETTERS ====================