Added constructors to script class types and hooked up their hover infos

This commit is contained in:
bigguy345 committed 2025-12-28 01:07:21 +02:00
1 parent dfc0232e99
commit ce5a67eaa2
6 files changed
+217 -3

No files matched your search

@@ -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<FieldInfo> 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<String, List<Map<String, String>>>.
@@ -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<MethodCallInfo.Argument> 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
@@ -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);
@@ -23,6 +23,7 @@ public class ScriptTypeInfo extends TypeInfo {
// Script-defined members
private final Map<String, FieldInfo> fields = new HashMap<>();
private final Map<String, List<MethodInfo>> methods = new HashMap<>(); // name -> list of overloads
private final List<MethodInfo> constructors = new ArrayList<>(); // List of constructors
private final List<ScriptTypeInfo> 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<MethodInfo> 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 ====================
/**
@@ -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<MethodInfo> 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.
@@ -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
@@ -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<FieldInfo> 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));