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 f9e8a4d26..cf9019122 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 @@ -907,14 +907,16 @@ public class ScriptDocument { String trimmed = param.trim(); if (trimmed.isEmpty()) continue; - int extendsIdx = trimmed.indexOf(" extends "); + int extendsIdx = trimmed.indexOf("extends"); if (extendsIdx >= 0) { String paramName = trimmed.substring(0, extendsIdx).trim(); - String boundName = trimmed.substring(extendsIdx + 9).trim(); - TypeParamInfo typeParam = new TypeParamInfo(paramName, boundName, null); - TypeInfo boundTypeInfo = resolveType(boundName, declOffset); - if (boundTypeInfo != null && boundTypeInfo.isResolved()) { - typeParam.setBoundTypeInfo(boundTypeInfo); + String boundName = trimmed.substring(extendsIdx + 7).trim(); + TypeParamInfo typeParam = new TypeParamInfo(paramName, boundName.isEmpty() ? null : boundName, null); + if (!boundName.isEmpty()) { + TypeInfo boundTypeInfo = resolveType(boundName, declOffset); + if (boundTypeInfo != null && boundTypeInfo.isResolved()) { + typeParam.setBoundTypeInfo(boundTypeInfo); + } } scriptType.addDeclaredTypeParam(typeParam); } else { @@ -953,10 +955,33 @@ public class ScriptDocument { int paramIdx = clause.indexOf(param.getName(), offset - (ltPos + 1)); if (paramIdx >= 0) { int paramStart = ltPos + 1 + paramIdx; - int paramEnd = paramStart + param.getName().length(); - marks.add(new ScriptLine.Mark(paramStart, paramEnd, TokenType.IMPORTED_CLASS, param)); + int paramLen = param.getName().length(); + int paramEnd = paramStart + paramLen; + marks.add(new ScriptLine.Mark(paramStart, paramEnd, TokenType.GENERIC_TYPE_PARAM, param)); + + int searchFrom = paramIdx + param.getName().length(); + int extendsIdx = clause.indexOf("extends", searchFrom); + if (extendsIdx >= 0) { + int extendsStart = ltPos + 1 + extendsIdx; + marks.add(new ScriptLine.Mark(extendsStart, extendsStart + 7, TokenType.KEYWORD)); + + TypeInfo boundType = param.getBoundTypeInfo(); + if (boundType != null) { + String remaining = clause.substring(extendsIdx + 7).trim(); + if (!remaining.isEmpty()) { + int boundEnd = remaining.indexOf(','); + if (boundEnd < 0) boundEnd = remaining.indexOf('&'); + String boundName = boundEnd < 0 ? remaining : remaining.substring(0, boundEnd).trim(); + if (!boundName.isEmpty()) { + int boundStart = extendsStart + 7; + while (boundStart < gtPos && Character.isWhitespace(text.charAt(boundStart))) boundStart++; + marks.add(new ScriptLine.Mark(boundStart, boundStart + boundName.length(), TokenType.getByType(boundType), boundType)); + } + } + } + } } - } + } } /** * Parse fields and methods inside a script-defined type. 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 f3deb5ecf..28dea5e08 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 @@ -4,6 +4,7 @@ import noppes.npcs.client.ClientProxy; 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.js_parser.TypeParamInfo; 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; @@ -180,6 +181,14 @@ public class ScriptLine { public void applyTokenMetadata(Token token, Object metadata) { if (metadata instanceof TypeInfo) { token.setTypeInfo((TypeInfo) metadata); + } else if (metadata instanceof TypeParamInfo) { + TypeParamInfo typeParam = (TypeParamInfo) metadata; + TypeInfo boundType = typeParam.getBoundTypeInfo(); + if (boundType != null) { + token.setTypeInfo(TypeInfo.typeParameter(typeParam.getName(), boundType)); + } else { + token.setTypeInfo(TypeInfo.typeParameter(typeParam.getName())); + } } else if (metadata instanceof MethodCallInfo) { MethodCallInfo callInfo = (MethodCallInfo) metadata; if (callInfo.isConstructor()) { 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 a452aaf2a..fd0db4839 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,7 @@ 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.js_parser.TypeParamInfo; 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; @@ -153,6 +154,7 @@ public class TokenHoverInfo { case INTERFACE_DECL: case ENUM_DECL: case TYPE_DECL: + case GENERIC_TYPE_PARAM: info.extractClassInfo(token); break; @@ -562,6 +564,7 @@ public class TokenHoverInfo { : TokenType.IMPORTED_CLASS.getHexColor(); // Render generics with correct segment colors if present addTypeSegments(typeInfo); + addDeclaredTypeParamSegments(typeInfo); // Extends Class superclass = clazz.getSuperclass(); @@ -621,8 +624,7 @@ public class TokenHoverInfo { : scriptType.getKind() == TypeInfo.Kind.ENUM ? TokenType.ENUM_DECL.getHexColor() : TokenType.IMPORTED_CLASS.getHexColor(); addTypeSegments(typeInfo); - - // Extends clause for ScriptTypeInfo + addDeclaredTypeParamSegments(typeInfo); if (scriptType.hasSuperClass()) { addSegment(" extends ", TokenType.MODIFIER.getHexColor()); TypeInfo superClass = scriptType.getSuperClass(); @@ -693,6 +695,7 @@ public class TokenHoverInfo { iconIndicator = "I"; addSegment("interface ", TokenType.MODIFIER.getHexColor()); addTypeSegments(typeInfo); + addDeclaredTypeParamSegments(typeInfo); if (jsType.getExtendsType() != null) { addSegment(" extends ", TokenType.MODIFIER.getHexColor()); @@ -728,7 +731,7 @@ public class TokenHoverInfo { String paramName = typeInfo.getTypeParameterName(); addSegment("type parameter ", TokenType.MODIFIER.getHexColor()); - addSegment(paramName, TokenType.IMPORTED_CLASS.getHexColor()); + addSegment(paramName, TokenType.GENERIC_TYPE_PARAM.getHexColor()); if (typeInfo.getJavaClass() != null && typeInfo.getJavaClass() != Object.class) { addSegment(" extends ", TokenType.MODIFIER.getHexColor()); @@ -1058,6 +1061,29 @@ public class TokenHoverInfo { splitAndAddTypeName(typeName, getColorForTypeInfo(typeInfo)); } + private void addDeclaredTypeParamSegments(TypeInfo typeInfo) { + List params = typeInfo.getTypeParams(); + if (params == null || params.isEmpty()) return; + if (typeInfo.isParameterized()) return; + + addSegment("<", TokenType.DEFAULT.getHexColor()); + for (int i = 0; i < params.size(); i++) { + if (i > 0) addSegment(", ", TokenType.DEFAULT.getHexColor()); + TypeParamInfo param = params.get(i); + addSegment(param.getName(), TokenType.GENERIC_TYPE_PARAM.getHexColor()); + if (param.getBoundTypeName() != null && !"Object".equals(param.getBoundTypeName())) { + addSegment(" extends ", TokenType.MODIFIER.getHexColor()); + TypeInfo boundType = param.getBoundTypeInfo(); + if (boundType != null && boundType.isResolved()) { + addTypeSegments(boundType); + } else { + addSegment(param.getBoundTypeName(), TokenType.IMPORTED_CLASS.getHexColor()); + } + } + } + addSegment(">", TokenType.DEFAULT.getHexColor()); + } + private void splitAndAddTypeName(String typeName, int typeColor) { if (typeName == null || !typeName.endsWith("[]")) { addSegment(typeName, typeColor); diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/token/TokenType.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/token/TokenType.java index 2cb57da62..9306a3c67 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/token/TokenType.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/token/TokenType.java @@ -30,6 +30,7 @@ public enum TokenType { ENUM_CONSTANT(0x55FFFF, 84, true, false), // enum constant values (blue, bold+italic) - like IntelliJ CLASS_DECL(0x00AAAA, 85), // class names in declarations IMPORTED_CLASS(0x00AAAA, 75), // imported class usages + GENERIC_TYPE_PARAM(0x00FA9A, 76), // generic type parameters like T, E, K, V (light green) TYPE_DECL(0x00AAAA, 70), // package paths, type references // Methods @@ -127,6 +128,8 @@ public enum TokenType { case INTERFACE_DECL: case GLOBAL_FIELD: return 'b'; // aqua + case GENERIC_TYPE_PARAM: + return 'a'; // green (distinct from class types) case CLASS_DECL: case IMPORTED_CLASS: case TYPE_DECL: diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java index b6c65bb3b..ca08529e9 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java @@ -683,6 +683,8 @@ public class TypeInfo { public TokenType getTokenType() { if (!resolved) return TokenType.UNDEFINED_VAR; + if (isTypeParameter()) + return TokenType.GENERIC_TYPE_PARAM; if(isPrimitive()) return TokenType.KEYWORD;