From ce5a67eaa2a956c957eae62a00682454b663e912 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Sun, 28 Dec 2025 01:07:21 +0200 Subject: [PATCH] Added constructors to script class types and hooked up their hover infos --- .../script/interpreter/ScriptDocument.java | 112 +++++++++++++++++- .../util/script/interpreter/ScriptLine.java | 4 + .../script/interpreter/ScriptTypeInfo.java | 31 +++++ .../gui/util/script/interpreter/TypeInfo.java | 27 +++++ .../script/interpreter/hover/HoverState.java | 2 +- .../interpreter/hover/TokenHoverInfo.java | 44 ++++++- 6 files changed, 217 insertions(+), 3 deletions(-) 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 b3b6bd21e..62a53d1ad 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 @@ -459,6 +459,45 @@ public class ScriptDocument { methodName, returnType, params, absPos, methodBodyStart, methodBodyEnd, modifiers, documentation); scriptType.addMethod(methodInfo); } + + // Parse constructors (ClassName(params) { ... }) + // Constructor pattern: ClassName matches the script type name, no return type + String typeName = scriptType.getSimpleName(); + Pattern constructorPattern = Pattern.compile( + "\\b" + Pattern.quote(typeName) + "\\s*\\(([^)]*)\\)\\s*\\{"); + Matcher cm = constructorPattern.matcher(bodyText); + while (cm.find()) { + int absPos = bodyStart + 1 + cm.start(); + if (isExcluded(absPos)) continue; + + String paramList = cm.group(1); + + int constructorBodyStart = bodyStart + 1 + cm.end() - 1; + int constructorBodyEnd = findMatchingBrace(constructorBodyStart); + if (constructorBodyEnd < 0) constructorBodyEnd = bodyEnd; + + // Extract documentation before this constructor + String documentation = extractDocumentationBefore(absPos); + + // Extract modifiers by scanning backwards + int modifiers = extractModifiersBackwards(cm.start() - 1, bodyText); + + // Parse parameters with their actual positions + List params = parseParametersWithPositions(paramList, bodyStart + 1 + cm.start(1)); + + // Constructors don't have a return type, but we'll use the containing type as a marker + MethodInfo constructorInfo = MethodInfo.declaration( + typeName, + scriptType, // Return type is the type itself + params, + absPos, + constructorBodyStart, + constructorBodyEnd, + modifiers, + documentation + ); + scriptType.addConstructor(constructorInfo); + } } /** @@ -1202,7 +1241,6 @@ public class ScriptDocument { // Type declarations and usages markTypeDeclarations(marks); - addPatternMarks(marks, NEW_TYPE_PATTERN, TokenType.NEW_TYPE, 1); // Methods markMethodDeclarations(marks); @@ -1534,6 +1572,19 @@ public class ScriptDocument { } } + /** + * Wrapper class to hold both TypeInfo and constructor MethodInfo for "new" expressions. + */ + static class NewExpressionInfo { + final TypeInfo typeInfo; + final MethodInfo constructor; + + NewExpressionInfo(TypeInfo typeInfo, MethodInfo constructor) { + this.typeInfo = typeInfo; + this.constructor = constructor; + } + } + /** * Recursively parse and mark generic type parameters. * Handles arbitrarily nested generics like Map>>. @@ -2534,6 +2585,19 @@ public class ScriptDocument { marks.add(new ScriptLine.Mark(pos, pos + name.length(), TokenType.PARAMETER, param)); } } + + // Mark constructor parameters from script types + for (ScriptTypeInfo scriptType : scriptTypes.values()) { + for (MethodInfo constructor : scriptType.getConstructors()) { + for (FieldInfo param : constructor.getParameters()) { + int pos = param.getDeclarationOffset(); + if (pos >= 0) { + String name = param.getName(); + marks.add(new ScriptLine.Mark(pos, pos + name.length(), TokenType.PARAMETER, param)); + } + } + } + } Pattern identifier = Pattern.compile("\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b"); Matcher m = identifier.matcher(text); @@ -3279,6 +3343,7 @@ public class ScriptDocument { while (tm.find()) { String className = tm.group(2); + String newKeyword = tm.group(1); int start = tm.start(2); int end = tm.end(2); @@ -3292,6 +3357,51 @@ public class ScriptDocument { // Try to resolve the class TypeInfo info = resolveType(className); if (info != null && info.isResolved()) { + boolean isNewCreation = newKeyword != null && newKeyword.trim().equals("new"); + boolean isConstructorDecl = info instanceof ScriptTypeInfo && className.equals(info.getSimpleName()); + + // Check if this is a "new" expression or constructor declaration + if (isNewCreation || isConstructorDecl) { + // Find opening paren after the class name + int searchPos = end; + while (searchPos < text.length() && Character.isWhitespace(text.charAt(searchPos))) + searchPos++; + + if (searchPos < text.length() && text.charAt(searchPos) == '(') { + int openParen = searchPos; + int closeParen = findMatchingParen(openParen); + + if (closeParen >= 0) { + // For constructor declarations, verify it's followed by opening brace + if (isConstructorDecl && !isNewCreation) { + int braceSearch = closeParen + 1; + while (braceSearch < text.length() && Character.isWhitespace(text.charAt(braceSearch))) + braceSearch++; + + if (braceSearch >= text.length() || text.charAt(braceSearch) != '{') { + // Not a constructor declaration, treat as normal type usage + marks.add(new ScriptLine.Mark(start, end, info.getTokenType(), info)); + continue; + } + } + + // Parse arguments to find matching constructor + List arguments = parseMethodArguments(openParen + 1, closeParen); + int argCount = arguments.size(); + + if (info.hasConstructors()) { + MethodInfo constructor = info.findConstructor(argCount); + if (constructor != null) { + // Attach both type info and constructor + NewExpressionInfo newExpr = new NewExpressionInfo(info, constructor); + marks.add(new ScriptLine.Mark(start, end, info.getTokenType(), newExpr)); + continue; + } + } + } + } + } + marks.add(new ScriptLine.Mark(start, end, info.getTokenType(), info)); } else { // Unknown type - mark as undefined diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptLine.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptLine.java index b32749ad5..4fd6d5cdc 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptLine.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptLine.java @@ -152,6 +152,10 @@ public class ScriptLine { if (mark.metadata != null) { if (mark.metadata instanceof TypeInfo) { token.setTypeInfo((TypeInfo) mark.metadata); + } else if (mark.metadata instanceof ScriptDocument.NewExpressionInfo) { + ScriptDocument.NewExpressionInfo newExpr = (ScriptDocument.NewExpressionInfo) mark.metadata; + token.setTypeInfo(newExpr.typeInfo); + token.setMethodInfo(newExpr.constructor); } else if (mark.metadata instanceof FieldInfo.ArgInfo) { FieldInfo.ArgInfo ctx = (FieldInfo.ArgInfo) mark.metadata; token.setFieldInfo(ctx.fieldInfo); diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptTypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptTypeInfo.java index 7c7f47619..a2fba036d 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptTypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptTypeInfo.java @@ -23,6 +23,7 @@ public class ScriptTypeInfo extends TypeInfo { // Script-defined members private final Map fields = new HashMap<>(); private final Map> methods = new HashMap<>(); // name -> list of overloads + private final List constructors = new ArrayList<>(); // List of constructors private final List innerClasses = new ArrayList<>(); // Parent class reference (for inner class resolution) @@ -143,6 +144,36 @@ public class ScriptTypeInfo extends TypeInfo { return new HashMap<>(methods); } + // ==================== CONSTRUCTOR MANAGEMENT ==================== + + public void addConstructor(MethodInfo constructor) { + constructors.add(constructor); + } + + @Override + public List getConstructors() { + return new ArrayList<>(constructors); + } + + @Override + public boolean hasConstructors() { + return !constructors.isEmpty(); + } + + /** + * Find the best matching constructor for the given argument count. + * Returns null if no constructor matches. + */ + @Override + public MethodInfo findConstructor(int argCount) { + for (MethodInfo constructor : constructors) { + if (constructor.getParameterCount() == argCount) { + return constructor; + } + } + return null; + } + // ==================== INNER CLASS LOOKUP ==================== /** diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/TypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/TypeInfo.java index 862e5f108..2184f42a6 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/TypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/TypeInfo.java @@ -1,5 +1,8 @@ package noppes.npcs.client.gui.util.script.interpreter; +import java.util.ArrayList; +import java.util.List; + /** * Represents resolved type information for a class/interface/enum. * Base class for type metadata. Extended by ScriptTypeInfo for script-defined types. @@ -175,6 +178,30 @@ public class TypeInfo { } return false; } + + /** + * Check if this type has constructors. + * Override in ScriptTypeInfo for script-defined types. + */ + public boolean hasConstructors() { + return false; + } + + /** + * Get the constructors for this type. + * Override in ScriptTypeInfo for script-defined types. + */ + public List getConstructors() { + return new ArrayList<>(); + } + + /** + * Find the best matching constructor for the given argument count. + * Override in ScriptTypeInfo for script-defined types. + */ + public MethodInfo findConstructor(int argCount) { + return null; + } /** * Check if this type has a field with the given name. diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/HoverState.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/HoverState.java index 65e9d7c2d..4025e0611 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/HoverState.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/HoverState.java @@ -151,7 +151,7 @@ public class HoverState { public void update(int mouseX, int mouseY, Token token, int tokenX, int tokenY, int tokenW) { lastMouseX = mouseX; lastMouseY = mouseY; - clickToPinEnabled=false; + clickToPinEnabled=true; // If a token has been pinned by click, ignore mouse movement updates if (pinnedToken != null) { // keep pinned tooltip visible 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 6fa3efc93..8a0c6c1e3 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 @@ -397,6 +397,14 @@ public class TokenHoverInfo { errors.add("Cannot resolve class '" + typeInfo.getSimpleName() + "'"); } } + + // If this is a NEW_TYPE token with constructor info, show the constructor signature + if (token.getMethodInfo() != null) { + MethodInfo constructor = token.getMethodInfo(); + declaration.add(new TextSegment("\n", TokenType.DEFAULT.getHexColor())); + additionalInfo.add("Constructor"); + buildConstructorDeclaration(constructor, typeInfo); + } } private void extractMethodCallInfo(Token token) { @@ -607,7 +615,41 @@ public class TokenHoverInfo { } } - // ==================== HELPER METHODS ==================== + private void buildConstructorDeclaration(MethodInfo constructor, TypeInfo containingType) { + // Modifiers (public, private, etc.) + declaration.clear(); + int mods = constructor.getModifiers(); + if (Modifier.isPublic(mods)) addSegment("public ", TokenType.MODIFIER.getHexColor()); + else if (Modifier.isProtected(mods)) addSegment("protected ", TokenType.MODIFIER.getHexColor()); + else if (Modifier.isPrivate(mods)) addSegment("private ", TokenType.MODIFIER.getHexColor()); + + // Constructor name (same as class name) + addSegment(constructor.getName(), containingType.getTokenType().getHexColor()); + + // Parameters + addSegment("(", TokenType.DEFAULT.getHexColor()); + List params = constructor.getParameters(); + for (int i = 0; i < params.size(); i++) { + if (i > 0) addSegment(", ", TokenType.DEFAULT.getHexColor()); + FieldInfo param = params.get(i); + TypeInfo paramType = param.getDeclaredType(); + if (paramType != null) { + int paramTypeColor = getColorForTypeInfo(paramType); + addSegment(paramType.getSimpleName(), paramTypeColor); + addSegment(" ", TokenType.DEFAULT.getHexColor()); + } + addSegment(param.getName(), TokenType.PARAMETER.getHexColor()); + } + addSegment(")", TokenType.DEFAULT.getHexColor()); + + // Add documentation if available + if (constructor.getDocumentation() != null && !constructor.getDocumentation().isEmpty()) { + String[] docLines = constructor.getDocumentation().split("\n"); + for (String line : docLines) { + documentation.add(line); + } + } + } private void addSegment(String text, int color) { declaration.add(new TextSegment(text, color));