mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 21:17:32 +00:00
More fixes and improvements
This commit is contained in:
1 parent
b1799b5a22
commit
555fb481d3
6 files changed
+422
-49
No files matched your search
@@ -48,10 +48,27 @@ public final class FieldInfo {
|
||||
return new FieldInfo(name, scope, null, -1, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a FieldInfo from reflection data for a class field.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getName() { return name; }
|
||||
public Scope getScope() { return scope; }
|
||||
public TypeInfo getDeclaredType() { return declaredType; }
|
||||
public TypeInfo getTypeInfo() { return declaredType; } // Alias for getDeclaredType
|
||||
public int getDeclarationOffset() { return declarationOffset; }
|
||||
public boolean isResolved() { return resolved; }
|
||||
public MethodInfo getContainingMethod() { return containingMethod; }
|
||||
|
||||
@@ -58,6 +58,24 @@ public final class MethodInfo {
|
||||
return new MethodInfo(name, null, null, params, -1, -1, -1, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MethodInfo from reflection data.
|
||||
* Used when resolving method calls on known types.
|
||||
*/
|
||||
public static MethodInfo fromReflection(java.lang.reflect.Method method, TypeInfo containingType) {
|
||||
String name = method.getName();
|
||||
TypeInfo returnType = TypeInfo.fromClass(method.getReturnType());
|
||||
|
||||
List<FieldInfo> params = new ArrayList<>();
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
TypeInfo paramType = TypeInfo.fromClass(paramTypes[i]);
|
||||
params.add(FieldInfo.reflectionParam("arg" + i, paramType));
|
||||
}
|
||||
|
||||
return new MethodInfo(name, returnType, containingType, params, -1, -1, -1, true, false);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getName() { return name; }
|
||||
public TypeInfo getReturnType() { return returnType; }
|
||||
|
||||
@@ -40,9 +40,9 @@ public class ScriptDocument {
|
||||
private static final Pattern KEYWORD_PATTERN = Pattern.compile(
|
||||
"\\b(null|boolean|int|float|double|long|char|byte|short|void|if|else|switch|case|for|while|do|try|catch|finally|return|throw|var|let|const|function|continue|break|this|new|typeof|instanceof|import)\\b");
|
||||
|
||||
// Declarations
|
||||
// Declarations - Updated to capture method parameters
|
||||
private static final Pattern IMPORT_PATTERN = Pattern.compile(
|
||||
"(?m)\\bimport\\s+(?:static\\s+)?([A-Za-z_][A-Za-z0-9_]*(?:\\s*\\.\\s*[A-Za-z_][A-Za-z0-9_]*)*)(?:\\s*\\.\\s*\\*)?\\s*(?:;|$)");
|
||||
"(?m)\\bimport\\s+(?:static\\s+)?([A-Za-z_][A-Za-z0-9_]*(?:\\s*\\.\\s*[A-Za-z_][A-Za-z0-9_]*)*)(?:\\s*\\.\\s*\\*?)?\\s*(?:;|$)");
|
||||
private static final Pattern CLASS_DECL_PATTERN = Pattern.compile(
|
||||
"\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)");
|
||||
private static final Pattern METHOD_DECL_PATTERN = Pattern.compile(
|
||||
@@ -66,6 +66,9 @@ public class ScriptDocument {
|
||||
private final Map<String, FieldInfo> globalFields = new HashMap<>();
|
||||
private final Set<String> wildcardPackages = new HashSet<>();
|
||||
private Map<String, ImportData> importsBySimpleName = new HashMap<>();
|
||||
|
||||
// Local variables per method (methodStartOffset -> {varName -> FieldInfo})
|
||||
private final Map<Integer, Map<String, FieldInfo>> methodLocals = new HashMap<>();
|
||||
|
||||
// Excluded regions (strings/comments) - positions where other patterns shouldn't match
|
||||
private final List<int[]> excludedRanges = new ArrayList<>();
|
||||
@@ -171,6 +174,7 @@ public class ScriptDocument {
|
||||
globalFields.clear();
|
||||
wildcardPackages.clear();
|
||||
excludedRanges.clear();
|
||||
methodLocals.clear();
|
||||
|
||||
// Phase 1: Find excluded regions (strings/comments)
|
||||
findExcludedRanges();
|
||||
@@ -178,7 +182,7 @@ public class ScriptDocument {
|
||||
// Phase 2: Parse imports
|
||||
parseImports();
|
||||
|
||||
// Phase 3: Parse structure (methods, fields)
|
||||
// Phase 3: Parse structure (methods, fields, locals)
|
||||
parseStructure();
|
||||
|
||||
// Phase 4: Build marks and assign to lines
|
||||
@@ -189,7 +193,7 @@ public class ScriptDocument {
|
||||
|
||||
// Phase 6: Build tokens for each line
|
||||
for (ScriptLine line : lines) {
|
||||
line.buildTokensFromMarks(marks, text);
|
||||
line.buildTokensFromMarks(marks, text, this);
|
||||
}
|
||||
|
||||
// Phase 7: Compute indent guides
|
||||
@@ -259,6 +263,11 @@ public class ScriptDocument {
|
||||
String matchText = m.group(0);
|
||||
boolean isWildcard = matchText.contains("*");
|
||||
boolean isStatic = matchText.contains("static");
|
||||
|
||||
// Skip if path ends with dot (incomplete import)
|
||||
if (fullPath.endsWith(".")) {
|
||||
// continue;
|
||||
}
|
||||
|
||||
int lastDot = fullPath.lastIndexOf('.');
|
||||
String simpleName = isWildcard ? null : (lastDot >= 0 ? fullPath.substring(lastDot + 1) : fullPath);
|
||||
@@ -284,6 +293,9 @@ public class ScriptDocument {
|
||||
// Parse methods
|
||||
parseMethodDeclarations();
|
||||
|
||||
// Parse local variables inside methods
|
||||
parseLocalVariables();
|
||||
|
||||
// Parse global fields (outside methods)
|
||||
parseGlobalFields();
|
||||
}
|
||||
@@ -306,8 +318,8 @@ public class ScriptDocument {
|
||||
if (bodyEnd < 0)
|
||||
bodyEnd = text.length();
|
||||
|
||||
// Parse parameters
|
||||
List<FieldInfo> params = parseParameters(paramList, m.start());
|
||||
// Parse parameters with their actual positions
|
||||
List<FieldInfo> params = parseParametersWithPositions(paramList, m.start(3));
|
||||
|
||||
MethodInfo methodInfo = MethodInfo.declaration(
|
||||
methodName,
|
||||
@@ -340,12 +352,13 @@ public class ScriptDocument {
|
||||
}
|
||||
}
|
||||
|
||||
private List<FieldInfo> parseParameters(String paramList, int declOffset) {
|
||||
private List<FieldInfo> parseParametersWithPositions(String paramList, int paramListStart) {
|
||||
List<FieldInfo> params = new ArrayList<>();
|
||||
if (paramList == null || paramList.trim().isEmpty()) {
|
||||
return params;
|
||||
}
|
||||
|
||||
// Pattern: Type varName (with optional spaces)
|
||||
Pattern paramPattern = Pattern.compile(
|
||||
"([a-zA-Z_][a-zA-Z0-9_<>\\[\\]]*(?:\\.{3})?)\\s+([a-zA-Z_][a-zA-Z0-9_]*)");
|
||||
Matcher m = paramPattern.matcher(paramList);
|
||||
@@ -353,11 +366,50 @@ public class ScriptDocument {
|
||||
String typeName = m.group(1);
|
||||
String paramName = m.group(2);
|
||||
TypeInfo typeInfo = resolveType(typeName);
|
||||
params.add(FieldInfo.parameter(paramName, typeInfo, declOffset, null));
|
||||
// Store the absolute position of the parameter name
|
||||
int paramNameStart = paramListStart + m.start(2);
|
||||
params.add(FieldInfo.parameter(paramName, typeInfo, paramNameStart, null));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private void parseLocalVariables() {
|
||||
for (MethodInfo method : methods) {
|
||||
Map<String, FieldInfo> locals = new HashMap<>();
|
||||
methodLocals.put(method.getDeclarationOffset(), locals);
|
||||
|
||||
int bodyStart = method.getBodyStart();
|
||||
int bodyEnd = method.getBodyEnd();
|
||||
if (bodyStart < 0 || bodyEnd <= bodyStart) continue;
|
||||
|
||||
String bodyText = text.substring(bodyStart, Math.min(bodyEnd, text.length()));
|
||||
|
||||
// Pattern for local variable declarations: Type varName = or Type varName;
|
||||
Pattern localDecl = Pattern.compile(
|
||||
"\\b([A-Za-z_][a-zA-Z0-9_<>\\[\\]]*)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(=|;|,)");
|
||||
Matcher m = localDecl.matcher(bodyText);
|
||||
while (m.find()) {
|
||||
int absPos = bodyStart + m.start();
|
||||
if (isExcluded(absPos)) continue;
|
||||
|
||||
String typeName = m.group(1);
|
||||
String varName = m.group(2);
|
||||
|
||||
// Skip if it looks like a method call or control flow
|
||||
if (typeName.equals("return") || typeName.equals("if") || typeName.equals("while") ||
|
||||
typeName.equals("for") || typeName.equals("switch") || typeName.equals("catch") ||
|
||||
typeName.equals("new") || typeName.equals("throw")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TypeInfo typeInfo = resolveType(typeName);
|
||||
int declPos = bodyStart + m.start(2);
|
||||
FieldInfo fieldInfo = FieldInfo.localField(varName, typeInfo, declPos, method);
|
||||
locals.put(varName, fieldInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseGlobalFields() {
|
||||
Matcher m = FIELD_DECL_PATTERN.matcher(text);
|
||||
while (m.find()) {
|
||||
@@ -453,22 +505,125 @@ public class ScriptDocument {
|
||||
private void markImports(List<ScriptLine.Mark> marks) {
|
||||
for (ImportData imp : imports) {
|
||||
// Mark 'import' keyword
|
||||
marks.add(new ScriptLine.Mark(imp.getStartOffset(), imp.getStartOffset() + 6, TokenType.IMPORT_KEYWORD));
|
||||
marks.add(new ScriptLine.Mark(imp.getStartOffset(), imp.getStartOffset() + 6, TokenType.IMPORT_KEYWORD, imp));
|
||||
|
||||
// Mark the path
|
||||
// Parse path tokens
|
||||
int pathStart = imp.getPathStartOffset();
|
||||
int pathEnd = imp.getPathEndOffset();
|
||||
|
||||
if (imp.isResolved()) {
|
||||
// Resolved import - mark as type declaration
|
||||
if (imp.getResolvedType() != null) {
|
||||
marks.add(new ScriptLine.Mark(pathStart, pathEnd, imp.getResolvedType().getTokenType()));
|
||||
String pathText = text.substring(pathStart, Math.min(pathEnd, text.length()));
|
||||
|
||||
// Skip if path ends with dot (incomplete)
|
||||
if (pathText.trim().endsWith(".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tokenize the path
|
||||
List<String> tokens = new ArrayList<>();
|
||||
List<Integer> tokenStarts = new ArrayList<>();
|
||||
List<Integer> tokenEnds = new ArrayList<>();
|
||||
Pattern idPattern = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
|
||||
Matcher idm = idPattern.matcher(pathText);
|
||||
while (idm.find()) {
|
||||
tokens.add(idm.group());
|
||||
tokenStarts.add(pathStart + idm.start());
|
||||
tokenEnds.add(pathStart + idm.end());
|
||||
}
|
||||
|
||||
if (tokens.isEmpty()) continue;
|
||||
|
||||
if (imp.isWildcard()) {
|
||||
// Wildcard import: mark all tokens as package (blue) if valid
|
||||
boolean pkgValid = typeResolver.isValidPackage(imp.getFullPath());
|
||||
|
||||
// Also check if it might be a class wildcard (OuterClass.*)
|
||||
TypeInfo outerType = typeResolver.resolveFullName(imp.getFullPath());
|
||||
|
||||
if (pkgValid || outerType != null) {
|
||||
// Mark package portion in blue
|
||||
int pkgTokenCount = outerType != null ? tokens.size() - 1 : tokens.size();
|
||||
for (int i = 0; i < Math.min(pkgTokenCount, tokens.size()); i++) {
|
||||
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), TokenType.TYPE_DECL, imp));
|
||||
}
|
||||
// If it's a class wildcard, mark the class with its type
|
||||
if (outerType != null && tokens.size() > 0) {
|
||||
int lastIdx = tokens.size() - 1;
|
||||
marks.add(new ScriptLine.Mark(tokenStarts.get(lastIdx), tokenEnds.get(lastIdx),
|
||||
outerType.getTokenType(), imp));
|
||||
}
|
||||
} else {
|
||||
marks.add(new ScriptLine.Mark(pathStart, pathEnd, TokenType.TYPE_DECL));
|
||||
// Unknown package/class - mark as undefined
|
||||
marks.add(new ScriptLine.Mark(pathStart, pathEnd, TokenType.UNDEFINED_VAR, imp));
|
||||
}
|
||||
} else {
|
||||
// Unresolved import
|
||||
marks.add(new ScriptLine.Mark(pathStart, pathEnd, TokenType.UNDEFINED_VAR));
|
||||
// Non-wildcard import: package is blue, class name(s) get type colors
|
||||
TypeInfo resolvedType = imp.getResolvedType();
|
||||
|
||||
if (resolvedType != null && resolvedType.isResolved()) {
|
||||
// Count package segments
|
||||
String fullPath = imp.getFullPath();
|
||||
String resolvedName = resolvedType.getFullName();
|
||||
String pkgName = resolvedType.getPackageName();
|
||||
|
||||
int pkgSegments = pkgName != null && !pkgName.isEmpty()
|
||||
? pkgName.split("\\.").length : 0;
|
||||
|
||||
// Mark package tokens in blue
|
||||
for (int i = 0; i < Math.min(pkgSegments, tokens.size()); i++) {
|
||||
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), TokenType.TYPE_DECL, imp));
|
||||
}
|
||||
|
||||
// Mark class tokens with appropriate type colors
|
||||
// For inner classes, each segment might have a different type
|
||||
for (int i = pkgSegments; i < tokens.size(); i++) {
|
||||
String segmentName = tokens.get(i);
|
||||
// Try to resolve this specific class/inner class
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
if (pkgName != null && !pkgName.isEmpty()) {
|
||||
classPath.append(pkgName).append(".");
|
||||
}
|
||||
for (int j = pkgSegments; j <= i; j++) {
|
||||
if (j > pkgSegments) classPath.append("$");
|
||||
classPath.append(tokens.get(j));
|
||||
}
|
||||
|
||||
TypeInfo segmentType = typeResolver.resolveFullName(classPath.toString());
|
||||
TokenType tokenType = segmentType != null
|
||||
? segmentType.getTokenType()
|
||||
: resolvedType.getTokenType();
|
||||
|
||||
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), tokenType, imp));
|
||||
}
|
||||
} else {
|
||||
// Try to figure out which part is invalid
|
||||
// Mark valid package portion in blue, invalid portion in red
|
||||
String fullPath = imp.getFullPath();
|
||||
int lastValidPkg = -1;
|
||||
StringBuilder pkgBuilder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < tokens.size(); i++) {
|
||||
if (i > 0) pkgBuilder.append(".");
|
||||
pkgBuilder.append(tokens.get(i));
|
||||
|
||||
// Check if this is a valid package
|
||||
if (typeResolver.isValidPackage(pkgBuilder.toString())) {
|
||||
lastValidPkg = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastValidPkg >= 0) {
|
||||
// Mark valid package in blue
|
||||
for (int i = 0; i <= lastValidPkg; i++) {
|
||||
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), TokenType.TYPE_DECL, imp));
|
||||
}
|
||||
// Mark rest as undefined
|
||||
for (int i = lastValidPkg + 1; i < tokens.size(); i++) {
|
||||
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), TokenType.UNDEFINED_VAR, imp));
|
||||
}
|
||||
} else {
|
||||
// Everything is unresolved
|
||||
marks.add(new ScriptLine.Mark(pathStart, pathEnd, TokenType.UNDEFINED_VAR, imp));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,6 +687,7 @@ public class ScriptDocument {
|
||||
while (m.find()) {
|
||||
int start = m.start(1);
|
||||
int end = m.end(1);
|
||||
String methodName = m.group(1);
|
||||
|
||||
if (isExcluded(start))
|
||||
continue;
|
||||
@@ -558,21 +714,42 @@ public class ScriptDocument {
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if we should highlight this as a method call
|
||||
boolean shouldHighlight = true;
|
||||
|
||||
if (hasReceiver && receiverName != null && !receiverName.isEmpty()) {
|
||||
// Check if receiver is an unresolved class reference
|
||||
// Check if receiver is an uppercase class reference (static call)
|
||||
if (Character.isUpperCase(receiverName.charAt(0))) {
|
||||
TypeInfo recvType = resolveType(receiverName);
|
||||
if (recvType == null || !recvType.isResolved()) {
|
||||
// Don't highlight method calls on unresolved types
|
||||
shouldHighlight = false;
|
||||
// Method call on unresolved type - mark as undefined
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
|
||||
continue;
|
||||
}
|
||||
// Check if method exists on resolved type
|
||||
if (!recvType.hasMethod(methodName) ) {
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
|
||||
continue;
|
||||
}
|
||||
// Valid method call on static type
|
||||
MethodInfo methodInfo = recvType.getMethodInfo(methodName);
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.METHOD_CALL, methodInfo));
|
||||
} else {
|
||||
// Lower-case receiver - this is a method call on an instance
|
||||
// Try to resolve the receiver's type
|
||||
FieldInfo receiverField = resolveVariable(receiverName, start);
|
||||
if (receiverField != null && receiverField.getTypeInfo() != null) {
|
||||
TypeInfo recvType = receiverField.getTypeInfo();
|
||||
if (recvType.hasMethod(methodName)) {
|
||||
MethodInfo methodInfo = recvType.getMethodInfo(methodName);
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.METHOD_CALL, methodInfo));
|
||||
} else {
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
|
||||
}
|
||||
} else {
|
||||
// Can't resolve receiver - mark method call anyway (could be valid at runtime)
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.METHOD_CALL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldHighlight) {
|
||||
} else {
|
||||
// No receiver - standalone method call (local function or global)
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.METHOD_CALL));
|
||||
}
|
||||
}
|
||||
@@ -589,6 +766,15 @@ public class ScriptDocument {
|
||||
"throws", "super", "assert", "volatile", "transient"
|
||||
));
|
||||
|
||||
// First pass: mark method parameters in their declaration positions
|
||||
for (MethodInfo method : methods) {
|
||||
for (FieldInfo param : method.getParameters()) {
|
||||
int pos = param.getDeclarationOffset();
|
||||
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);
|
||||
|
||||
@@ -601,7 +787,7 @@ public class ScriptDocument {
|
||||
if (knownKeywords.contains(name))
|
||||
continue;
|
||||
|
||||
// Skip if preceded by dot (field access)
|
||||
// Skip if preceded by dot (field access - handled separately)
|
||||
if (isPrecededByDot(position))
|
||||
continue;
|
||||
|
||||
@@ -613,29 +799,53 @@ public class ScriptDocument {
|
||||
if (Character.isUpperCase(name.charAt(0)))
|
||||
continue;
|
||||
|
||||
// Skip method calls (followed by paren)
|
||||
if (isFollowedByParen(m.end(1)))
|
||||
continue;
|
||||
|
||||
// Find containing method
|
||||
MethodInfo containingMethod = findMethodAtPosition(position);
|
||||
|
||||
if (containingMethod != null) {
|
||||
// Check parameters
|
||||
if (containingMethod.hasParameter(name)) {
|
||||
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.PARAMETER));
|
||||
FieldInfo paramInfo = containingMethod.getParameter(name);
|
||||
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.PARAMETER, paramInfo));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check local variables
|
||||
Map<String, FieldInfo> locals = methodLocals.get(containingMethod.getDeclarationOffset());
|
||||
if (locals != null && locals.containsKey(name)) {
|
||||
FieldInfo localInfo = locals.get(name);
|
||||
// Only highlight if the position is after the declaration
|
||||
if (localInfo.isVisibleAt(position)) {
|
||||
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.LOCAL_FIELD, localInfo));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check global fields
|
||||
if (globalFields.containsKey(name)) {
|
||||
FieldInfo fieldInfo = globalFields.get(name);
|
||||
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.GLOBAL_FIELD, fieldInfo));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unknown variable inside method - mark as undefined
|
||||
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.UNDEFINED_VAR));
|
||||
} else {
|
||||
// Outside any method
|
||||
// Check global fields
|
||||
if (globalFields.containsKey(name)) {
|
||||
FieldInfo fieldInfo = globalFields.get(name);
|
||||
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.GLOBAL_FIELD, fieldInfo));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unknown variable outside method - mark as undefined
|
||||
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.UNDEFINED_VAR));
|
||||
}
|
||||
|
||||
// Check global fields
|
||||
if (globalFields.containsKey(name)) {
|
||||
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.GLOBAL_FIELD));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if it's a method call (followed by paren)
|
||||
if (isFollowedByParen(m.end(1)))
|
||||
continue;
|
||||
|
||||
// Unknown identifier - could mark as undefined, but be conservative
|
||||
// Only mark as undefined if it looks like a variable usage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,13 +862,42 @@ public class ScriptDocument {
|
||||
if (isExcluded(start))
|
||||
continue;
|
||||
|
||||
// Skip in import/package statements
|
||||
if (isInImportOrPackage(start))
|
||||
continue;
|
||||
|
||||
// Try to resolve the class
|
||||
TypeInfo info = resolveType(className);
|
||||
if (info != null && info.isResolved()) {
|
||||
marks.add(new ScriptLine.Mark(start, end, info.getTokenType()));
|
||||
} else if (!importsBySimpleName.containsKey(className) &&
|
||||
!TypeResolver.JAVA_LANG_CLASSES.contains(className) &&
|
||||
wildcardPackages.isEmpty()) {
|
||||
marks.add(new ScriptLine.Mark(start, end, info.getTokenType(), info));
|
||||
} else {
|
||||
// Unknown type - mark as undefined
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
|
||||
}
|
||||
}
|
||||
|
||||
// Also mark uppercase identifiers in type positions (new X(), X variable, etc.)
|
||||
Pattern typeUsage = Pattern.compile("\\b(new\\s+)?([A-Z][a-zA-Z0-9_]*)(?:\\s*<[^>]*>)?\\s*(?:\\(|\\[|\\b[a-z])");
|
||||
Matcher tm = typeUsage.matcher(text);
|
||||
|
||||
while (tm.find()) {
|
||||
String className = tm.group(2);
|
||||
int start = tm.start(2);
|
||||
int end = tm.end(2);
|
||||
|
||||
if (isExcluded(start))
|
||||
continue;
|
||||
|
||||
// Skip in import/package statements
|
||||
if (isInImportOrPackage(start))
|
||||
continue;
|
||||
|
||||
// Try to resolve the class
|
||||
TypeInfo info = resolveType(className);
|
||||
if (info != null && info.isResolved()) {
|
||||
marks.add(new ScriptLine.Mark(start, end, info.getTokenType(), info));
|
||||
} else {
|
||||
// Unknown type - mark as undefined
|
||||
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
|
||||
}
|
||||
}
|
||||
@@ -771,6 +1010,34 @@ public class ScriptDocument {
|
||||
|
||||
// ==================== UTILITY METHODS ====================
|
||||
|
||||
private FieldInfo resolveVariable(String name, int position) {
|
||||
// Find containing method
|
||||
MethodInfo containingMethod = findMethodAtPosition(position);
|
||||
|
||||
if (containingMethod != null) {
|
||||
// Check parameters
|
||||
if (containingMethod.hasParameter(name)) {
|
||||
return containingMethod.getParameter(name);
|
||||
}
|
||||
|
||||
// Check local variables
|
||||
Map<String, FieldInfo> locals = methodLocals.get(containingMethod.getDeclarationOffset());
|
||||
if (locals != null && locals.containsKey(name)) {
|
||||
FieldInfo localInfo = locals.get(name);
|
||||
if (localInfo.isVisibleAt(position)) {
|
||||
return localInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check global fields
|
||||
if (globalFields.containsKey(name)) {
|
||||
return globalFields.get(name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isPrecededByDot(int position) {
|
||||
if (position <= 0)
|
||||
return false;
|
||||
|
||||
@@ -102,11 +102,13 @@ public class ScriptLine {
|
||||
/**
|
||||
* Build tokens from a list of marks (highlight regions).
|
||||
* Fills gaps between marks with DEFAULT tokens.
|
||||
* Attaches metadata from marks to the created tokens.
|
||||
*
|
||||
* @param marks List of highlight marks that overlap this line
|
||||
* @param fullText The complete document text
|
||||
* @param document The parent ScriptDocument (for context lookups)
|
||||
*/
|
||||
public void buildTokensFromMarks(List<Mark> marks, String fullText) {
|
||||
public void buildTokensFromMarks(List<Mark> marks, String fullText, ScriptDocument document) {
|
||||
clearTokens();
|
||||
int cursor = globalStart;
|
||||
|
||||
@@ -131,10 +133,24 @@ public class ScriptLine {
|
||||
addToken(Token.defaultToken(gapText, cursor, gapEnd));
|
||||
}
|
||||
|
||||
// Add the marked token
|
||||
// Add the marked token with metadata
|
||||
if (tokenStart < tokenEnd) {
|
||||
String tokenText = fullText.substring(tokenStart, tokenEnd);
|
||||
Token token = new Token(tokenText, tokenStart, tokenEnd, mark.type);
|
||||
|
||||
// Attach metadata based on type
|
||||
if (mark.metadata != null) {
|
||||
if (mark.metadata instanceof TypeInfo) {
|
||||
token.setTypeInfo((TypeInfo) mark.metadata);
|
||||
} else if (mark.metadata instanceof FieldInfo) {
|
||||
token.setFieldInfo((FieldInfo) mark.metadata);
|
||||
} else if (mark.metadata instanceof MethodInfo) {
|
||||
token.setMethodInfo((MethodInfo) mark.metadata);
|
||||
} else if (mark.metadata instanceof ImportData) {
|
||||
token.setImportData((ImportData) mark.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
addToken(token);
|
||||
}
|
||||
|
||||
@@ -283,21 +299,31 @@ public class ScriptLine {
|
||||
/**
|
||||
* A simple mark representing a highlighted region.
|
||||
* Used during token building phase.
|
||||
* Can optionally carry metadata (TypeInfo, FieldInfo, MethodInfo, ImportData).
|
||||
*/
|
||||
public static class Mark {
|
||||
public final int start;
|
||||
public final int end;
|
||||
public final TokenType type;
|
||||
public final Object metadata;
|
||||
|
||||
public Mark(int start, int end, TokenType type) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.type = type;
|
||||
this.metadata = null;
|
||||
}
|
||||
|
||||
public Mark(int start, int end, TokenType type, Object metadata) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.type = type;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Mark{" + type + " [" + start + "-" + end + "]}";
|
||||
return "Mark{" + type + " [" + start + "-" + end + "]" + (metadata != null ? " " + metadata.getClass().getSimpleName() : "") + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,44 @@ public final class TypeInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MethodInfo for a method by name. Returns null if not found.
|
||||
* Creates a synthetic MethodInfo based on reflection data.
|
||||
*/
|
||||
public MethodInfo getMethodInfo(String methodName) {
|
||||
if (javaClass == null) return null;
|
||||
try {
|
||||
for (java.lang.reflect.Method m : javaClass.getMethods()) {
|
||||
if (m.getName().equals(methodName)) {
|
||||
// Create a synthetic MethodInfo from reflection
|
||||
return MethodInfo.fromReflection(m, this);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Security or linkage error
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get FieldInfo for a field by name. Returns null if not found.
|
||||
* Creates a synthetic FieldInfo based on reflection data.
|
||||
*/
|
||||
public FieldInfo getFieldInfo(String fieldName) {
|
||||
if (javaClass == null) return null;
|
||||
try {
|
||||
for (java.lang.reflect.Field f : javaClass.getFields()) {
|
||||
if (f.getName().equals(fieldName)) {
|
||||
// Create a synthetic FieldInfo from reflection
|
||||
return FieldInfo.fromReflection(f, this);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Security or linkage error
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TypeInfo{" + fullName + ", " + kind + ", resolved=" + resolved + "}";
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter;
|
||||
|
||||
import noppes.npcs.client.gui.util.script.PackageFinder;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -213,6 +215,11 @@ public class TypeResolver {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PackageFinder.find(packagePath)) {
|
||||
registerPackage(packagePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to find a class in this package to validate it
|
||||
String[] testClasses = getTestClassesForPackage(packagePath);
|
||||
for (String testClass : testClasses) {
|
||||
|
||||
Reference in new issue
Block a user