Added init values to declaration of local/global fields and their hover info

This commit is contained in:
bigguy345 committed 2025-12-27 16:30:12 +02:00
1 parent c4bbf6108b
commit a9cd2ec7ef
3 files changed
+125 -10

No files matched your search

@@ -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; }
@@ -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<Token> getTokensInRange(int start, int end) {
List<Token> 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.
@@ -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<Token> 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 ====================